mirror of
https://github.com/DSpace/DSpace.git
synced 2025-10-07 10:04:21 +00:00
Compare commits
1 Commits
dspace-7.6
...
rest-tutor
Author | SHA1 | Date | |
---|---|---|---|
![]() |
cfc200f2c7 |
28
.codecov.yml
28
.codecov.yml
@@ -1,28 +0,0 @@
|
||||
# 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:
|
||||
# Enable informational mode, which just provides info to reviewers & always passes
|
||||
# https://docs.codecov.io/docs/commit-status#section-informational
|
||||
informational: true
|
||||
|
||||
# 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
|
@@ -1,10 +0,0 @@
|
||||
.git/
|
||||
.idea/
|
||||
.settings/
|
||||
*/target/
|
||||
dspace/modules/*/target/
|
||||
Dockerfile.*
|
||||
dspace/src/main/docker/dspace-postgres-pgcrypto
|
||||
dspace/src/main/docker/dspace-postgres-pgcrypto-curl
|
||||
dspace/src/main/docker/README.md
|
||||
dspace/src/main/docker-compose/
|
6
.gitattributes
vendored
6
.gitattributes
vendored
@@ -1,12 +1,6 @@
|
||||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
||||
|
||||
# Ensure Unix files always keep Unix line endings
|
||||
*.sh text eol=lf
|
||||
|
||||
# Ensure Windows files always keep Windows line endings
|
||||
*.bat text eol=crlf
|
||||
|
||||
# Standard to msysgit
|
||||
*.doc diff=astextplain
|
||||
*.DOC diff=astextplain
|
||||
|
22
.github/ISSUE_TEMPLATE/bug_report.md
vendored
22
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,22 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: bug, needs triage
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is. Include the version(s) of DSpace where you've seen this problem. Link to examples if they are public.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Do this
|
||||
2. Then this...
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Related work**
|
||||
Link to any related tickets or PRs here.
|
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,20 +0,0 @@
|
||||
---
|
||||
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.
|
118
.github/dependabot.yml
vendored
118
.github/dependabot.yml
vendored
@@ -1,118 +0,0 @@
|
||||
#-------------------
|
||||
# DSpace's dependabot rules. Enables maven updates for all dependencies on a weekly basis
|
||||
# for main and any maintenance branches. Security updates only apply to main.
|
||||
#-------------------
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "maven"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
# Allow up to 10 open PRs for dependencies
|
||||
open-pull-requests-limit: 10
|
||||
# Group together some upgrades in a single PR
|
||||
groups:
|
||||
# Group together all Build Tools in a single PR
|
||||
build-tools:
|
||||
applies-to: version-updates
|
||||
patterns:
|
||||
- "org.apache.maven.plugins:*"
|
||||
- "*:*-maven-plugin"
|
||||
- "*:maven-*-plugin"
|
||||
- "com.github.spotbugs:spotbugs"
|
||||
- "com.google.code.findbugs:*"
|
||||
- "com.google.errorprone:*"
|
||||
- "com.puppycrawl.tools:checkstyle"
|
||||
- "org.sonatype.plugins:*"
|
||||
exclude-patterns:
|
||||
# Exclude anything from Spring, as that is in a separate group
|
||||
- "org.springframework.*:*"
|
||||
update-types:
|
||||
- "minor"
|
||||
- "patch"
|
||||
test-tools:
|
||||
applies-to: version-updates
|
||||
patterns:
|
||||
- "junit:*"
|
||||
- "com.github.stefanbirker:system-rules"
|
||||
- "com.h2database:*"
|
||||
- "io.findify:s3mock*"
|
||||
- "io.netty:*"
|
||||
- "org.hamcrest:*"
|
||||
- "org.mock-server:*"
|
||||
- "org.mockito:*"
|
||||
update-types:
|
||||
- "minor"
|
||||
- "patch"
|
||||
# Group together all Apache Commons deps in a single PR
|
||||
apache-commons:
|
||||
applies-to: version-updates
|
||||
patterns:
|
||||
- "org.apache.commons:*"
|
||||
- "commons-*:commons-*"
|
||||
update-types:
|
||||
- "minor"
|
||||
- "patch"
|
||||
# Group together all fasterxml deps in a single PR
|
||||
fasterxml:
|
||||
applies-to: version-updates
|
||||
patterns:
|
||||
- "com.fasterxml:*"
|
||||
- "com.fasterxml.*:*"
|
||||
update-types:
|
||||
- "minor"
|
||||
- "patch"
|
||||
# Group together all Hibernate deps in a single PR
|
||||
hibernate:
|
||||
applies-to: version-updates
|
||||
patterns:
|
||||
- "org.hibernate.*:*"
|
||||
update-types:
|
||||
- "minor"
|
||||
- "patch"
|
||||
# Group together all Jakarta deps in a single PR
|
||||
jakarta:
|
||||
applies-to: version-updates
|
||||
patterns:
|
||||
- "jakarta.*:*"
|
||||
- "org.eclipse.angus:jakarta.mail"
|
||||
- "org.glassfish.jaxb:jaxb-runtime"
|
||||
update-types:
|
||||
- "minor"
|
||||
- "patch"
|
||||
# Group together all Google deps in a single PR
|
||||
google-apis:
|
||||
applies-to: version-updates
|
||||
patterns:
|
||||
- "com.google.apis:*"
|
||||
- "com.google.api-client:*"
|
||||
- "com.google.http-client:*"
|
||||
- "com.google.oauth-client:*"
|
||||
update-types:
|
||||
- "minor"
|
||||
- "patch"
|
||||
# Group together all Spring deps in a single PR
|
||||
spring:
|
||||
applies-to: version-updates
|
||||
patterns:
|
||||
- "org.springframework:*"
|
||||
- "org.springframework.*:*"
|
||||
update-types:
|
||||
- "minor"
|
||||
- "patch"
|
||||
# Group together all WebJARs deps in a single PR
|
||||
webjars:
|
||||
applies-to: version-updates
|
||||
patterns:
|
||||
- "org.webjars:*"
|
||||
- "org.webjars.*:*"
|
||||
update-types:
|
||||
- "minor"
|
||||
- "patch"
|
||||
ignore:
|
||||
# Don't try to auto-update any DSpace dependencies
|
||||
- dependency-name: "org.dspace:*"
|
||||
- dependency-name: "org.dspace.*:*"
|
||||
# Ignore all major version updates for all dependencies. We'll only automate minor/patch updates.
|
||||
- dependency-name: "*"
|
||||
update-types: ["version-update:semver-major"]
|
28
.github/pull_request_template.md
vendored
28
.github/pull_request_template.md
vendored
@@ -1,28 +0,0 @@
|
||||
## References
|
||||
_Add references/links to any related issues or PRs. These may include:_
|
||||
* Fixes #`issue-number` (if this fixes an issue ticket)
|
||||
* Related to DSpace/RestContract#`pr-number` (if a corresponding REST Contract PR exists)
|
||||
|
||||
## Description
|
||||
Short summary of changes (1-2 sentences).
|
||||
|
||||
## Instructions for Reviewers
|
||||
Please add a more detailed description of the changes made by your PR. At a minimum, providing a bulleted list of changes in your PR is helpful to reviewers.
|
||||
|
||||
List of changes in this PR:
|
||||
* First, ...
|
||||
* Second, ...
|
||||
|
||||
**Include guidance for how to test or review your PR.** This may include: steps to reproduce a bug, screenshots or description of a new feature, or reasons behind specific changes.
|
||||
|
||||
## Checklist
|
||||
_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 includes Javadoc for _all new (or modified) public methods and classes_. It also includes Javadoc for large or complex private methods.
|
||||
- [ ] My PR passes all tests and includes new/updated Unit or Integration Tests based on the [Code Testing Guide](https://wiki.lyrasis.org/display/DSPACE/Code+Testing+Guide).
|
||||
- [ ] If my PR includes new libraries/dependencies (in any `pom.xml`), I've made sure their licenses align with the [DSpace BSD License](https://github.com/DSpace/DSpace/blob/main/LICENSE) based on the [Licensing of Contributions](https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines#CodeContributionGuidelines-LicensingofContributions) documentation.
|
||||
- [ ] If my PR modifies REST API endpoints, I've opened a separate [REST Contract](https://github.com/DSpace/RestContract/blob/main/README.md) PR related to this change.
|
||||
- [ ] If my PR includes new configurations, I've provided basic technical documentation in the PR itself.
|
||||
- [ ] If my PR fixes an issue ticket, I've [linked them together](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue).
|
112
.github/workflows/build.yml
vendored
112
.github/workflows/build.yml
vendored
@@ -1,112 +0,0 @@
|
||||
# DSpace Continuous Integration/Build via GitHub Actions
|
||||
# Concepts borrowed from
|
||||
# https://docs.github.com/en/free-pro-team@latest/actions/guides/building-and-testing-java-with-maven
|
||||
name: Build
|
||||
|
||||
# Run this Build for all pushes / PRs to current branch
|
||||
on: [push, pull_request]
|
||||
|
||||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
# Give Maven 1GB of memory to work with
|
||||
MAVEN_OPTS: "-Xmx1024M"
|
||||
strategy:
|
||||
# Create a matrix of two separate configurations for Unit vs Integration Tests
|
||||
# This will ensure those tasks are run in parallel
|
||||
# Also specify version of Java to use (this can allow us to optionally run tests on multiple JDKs in future)
|
||||
matrix:
|
||||
include:
|
||||
# NOTE: Unit Tests include deprecated REST API v6 (as it has unit tests)
|
||||
# - surefire.rerunFailingTestsCount => try again for flakey tests, and keep track of/report on number of retries
|
||||
- type: "Unit Tests"
|
||||
java: 11
|
||||
mvnflags: "-DskipUnitTests=false -Pdspace-rest -Dsurefire.rerunFailingTestsCount=2"
|
||||
resultsdir: "**/target/surefire-reports/**"
|
||||
# NOTE: ITs skip all code validation checks, as they are already done by Unit Test job.
|
||||
# - 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
|
||||
# - failsafe.rerunFailingTestsCount => try again for flakey tests, and keep track of/report on number of retries
|
||||
- type: "Integration Tests"
|
||||
java: 11
|
||||
mvnflags: "-DskipIntegrationTests=false -Denforcer.skip=true -Dcheckstyle.skip=true -Dlicense.skip=true -Dxml.skip=true -Dfailsafe.rerunFailingTestsCount=2"
|
||||
resultsdir: "**/target/failsafe-reports/**"
|
||||
# Do NOT exit immediately if one matrix job fails
|
||||
# This ensures ITs continue running even if Unit Tests fail, or visa versa
|
||||
fail-fast: false
|
||||
name: Run ${{ matrix.type }}
|
||||
# These are the actual CI steps to perform per job
|
||||
steps:
|
||||
# https://github.com/actions/checkout
|
||||
- name: Checkout codebase
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# https://github.com/actions/setup-java
|
||||
- name: Install JDK ${{ matrix.java }}
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: ${{ matrix.java }}
|
||||
distribution: 'temurin'
|
||||
cache: 'maven'
|
||||
|
||||
# Run parallel Maven builds based on the above 'strategy.matrix'
|
||||
- name: Run Maven ${{ matrix.type }}
|
||||
env:
|
||||
TEST_FLAGS: ${{ matrix.mvnflags }}
|
||||
run: mvn --no-transfer-progress -V install -P-assembly -Pcoverage-report $TEST_FLAGS
|
||||
|
||||
# If previous step failed, save results of tests to downloadable artifact for this job
|
||||
# (This artifact is downloadable at the bottom of any job's summary page)
|
||||
- name: Upload Results of ${{ matrix.type }} to Artifact
|
||||
if: ${{ failure() }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.type }} results
|
||||
path: ${{ matrix.resultsdir }}
|
||||
|
||||
# Upload code coverage report to artifact, so that it can be shared with the 'codecov' job (see below)
|
||||
- name: Upload code coverage report to Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.type }} coverage report
|
||||
path: 'dspace/target/site/jacoco-aggregate/jacoco.xml'
|
||||
retention-days: 14
|
||||
|
||||
# Codecov upload is a separate job in order to allow us to restart this separate from the entire build/test
|
||||
# job above. This is necessary because Codecov uploads seem to randomly fail at times.
|
||||
# See https://community.codecov.com/t/upload-issues-unable-to-locate-build-via-github-actions-api/3954
|
||||
codecov:
|
||||
# Must run after 'tests' job above
|
||||
needs: tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Download artifacts from previous 'tests' job
|
||||
- name: Download coverage artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
|
||||
# Now attempt upload to Codecov using its action.
|
||||
# NOTE: We use a retry action to retry the Codecov upload if it fails the first time.
|
||||
#
|
||||
# Retry action: https://github.com/marketplace/actions/retry-action
|
||||
# Codecov action: https://github.com/codecov/codecov-action
|
||||
- name: Upload coverage to Codecov.io
|
||||
uses: Wandalen/wretry.action@v1.3.0
|
||||
with:
|
||||
action: codecov/codecov-action@v4
|
||||
# Ensure codecov-action throws an error when it fails to upload
|
||||
with: |
|
||||
fail_ci_if_error: true
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
# Try re-running action 5 times max
|
||||
attempt_limit: 5
|
||||
# Run again in 30 seconds
|
||||
attempt_delay: 30000
|
63
.github/workflows/codescan.yml
vendored
63
.github/workflows/codescan.yml
vendored
@@ -1,63 +0,0 @@
|
||||
# DSpace CodeQL code scanning configuration for GitHub
|
||||
# https://docs.github.com/en/code-security/code-scanning
|
||||
#
|
||||
# NOTE: Code scanning must be run separate from our default build.yml
|
||||
# because CodeQL requires a fresh build with all tests *disabled*.
|
||||
name: "Code Scanning"
|
||||
|
||||
# Run this code scan for all pushes / PRs to main or maintenance branches. Also run once a week.
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- 'dspace-**'
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- 'dspace-**'
|
||||
# Don't run if PR is only updating static documentation
|
||||
paths-ignore:
|
||||
- '**/*.md'
|
||||
- '**/*.txt'
|
||||
schedule:
|
||||
- cron: "37 0 * * 1"
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze Code
|
||||
runs-on: ubuntu-latest
|
||||
# Limit permissions of this GitHub action. Can only write to security-events
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
steps:
|
||||
# https://github.com/actions/checkout
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# https://github.com/actions/setup-java
|
||||
- name: Install JDK
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: 11
|
||||
distribution: 'temurin'
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
# https://github.com/github/codeql-action
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
# Codescan Javascript as well since a few JS files exist in REST API's interface
|
||||
languages: java, javascript
|
||||
|
||||
# Autobuild attempts to build any compiled languages
|
||||
# NOTE: Based on testing, this autobuild process works well for DSpace. A custom
|
||||
# DSpace build w/caching (like in build.yml) was about the same speed as autobuild.
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
|
||||
# Perform GitHub Code Scanning.
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
248
.github/workflows/docker.yml
vendored
248
.github/workflows/docker.yml
vendored
@@ -1,248 +0,0 @@
|
||||
# DSpace Docker image build for hub.docker.com
|
||||
name: Docker images
|
||||
|
||||
# Run this Build for all pushes to 'main' or maintenance branches, or tagged releases.
|
||||
# Also run for PRs to ensure PR doesn't break Docker build process
|
||||
# NOTE: uses "reusable-docker-build.yml" to actually build each of the Docker images.
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- 'dspace-**'
|
||||
tags:
|
||||
- 'dspace-**'
|
||||
pull_request:
|
||||
|
||||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
packages: write # to write images to GitHub Container Registry (GHCR)
|
||||
|
||||
jobs:
|
||||
####################################################
|
||||
# Build/Push the 'dspace/dspace-dependencies' image.
|
||||
# This image is used by all other DSpace build jobs.
|
||||
####################################################
|
||||
dspace-dependencies:
|
||||
# Ensure this job never runs on forked repos. It's only executed for 'dspace/dspace'
|
||||
if: github.repository == 'dspace/dspace'
|
||||
uses: ./.github/workflows/reusable-docker-build.yml
|
||||
with:
|
||||
build_id: dspace-dependencies
|
||||
image_name: dspace/dspace-dependencies
|
||||
dockerfile_path: ./Dockerfile.dependencies
|
||||
secrets:
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
DOCKER_ACCESS_TOKEN: ${{ secrets.DOCKER_ACCESS_TOKEN }}
|
||||
|
||||
#######################################
|
||||
# Build/Push the 'dspace/dspace' image
|
||||
#######################################
|
||||
dspace:
|
||||
# Ensure this job never runs on forked repos. It's only executed for 'dspace/dspace'
|
||||
if: github.repository == 'dspace/dspace'
|
||||
# Must run after 'dspace-dependencies' job above
|
||||
needs: dspace-dependencies
|
||||
uses: ./.github/workflows/reusable-docker-build.yml
|
||||
with:
|
||||
build_id: dspace-prod
|
||||
image_name: dspace/dspace
|
||||
dockerfile_path: ./Dockerfile
|
||||
secrets:
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
DOCKER_ACCESS_TOKEN: ${{ secrets.DOCKER_ACCESS_TOKEN }}
|
||||
# Enable redeploy of sandbox & demo if the branch for this image matches the deployment branch of
|
||||
# these sites as specified in reusable-docker-build.xml
|
||||
REDEPLOY_SANDBOX_URL: ${{ secrets.REDEPLOY_SANDBOX_URL }}
|
||||
REDEPLOY_DEMO_URL: ${{ secrets.REDEPLOY_DEMO_URL }}
|
||||
|
||||
#############################################################
|
||||
# Build/Push the 'dspace/dspace' image ('-test' tag)
|
||||
#############################################################
|
||||
dspace-test:
|
||||
# Ensure this job never runs on forked repos. It's only executed for 'dspace/dspace'
|
||||
if: github.repository == 'dspace/dspace'
|
||||
# Must run after 'dspace-dependencies' job above
|
||||
needs: dspace-dependencies
|
||||
uses: ./.github/workflows/reusable-docker-build.yml
|
||||
with:
|
||||
build_id: dspace-test
|
||||
image_name: dspace/dspace
|
||||
dockerfile_path: ./Dockerfile.test
|
||||
# As this is a test/development image, its tags are all suffixed with "-test". Otherwise, it uses the same
|
||||
# tagging logic as the primary 'dspace/dspace' image above.
|
||||
tags_flavor: suffix=-test
|
||||
secrets:
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
DOCKER_ACCESS_TOKEN: ${{ secrets.DOCKER_ACCESS_TOKEN }}
|
||||
|
||||
###########################################
|
||||
# Build/Push the 'dspace/dspace-cli' image
|
||||
###########################################
|
||||
dspace-cli:
|
||||
# Ensure this job never runs on forked repos. It's only executed for 'dspace/dspace'
|
||||
if: github.repository == 'dspace/dspace'
|
||||
# Must run after 'dspace-dependencies' job above
|
||||
needs: dspace-dependencies
|
||||
uses: ./.github/workflows/reusable-docker-build.yml
|
||||
with:
|
||||
build_id: dspace-cli
|
||||
image_name: dspace/dspace-cli
|
||||
dockerfile_path: ./Dockerfile.cli
|
||||
secrets:
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
DOCKER_ACCESS_TOKEN: ${{ secrets.DOCKER_ACCESS_TOKEN }}
|
||||
|
||||
###########################################
|
||||
# Build/Push the 'dspace/dspace-solr' image
|
||||
###########################################
|
||||
dspace-solr:
|
||||
# Ensure this job never runs on forked repos. It's only executed for 'dspace/dspace'
|
||||
if: github.repository == 'dspace/dspace'
|
||||
uses: ./.github/workflows/reusable-docker-build.yml
|
||||
with:
|
||||
build_id: dspace-solr
|
||||
image_name: dspace/dspace-solr
|
||||
dockerfile_path: ./dspace/src/main/docker/dspace-solr/Dockerfile
|
||||
# Must pass solrconfigs to the Dockerfile so that it can find the required Solr config files
|
||||
dockerfile_additional_contexts: 'solrconfigs=./dspace/solr/'
|
||||
secrets:
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
DOCKER_ACCESS_TOKEN: ${{ secrets.DOCKER_ACCESS_TOKEN }}
|
||||
# Enable redeploy of sandbox & demo SOLR instance whenever dspace-solr image changes for deployed branch.
|
||||
# These URLs MUST use different secrets than 'dspace/dspace' image build above as they are deployed separately.
|
||||
REDEPLOY_SANDBOX_URL: ${{ secrets.REDEPLOY_SANDBOX_SOLR_URL }}
|
||||
REDEPLOY_DEMO_URL: ${{ secrets.REDEPLOY_DEMO_SOLR_URL }}
|
||||
|
||||
###########################################################
|
||||
# Build/Push the 'dspace/dspace-postgres-pgcrypto' image
|
||||
###########################################################
|
||||
dspace-postgres-pgcrypto:
|
||||
# Ensure this job never runs on forked repos. It's only executed for 'dspace/dspace'
|
||||
if: github.repository == 'dspace/dspace'
|
||||
uses: ./.github/workflows/reusable-docker-build.yml
|
||||
with:
|
||||
build_id: dspace-postgres-pgcrypto-prod
|
||||
image_name: dspace/dspace-postgres-pgcrypto
|
||||
# Must build out of subdirectory to have access to install script for pgcrypto.
|
||||
# NOTE: this context will build the image based on the Dockerfile in the specified directory
|
||||
dockerfile_context: ./dspace/src/main/docker/dspace-postgres-pgcrypto/
|
||||
secrets:
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
DOCKER_ACCESS_TOKEN: ${{ secrets.DOCKER_ACCESS_TOKEN }}
|
||||
|
||||
########################################################################
|
||||
# Build/Push the 'dspace/dspace-postgres-pgcrypto' image (-loadsql tag)
|
||||
########################################################################
|
||||
dspace-postgres-pgcrypto-loadsql:
|
||||
# Ensure this job never runs on forked repos. It's only executed for 'dspace/dspace'
|
||||
if: github.repository == 'dspace/dspace'
|
||||
uses: ./.github/workflows/reusable-docker-build.yml
|
||||
with:
|
||||
build_id: dspace-postgres-pgcrypto-loadsql
|
||||
image_name: dspace/dspace-postgres-pgcrypto
|
||||
# Must build out of subdirectory to have access to install script for pgcrypto.
|
||||
# NOTE: this context will build the image based on the Dockerfile in the specified directory
|
||||
dockerfile_context: ./dspace/src/main/docker/dspace-postgres-pgcrypto-curl/
|
||||
# Suffix all tags with "-loadsql". Otherwise, it uses the same
|
||||
# tagging logic as the primary 'dspace/dspace-postgres-pgcrypto' image above.
|
||||
tags_flavor: suffix=-loadsql
|
||||
secrets:
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
DOCKER_ACCESS_TOKEN: ${{ secrets.DOCKER_ACCESS_TOKEN }}
|
||||
|
||||
#################################################################################
|
||||
# Test Deployment via Docker to ensure newly built images are working properly
|
||||
#################################################################################
|
||||
docker-deploy:
|
||||
# Ensure this job never runs on forked repos. It's only executed for 'dspace/dspace'
|
||||
if: github.repository == 'dspace/dspace'
|
||||
runs-on: ubuntu-latest
|
||||
# Must run after all major images are built
|
||||
needs: [dspace, dspace-test, dspace-cli, dspace-postgres-pgcrypto, dspace-solr]
|
||||
env:
|
||||
# Override defaults dspace.server.url because backend starts at http://127.0.0.1:8080
|
||||
dspace__P__server__P__url: http://127.0.0.1:8080/server
|
||||
# Enable all optional modules / controllers for this test deployment.
|
||||
# This helps check for errors in deploying these modules via Spring Boot
|
||||
iiif__P__enabled: true
|
||||
oai__P__enabled: true
|
||||
rdf__P__enabled: true
|
||||
signposting__P__enabled: true
|
||||
sword__D__server__P__enabled: true
|
||||
swordv2__D__server__P__enabled: true
|
||||
# If this is a PR against main (default branch), use "latest".
|
||||
# Else if this is a PR against a different branch, used the base branch name.
|
||||
# Else if this is a commit on main (default branch), use the "latest" tag.
|
||||
# Else, just use the branch name.
|
||||
# NOTE: DSPACE_VER is used because our docker compose scripts default to using the "-test" image.
|
||||
DSPACE_VER: ${{ (github.event_name == 'pull_request' && github.event.pull_request.base.ref == github.event.repository.default_branch && 'latest') || (github.event_name == 'pull_request' && github.event.pull_request.base.ref) || (github.ref_name == github.event.repository.default_branch && 'latest') || github.ref_name }}
|
||||
# Docker Registry to use for Docker compose scripts below.
|
||||
# We use GitHub's Container Registry to avoid aggressive rate limits at DockerHub.
|
||||
DOCKER_REGISTRY: ghcr.io
|
||||
steps:
|
||||
# Checkout our codebase (to get access to Docker Compose scripts)
|
||||
- name: Checkout codebase
|
||||
uses: actions/checkout@v4
|
||||
# Download Docker image artifacts (which were just built by reusable-docker-build.yml)
|
||||
- name: Download Docker image artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
# Download all amd64 Docker images (TAR files) into the /tmp/docker directory
|
||||
pattern: docker-image-*-linux-amd64
|
||||
path: /tmp/docker
|
||||
merge-multiple: true
|
||||
# Load each of the images into Docker by calling "docker image load" for each.
|
||||
# This ensures we are using the images just built & not any prior versions on DockerHub
|
||||
- name: Load all downloaded Docker images
|
||||
run: |
|
||||
find /tmp/docker -type f -name "*.tar" -exec docker image load --input "{}" \;
|
||||
docker image ls -a
|
||||
# Start backend using our compose script in the codebase.
|
||||
- name: Start backend in Docker
|
||||
run: |
|
||||
docker compose -f docker-compose.yml up -d
|
||||
sleep 10
|
||||
docker container ls
|
||||
# Create a test admin account. Load test data from a simple set of AIPs as defined in cli.ingest.yml
|
||||
- name: Load test data into Backend
|
||||
run: |
|
||||
docker compose -f docker-compose-cli.yml run --rm dspace-cli create-administrator -e test@test.edu -f admin -l user -p admin -c en
|
||||
docker compose -f docker-compose-cli.yml -f dspace/src/main/docker-compose/cli.ingest.yml run --rm dspace-cli
|
||||
# Verify backend started successfully.
|
||||
# 1. Make sure root endpoint is responding (check for dspace.name defined in docker-compose.yml)
|
||||
# 2. Also check /collections endpoint to ensure the test data loaded properly (check for a collection name in AIPs)
|
||||
- name: Verify backend is responding properly
|
||||
run: |
|
||||
result=$(wget -O- -q http://127.0.0.1:8080/server/api)
|
||||
echo "$result"
|
||||
echo "$result" | grep -oE "\"DSpace Started with Docker Compose\","
|
||||
result=$(wget -O- -q http://127.0.0.1:8080/server/api/core/collections)
|
||||
echo "$result"
|
||||
echo "$result" | grep -oE "\"Dog in Yard\","
|
||||
# Verify Handle Server can be stared and is working properly
|
||||
# 1. First generate the "[dspace]/handle-server" folder with the sitebndl.zip
|
||||
# 2. Start the Handle Server (and wait 20 seconds to let it start up)
|
||||
# 3. Verify logs do NOT include "Exception" in the text (as that means an error occurred)
|
||||
# 4. Check that Handle Proxy HTML page is responding on default port (8000)
|
||||
- name: Verify Handle Server is working properly
|
||||
run: |
|
||||
docker exec -i dspace /dspace/bin/make-handle-config
|
||||
echo "Starting Handle Server..."
|
||||
docker exec -i dspace /dspace/bin/start-handle-server
|
||||
sleep 20
|
||||
echo "Checking for errors in error.log"
|
||||
result=$(docker exec -i dspace sh -c "cat /dspace/handle-server/logs/error.log* || echo ''")
|
||||
echo "$result"
|
||||
echo "$result" | grep -vqz "Exception"
|
||||
echo "Checking for errors in handle-server.log..."
|
||||
result=$(docker exec -i dspace cat /dspace/log/handle-server.log)
|
||||
echo "$result"
|
||||
echo "$result" | grep -vqz "Exception"
|
||||
echo "Checking to see if Handle Proxy webpage is available..."
|
||||
result=$(wget -O- -q http://127.0.0.1:8000/)
|
||||
echo "$result"
|
||||
echo "$result" | grep -oE "Handle Proxy"
|
||||
# Shutdown our containers
|
||||
- name: Shutdown Docker containers
|
||||
run: |
|
||||
docker compose -f docker-compose.yml down
|
26
.github/workflows/issue_opened.yml
vendored
26
.github/workflows/issue_opened.yml
vendored
@@ -1,26 +0,0 @@
|
||||
# This workflow runs whenever a new issue is created
|
||||
name: Issue opened
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
|
||||
permissions: {}
|
||||
jobs:
|
||||
automation:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# Add the new issue to a project board, if it needs triage
|
||||
# See https://github.com/actions/add-to-project
|
||||
- name: Add issue to triage board
|
||||
# Only add to project board if issue is flagged as "needs triage" or has no labels
|
||||
# NOTE: By default we flag new issues as "needs triage" in our issue template
|
||||
if: (contains(github.event.issue.labels.*.name, 'needs triage') || join(github.event.issue.labels.*.name) == '')
|
||||
uses: actions/add-to-project@v1.0.0
|
||||
# Note, the authentication token below is an ORG level Secret.
|
||||
# It must be created/recreated manually via a personal access token with admin:org, project, public_repo permissions
|
||||
# See: https://docs.github.com/en/actions/configuring-and-managing-workflows/authenticating-with-the-github_token#permissions-for-the-github_token
|
||||
# This is necessary because the "DSpace Backlog" project is an org level project (i.e. not repo specific)
|
||||
with:
|
||||
github-token: ${{ secrets.TRIAGE_PROJECT_TOKEN }}
|
||||
project-url: https://github.com/orgs/DSpace/projects/24
|
39
.github/workflows/label_merge_conflicts.yml
vendored
39
.github/workflows/label_merge_conflicts.yml
vendored
@@ -1,39 +0,0 @@
|
||||
# This workflow checks open PRs for merge conflicts and labels them when conflicts are found
|
||||
name: Check for merge conflicts
|
||||
|
||||
# Run this for all pushes (i.e. merges) to 'main' or maintenance branches
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- 'dspace-**'
|
||||
# So that the `conflict_label_name` is removed if conflicts are resolved,
|
||||
# we allow this to run for `pull_request_target` so that github secrets are available.
|
||||
pull_request_target:
|
||||
types: [ synchronize ]
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
triage:
|
||||
# Ensure this job never runs on forked repos. It's only executed for 'dspace/dspace'
|
||||
if: github.repository == 'dspace/dspace'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
pull-requests: write
|
||||
steps:
|
||||
# See: https://github.com/prince-chrismc/label-merge-conflicts-action
|
||||
- name: Auto-label PRs with merge conflicts
|
||||
uses: prince-chrismc/label-merge-conflicts-action@v3
|
||||
# Ignore any failures -- may occur (randomly?) for older, outdated PRs.
|
||||
continue-on-error: true
|
||||
# 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 }}
|
||||
conflict_comment: |
|
||||
Hi @${author},
|
||||
Conflicts have been detected against the base branch.
|
||||
Please [resolve these conflicts](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/addressing-merge-conflicts/about-merge-conflicts) as soon as you can. Thanks!
|
46
.github/workflows/port_merged_pull_request.yml
vendored
46
.github/workflows/port_merged_pull_request.yml
vendored
@@ -1,46 +0,0 @@
|
||||
# This workflow will attempt to port a merged pull request to
|
||||
# the branch specified in a "port to" label (if exists)
|
||||
name: Port merged Pull Request
|
||||
|
||||
# Only run for merged PRs against the "main" or maintenance branches
|
||||
# We allow this to run for `pull_request_target` so that github secrets are available
|
||||
# (This is required when the PR comes from a forked repo)
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [ closed ]
|
||||
branches:
|
||||
- main
|
||||
- 'dspace-**'
|
||||
|
||||
permissions:
|
||||
contents: write # so action can add comments
|
||||
pull-requests: write # so action can create pull requests
|
||||
|
||||
jobs:
|
||||
port_pr:
|
||||
runs-on: ubuntu-latest
|
||||
# Don't run on closed *unmerged* pull requests
|
||||
if: github.event.pull_request.merged
|
||||
steps:
|
||||
# Checkout code
|
||||
- uses: actions/checkout@v4
|
||||
# Port PR to other branch (ONLY if labeled with "port to")
|
||||
# See https://github.com/korthout/backport-action
|
||||
- name: Create backport pull requests
|
||||
uses: korthout/backport-action@v2
|
||||
with:
|
||||
# Trigger based on a "port to [branch]" label on PR
|
||||
# (This label must specify the branch name to port to)
|
||||
label_pattern: '^port to ([^ ]+)$'
|
||||
# Title to add to the (newly created) port PR
|
||||
pull_title: '[Port ${target_branch}] ${pull_title}'
|
||||
# Description to add to the (newly created) port PR
|
||||
pull_description: 'Port of #${pull_number} by @${pull_author} to `${target_branch}`.'
|
||||
# Copy all labels from original PR to (newly created) port PR
|
||||
# NOTE: The labels matching 'label_pattern' are automatically excluded
|
||||
copy_labels_pattern: '.*'
|
||||
# Skip any merge commits in the ported PR. This means only non-merge commits are cherry-picked to the new PR
|
||||
merge_commits: 'skip'
|
||||
# Use a personal access token (PAT) to create PR as 'dspace-bot' user.
|
||||
# A PAT is required in order for the new PR to trigger its own actions (for CI checks)
|
||||
github_token: ${{ secrets.PR_PORT_TOKEN }}
|
24
.github/workflows/pull_request_opened.yml
vendored
24
.github/workflows/pull_request_opened.yml
vendored
@@ -1,24 +0,0 @@
|
||||
# This workflow runs whenever a new pull request is created
|
||||
name: Pull Request opened
|
||||
|
||||
# Only run for newly opened PRs against the "main" or maintenance branches
|
||||
# We allow this to run for `pull_request_target` so that github secrets are available
|
||||
# (This is required to assign a PR back to the creator when the PR comes from a forked repo)
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [ opened ]
|
||||
branches:
|
||||
- main
|
||||
- 'dspace-**'
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
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/toshimaru/auto-author-assign
|
||||
- name: Assign PR to creator
|
||||
uses: toshimaru/auto-author-assign@v2.1.0
|
359
.github/workflows/reusable-docker-build.yml
vendored
359
.github/workflows/reusable-docker-build.yml
vendored
@@ -1,359 +0,0 @@
|
||||
#
|
||||
# DSpace's reusable Docker build/push workflow.
|
||||
#
|
||||
# This is used by docker.yml for all Docker image builds
|
||||
name: Reusable DSpace Docker Build
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
# Possible Inputs to this reusable job
|
||||
inputs:
|
||||
# Build name/id for this Docker build. Used for digest storage to avoid digest overlap between builds.
|
||||
build_id:
|
||||
required: true
|
||||
type: string
|
||||
# Requires the image name to build (e.g dspace/dspace-test)
|
||||
image_name:
|
||||
required: true
|
||||
type: string
|
||||
# Optionally the path to the Dockerfile to use for the build. (Default is [dockerfile_context]/Dockerfile)
|
||||
dockerfile_path:
|
||||
required: false
|
||||
type: string
|
||||
# Optionally the context directory to build the Dockerfile within. Defaults to "." (current directory)
|
||||
dockerfile_context:
|
||||
required: false
|
||||
type: string
|
||||
default: '.'
|
||||
# Optionally a list of "additional_contexts" to pass to Dockerfile. Defaults to empty
|
||||
dockerfile_additional_contexts:
|
||||
required: false
|
||||
type: string
|
||||
default: ''
|
||||
# If Docker image should have additional tag flavor details (e.g. a suffix), it may be passed in.
|
||||
tags_flavor:
|
||||
required: false
|
||||
type: string
|
||||
secrets:
|
||||
# Requires that Docker login info be passed in as secrets.
|
||||
DOCKER_USERNAME:
|
||||
required: true
|
||||
DOCKER_ACCESS_TOKEN:
|
||||
required: true
|
||||
# These URL secrets are optional. When specified & branch checks match, the redeployment code below will trigger.
|
||||
# Therefore builds which need to trigger redeployment MUST specify these URLs. All others should leave them empty.
|
||||
REDEPLOY_SANDBOX_URL:
|
||||
required: false
|
||||
REDEPLOY_DEMO_URL:
|
||||
required: false
|
||||
|
||||
# Define shared default settings as environment variables
|
||||
env:
|
||||
IMAGE_NAME: ${{ inputs.image_name }}
|
||||
# Define tags to use for Docker images based on Git tags/branches (for docker/metadata-action)
|
||||
# For a new commit on default branch (main), use the literal tag 'latest' on Docker image.
|
||||
# For a new commit on other branches, use the branch name as the tag for Docker image.
|
||||
# For a new tag, copy that tag name as the tag for Docker image.
|
||||
# For a pull request, use the name of the base branch that the PR was created against or "latest" (for main).
|
||||
# e.g. PR against 'main' will use "latest". a PR against 'dspace-7_x' will use 'dspace-7_x'.
|
||||
IMAGE_TAGS: |
|
||||
type=raw,value=latest,enable=${{ github.ref_name == github.event.repository.default_branch }}
|
||||
type=ref,event=branch,enable=${{ github.ref_name != github.event.repository.default_branch }}
|
||||
type=ref,event=tag
|
||||
type=raw,value=${{ (github.event.pull_request.base.ref == github.event.repository.default_branch && 'latest') || github.event.pull_request.base.ref }},enable=${{ github.event_name == 'pull_request' }}
|
||||
# Define default tag "flavor" for docker/metadata-action per
|
||||
# https://github.com/docker/metadata-action#flavor-input
|
||||
# We manage the 'latest' tag ourselves to the 'main' branch (see settings above)
|
||||
TAGS_FLAVOR: |
|
||||
latest=false
|
||||
${{ inputs.tags_flavor }}
|
||||
# When these URL variables are specified & required branch matches, then the sandbox or demo site will be redeployed.
|
||||
# See "Redeploy" steps below for more details.
|
||||
REDEPLOY_SANDBOX_URL: ${{ secrets.REDEPLOY_SANDBOX_URL }}
|
||||
REDEPLOY_DEMO_URL: ${{ secrets.REDEPLOY_DEMO_URL }}
|
||||
# Current DSpace maintenance branch (and architecture) which is deployed to demo.dspace.org / sandbox.dspace.org
|
||||
# (NOTE: No deployment branch specified for sandbox.dspace.org as it uses the default_branch)
|
||||
DEPLOY_DEMO_BRANCH: 'dspace-8_x'
|
||||
DEPLOY_SANDBOX_BRANCH: 'main'
|
||||
DEPLOY_ARCH: 'linux/amd64'
|
||||
# Registry used during building of Docker images. (All images are later copied to docker.io registry)
|
||||
# We use GitHub's Container Registry to avoid aggressive rate limits at DockerHub.
|
||||
DOCKER_BUILD_REGISTRY: ghcr.io
|
||||
|
||||
jobs:
|
||||
docker-build:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
# Architectures / Platforms for which we will build Docker images
|
||||
arch: [ 'linux/amd64', 'linux/arm64' ]
|
||||
os: [ ubuntu-latest ]
|
||||
isPr:
|
||||
- ${{ github.event_name == 'pull_request' }}
|
||||
# If this is a PR, we ONLY build for AMD64. For PRs we only do a sanity check test to ensure Docker builds work.
|
||||
# The below exclude therefore ensures we do NOT build ARM64 for PRs.
|
||||
exclude:
|
||||
- isPr: true
|
||||
os: ubuntu-latest
|
||||
arch: linux/arm64
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
# This step converts the slashes in the "arch" matrix values above into dashes & saves to env.ARCH_NAME
|
||||
# E.g. "linux/amd64" becomes "linux-amd64"
|
||||
# This is necessary because all upload artifacts CANNOT have special chars (like slashes)
|
||||
# NOTE: The regex-like syntax below is Bash Parameter Substitution
|
||||
- name: Prepare
|
||||
run: |
|
||||
platform=${{ matrix.arch }}
|
||||
echo "ARCH_NAME=${platform//\//-}" >> $GITHUB_ENV
|
||||
|
||||
# https://github.com/actions/checkout
|
||||
- name: Checkout codebase
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# https://github.com/docker/login-action
|
||||
# NOTE: This login occurs for BOTH non-PRs or PRs. PRs *must* also login to access private images from GHCR
|
||||
# during the build process
|
||||
- name: Login to ${{ env.DOCKER_BUILD_REGISTRY }}
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.DOCKER_BUILD_REGISTRY }}
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# https://github.com/docker/setup-qemu-action
|
||||
- name: Set up QEMU emulation to build for multiple architectures
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
# https://github.com/docker/setup-buildx-action
|
||||
- name: Setup Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
# https://github.com/docker/metadata-action
|
||||
# Extract metadata used for Docker images in all build steps below
|
||||
- name: Extract metadata (tags, labels) from GitHub for Docker image
|
||||
id: meta_build
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.DOCKER_BUILD_REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
tags: ${{ env.IMAGE_TAGS }}
|
||||
flavor: ${{ env.TAGS_FLAVOR }}
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
# First, for all branch commits (non-PRs) we build the image & upload
|
||||
# to GitHub Container Registry (GHCR). After uploading the image
|
||||
# to GHCR, we store the image digest in an artifact, so we can
|
||||
# create a merged manifest later (see 'docker-build_manifest' job).
|
||||
#
|
||||
# NOTE: We use GHCR in order to avoid aggressive rate limits at DockerHub.
|
||||
#--------------------------------------------------------------------
|
||||
# https://github.com/docker/build-push-action
|
||||
- name: Build and push image to ${{ env.DOCKER_BUILD_REGISTRY }}
|
||||
if: ${{ ! matrix.isPr }}
|
||||
id: docker_build
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
build-contexts: |
|
||||
${{ inputs.dockerfile_additional_contexts }}
|
||||
context: ${{ inputs.dockerfile_context }}
|
||||
file: ${{ inputs.dockerfile_path }}
|
||||
# Tell DSpace's Docker files to use the build registry instead of DockerHub
|
||||
build-args:
|
||||
DOCKER_REGISTRY=${{ env.DOCKER_BUILD_REGISTRY }}
|
||||
platforms: ${{ matrix.arch }}
|
||||
push: true
|
||||
# Use tags / labels provided by 'docker/metadata-action' above
|
||||
tags: ${{ steps.meta_build.outputs.tags }}
|
||||
labels: ${{ steps.meta_build.outputs.labels }}
|
||||
# Use GitHub cache to load cached Docker images and cache the results of this build
|
||||
# This decreases the number of images we need to fetch from DockerHub
|
||||
cache-from: type=gha,scope=${{ inputs.build_id }}
|
||||
cache-to: type=gha,scope=${{ inputs.build_id }},mode=max
|
||||
|
||||
# Export the digest of Docker build locally
|
||||
- name: Export Docker build digest
|
||||
if: ${{ ! matrix.isPr }}
|
||||
run: |
|
||||
mkdir -p /tmp/digests
|
||||
digest="${{ steps.docker_build.outputs.digest }}"
|
||||
touch "/tmp/digests/${digest#sha256:}"
|
||||
|
||||
# Upload digest to an artifact, so that it can be used in combined manifest below
|
||||
# (The purpose of the combined manifest is to list both amd64 and arm64 builds under same tag)
|
||||
- name: Upload Docker build digest to artifact
|
||||
if: ${{ ! matrix.isPr }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: digests-${{ inputs.build_id }}-${{ env.ARCH_NAME }}
|
||||
path: /tmp/digests/*
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Second, we build the image again in order to store it in a local TAR file.
|
||||
# This TAR of the image is cached/saved as an artifact, so that it can be used
|
||||
# by later jobs to install the brand-new images for automated testing.
|
||||
# This TAR build is performed BOTH for PRs and for branch commits (non-PRs).
|
||||
#
|
||||
# (This approach has the advantage of avoiding having to download the newly built
|
||||
# image from DockerHub or GHCR during automated testing.)
|
||||
#
|
||||
# See the 'docker-deploy' job in docker.yml as an example of where this TAR is used.
|
||||
#-------------------------------------------------------------------------------
|
||||
# Build local image (again) and store in a TAR file in /tmp directory
|
||||
# This step is only done for AMD64, as that's the only image we use in our automated testing (at this time).
|
||||
# NOTE: This step cannot be combined with the build above as it's a different type of output.
|
||||
- name: Build and push image to local TAR file
|
||||
if: ${{ matrix.arch == 'linux/amd64'}}
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
build-contexts: |
|
||||
${{ inputs.dockerfile_additional_contexts }}
|
||||
context: ${{ inputs.dockerfile_context }}
|
||||
file: ${{ inputs.dockerfile_path }}
|
||||
# Tell DSpace's Docker files to use the build registry instead of DockerHub
|
||||
build-args:
|
||||
DOCKER_REGISTRY=${{ env.DOCKER_BUILD_REGISTRY }}
|
||||
platforms: ${{ matrix.arch }}
|
||||
tags: ${{ steps.meta_build.outputs.tags }}
|
||||
labels: ${{ steps.meta_build.outputs.labels }}
|
||||
# Use GitHub cache to load cached Docker images and cache the results of this build
|
||||
# This decreases the number of images we need to fetch from DockerHub
|
||||
cache-from: type=gha,scope=${{ inputs.build_id }}
|
||||
cache-to: type=gha,scope=${{ inputs.build_id }},mode=max
|
||||
# Export image to a local TAR file
|
||||
outputs: type=docker,dest=/tmp/${{ inputs.build_id }}.tar
|
||||
|
||||
# Upload the local docker image (in TAR file) to a build Artifact
|
||||
# This step is only done for AMD64, as that's the only image we use in our automated testing (at this time).
|
||||
- name: Upload local image TAR to artifact
|
||||
if: ${{ matrix.arch == 'linux/amd64'}}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: docker-image-${{ inputs.build_id }}-${{ env.ARCH_NAME }}
|
||||
path: /tmp/${{ inputs.build_id }}.tar
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
|
||||
##########################################################################################
|
||||
# Merge Docker digests (from various architectures) into a single manifest.
|
||||
# This runs after all Docker builds complete above. The purpose is to include all builds
|
||||
# under a single manifest for this tag.
|
||||
# (e.g. both linux/amd64 and linux/arm64 should be listed under the same tagged Docker image)
|
||||
##########################################################################################
|
||||
docker-build_manifest:
|
||||
# Only run if this is NOT a PR
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- docker-build
|
||||
steps:
|
||||
- name: Download Docker build digests
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: /tmp/digests
|
||||
# Download digests for both AMD64 and ARM64 into same directory
|
||||
pattern: digests-${{ inputs.build_id }}-*
|
||||
merge-multiple: true
|
||||
|
||||
- name: Login to ${{ env.DOCKER_BUILD_REGISTRY }}
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.DOCKER_BUILD_REGISTRY }}
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Add Docker metadata for image
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.DOCKER_BUILD_REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
tags: ${{ env.IMAGE_TAGS }}
|
||||
flavor: ${{ env.TAGS_FLAVOR }}
|
||||
|
||||
- name: Create manifest list from digests and push to ${{ env.DOCKER_BUILD_REGISTRY }}
|
||||
working-directory: /tmp/digests
|
||||
run: |
|
||||
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
|
||||
$(printf '${{ env.DOCKER_BUILD_REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *)
|
||||
|
||||
- name: Inspect manifest in ${{ env.DOCKER_BUILD_REGISTRY }}
|
||||
run: |
|
||||
docker buildx imagetools inspect ${{ env.DOCKER_BUILD_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }}
|
||||
|
||||
##########################################################################################
|
||||
# Copy images / manifest to DockerHub.
|
||||
# This MUST run after *both* images (AMD64 and ARM64) are built and uploaded to GitHub
|
||||
# Container Registry (GHCR). Attempting to run this in parallel to GHCR builds can result
|
||||
# in a race condition...i.e. the copy to DockerHub may fail if GHCR image has been updated
|
||||
# at the moment when the copy occurs.
|
||||
##########################################################################################
|
||||
docker-copy_to_dockerhub:
|
||||
# Only run if this is NOT a PR
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- docker-build_manifest
|
||||
|
||||
steps:
|
||||
# 'regctl' is used to more easily copy the image to DockerHub and obtain the digest from DockerHub
|
||||
# See https://github.com/regclient/regclient/blob/main/docs/regctl.md
|
||||
- name: Install regctl for Docker registry tools
|
||||
uses: regclient/actions/regctl-installer@main
|
||||
with:
|
||||
release: 'v0.8.0'
|
||||
|
||||
# This recreates Docker tags for DockerHub
|
||||
- name: Add Docker metadata for image
|
||||
id: meta_dockerhub
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.IMAGE_NAME }}
|
||||
tags: ${{ env.IMAGE_TAGS }}
|
||||
flavor: ${{ env.TAGS_FLAVOR }}
|
||||
|
||||
# Login to source registry first, as this is where we are copying *from*
|
||||
- name: Login to ${{ env.DOCKER_BUILD_REGISTRY }}
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.DOCKER_BUILD_REGISTRY }}
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# Login to DockerHub, since this is where we are copying *to*
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
|
||||
|
||||
# Copy the image from source to DockerHub
|
||||
- name: Copy image from ${{ env.DOCKER_BUILD_REGISTRY }} to docker.io
|
||||
run: |
|
||||
regctl image copy ${{ env.DOCKER_BUILD_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta_dockerhub.outputs.version }} docker.io/${{ env.IMAGE_NAME }}:${{ steps.meta_dockerhub.outputs.version }}
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
# Finally, check whether demo.dspace.org or sandbox.dspace.org need
|
||||
# to be redeployed based on these new DockerHub images.
|
||||
#--------------------------------------------------------------------
|
||||
# If this build is for the branch that Sandbox uses and passed in a REDEPLOY_SANDBOX_URL secret,
|
||||
# Then redeploy https://sandbox.dspace.org
|
||||
- name: Redeploy sandbox.dspace.org (based on main branch)
|
||||
if: |
|
||||
env.REDEPLOY_SANDBOX_URL != '' &&
|
||||
github.ref_name == env.DEPLOY_SANDBOX_BRANCH
|
||||
run: |
|
||||
curl -X POST $REDEPLOY_SANDBOX_URL
|
||||
# If this build is for the branch that Demo uses and passed in a REDEPLOY_DEMO_URL secret,
|
||||
# Then redeploy https://demo.dspace.org
|
||||
- name: Redeploy demo.dspace.org (based on maintenance branch)
|
||||
if: |
|
||||
env.REDEPLOY_DEMO_URL != '' &&
|
||||
github.ref_name == env.DEPLOY_DEMO_BRANCH
|
||||
run: |
|
||||
curl -X POST $REDEPLOY_DEMO_URL
|
7
.gitignore
vendored
7
.gitignore
vendored
@@ -10,7 +10,6 @@ tags
|
||||
.project
|
||||
.classpath
|
||||
.checkstyle
|
||||
.factorypath
|
||||
|
||||
## Ignore project files created by IntelliJ IDEA
|
||||
*.iml
|
||||
@@ -20,7 +19,7 @@ tags
|
||||
overlays/
|
||||
|
||||
## Ignore project files created by NetBeans
|
||||
nbproject/
|
||||
nbproject/private/
|
||||
build/
|
||||
nbbuild/
|
||||
dist/
|
||||
@@ -43,7 +42,3 @@ nb-configuration.xml
|
||||
|
||||
##Ignore JRebel project configuration
|
||||
rebel.xml
|
||||
|
||||
|
||||
## Ignore jenv configuration
|
||||
.java-version
|
||||
|
@@ -1,9 +0,0 @@
|
||||
# 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
|
44
.travis.yml
Normal file
44
.travis.yml
Normal file
@@ -0,0 +1,44 @@
|
||||
language: java
|
||||
sudo: false
|
||||
|
||||
env:
|
||||
# Give Maven 1GB of memory to work with
|
||||
- MAVEN_OPTS=-Xmx1024M
|
||||
|
||||
jdk:
|
||||
# DS-3384 Oracle JDK 8 has DocLint enabled by default.
|
||||
# Let's use this to catch any newly introduced DocLint issues.
|
||||
- oraclejdk8
|
||||
|
||||
## 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
|
||||
|
||||
# Install prerequisites for building Mirage2 more rapidly
|
||||
before_install:
|
||||
# Remove outdated settings.xml from Travis builds. Workaround for https://github.com/travis-ci/travis-ci/issues/4629
|
||||
- rm ~/.m2/settings.xml
|
||||
|
||||
# Skip install stage, as we'll do it below
|
||||
install: "echo 'Skipping install stage, dependencies will be downloaded during build and test stages.'"
|
||||
|
||||
# Two stage Build and Test
|
||||
# 1. Install & Unit Test APIs
|
||||
# 2. Assemble DSpace
|
||||
script:
|
||||
# 1. [Install & Unit Test] Check source code licenses and run source code Unit Tests
|
||||
# license:check => Validate all source code license headers
|
||||
# -Dmaven.test.skip=false => Enable DSpace Unit Tests
|
||||
# -DskipITs=false => Enable DSpace Integration Tests
|
||||
# -P !assembly => Skip normal assembly (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 -P !assembly -B -V -Dsurefire.rerunFailingTestsCount=2"
|
||||
# 2. [Assemble DSpace] Ensure overlay & assembly process works (from [src]/dspace/)
|
||||
# -P !assembly => SKIP the actual building of [src]/dspace/dspace-installer (as it can be memory intensive)
|
||||
- "cd dspace && mvn package -P !assembly -B -V -Dsurefire.rerunFailingTestsCount=2"
|
@@ -1,45 +0,0 @@
|
||||
# How to Contribute
|
||||
|
||||
DSpace is a community built and supported project. We do not have a centralized development or support team, but have a dedicated group of volunteers who help us improve the software, documentation, resources, etc.
|
||||
|
||||
* [Contribute new code via a Pull Request](#contribute-new-code-via-a-pull-request)
|
||||
* [Contribute documentation](#contribute-documentation)
|
||||
* [Help others on mailing lists or Slack](#help-others-on-mailing-lists-or-slack)
|
||||
* [Join a working or interest group](#join-a-working-or-interest-group)
|
||||
|
||||
## Contribute new code via a Pull Request
|
||||
|
||||
We accept [GitHub Pull Requests (PRs)](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request-from-a-fork) at any time from anyone.
|
||||
Contributors to each release are recognized in our [Release Notes](https://wiki.lyrasis.org/display/DSDOC7x/Release+Notes).
|
||||
|
||||
Code Contribution Checklist
|
||||
- [ ] PRs _should_ be smaller in size (ideally less than 1,000 lines of code, not including comments & tests)
|
||||
- [ ] PRs **must** pass Checkstyle validation based on our [Code Style Guide](https://wiki.lyrasis.org/display/DSPACE/Code+Style+Guide).
|
||||
- [ ] PRs **must** include Javadoc for _all new/modified public methods and classes_. Larger private methods should also have Javadoc
|
||||
- [ ] PRs **must** pass all automated tests and include new/updated Unit or Integration tests based on our [Code Testing Guide](https://wiki.lyrasis.org/display/DSPACE/Code+Testing+Guide).
|
||||
- [ ] If a PR includes new libraries/dependencies (in any `pom.xml`), then their software licenses **must** align with the [DSpace BSD License](https://github.com/DSpace/DSpace/blob/main/LICENSE) based on the [Licensing of Contributions](https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines#CodeContributionGuidelines-LicensingofContributions) documentation.
|
||||
- [ ] Basic technical documentation _should_ be provided for any new features or changes to the REST API. REST API changes should be documented in our [Rest Contract](https://github.com/DSpace/RestContract).
|
||||
- [ ] If a PR fixes an issue ticket, please [link them together](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue).
|
||||
|
||||
Additional details on the code contribution process can be found in our [Code Contribution Guidelines](https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines)
|
||||
|
||||
## Contribute documentation
|
||||
|
||||
DSpace Documentation is a collaborative effort in a shared Wiki. The latest documentation is at https://wiki.lyrasis.org/display/DSDOC7x
|
||||
|
||||
If you find areas of the DSpace Documentation which you wish to improve, please request a Wiki account by emailing wikihelp@lyrasis.org.
|
||||
Once you have an account setup, contact @tdonohue (via [Slack](https://wiki.lyrasis.org/display/DSPACE/Slack) or email) for access to edit our Documentation.
|
||||
|
||||
## Help others on mailing lists or Slack
|
||||
|
||||
DSpace has our own [Slack](https://wiki.lyrasis.org/display/DSPACE/Slack) community and [Mailing Lists](https://wiki.lyrasis.org/display/DSPACE/Mailing+Lists) where discussions take place and questions are answered.
|
||||
Anyone is welcome to join and help others. We just ask you to follow our [Code of Conduct](https://www.lyrasis.org/about/Pages/Code-of-Conduct.aspx) (adopted via LYRASIS).
|
||||
|
||||
## Join a working or interest group
|
||||
|
||||
Most of the work in building/improving DSpace comes via [Working Groups](https://wiki.lyrasis.org/display/DSPACE/DSpace+Working+Groups) or [Interest Groups](https://wiki.lyrasis.org/display/DSPACE/DSpace+Interest+Groups).
|
||||
|
||||
All working/interest groups are open to anyone to join and participate. A few key groups to be aware of include:
|
||||
|
||||
* [DSpace 7 Working Group](https://wiki.lyrasis.org/display/DSPACE/DSpace+7+Working+Group) - This is the main (mostly volunteer) development team. We meet weekly to review our current development [project board](https://github.com/orgs/DSpace/projects), assigning tickets and/or PRs.
|
||||
* [DSpace Community Advisory Team (DCAT)](https://wiki.lyrasis.org/display/cmtygp/DSpace+Community+Advisory+Team) - This is an interest group for repository managers/administrators. We meet monthly to discuss DSpace, share tips & provide feedback back to developers.
|
78
Dockerfile
78
Dockerfile
@@ -1,78 +0,0 @@
|
||||
# This image will be published as dspace/dspace
|
||||
# See https://github.com/DSpace/DSpace/tree/main/dspace/src/main/docker for usage details
|
||||
#
|
||||
# - note: default tag for branch: dspace/dspace: dspace/dspace:dspace-7_x
|
||||
|
||||
# This Dockerfile uses JDK11 by default, but has also been tested with JDK17.
|
||||
# To build with JDK17, use "--build-arg JDK_VERSION=17"
|
||||
ARG JDK_VERSION=11
|
||||
# The Docker version tag to build from
|
||||
ARG DSPACE_VERSION=dspace-7_x
|
||||
# The Docker registry to use for DSpace images. Defaults to "docker.io"
|
||||
# NOTE: non-DSpace images are hardcoded to use "docker.io" and are not impacted by this build argument
|
||||
ARG DOCKER_REGISTRY=docker.io
|
||||
|
||||
# Step 1 - Run Maven Build
|
||||
FROM ${DOCKER_REGISTRY}/dspace/dspace-dependencies:${DSPACE_VERSION} AS build
|
||||
ARG TARGET_DIR=dspace-installer
|
||||
WORKDIR /app
|
||||
# The dspace-installer directory will be written to /install
|
||||
RUN mkdir /install \
|
||||
&& chown -Rv dspace: /install \
|
||||
&& chown -Rv dspace: /app
|
||||
USER dspace
|
||||
# Copy the DSpace source code (from local machine) into the workdir (excluding .dockerignore contents)
|
||||
ADD --chown=dspace . /app/
|
||||
# Build DSpace (note: this build doesn't include the optional, deprecated "dspace-rest" webapp)
|
||||
# Copy the dspace-installer directory to /install. Clean up the build to keep the docker image small
|
||||
# Maven flags here ensure that we skip building test environment and skip all code verification checks.
|
||||
# These flags speed up this compilation as much as reasonably possible.
|
||||
ENV MAVEN_FLAGS="-P-test-environment -Denforcer.skip=true -Dcheckstyle.skip=true -Dlicense.skip=true -Dxml.skip=true"
|
||||
RUN mvn --no-transfer-progress package ${MAVEN_FLAGS} && \
|
||||
mv /app/dspace/target/${TARGET_DIR}/* /install && \
|
||||
mvn clean
|
||||
|
||||
# Step 2 - Run Ant Deploy
|
||||
FROM docker.io/eclipse-temurin:${JDK_VERSION} AS ant_build
|
||||
ARG TARGET_DIR=dspace-installer
|
||||
# COPY the /install directory from 'build' container to /dspace-src in this container
|
||||
COPY --from=build /install /dspace-src
|
||||
WORKDIR /dspace-src
|
||||
# Create the initial install deployment using ANT
|
||||
ENV ANT_VERSION=1.10.13
|
||||
ENV ANT_HOME=/tmp/ant-$ANT_VERSION
|
||||
ENV PATH=$ANT_HOME/bin:$PATH
|
||||
# Download and install 'ant'
|
||||
RUN mkdir $ANT_HOME && \
|
||||
curl --silent --show-error --location --fail --retry 5 --output /tmp/apache-ant.tar.gz \
|
||||
https://archive.apache.org/dist/ant/binaries/apache-ant-${ANT_VERSION}-bin.tar.gz && \
|
||||
tar -zx --strip-components=1 -f /tmp/apache-ant.tar.gz -C $ANT_HOME && \
|
||||
rm /tmp/apache-ant.tar.gz
|
||||
# Run necessary 'ant' deploy scripts
|
||||
RUN ant init_installation update_configs update_code update_webapps
|
||||
|
||||
# Step 3 - Run tomcat
|
||||
# Create a new tomcat image that does not retain the the build directory contents
|
||||
FROM docker.io/tomcat:9-jdk${JDK_VERSION}
|
||||
# NOTE: DSPACE_INSTALL must align with the "dspace.dir" default configuration.
|
||||
ENV DSPACE_INSTALL=/dspace
|
||||
# Copy the /dspace directory from 'ant_build' container to /dspace in this container
|
||||
COPY --from=ant_build /dspace $DSPACE_INSTALL
|
||||
# Need host command for "[dspace]/bin/make-handle-config"
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends host \
|
||||
&& apt-get purge -y --auto-remove \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
# Expose Tomcat port (8080) and AJP port (8009) and Handle Server HTTP port (8000)
|
||||
EXPOSE 8080 8009 8000
|
||||
# Give java extra memory (2GB)
|
||||
ENV JAVA_OPTS=-Xmx2000m
|
||||
|
||||
# Link the DSpace 'server' webapp into Tomcat's webapps directory.
|
||||
# This ensures that when we start Tomcat, it runs from /server path (e.g. http://localhost:8080/server/)
|
||||
RUN ln -s $DSPACE_INSTALL/webapps/server /usr/local/tomcat/webapps/server
|
||||
# If you wish to run "server" webapp off the ROOT path, then comment out the above RUN, and uncomment the below RUN.
|
||||
# You also MUST update the 'dspace.server.url' configuration to match.
|
||||
# Please note that server webapp should only run on one path at a time.
|
||||
#RUN mv /usr/local/tomcat/webapps/ROOT /usr/local/tomcat/webapps/ROOT.bk && \
|
||||
# ln -s $DSPACE_INSTALL/webapps/server /usr/local/tomcat/webapps/ROOT
|
@@ -1,62 +0,0 @@
|
||||
# This image will be published as dspace/dspace-cli
|
||||
# See https://github.com/DSpace/DSpace/tree/main/dspace/src/main/docker for usage details
|
||||
#
|
||||
# - note: default tag for branch: dspace/dspace-cli: dspace/dspace-cli:dspace-7_x
|
||||
|
||||
# This Dockerfile uses JDK11 by default, but has also been tested with JDK17.
|
||||
# To build with JDK17, use "--build-arg JDK_VERSION=17"
|
||||
ARG JDK_VERSION=11
|
||||
# The Docker version tag to build from
|
||||
ARG DSPACE_VERSION=dspace-7_x
|
||||
# The Docker registry to use for DSpace images. Defaults to "docker.io"
|
||||
# NOTE: non-DSpace images are hardcoded to use "docker.io" and are not impacted by this build argument
|
||||
ARG DOCKER_REGISTRY=docker.io
|
||||
|
||||
# Step 1 - Run Maven Build
|
||||
FROM ${DOCKER_REGISTRY}/dspace/dspace-dependencies:${DSPACE_VERSION} AS build
|
||||
ARG TARGET_DIR=dspace-installer
|
||||
WORKDIR /app
|
||||
# The dspace-installer directory will be written to /install
|
||||
RUN mkdir /install \
|
||||
&& chown -Rv dspace: /install \
|
||||
&& chown -Rv dspace: /app
|
||||
USER dspace
|
||||
# Copy the DSpace source code (from local machine) into the workdir (excluding .dockerignore contents)
|
||||
ADD --chown=dspace . /app/
|
||||
# Build DSpace. Copy the dspace-installer directory to /install. Clean up the build to keep the docker image small
|
||||
RUN mvn --no-transfer-progress package && \
|
||||
mv /app/dspace/target/${TARGET_DIR}/* /install && \
|
||||
mvn clean
|
||||
|
||||
# Step 2 - Run Ant Deploy
|
||||
FROM docker.io/eclipse-temurin:${JDK_VERSION} AS ant_build
|
||||
ARG TARGET_DIR=dspace-installer
|
||||
# COPY the /install directory from 'build' container to /dspace-src in this container
|
||||
COPY --from=build /install /dspace-src
|
||||
WORKDIR /dspace-src
|
||||
# Create the initial install deployment using ANT
|
||||
ENV ANT_VERSION=1.10.13
|
||||
ENV ANT_HOME=/tmp/ant-$ANT_VERSION
|
||||
ENV PATH=$ANT_HOME/bin:$PATH
|
||||
# Download and install 'ant'
|
||||
RUN mkdir $ANT_HOME && \
|
||||
curl --silent --show-error --location --fail --retry 5 --output /tmp/apache-ant.tar.gz \
|
||||
https://archive.apache.org/dist/ant/binaries/apache-ant-${ANT_VERSION}-bin.tar.gz && \
|
||||
tar -zx --strip-components=1 -f /tmp/apache-ant.tar.gz -C $ANT_HOME && \
|
||||
rm /tmp/apache-ant.tar.gz
|
||||
# Run necessary 'ant' deploy scripts
|
||||
RUN ant init_installation update_configs update_code
|
||||
|
||||
# Step 3 - Run jdk
|
||||
FROM docker.io/eclipse-temurin:${JDK_VERSION}
|
||||
# NOTE: DSPACE_INSTALL must align with the "dspace.dir" default configuration.
|
||||
ENV DSPACE_INSTALL=/dspace
|
||||
# Copy the /dspace directory from 'ant_build' container to /dspace in this container
|
||||
COPY --from=ant_build /dspace $DSPACE_INSTALL
|
||||
# Give java extra memory (1GB)
|
||||
ENV JAVA_OPTS=-Xmx1000m
|
||||
# Install unzip for AIPs
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends unzip \
|
||||
&& apt-get purge -y --auto-remove \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
@@ -1,78 +0,0 @@
|
||||
# This image will be published as dspace/dspace-dependencies
|
||||
# The purpose of this image is to make the build for dspace/dspace run faster
|
||||
#
|
||||
|
||||
# This Dockerfile uses JDK11 by default, but has also been tested with JDK17.
|
||||
# To build with JDK17, use "--build-arg JDK_VERSION=17"
|
||||
ARG JDK_VERSION=11
|
||||
|
||||
# Step 1 - Download all Dependencies
|
||||
FROM docker.io/maven:3-eclipse-temurin-${JDK_VERSION} AS build
|
||||
ARG TARGET_DIR=dspace-installer
|
||||
WORKDIR /app
|
||||
# Create the 'dspace' user account & home directory
|
||||
RUN useradd dspace \
|
||||
&& mkdir -p /home/dspace \
|
||||
&& chown -Rv dspace: /home/dspace
|
||||
RUN chown -Rv dspace: /app
|
||||
|
||||
# Switch to dspace user & run below commands as that user
|
||||
USER dspace
|
||||
|
||||
# This next part may look odd, but it speeds up the build of this image *significantly*.
|
||||
# Copy ONLY the POMs to this image (from local machine). This will allow us to download all dependencies *without*
|
||||
# performing any code compilation steps.
|
||||
|
||||
# Parent POM
|
||||
ADD --chown=dspace pom.xml /app/
|
||||
RUN mkdir -p /app/dspace
|
||||
|
||||
# 'dspace' module POM. Includes 'additions' ONLY, as it's the only submodule that is required to exist.
|
||||
ADD --chown=dspace dspace/pom.xml /app/dspace/
|
||||
RUN mkdir -p /app/dspace/modules/
|
||||
ADD --chown=dspace dspace/modules/pom.xml /app/dspace/modules/
|
||||
RUN mkdir -p /app/dspace/modules/additions
|
||||
ADD --chown=dspace dspace/modules/additions/pom.xml /app/dspace/modules/additions/
|
||||
|
||||
# 'dspace-api' module POM
|
||||
RUN mkdir -p /app/dspace-api
|
||||
ADD --chown=dspace dspace-api/pom.xml /app/dspace-api/
|
||||
|
||||
# 'dspace-iiif' module POM
|
||||
RUN mkdir -p /app/dspace-iiif
|
||||
ADD --chown=dspace dspace-iiif/pom.xml /app/dspace-iiif/
|
||||
|
||||
# 'dspace-oai' module POM
|
||||
RUN mkdir -p /app/dspace-oai
|
||||
ADD --chown=dspace dspace-oai/pom.xml /app/dspace-oai/
|
||||
|
||||
# 'dspace-rdf' module POM
|
||||
RUN mkdir -p /app/dspace-rdf
|
||||
ADD --chown=dspace dspace-rdf/pom.xml /app/dspace-rdf/
|
||||
|
||||
# 'dspace-server-webapp' module POM
|
||||
RUN mkdir -p /app/dspace-server-webapp
|
||||
ADD --chown=dspace dspace-server-webapp/pom.xml /app/dspace-server-webapp/
|
||||
|
||||
# 'dspace-services' module POM
|
||||
RUN mkdir -p /app/dspace-services
|
||||
ADD --chown=dspace dspace-services/pom.xml /app/dspace-services/
|
||||
|
||||
# 'dspace-sword' module POM
|
||||
RUN mkdir -p /app/dspace-sword
|
||||
ADD --chown=dspace dspace-sword/pom.xml /app/dspace-sword/
|
||||
|
||||
# 'dspace-swordv2' module POM
|
||||
RUN mkdir -p /app/dspace-swordv2
|
||||
ADD --chown=dspace dspace-swordv2/pom.xml /app/dspace-swordv2/
|
||||
|
||||
# Trigger the installation of all maven dependencies (hide download progress messages)
|
||||
# Maven flags here ensure that we skip final assembly, skip building test environment and skip all code verification checks.
|
||||
# These flags speed up this installation and skip tasks we cannot perform as we don't have the full source code.
|
||||
ENV MAVEN_FLAGS="-P-assembly -P-test-environment -Denforcer.skip=true -Dcheckstyle.skip=true -Dlicense.skip=true -Dxjc.skip=true -Dxml.skip=true"
|
||||
RUN mvn --no-transfer-progress verify ${MAVEN_FLAGS}
|
||||
|
||||
# Clear the contents of the /app directory (including all maven target folders), so no artifacts remain.
|
||||
# This ensures when dspace:dspace is built, it will use the Maven local cache (~/.m2) for dependencies
|
||||
USER root
|
||||
RUN rm -rf /app/*
|
@@ -1,90 +0,0 @@
|
||||
# This image will be published as dspace/dspace
|
||||
# See https://github.com/DSpace/DSpace/tree/main/dspace/src/main/docker for usage details
|
||||
#
|
||||
# - note: default tag for branch: dspace/dspace: dspace/dspace:dspace-7_x-test
|
||||
#
|
||||
# This image is meant for TESTING/DEVELOPMENT ONLY as it deploys the old v6 REST API under HTTP (not HTTPS)
|
||||
|
||||
# This Dockerfile uses JDK11 by default, but has also been tested with JDK17.
|
||||
# To build with JDK17, use "--build-arg JDK_VERSION=17"
|
||||
ARG JDK_VERSION=11
|
||||
# The Docker version tag to build from
|
||||
ARG DSPACE_VERSION=dspace-7_x
|
||||
# The Docker registry to use for DSpace images. Defaults to "docker.io"
|
||||
# NOTE: non-DSpace images are hardcoded to use "docker.io" and are not impacted by this build argument
|
||||
ARG DOCKER_REGISTRY=docker.io
|
||||
|
||||
# Step 1 - Run Maven Build
|
||||
FROM ${DOCKER_REGISTRY}/dspace/dspace-dependencies:${DSPACE_VERSION} AS build
|
||||
ARG TARGET_DIR=dspace-installer
|
||||
WORKDIR /app
|
||||
# The dspace-installer directory will be written to /install
|
||||
RUN mkdir /install \
|
||||
&& chown -Rv dspace: /install \
|
||||
&& chown -Rv dspace: /app
|
||||
USER dspace
|
||||
# Copy the DSpace source code (from local machine) into the workdir (excluding .dockerignore contents)
|
||||
ADD --chown=dspace . /app/
|
||||
# Build DSpace (INCLUDING the optional, deprecated "dspace-rest" webapp)
|
||||
# Copy the dspace-installer directory to /install. Clean up the build to keep the docker image small
|
||||
RUN mvn --no-transfer-progress package -Pdspace-rest && \
|
||||
mv /app/dspace/target/${TARGET_DIR}/* /install && \
|
||||
mvn clean
|
||||
|
||||
# Step 2 - Run Ant Deploy
|
||||
FROM docker.io/eclipse-temurin:${JDK_VERSION} AS ant_build
|
||||
ARG TARGET_DIR=dspace-installer
|
||||
# COPY the /install directory from 'build' container to /dspace-src in this container
|
||||
COPY --from=build /install /dspace-src
|
||||
WORKDIR /dspace-src
|
||||
# Create the initial install deployment using ANT
|
||||
ENV ANT_VERSION=1.10.12
|
||||
ENV ANT_HOME=/tmp/ant-$ANT_VERSION
|
||||
ENV PATH=$ANT_HOME/bin:$PATH
|
||||
# Download and install 'ant'
|
||||
RUN mkdir $ANT_HOME && \
|
||||
curl --silent --show-error --location --fail --retry 5 --output /tmp/apache-ant.tar.gz \
|
||||
https://archive.apache.org/dist/ant/binaries/apache-ant-${ANT_VERSION}-bin.tar.gz && \
|
||||
tar -zx --strip-components=1 -f /tmp/apache-ant.tar.gz -C $ANT_HOME && \
|
||||
rm /tmp/apache-ant.tar.gz
|
||||
# Run necessary 'ant' deploy scripts
|
||||
RUN ant init_installation update_configs update_code update_webapps
|
||||
|
||||
# Step 3 - Run tomcat
|
||||
# Create a new tomcat image that does not retain the the build directory contents
|
||||
FROM docker.io/tomcat:9-jdk${JDK_VERSION}
|
||||
ENV DSPACE_INSTALL=/dspace
|
||||
ENV TOMCAT_INSTALL=/usr/local/tomcat
|
||||
# Copy the /dspace directory from 'ant_build' containger to /dspace in this container
|
||||
COPY --from=ant_build /dspace $DSPACE_INSTALL
|
||||
# Need host command for "[dspace]/bin/make-handle-config"
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends host \
|
||||
&& apt-get purge -y --auto-remove \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
# Enable the AJP connector in Tomcat's server.xml
|
||||
# NOTE: secretRequired="false" should only be used when AJP is NOT accessible from an external network. But, secretRequired="true" isn't supported by mod_proxy_ajp until Apache 2.5
|
||||
RUN sed -i '/Service name="Catalina".*/a \\n <Connector protocol="AJP/1.3" port="8009" address="0.0.0.0" redirectPort="8443" URIEncoding="UTF-8" secretRequired="false" />' $TOMCAT_INSTALL/conf/server.xml
|
||||
# Expose Tomcat port and AJP port
|
||||
EXPOSE 8080 8009 8000
|
||||
# Give java extra memory (2GB)
|
||||
ENV JAVA_OPTS=-Xmx2000m
|
||||
# Set up debugging
|
||||
ENV CATALINA_OPTS=-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=*:8000
|
||||
|
||||
# Link the DSpace 'server' webapp into Tomcat's webapps directory.
|
||||
# This ensures that when we start Tomcat, it runs from /server path (e.g. http://localhost:8080/server/)
|
||||
# Also link the v6.x (deprecated) REST API off the "/rest" path
|
||||
RUN ln -s $DSPACE_INSTALL/webapps/server /usr/local/tomcat/webapps/server && \
|
||||
ln -s $DSPACE_INSTALL/webapps/rest /usr/local/tomcat/webapps/rest
|
||||
# If you wish to run "server" webapp off the ROOT path, then comment out the above RUN, and uncomment the below RUN.
|
||||
# You also MUST update the 'dspace.server.url' configuration to match.
|
||||
# Please note that server webapp should only run on one path at a time.
|
||||
#RUN mv /usr/local/tomcat/webapps/ROOT /usr/local/tomcat/webapps/ROOT.bk && \
|
||||
# ln -s $DSPACE_INSTALL/webapps/server /usr/local/tomcat/webapps/ROOT && \
|
||||
# ln -s $DSPACE_INSTALL/webapps/rest /usr/local/tomcat/webapps/rest
|
||||
|
||||
# Overwrite the v6.x (deprecated) REST API's web.xml, so that we can run it on HTTP (defaults to requiring HTTPS)
|
||||
# WARNING: THIS IS OBVIOUSLY INSECURE. NEVER DO THIS IN PRODUCTION.
|
||||
COPY dspace/src/main/docker/test/rest_web.xml $DSPACE_INSTALL/webapps/rest/WEB-INF/web.xml
|
||||
RUN sed -i -e "s|\${dspace.dir}|$DSPACE_INSTALL|" $DSPACE_INSTALL/webapps/rest/WEB-INF/web.xml
|
23
LICENSE
23
LICENSE
@@ -1,6 +1,7 @@
|
||||
BSD 3-Clause License
|
||||
DSpace source code license:
|
||||
|
||||
Copyright (c) 2002-2021, LYRASIS. All rights reserved.
|
||||
|
||||
Copyright (c) 2002-2016, DuraSpace. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
@@ -13,12 +14,13 @@ notice, this list of conditions and the following disclaimer.
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
- Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
- Neither the name DuraSpace nor the name of the DSpace Foundation
|
||||
nor the names of its contributors may be used to endorse or promote
|
||||
products derived from this software without specific prior written
|
||||
permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
@@ -28,4 +30,11 @@ OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
|
||||
TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
||||
USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
||||
DAMAGE.
|
||||
DAMAGE.
|
||||
|
||||
|
||||
DSpace uses third-party libraries which may be distributed under
|
||||
different licenses to the above. Information about these licenses
|
||||
is detailed in the LICENSES_THIRD_PARTY file at the root of the source
|
||||
tree. You must agree to the terms of these licenses, in addition to
|
||||
the above DSpace source code license, in order to use this software.
|
||||
|
File diff suppressed because it is too large
Load Diff
31
NOTICE
31
NOTICE
@@ -1,28 +1,15 @@
|
||||
Licenses of Third-Party Libraries
|
||||
=================================
|
||||
|
||||
DSpace uses third-party libraries which may be distributed under
|
||||
different licenses than specified in our LICENSE file. Information
|
||||
about these licenses is detailed in the LICENSES_THIRD_PARTY file at
|
||||
the root of the source tree. You must agree to the terms of these
|
||||
licenses, in addition to the DSpace source code license, in order to
|
||||
use this software.
|
||||
Licensing Notice
|
||||
|
||||
Licensing Notices
|
||||
=================
|
||||
|
||||
[July 2019] DuraSpace joined with LYRASIS (another 501(c)3 organization) in July 2019.
|
||||
LYRASIS holds the copyrights of DuraSpace.
|
||||
|
||||
[July 2009] Fedora Commons joined with the DSpace Foundation and began operating under
|
||||
Fedora Commons joined with the DSpace Foundation and began operating under
|
||||
the new name DuraSpace in July 2009. DuraSpace holds the copyrights of
|
||||
the DSpace Foundation, Inc.
|
||||
|
||||
[July 2007] The DSpace Foundation, Inc. is a 501(c)3 corporation established in July 2007
|
||||
with a mission to promote and advance the dspace platform enabling management,
|
||||
access and preservation of digital works. The Foundation was able to transfer
|
||||
the legal copyright from Hewlett-Packard Company (HP) and Massachusetts
|
||||
Institute of Technology (MIT) to the DSpace Foundation in October 2007. Many
|
||||
of the files in the source code may contain a copyright statement stating HP
|
||||
and MIT possess the copyright, in these instances please note that the copy
|
||||
The DSpace Foundation, Inc. is a 501(c)3 corporation established in July 2007
|
||||
with a mission to promote and advance the dspace platform enabling management,
|
||||
access and preservation of digital works. The Foundation was able to transfer
|
||||
the legal copyright from Hewlett-Packard Company (HP) and Massachusetts
|
||||
Institute of Technology (MIT) to the DSpace Foundation in October 2007. Many
|
||||
of the files in the source code may contain a copyright statement stating HP
|
||||
and MIT possess the copyright, in these instances please note that the copy
|
||||
right has transferred to the DSpace foundation, and subsequently to DuraSpace.
|
||||
|
127
README.md
127
README.md
@@ -1,54 +1,62 @@
|
||||
|
||||
# DSpace
|
||||
|
||||
[](https://github.com/DSpace/DSpace/actions?query=workflow%3ABuild)
|
||||
## NOTE: The rest-tutorial branch has been created to support the [DSpace 7 REST documentation](https://dspace-labs.github.io/DSpace7RestTutorial/walkthrough/intro)
|
||||
- This branch provides stable, referencable line numbers in code
|
||||
|
||||
[DSpace Documentation](https://wiki.lyrasis.org/display/DSDOC/) |
|
||||
[](https://travis-ci.org/DSpace/DSpace)
|
||||
|
||||
[DSpace Documentation](https://wiki.duraspace.org/display/DSDOC/) |
|
||||
[DSpace Releases](https://github.com/DSpace/DSpace/releases) |
|
||||
[DSpace Wiki](https://wiki.lyrasis.org/display/DSPACE/Home) |
|
||||
[Support](https://wiki.lyrasis.org/display/DSPACE/Support)
|
||||
[DSpace Wiki](https://wiki.duraspace.org/display/DSPACE/Home) |
|
||||
[Support](https://wiki.duraspace.org/display/DSPACE/Support)
|
||||
|
||||
## Overview
|
||||
|
||||
DSpace open source software is a turnkey repository application used by more than
|
||||
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/
|
||||
|
||||
DSpace consists of both a Java-based backend and an Angular-based frontend.
|
||||
***
|
||||
: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-spring-rest) of this repository.
|
||||
* The REST Contract is being documented 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.
|
||||
|
||||
* Backend (this codebase) provides a REST API, along with other machine-based interfaces (e.g. OAI-PMH, SWORD, etc)
|
||||
* The REST Contract is at https://github.com/DSpace/RestContract
|
||||
* Frontend (https://github.com/DSpace/dspace-angular/) is the User Interface built on the REST API
|
||||
|
||||
Prior versions of DSpace (v6.x and below) used two different UIs (XMLUI and JSPUI). Those UIs are no longer supported in v7 (and above).
|
||||
* A maintenance branch for older versions is still available, see `dspace-6_x` for 6.x maintenance.
|
||||
**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.
|
||||
***
|
||||
|
||||
## Downloads
|
||||
|
||||
* Backend (REST API): https://github.com/DSpace/DSpace/releases
|
||||
* Frontend (User Interface): https://github.com/DSpace/dspace-angular/releases
|
||||
The latest release of DSpace can be downloaded from the [DSpace website](http://www.dspace.org/latest-release/) or from [GitHub](https://github.com/DSpace/DSpace/releases).
|
||||
|
||||
Past releases are all available via GitHub at https://github.com/DSpace/DSpace/releases
|
||||
|
||||
## Documentation / Installation
|
||||
|
||||
Documentation for each release may be viewed online or downloaded via our [Documentation Wiki](https://wiki.lyrasis.org/display/DSDOC/).
|
||||
Documentation for each release may be viewed online or downloaded via our [Documentation Wiki](https://wiki.duraspace.org/display/DSDOC/).
|
||||
|
||||
The latest DSpace Installation instructions are available at:
|
||||
https://wiki.lyrasis.org/display/DSDOC7x/Installing+DSpace
|
||||
https://wiki.duraspace.org/display/DSDOC6x/Installing+DSpace
|
||||
|
||||
Please be aware that, as a Java web application, DSpace requires a database (PostgreSQL)
|
||||
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.
|
||||
More information about these and all other prerequisites can be found in the Installation instructions above.
|
||||
|
||||
## Running DSpace 7 in Docker
|
||||
|
||||
NOTE: At this time, we do not have production-ready Docker images for DSpace.
|
||||
That said, we do have quick-start Docker Compose scripts for development or testing purposes.
|
||||
|
||||
See [Running DSpace 7 with Docker Compose](dspace/src/main/docker-compose/README.md)
|
||||
|
||||
## Contributing
|
||||
|
||||
See [Contributing documentation](CONTRIBUTING.md)
|
||||
DSpace is a community built and supported project. We do not have a centralized development or support team,
|
||||
but have a dedicated group of volunteers who help us improve the software, documentation, resources, etc.
|
||||
|
||||
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).
|
||||
|
||||
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.
|
||||
|
||||
In addition, a listing of all known contributors to DSpace software can be
|
||||
found online at: https://wiki.duraspace.org/display/DSPACE/DSpaceContributors
|
||||
|
||||
## Getting Help
|
||||
|
||||
@@ -56,75 +64,22 @@ 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.lyrasis.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.duraspace.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 at https://wiki.lyrasis.org/display/DSPACE/Support
|
||||
Additional support options are listed at https://wiki.duraspace.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
|
||||
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
|
||||
[Registered Service Providers](http://www.dspace.org/service-providers).
|
||||
|
||||
## Issue Tracker
|
||||
|
||||
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 [GitHub Actions](https://github.com/DSpace/DSpace/actions?query=workflow%3ABuild) for all Pull Requests and code commits.
|
||||
|
||||
* How to run both Unit Tests (via `maven-surefire-plugin`) and Integration Tests (via `maven-failsafe-plugin`):
|
||||
```
|
||||
mvn install -DskipUnitTests=false -DskipIntegrationTests=false
|
||||
```
|
||||
* How to run _only_ Unit Tests:
|
||||
```
|
||||
mvn test -DskipUnitTests=false
|
||||
```
|
||||
* How to run a *single* Unit Test
|
||||
```
|
||||
# Run all tests in a specific test class
|
||||
# NOTE: failIfNoTests=false is required to skip tests in other modules
|
||||
mvn test -DskipUnitTests=false -Dtest=[full.package.testClassName] -DfailIfNoTests=false
|
||||
|
||||
# Run one test method in a specific test class
|
||||
mvn test -DskipUnitTests=false -Dtest=[full.package.testClassName]#[testMethodName] -DfailIfNoTests=false
|
||||
```
|
||||
* How to run _only_ Integration Tests
|
||||
```
|
||||
mvn install -DskipIntegrationTests=false
|
||||
```
|
||||
* How to run a *single* Integration Test
|
||||
```
|
||||
# Run all integration tests in a specific test class
|
||||
# NOTE: failIfNoTests=false is required to skip tests in other modules
|
||||
mvn install -DskipIntegrationTests=false -Dit.test=[full.package.testClassName] -DfailIfNoTests=false
|
||||
|
||||
# Run one test method in a specific test class
|
||||
mvn install -DskipIntegrationTests=false -Dit.test=[full.package.testClassName]#[testMethodName] -DfailIfNoTests=false
|
||||
```
|
||||
* How to run only tests of a specific DSpace module
|
||||
```
|
||||
# Before you can run only one module's tests, other modules may need installing into your ~/.m2
|
||||
cd [dspace-src]
|
||||
mvn clean install
|
||||
|
||||
# Then, move into a module subdirectory, and run the test command
|
||||
cd [dspace-src]/dspace-server-webapp
|
||||
# Choose your test command from the lists above
|
||||
```
|
||||
The DSpace Issue Tracker can be found at: https://jira.duraspace.org/projects/DS/summary
|
||||
|
||||
## 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 in the [LICENSE](LICENSE) file or online at http://www.dspace.org/license/
|
||||
|
||||
DSpace uses third-party libraries which may be distributed under different licenses. Those licenses are listed
|
||||
in the [LICENSES_THIRD_PARTY](LICENSES_THIRD_PARTY) file.
|
||||
The full license is available at http://www.dspace.org/license/
|
||||
|
15
SECURITY.md
15
SECURITY.md
@@ -1,15 +0,0 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
For information regarding which versions of DSpace are currently under support, please see our DSpace Software Support Policy:
|
||||
|
||||
https://wiki.lyrasis.org/display/DSPACE/DSpace+Software+Support+Policy
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
If you believe you have found a security vulnerability in a supported version of DSpace, we encourage you to let us know right away.
|
||||
We will investigate all legitimate reports and do our best to quickly fix the problem. Please see our DSpace Software Support Policy
|
||||
for information on privately reporting vulnerabilities:
|
||||
|
||||
https://wiki.lyrasis.org/display/DSPACE/DSpace+Software+Support+Policy
|
@@ -44,16 +44,15 @@ For more information on CheckStyle configurations below, see: http://checkstyle.
|
||||
with @SuppressWarnings. See also SuppressWarningsHolder below -->
|
||||
<module name="SuppressWarningsFilter" />
|
||||
|
||||
<!-- Maximum line length is 120 characters -->
|
||||
<module name="LineLength">
|
||||
<property name="fileExtensions" value="java"/>
|
||||
<property name="max" value="120"/>
|
||||
<!-- Only exceptions for packages, imports, URLs, and JavaDoc {@link} tags -->
|
||||
<property name="ignorePattern" value="^package.*|^import.*|http://|https://|@link"/>
|
||||
</module>
|
||||
|
||||
<!-- Check individual Java source files for specific rules -->
|
||||
<module name="TreeWalker">
|
||||
<!-- Maximum line length is 120 characters -->
|
||||
<module name="LineLength">
|
||||
<property name="max" value="120"/>
|
||||
<!-- Only exceptions for packages, imports, URLs, and JavaDoc {@link} tags -->
|
||||
<property name="ignorePattern" value="^package.*|^import.*|http://|https://|@link"/>
|
||||
</module>
|
||||
|
||||
<!-- Highlight any TODO or FIXME comments in info messages -->
|
||||
<module name="TodoComment">
|
||||
<property name="severity" value="info"/>
|
||||
@@ -92,9 +91,14 @@ For more information on CheckStyle configurations below, see: http://checkstyle.
|
||||
<!-- Requirements for Javadocs for methods -->
|
||||
<module name="JavadocMethod">
|
||||
<!-- All public methods MUST HAVE Javadocs -->
|
||||
<property name="accessModifiers" value="public"/>
|
||||
<!-- <property name="scope" value="public"/> -->
|
||||
<!-- TODO: Above rule has been disabled because of large amount of missing public method Javadocs -->
|
||||
<property name="scope" value="nothing"/>
|
||||
<!-- Allow RuntimeExceptions to be undeclared -->
|
||||
<property name="allowUndeclaredRTE" value="true"/>
|
||||
<!-- Allow params, throws and return tags to be optional -->
|
||||
<property name="allowMissingParamTags" value="true"/>
|
||||
<property name="allowMissingThrowsTags" value="true"/>
|
||||
<property name="allowMissingReturnTag" value="true"/>
|
||||
</module>
|
||||
|
||||
|
@@ -1,37 +0,0 @@
|
||||
networks:
|
||||
# Default to using network named 'dspacenet' from docker-compose.yml.
|
||||
# Its full name will be prepended with the project name (e.g. "-p d7" means it will be named "d7_dspacenet")
|
||||
default:
|
||||
name: ${COMPOSE_PROJECT_NAME}_dspacenet
|
||||
external: true
|
||||
services:
|
||||
dspace-cli:
|
||||
image: "${DOCKER_REGISTRY:-docker.io}/${DOCKER_OWNER:-dspace}/dspace-cli:${DSPACE_VER:-dspace-7_x}"
|
||||
container_name: dspace-cli
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.cli
|
||||
environment:
|
||||
# Below syntax may look odd, but it is how to override dspace.cfg settings via env variables.
|
||||
# See https://github.com/DSpace/DSpace/blob/main/dspace/config/config-definition.xml
|
||||
# __P__ => "." (e.g. dspace__P__dir => dspace.dir)
|
||||
# __D__ => "-" (e.g. google__D__metadata => google-metadata)
|
||||
# dspace.dir: Must match with Dockerfile's DSPACE_INSTALL directory.
|
||||
dspace__P__dir: /dspace
|
||||
# db.url: Ensure we are using the 'dspacedb' image for our database
|
||||
db__P__url: 'jdbc:postgresql://dspacedb:5432/dspace'
|
||||
# solr.server: Ensure we are using the 'dspacesolr' image for Solr
|
||||
solr__P__server: http://dspacesolr:8983/solr
|
||||
volumes:
|
||||
# Keep DSpace assetstore directory between reboots
|
||||
- assetstore:/dspace/assetstore
|
||||
# Mount local [src]/dspace/config/ to container. This syncs your local configs with container
|
||||
# NOTE: Environment variables specified above will OVERRIDE any configs in local.cfg or dspace.cfg
|
||||
- ./dspace/config:/dspace/config
|
||||
entrypoint: /dspace/bin/dspace
|
||||
command: help
|
||||
tty: true
|
||||
stdin_open: true
|
||||
|
||||
volumes:
|
||||
assetstore:
|
@@ -1,129 +0,0 @@
|
||||
networks:
|
||||
dspacenet:
|
||||
ipam:
|
||||
config:
|
||||
# Define a custom subnet for our DSpace network, so that we can easily trust requests from host to container.
|
||||
# If you customize this value, be sure to customize the 'proxies.trusted.ipranges' env variable below.
|
||||
- subnet: 172.23.0.0/16
|
||||
services:
|
||||
# DSpace (backend) webapp container
|
||||
dspace:
|
||||
container_name: dspace
|
||||
environment:
|
||||
# Below syntax may look odd, but it is how to override dspace.cfg settings via env variables.
|
||||
# See https://github.com/DSpace/DSpace/blob/main/dspace/config/config-definition.xml
|
||||
# __P__ => "." (e.g. dspace__P__dir => dspace.dir)
|
||||
# __D__ => "-" (e.g. google__D__metadata => google-metadata)
|
||||
# dspace.dir: Must match with Dockerfile's DSPACE_INSTALL directory.
|
||||
dspace__P__dir: /dspace
|
||||
# Uncomment to set a non-default value for dspace.server.url or dspace.ui.url
|
||||
# dspace__P__server__P__url: http://localhost:8080/server
|
||||
# dspace__P__ui__P__url: http://localhost:4000
|
||||
dspace__P__name: 'DSpace Started with Docker Compose'
|
||||
# db.url: Ensure we are using the 'dspacedb' image for our database
|
||||
db__P__url: 'jdbc:postgresql://dspacedb:5432/dspace'
|
||||
# solr.server: Ensure we are using the 'dspacesolr' image for Solr
|
||||
solr__P__server: http://dspacesolr:8983/solr
|
||||
# proxies.trusted.ipranges: This setting is required for a REST API running in Docker to trust requests
|
||||
# from the host machine. This IP range MUST correspond to the 'dspacenet' subnet defined above.
|
||||
proxies__P__trusted__P__ipranges: '172.23.0'
|
||||
LOGGING_CONFIG: /dspace/config/log4j2-container.xml
|
||||
image: "${DOCKER_REGISTRY:-docker.io}/${DOCKER_OWNER:-dspace}/dspace:${DSPACE_VER:-dspace-7_x-test}"
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.test
|
||||
depends_on:
|
||||
- dspacedb
|
||||
networks:
|
||||
- dspacenet
|
||||
ports:
|
||||
- published: 8080
|
||||
target: 8080
|
||||
- published: 8009
|
||||
target: 8009
|
||||
- published: 8000
|
||||
target: 8000
|
||||
stdin_open: true
|
||||
tty: true
|
||||
volumes:
|
||||
# Keep DSpace assetstore directory between reboots
|
||||
- assetstore:/dspace/assetstore
|
||||
# Mount local [src]/dspace/config/ to container. This syncs your local configs with container
|
||||
# NOTE: Environment variables specified above will OVERRIDE any configs in local.cfg or dspace.cfg
|
||||
- ./dspace/config:/dspace/config
|
||||
# Ensure that the database is ready BEFORE starting tomcat
|
||||
# 1. While a TCP connection to dspacedb port 5432 is not available, continue to sleep
|
||||
# 2. Then, run database migration to init database tables
|
||||
# 3. Finally, start Tomcat
|
||||
entrypoint:
|
||||
- /bin/bash
|
||||
- '-c'
|
||||
- |
|
||||
while (!</dev/tcp/dspacedb/5432) > /dev/null 2>&1; do sleep 1; done;
|
||||
/dspace/bin/dspace database migrate
|
||||
catalina.sh run
|
||||
# DSpace PostgreSQL database container
|
||||
dspacedb:
|
||||
container_name: dspacedb
|
||||
# Uses a custom Postgres image with pgcrypto installed
|
||||
image: "${DOCKER_REGISTRY:-docker.io}/${DOCKER_OWNER:-dspace}/dspace-postgres-pgcrypto:${DSPACE_VER:-dspace-7_x}"
|
||||
build:
|
||||
# Must build out of subdirectory to have access to install script for pgcrypto
|
||||
context: ./dspace/src/main/docker/dspace-postgres-pgcrypto/
|
||||
environment:
|
||||
PGDATA: /pgdata
|
||||
POSTGRES_PASSWORD: dspace
|
||||
networks:
|
||||
dspacenet:
|
||||
ports:
|
||||
- published: 5432
|
||||
target: 5432
|
||||
stdin_open: true
|
||||
tty: true
|
||||
volumes:
|
||||
# Keep Postgres data directory between reboots
|
||||
- pgdata:/pgdata
|
||||
# DSpace Solr container
|
||||
dspacesolr:
|
||||
container_name: dspacesolr
|
||||
image: "${DOCKER_REGISTRY:-docker.io}/${DOCKER_OWNER:-dspace}/dspace-solr:${DSPACE_VER:-dspace-7_x}"
|
||||
build:
|
||||
context: ./dspace/src/main/docker/dspace-solr/
|
||||
# Provide path to Solr configs necessary to build Docker image
|
||||
additional_contexts:
|
||||
solrconfigs: ./dspace/solr/
|
||||
args:
|
||||
SOLR_VERSION: "${SOLR_VER:-8.11}"
|
||||
networks:
|
||||
dspacenet:
|
||||
ports:
|
||||
- published: 8983
|
||||
target: 8983
|
||||
stdin_open: true
|
||||
tty: true
|
||||
working_dir: /var/solr/data
|
||||
volumes:
|
||||
# Keep Solr data directory between reboots
|
||||
- solr_data:/var/solr/data
|
||||
# Initialize all DSpace Solr cores then start Solr:
|
||||
# * First, run precreate-core to create the core (if it doesn't yet exist). If exists already, this is a no-op
|
||||
# * Second, copy configsets to this core:
|
||||
# Updates to Solr configs require the container to be rebuilt/restarted: `docker compose -p d7 up -d --build dspacesolr`
|
||||
entrypoint:
|
||||
- /bin/bash
|
||||
- '-c'
|
||||
- |
|
||||
init-var-solr
|
||||
precreate-core authority /opt/solr/server/solr/configsets/authority
|
||||
cp -r /opt/solr/server/solr/configsets/authority/* authority
|
||||
precreate-core oai /opt/solr/server/solr/configsets/oai
|
||||
cp -r /opt/solr/server/solr/configsets/oai/* oai
|
||||
precreate-core search /opt/solr/server/solr/configsets/search
|
||||
cp -r /opt/solr/server/solr/configsets/search/* search
|
||||
precreate-core statistics /opt/solr/server/solr/configsets/statistics
|
||||
cp -r /opt/solr/server/solr/configsets/statistics/* statistics
|
||||
exec solr -f
|
||||
volumes:
|
||||
assetstore:
|
||||
pgdata:
|
||||
solr_data:
|
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,163 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.solr.handler.extraction;
|
||||
|
||||
|
||||
/**
|
||||
* The various Solr Parameters names to use when extracting content.
|
||||
**/
|
||||
public interface ExtractingParams {
|
||||
|
||||
/**
|
||||
* Map all generated attribute names to field names with lowercase and underscores.
|
||||
*/
|
||||
public static final String LOWERNAMES = "lowernames";
|
||||
|
||||
/**
|
||||
* if true, ignore TikaException (give up to extract text but index meta data)
|
||||
*/
|
||||
public static final String IGNORE_TIKA_EXCEPTION = "ignoreTikaException";
|
||||
|
||||
|
||||
/**
|
||||
* The param prefix for mapping Tika metadata to Solr fields.
|
||||
* <p>
|
||||
* To map a field, add a name like:
|
||||
* <pre>fmap.title=solr.title</pre>
|
||||
*
|
||||
* In this example, the tika "title" metadata value will be added to a Solr field named "solr.title"
|
||||
*/
|
||||
public static final String MAP_PREFIX = "fmap.";
|
||||
|
||||
/**
|
||||
* The boost value for the name of the field. The boost can be specified by a name mapping.
|
||||
* <p>
|
||||
* For example
|
||||
* <pre>
|
||||
* map.title=solr.title
|
||||
* boost.solr.title=2.5
|
||||
* </pre>
|
||||
* will boost the solr.title field for this document by 2.5
|
||||
*/
|
||||
public static final String BOOST_PREFIX = "boost.";
|
||||
|
||||
/**
|
||||
* Pass in literal values to be added to the document, as in
|
||||
* <pre>
|
||||
* literal.myField=Foo
|
||||
* </pre>
|
||||
*/
|
||||
public static final String LITERALS_PREFIX = "literal.";
|
||||
|
||||
|
||||
/**
|
||||
* Restrict the extracted parts of a document to be indexed
|
||||
* by passing in an XPath expression. All content that satisfies the XPath expr.
|
||||
* will be passed to the {@link org.apache.solr.handler.extraction.SolrContentHandler}.
|
||||
* <p>
|
||||
* See Tika's docs for what the extracted document looks like.
|
||||
*
|
||||
* @see #CAPTURE_ELEMENTS
|
||||
*/
|
||||
public static final String XPATH_EXPRESSION = "xpath";
|
||||
|
||||
|
||||
/**
|
||||
* Only extract and return the content, do not index it.
|
||||
*/
|
||||
public static final String EXTRACT_ONLY = "extractOnly";
|
||||
|
||||
/**
|
||||
* Content output format if extractOnly is true. Default is "xml", alternative is "text".
|
||||
*/
|
||||
public static final String EXTRACT_FORMAT = "extractFormat";
|
||||
|
||||
/**
|
||||
* Capture attributes separately according to the name of the element, instead of just adding them to the string
|
||||
* buffer
|
||||
*/
|
||||
public static final String CAPTURE_ATTRIBUTES = "captureAttr";
|
||||
|
||||
/**
|
||||
* Literal field values will by default override other values such as metadata and content. Set this to false to
|
||||
* revert to pre-4.0 behaviour
|
||||
*/
|
||||
public static final String LITERALS_OVERRIDE = "literalsOverride";
|
||||
|
||||
/**
|
||||
* Capture the specified fields (and everything included below it that isn't capture by some other capture field)
|
||||
* separately from the default. This is different
|
||||
* then the case of passing in an XPath expression.
|
||||
* <p>
|
||||
* The Capture field is based on the localName returned to the
|
||||
* {@link org.apache.solr.handler.extraction.SolrContentHandler}
|
||||
* by Tika, not to be confused by the mapped field. The field name can then
|
||||
* be mapped into the index schema.
|
||||
* <p>
|
||||
* For instance, a Tika document may look like:
|
||||
* <pre>
|
||||
* <html>
|
||||
* ...
|
||||
* <body>
|
||||
* <p>some text here. <div>more text</div></p>
|
||||
* Some more text
|
||||
* </body>
|
||||
* </pre>
|
||||
* By passing in the p tag, you could capture all P tags separately from the rest of the t
|
||||
* Thus, in the example, the capture of the P tag would be: "some text here. more text"
|
||||
*/
|
||||
public static final String CAPTURE_ELEMENTS = "capture";
|
||||
|
||||
/**
|
||||
* The type of the stream. If not specified, Tika will use mime type detection.
|
||||
*/
|
||||
public static final String STREAM_TYPE = "stream.type";
|
||||
|
||||
|
||||
/**
|
||||
* Optional. The file name. If specified, Tika can take this into account while
|
||||
* guessing the MIME type.
|
||||
*/
|
||||
public static final String RESOURCE_NAME = "resource.name";
|
||||
|
||||
/**
|
||||
* Optional. The password for this resource. Will be used instead of the rule based password lookup mechanisms
|
||||
*/
|
||||
public static final String RESOURCE_PASSWORD = "resource.password";
|
||||
|
||||
/**
|
||||
* Optional. If specified, the prefix will be prepended to all Metadata, such that it would be possible
|
||||
* to setup a dynamic field to automatically capture it
|
||||
*/
|
||||
public static final String UNKNOWN_FIELD_PREFIX = "uprefix";
|
||||
|
||||
/**
|
||||
* Optional. If specified and the name of a potential field cannot be determined, the default Field specified
|
||||
* will be used instead.
|
||||
*/
|
||||
public static final String DEFAULT_FIELD = "defaultField";
|
||||
|
||||
/**
|
||||
* Optional. If specified, loads the file as a source for password lookups for Tika encrypted documents.
|
||||
* <p>
|
||||
* File format is Java properties format with one key=value per line.
|
||||
* The key is evaluated as a regex against the file name, and the value is the password
|
||||
* The rules are evaluated top-bottom, i.e. the first match will be used
|
||||
* If you want a fallback password to be always used, supply a .*=<defaultmypassword> at the end
|
||||
*/
|
||||
public static final String PASSWORD_MAP_FILE = "passwordsFile";
|
||||
}
|
@@ -1,42 +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.access.status;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.Date;
|
||||
|
||||
import org.dspace.content.Item;
|
||||
import org.dspace.core.Context;
|
||||
|
||||
/**
|
||||
* Plugin interface for the access status calculation.
|
||||
*/
|
||||
public interface AccessStatusHelper {
|
||||
/**
|
||||
* Calculate the access status for the item.
|
||||
*
|
||||
* @param context the DSpace context
|
||||
* @param item the item
|
||||
* @param threshold the embargo threshold date
|
||||
* @return an access status value
|
||||
* @throws SQLException An exception that provides information on a database access error or other errors.
|
||||
*/
|
||||
public String getAccessStatusFromItem(Context context, Item item, Date threshold)
|
||||
throws SQLException;
|
||||
|
||||
/**
|
||||
* Retrieve embargo information for the item
|
||||
*
|
||||
* @param context the DSpace context
|
||||
* @param item the item to check for embargo information
|
||||
* @param threshold the embargo threshold date
|
||||
* @return an embargo date
|
||||
* @throws SQLException An exception that provides information on a database access error or other errors.
|
||||
*/
|
||||
public String getEmbargoFromItem(Context context, Item item, Date threshold) throws SQLException;
|
||||
}
|
@@ -1,71 +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.access.status;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.Date;
|
||||
|
||||
import org.dspace.access.status.service.AccessStatusService;
|
||||
import org.dspace.content.Item;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.core.service.PluginService;
|
||||
import org.dspace.services.ConfigurationService;
|
||||
import org.joda.time.LocalDate;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
/**
|
||||
* Implementation for the access status calculation service.
|
||||
*/
|
||||
public class AccessStatusServiceImpl implements AccessStatusService {
|
||||
// Plugin implementation, set from the DSpace configuration by init().
|
||||
protected AccessStatusHelper helper = null;
|
||||
|
||||
protected Date forever_date = null;
|
||||
|
||||
@Autowired(required = true)
|
||||
protected ConfigurationService configurationService;
|
||||
|
||||
@Autowired(required = true)
|
||||
protected PluginService pluginService;
|
||||
|
||||
/**
|
||||
* Initialize the bean (after dependency injection has already taken place).
|
||||
* Ensures the configurationService is injected, so that we can get the plugin
|
||||
* and the forever embargo date threshold from the configuration.
|
||||
* Called by "init-method" in Spring configuration.
|
||||
*
|
||||
* @throws Exception on generic exception
|
||||
*/
|
||||
public void init() throws Exception {
|
||||
if (helper == null) {
|
||||
helper = (AccessStatusHelper) pluginService.getSinglePlugin(AccessStatusHelper.class);
|
||||
if (helper == null) {
|
||||
throw new IllegalStateException("The AccessStatusHelper plugin was not defined in "
|
||||
+ "DSpace configuration.");
|
||||
}
|
||||
|
||||
// Defines the embargo forever date threshold for the access status.
|
||||
// Look at EmbargoService.FOREVER for some improvements?
|
||||
int year = configurationService.getIntProperty("access.status.embargo.forever.year");
|
||||
int month = configurationService.getIntProperty("access.status.embargo.forever.month");
|
||||
int day = configurationService.getIntProperty("access.status.embargo.forever.day");
|
||||
|
||||
forever_date = new LocalDate(year, month, day).toDate();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAccessStatus(Context context, Item item) throws SQLException {
|
||||
return helper.getAccessStatusFromItem(context, item, forever_date);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEmbargoFromItem(Context context, Item item) throws SQLException {
|
||||
return helper.getEmbargoFromItem(context, item, forever_date);
|
||||
}
|
||||
}
|
@@ -1,248 +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.access.status;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.dspace.authorize.ResourcePolicy;
|
||||
import org.dspace.authorize.factory.AuthorizeServiceFactory;
|
||||
import org.dspace.authorize.service.AuthorizeService;
|
||||
import org.dspace.authorize.service.ResourcePolicyService;
|
||||
import org.dspace.content.Bitstream;
|
||||
import org.dspace.content.Bundle;
|
||||
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.core.Context;
|
||||
import org.dspace.eperson.Group;
|
||||
import org.joda.time.LocalDate;
|
||||
|
||||
/**
|
||||
* Default plugin implementation of the access status helper.
|
||||
* The getAccessStatusFromItem method provides a simple logic to
|
||||
* calculate the access status of an item based on the policies of
|
||||
* the primary or the first bitstream in the original bundle.
|
||||
* Users can override this method for enhanced functionality.
|
||||
*
|
||||
* The getEmbargoInformationFromItem method provides a simple logic to
|
||||
* * retrieve embargo information of bitstreams from an item based on the policies of
|
||||
* * the primary or the first bitstream in the original bundle.
|
||||
* * Users can override this method for enhanced functionality.
|
||||
*/
|
||||
public class DefaultAccessStatusHelper implements AccessStatusHelper {
|
||||
public static final String EMBARGO = "embargo";
|
||||
public static final String METADATA_ONLY = "metadata.only";
|
||||
public static final String OPEN_ACCESS = "open.access";
|
||||
public static final String RESTRICTED = "restricted";
|
||||
public static final String UNKNOWN = "unknown";
|
||||
|
||||
protected ItemService itemService =
|
||||
ContentServiceFactory.getInstance().getItemService();
|
||||
protected ResourcePolicyService resourcePolicyService =
|
||||
AuthorizeServiceFactory.getInstance().getResourcePolicyService();
|
||||
protected AuthorizeService authorizeService =
|
||||
AuthorizeServiceFactory.getInstance().getAuthorizeService();
|
||||
|
||||
public DefaultAccessStatusHelper() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Look at the item's policies to determine an access status value.
|
||||
* It is also considering a date threshold for embargoes and restrictions.
|
||||
*
|
||||
* If the item is null, simply returns the "unknown" value.
|
||||
*
|
||||
* @param context the DSpace context
|
||||
* @param item the item to check for embargoes
|
||||
* @param threshold the embargo threshold date
|
||||
* @return an access status value
|
||||
*/
|
||||
@Override
|
||||
public String getAccessStatusFromItem(Context context, Item item, Date threshold)
|
||||
throws SQLException {
|
||||
if (item == null) {
|
||||
return UNKNOWN;
|
||||
}
|
||||
// Consider only the original bundles.
|
||||
List<Bundle> bundles = item.getBundles(Constants.DEFAULT_BUNDLE_NAME);
|
||||
// Check for primary bitstreams first.
|
||||
Bitstream bitstream = bundles.stream()
|
||||
.map(bundle -> bundle.getPrimaryBitstream())
|
||||
.filter(Objects::nonNull)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
if (bitstream == null) {
|
||||
// If there is no primary bitstream,
|
||||
// take the first bitstream in the bundles.
|
||||
bitstream = bundles.stream()
|
||||
.map(bundle -> bundle.getBitstreams())
|
||||
.flatMap(List::stream)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
return calculateAccessStatusForDso(context, bitstream, threshold);
|
||||
}
|
||||
|
||||
/**
|
||||
* Look at the DSpace object's policies to determine an access status value.
|
||||
*
|
||||
* If the object is null, returns the "metadata.only" value.
|
||||
* If any policy attached to the object is valid for the anonymous group,
|
||||
* returns the "open.access" value.
|
||||
* Otherwise, if the policy start date is before the embargo threshold date,
|
||||
* returns the "embargo" value.
|
||||
* Every other cases return the "restricted" value.
|
||||
*
|
||||
* @param context the DSpace context
|
||||
* @param dso the DSpace object
|
||||
* @param threshold the embargo threshold date
|
||||
* @return an access status value
|
||||
*/
|
||||
private String calculateAccessStatusForDso(Context context, DSpaceObject dso, Date threshold)
|
||||
throws SQLException {
|
||||
if (dso == null) {
|
||||
return METADATA_ONLY;
|
||||
}
|
||||
// Only consider read policies.
|
||||
List<ResourcePolicy> policies = authorizeService
|
||||
.getPoliciesActionFilter(context, dso, Constants.READ);
|
||||
int openAccessCount = 0;
|
||||
int embargoCount = 0;
|
||||
int restrictedCount = 0;
|
||||
int unknownCount = 0;
|
||||
// Looks at all read policies.
|
||||
for (ResourcePolicy policy : policies) {
|
||||
boolean isValid = resourcePolicyService.isDateValid(policy);
|
||||
Group group = policy.getGroup();
|
||||
// The group must not be null here. However,
|
||||
// if it is, consider this as an unexpected case.
|
||||
if (group == null) {
|
||||
unknownCount++;
|
||||
} else if (StringUtils.equals(group.getName(), Group.ANONYMOUS)) {
|
||||
// Only calculate the status for the anonymous group.
|
||||
if (isValid) {
|
||||
// If the policy is valid, the anonymous group have access
|
||||
// to the bitstream.
|
||||
openAccessCount++;
|
||||
} else {
|
||||
Date startDate = policy.getStartDate();
|
||||
if (startDate != null && !startDate.before(threshold)) {
|
||||
// If the policy start date have a value and if this value
|
||||
// is equal or superior to the configured forever date, the
|
||||
// access status is also restricted.
|
||||
restrictedCount++;
|
||||
} else {
|
||||
// If the current date is not between the policy start date
|
||||
// and end date, the access status is embargo.
|
||||
embargoCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (openAccessCount > 0) {
|
||||
return OPEN_ACCESS;
|
||||
}
|
||||
if (embargoCount > 0 && restrictedCount == 0) {
|
||||
return EMBARGO;
|
||||
}
|
||||
if (unknownCount > 0) {
|
||||
return UNKNOWN;
|
||||
}
|
||||
return RESTRICTED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Look at the policies of the primary (or first) bitstream of the item to retrieve its embargo.
|
||||
*
|
||||
* If the item is null, simply returns an empty map with no embargo information.
|
||||
*
|
||||
* @param context the DSpace context
|
||||
* @param item the item to embargo
|
||||
* @return an access status value
|
||||
*/
|
||||
@Override
|
||||
public String getEmbargoFromItem(Context context, Item item, Date threshold)
|
||||
throws SQLException {
|
||||
Date embargoDate;
|
||||
|
||||
// If Item status is not "embargo" then return a null embargo date.
|
||||
String accessStatus = getAccessStatusFromItem(context, item, threshold);
|
||||
|
||||
if (item == null || !accessStatus.equals(EMBARGO)) {
|
||||
return null;
|
||||
}
|
||||
// Consider only the original bundles.
|
||||
List<Bundle> bundles = item.getBundles(Constants.DEFAULT_BUNDLE_NAME);
|
||||
// Check for primary bitstreams first.
|
||||
Bitstream bitstream = bundles.stream()
|
||||
.map(bundle -> bundle.getPrimaryBitstream())
|
||||
.filter(Objects::nonNull)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
if (bitstream == null) {
|
||||
// If there is no primary bitstream,
|
||||
// take the first bitstream in the bundles.
|
||||
bitstream = bundles.stream()
|
||||
.map(bundle -> bundle.getBitstreams())
|
||||
.flatMap(List::stream)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
if (bitstream == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
embargoDate = this.retrieveShortestEmbargo(context, bitstream);
|
||||
|
||||
return embargoDate != null ? embargoDate.toString() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private Date retrieveShortestEmbargo(Context context, Bitstream bitstream) throws SQLException {
|
||||
Date embargoDate = null;
|
||||
// Only consider read policies.
|
||||
List<ResourcePolicy> policies = authorizeService
|
||||
.getPoliciesActionFilter(context, bitstream, Constants.READ);
|
||||
|
||||
// Looks at all read policies.
|
||||
for (ResourcePolicy policy : policies) {
|
||||
boolean isValid = resourcePolicyService.isDateValid(policy);
|
||||
Group group = policy.getGroup();
|
||||
|
||||
if (group != null && StringUtils.equals(group.getName(), Group.ANONYMOUS)) {
|
||||
// Only calculate the status for the anonymous group.
|
||||
if (!isValid) {
|
||||
// If the policy is not valid there is an active embargo
|
||||
Date startDate = policy.getStartDate();
|
||||
|
||||
if (startDate != null && !startDate.before(LocalDate.now().toDate())) {
|
||||
// There is an active embargo: aim to take the shortest embargo (account for rare cases where
|
||||
// more than one resource policy exists)
|
||||
if (embargoDate == null) {
|
||||
embargoDate = startDate;
|
||||
} else {
|
||||
embargoDate = startDate.before(embargoDate) ? startDate : embargoDate;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return embargoDate;
|
||||
}
|
||||
}
|
@@ -1,25 +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.access.status.factory;
|
||||
|
||||
import org.dspace.access.status.service.AccessStatusService;
|
||||
import org.dspace.services.factory.DSpaceServicesFactory;
|
||||
|
||||
/**
|
||||
* Abstract factory to get services for the access status package,
|
||||
* use AccessStatusServiceFactory.getInstance() to retrieve an implementation.
|
||||
*/
|
||||
public abstract class AccessStatusServiceFactory {
|
||||
|
||||
public abstract AccessStatusService getAccessStatusService();
|
||||
|
||||
public static AccessStatusServiceFactory getInstance() {
|
||||
return DSpaceServicesFactory.getInstance().getServiceManager()
|
||||
.getServiceByName("accessStatusServiceFactory", AccessStatusServiceFactory.class);
|
||||
}
|
||||
}
|
@@ -1,26 +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.access.status.factory;
|
||||
|
||||
import org.dspace.access.status.service.AccessStatusService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
/**
|
||||
* Factory implementation to get services for the access status package,
|
||||
* use AccessStatusServiceFactory.getInstance() to retrieve an implementation.
|
||||
*/
|
||||
public class AccessStatusServiceFactoryImpl extends AccessStatusServiceFactory {
|
||||
|
||||
@Autowired(required = true)
|
||||
private AccessStatusService accessStatusService;
|
||||
|
||||
@Override
|
||||
public AccessStatusService getAccessStatusService() {
|
||||
return accessStatusService;
|
||||
}
|
||||
}
|
@@ -1,30 +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/
|
||||
*/
|
||||
/**
|
||||
* <p>
|
||||
* Access status allows the users to view the bitstreams availability before
|
||||
* browsing into the item itself.
|
||||
* </p>
|
||||
* <p>
|
||||
* The access status is calculated through a pluggable class:
|
||||
* {@link org.dspace.access.status.AccessStatusHelper}.
|
||||
* The {@link org.dspace.access.status.AccessStatusServiceImpl}
|
||||
* must be configured to specify this class, as well as a forever embargo date
|
||||
* threshold year, month and day.
|
||||
* </p>
|
||||
* <p>
|
||||
* See {@link org.dspace.access.status.DefaultAccessStatusHelper} for a simple calculation
|
||||
* based on the primary or the first bitstream of the original bundle. You can
|
||||
* supply your own class to implement more complex access statuses.
|
||||
* </p>
|
||||
* <p>
|
||||
* For now, the access status is calculated when the item is shown in a list.
|
||||
* </p>
|
||||
*/
|
||||
|
||||
package org.dspace.access.status;
|
@@ -1,57 +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.access.status.service;
|
||||
|
||||
import java.sql.SQLException;
|
||||
|
||||
import org.dspace.content.Item;
|
||||
import org.dspace.core.Context;
|
||||
|
||||
/**
|
||||
* Public interface to the access status subsystem.
|
||||
* <p>
|
||||
* Configuration properties: (with examples)
|
||||
* {@code
|
||||
* # values for the forever embargo date threshold
|
||||
* # This threshold date is used in the default access status helper to dermine if an item is
|
||||
* # restricted or embargoed based on the start date of the primary (or first) file policies.
|
||||
* # In this case, if the policy start date is inferior to the threshold date, the status will
|
||||
* # be embargo, else it will be restricted.
|
||||
* # You might want to change this threshold based on your needs. For example: some databases
|
||||
* # doesn't accept a date superior to 31 december 9999.
|
||||
* access.status.embargo.forever.year = 10000
|
||||
* access.status.embargo.forever.month = 1
|
||||
* access.status.embargo.forever.day = 1
|
||||
* # implementation of access status helper plugin - replace with local implementation if applicable
|
||||
* # This default access status helper provides an item status based on the policies of the primary
|
||||
* # bitstream (or first bitstream in the original bundles if no primary file is specified).
|
||||
* plugin.single.org.dspace.access.status.AccessStatusHelper = org.dspace.access.status.DefaultAccessStatusHelper
|
||||
* }
|
||||
*/
|
||||
public interface AccessStatusService {
|
||||
|
||||
/**
|
||||
* Calculate the access status for an Item while considering the forever embargo date threshold.
|
||||
*
|
||||
* @param context the DSpace context
|
||||
* @param item the item
|
||||
* @return an access status value
|
||||
* @throws SQLException An exception that provides information on a database access error or other errors.
|
||||
*/
|
||||
public String getAccessStatus(Context context, Item item) throws SQLException;
|
||||
|
||||
/**
|
||||
* Retrieve embargo information for the item
|
||||
*
|
||||
* @param context the DSpace context
|
||||
* @param item the item to check for embargo information
|
||||
* @return an embargo date
|
||||
* @throws SQLException An exception that provides information on a database access error or other errors.
|
||||
*/
|
||||
public String getEmbargoFromItem(Context context, Item item) throws SQLException;
|
||||
}
|
@@ -14,10 +14,10 @@ import java.util.UUID;
|
||||
|
||||
import org.apache.commons.cli.CommandLine;
|
||||
import org.apache.commons.cli.CommandLineParser;
|
||||
import org.apache.commons.cli.DefaultParser;
|
||||
import org.apache.commons.cli.HelpFormatter;
|
||||
import org.apache.commons.cli.Options;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.cli.PosixParser;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.dspace.authorize.AuthorizeException;
|
||||
import org.dspace.content.Community;
|
||||
import org.dspace.content.factory.ContentServiceFactory;
|
||||
@@ -51,7 +51,7 @@ public class CommunityFiliator {
|
||||
*/
|
||||
public static void main(String[] argv) throws Exception {
|
||||
// create an options object and populate it
|
||||
CommandLineParser parser = new DefaultParser();
|
||||
CommandLineParser parser = new PosixParser();
|
||||
|
||||
Options options = new Options();
|
||||
|
||||
@@ -180,9 +180,13 @@ public class CommunityFiliator {
|
||||
// second test - circularity: parent's parents can't include proposed
|
||||
// child
|
||||
List<Community> parentDads = parent.getParentCommunities();
|
||||
if (parentDads.contains(child)) {
|
||||
System.out.println("Error, circular parentage - child is parent of parent");
|
||||
System.exit(1);
|
||||
|
||||
for (int i = 0; i < parentDads.size(); i++) {
|
||||
if (parentDads.get(i).getID().equals(child.getID())) {
|
||||
System.out
|
||||
.println("Error, circular parentage - child is parent of parent");
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// everthing's OK
|
||||
@@ -206,15 +210,26 @@ public class CommunityFiliator {
|
||||
throws SQLException, AuthorizeException, IOException {
|
||||
// verify that child is indeed a child of parent
|
||||
List<Community> parentKids = parent.getSubcommunities();
|
||||
if (!parentKids.contains(child)) {
|
||||
System.out.println("Error, child community not a child of parent community");
|
||||
boolean isChild = false;
|
||||
|
||||
for (int i = 0; i < parentKids.size(); i++) {
|
||||
if (parentKids.get(i).getID().equals(child.getID())) {
|
||||
isChild = true;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isChild) {
|
||||
System.out
|
||||
.println("Error, child community not a child of parent community");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
// OK remove the mappings - but leave the community, which will become
|
||||
// top-level
|
||||
child.removeParentCommunity(parent);
|
||||
parent.removeSubCommunity(child);
|
||||
child.getParentCommunities().remove(parent);
|
||||
parent.getSubcommunities().remove(child);
|
||||
communityService.update(c, child);
|
||||
communityService.update(c, parent);
|
||||
|
||||
|
@@ -13,10 +13,10 @@ import java.util.Locale;
|
||||
|
||||
import org.apache.commons.cli.CommandLine;
|
||||
import org.apache.commons.cli.CommandLineParser;
|
||||
import org.apache.commons.cli.DefaultParser;
|
||||
import org.apache.commons.cli.HelpFormatter;
|
||||
import org.apache.commons.cli.Options;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.cli.PosixParser;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.dspace.core.ConfigurationManager;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.core.I18nUtil;
|
||||
import org.dspace.eperson.EPerson;
|
||||
@@ -24,8 +24,6 @@ import org.dspace.eperson.Group;
|
||||
import org.dspace.eperson.factory.EPersonServiceFactory;
|
||||
import org.dspace.eperson.service.EPersonService;
|
||||
import org.dspace.eperson.service.GroupService;
|
||||
import org.dspace.services.ConfigurationService;
|
||||
import org.dspace.services.factory.DSpaceServicesFactory;
|
||||
|
||||
/**
|
||||
* A command-line tool for creating an initial administrator for setting up a
|
||||
@@ -55,56 +53,34 @@ public final class CreateAdministrator {
|
||||
protected GroupService groupService;
|
||||
|
||||
/**
|
||||
* For invoking via the command line. If called with no command line arguments,
|
||||
* For invoking via the command line. If called with no command line arguments,
|
||||
* it will negotiate with the user for the administrator details
|
||||
*
|
||||
* @param argv the command line arguments given
|
||||
* @throws Exception if error
|
||||
*/
|
||||
public static void main(String[] argv)
|
||||
throws Exception {
|
||||
CommandLineParser parser = new DefaultParser();
|
||||
throws Exception {
|
||||
CommandLineParser parser = new PosixParser();
|
||||
Options options = new Options();
|
||||
|
||||
CreateAdministrator ca = new CreateAdministrator();
|
||||
|
||||
options.addOption("e", "email", true, "administrator email address");
|
||||
options.addOption("f", "first", true, "administrator first name");
|
||||
options.addOption("h", "help", false, "explain create-administrator options");
|
||||
options.addOption("l", "last", true, "administrator last name");
|
||||
options.addOption("c", "language", true, "administrator language");
|
||||
options.addOption("p", "password", true, "administrator password");
|
||||
|
||||
CommandLine line = null;
|
||||
|
||||
try {
|
||||
|
||||
line = parser.parse(options, argv);
|
||||
|
||||
} catch (Exception e) {
|
||||
|
||||
System.out.println(e.getMessage() + "\nTry \"dspace create-administrator -h\" to print help information.");
|
||||
System.exit(1);
|
||||
|
||||
}
|
||||
CommandLine line = parser.parse(options, argv);
|
||||
|
||||
if (line.hasOption("e") && line.hasOption("f") && line.hasOption("l") &&
|
||||
line.hasOption("c") && line.hasOption("p")) {
|
||||
line.hasOption("c") && line.hasOption("p")) {
|
||||
ca.createAdministrator(line.getOptionValue("e"),
|
||||
line.getOptionValue("f"), line.getOptionValue("l"),
|
||||
line.getOptionValue("c"), line.getOptionValue("p"));
|
||||
} else if (line.hasOption("h")) {
|
||||
String header = "\nA command-line tool for creating an initial administrator for setting up a" +
|
||||
" DSpace site. Unless all the required parameters are passed it will" +
|
||||
" prompt for an e-mail address, last name, first name and password from" +
|
||||
" standard input.. An administrator group is then created and the data passed" +
|
||||
" in used to create an e-person in that group.\n\n";
|
||||
String footer = "\n";
|
||||
HelpFormatter formatter = new HelpFormatter();
|
||||
formatter.printHelp("dspace create-administrator", header, options, footer, true);
|
||||
return;
|
||||
line.getOptionValue("f"), line.getOptionValue("l"),
|
||||
line.getOptionValue("c"), line.getOptionValue("p"));
|
||||
} else {
|
||||
ca.negotiateAdministratorDetails(line);
|
||||
ca.negotiateAdministratorDetails();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,19 +90,8 @@ public final class CreateAdministrator {
|
||||
* @throws Exception if error
|
||||
*/
|
||||
protected CreateAdministrator()
|
||||
throws Exception {
|
||||
throws Exception {
|
||||
context = new Context();
|
||||
try {
|
||||
context.getDBConfig();
|
||||
} catch (NullPointerException npr) {
|
||||
// if database is null, there is no point in continuing. Prior to this exception and catch,
|
||||
// NullPointerException was thrown, that wasn't very helpful.
|
||||
throw new IllegalStateException("Problem connecting to database. This " +
|
||||
"indicates issue with either network or version (or possibly some other). " +
|
||||
"If you are running this in docker-compose, please make sure dspace-cli was " +
|
||||
"built from the same sources as running dspace container AND that they are in " +
|
||||
"the same project/network.");
|
||||
}
|
||||
groupService = EPersonServiceFactory.getInstance().getGroupService();
|
||||
ePersonService = EPersonServiceFactory.getInstance().getEPersonService();
|
||||
}
|
||||
@@ -137,20 +102,20 @@ public final class CreateAdministrator {
|
||||
*
|
||||
* @throws Exception if error
|
||||
*/
|
||||
protected void negotiateAdministratorDetails(CommandLine line)
|
||||
throws Exception {
|
||||
protected void negotiateAdministratorDetails()
|
||||
throws Exception {
|
||||
Console console = System.console();
|
||||
|
||||
System.out.println("Creating an initial administrator account");
|
||||
|
||||
String email = line.getOptionValue('e');
|
||||
String firstName = line.getOptionValue('f');
|
||||
String lastName = line.getOptionValue('l');
|
||||
String language = I18nUtil.getDefaultLocale().getLanguage();
|
||||
ConfigurationService cfg = DSpaceServicesFactory.getInstance().getConfigurationService();
|
||||
boolean flag = line.hasOption('p');
|
||||
char[] password = null;
|
||||
boolean dataOK = line.hasOption('f') && line.hasOption('e') && line.hasOption('l');
|
||||
boolean dataOK = false;
|
||||
|
||||
String email = null;
|
||||
String firstName = null;
|
||||
String lastName = null;
|
||||
char[] password1 = null;
|
||||
char[] password2 = null;
|
||||
String language = I18nUtil.DEFAULTLOCALE.getLanguage();
|
||||
|
||||
while (!dataOK) {
|
||||
System.out.print("E-mail address: ");
|
||||
@@ -181,9 +146,10 @@ public final class CreateAdministrator {
|
||||
if (lastName != null) {
|
||||
lastName = lastName.trim();
|
||||
}
|
||||
if (cfg.hasProperty("webui.supported.locales")) {
|
||||
System.out.println("Select one of the following languages: "
|
||||
+ cfg.getProperty("webui.supported.locales"));
|
||||
|
||||
if (ConfigurationManager.getProperty("webui.supported.locales") != null) {
|
||||
System.out.println("Select one of the following languages: " + ConfigurationManager
|
||||
.getProperty("webui.supported.locales"));
|
||||
System.out.print("Language: ");
|
||||
System.out.flush();
|
||||
|
||||
@@ -195,59 +161,46 @@ public final class CreateAdministrator {
|
||||
}
|
||||
}
|
||||
|
||||
System.out.print("Is the above data correct? (y or n): ");
|
||||
System.out.println("Password will not display on screen.");
|
||||
System.out.print("Password: ");
|
||||
System.out.flush();
|
||||
|
||||
String s = console.readLine();
|
||||
password1 = console.readPassword();
|
||||
|
||||
if (s != null) {
|
||||
s = s.trim();
|
||||
if (s.toLowerCase().startsWith("y")) {
|
||||
dataOK = true;
|
||||
System.out.print("Again to confirm: ");
|
||||
System.out.flush();
|
||||
|
||||
password2 = console.readPassword();
|
||||
|
||||
//TODO real password validation
|
||||
if (password1.length > 1 && Arrays.equals(password1, password2)) {
|
||||
// password OK
|
||||
System.out.print("Is the above data correct? (y or n): ");
|
||||
System.out.flush();
|
||||
|
||||
String s = console.readLine();
|
||||
|
||||
if (s != null) {
|
||||
s = s.trim();
|
||||
if (s.toLowerCase().startsWith("y")) {
|
||||
dataOK = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
System.out.println("Passwords don't match");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
if (!flag) {
|
||||
password = getPassword(console);
|
||||
if (password == null) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
password = line.getOptionValue("p").toCharArray();
|
||||
}
|
||||
// if we make it to here, we are ready to create an administrator
|
||||
createAdministrator(email, firstName, lastName, language, String.valueOf(password));
|
||||
createAdministrator(email, firstName, lastName, language, String.valueOf(password1));
|
||||
|
||||
}
|
||||
|
||||
private char[] getPassword(Console console) {
|
||||
char[] password1 = null;
|
||||
char[] password2 = null;
|
||||
System.out.println("Password will not display on screen.");
|
||||
System.out.print("Password: ");
|
||||
System.out.flush();
|
||||
|
||||
password1 = console.readPassword();
|
||||
|
||||
System.out.print("Again to confirm: ");
|
||||
System.out.flush();
|
||||
|
||||
password2 = console.readPassword();
|
||||
|
||||
// TODO real password validation
|
||||
if (password1.length > 1 && Arrays.equals(password1, password2)) {
|
||||
// password OK
|
||||
Arrays.fill(password2, ' ');
|
||||
return password1;
|
||||
} else {
|
||||
System.out.println("Passwords don't match");
|
||||
return null;
|
||||
}
|
||||
//Cleaning arrays that held password
|
||||
Arrays.fill(password1, ' ');
|
||||
Arrays.fill(password2, ' ');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the administrator with the given details. If the user
|
||||
* Create the administrator with the given details. If the user
|
||||
* already exists then they are simply upped to administrator status
|
||||
*
|
||||
* @param email the email for the user
|
||||
@@ -258,8 +211,8 @@ public final class CreateAdministrator {
|
||||
* @throws Exception if error
|
||||
*/
|
||||
protected void createAdministrator(String email, String first, String last,
|
||||
String language, String pw)
|
||||
throws Exception {
|
||||
String language, String pw)
|
||||
throws Exception {
|
||||
// Of course we aren't an administrator yet so we need to
|
||||
// circumvent authorisation
|
||||
context.turnOffAuthorisationSystem();
|
||||
|
@@ -10,7 +10,6 @@ package org.dspace.administer;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.sql.SQLException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@@ -18,28 +17,25 @@ import java.util.Map;
|
||||
|
||||
import org.apache.commons.cli.CommandLine;
|
||||
import org.apache.commons.cli.CommandLineParser;
|
||||
import org.apache.commons.cli.DefaultParser;
|
||||
import org.apache.commons.cli.Options;
|
||||
import org.apache.commons.cli.ParseException;
|
||||
import org.apache.commons.cli.PosixParser;
|
||||
import org.apache.xml.serialize.Method;
|
||||
import org.apache.xml.serialize.OutputFormat;
|
||||
import org.apache.xml.serialize.XMLSerializer;
|
||||
import org.dspace.content.MetadataField;
|
||||
import org.dspace.content.MetadataSchema;
|
||||
import org.dspace.content.factory.ContentServiceFactory;
|
||||
import org.dspace.content.service.MetadataFieldService;
|
||||
import org.dspace.content.service.MetadataSchemaService;
|
||||
import org.dspace.core.Context;
|
||||
import org.w3c.dom.DOMConfiguration;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.bootstrap.DOMImplementationRegistry;
|
||||
import org.w3c.dom.ls.DOMImplementationLS;
|
||||
import org.w3c.dom.ls.LSOutput;
|
||||
import org.w3c.dom.ls.LSSerializer;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
|
||||
/**
|
||||
* @author Graham Triggs
|
||||
*
|
||||
* This class creates an XML document as passed in the arguments and
|
||||
* This class creates an xml document as passed in the arguments and
|
||||
* from the metadata schemas for the repository.
|
||||
*
|
||||
* The form of the XML is as follows
|
||||
@@ -65,20 +61,17 @@ public class MetadataExporter {
|
||||
private MetadataExporter() { }
|
||||
|
||||
/**
|
||||
* @param args command line arguments
|
||||
* @param args commandline arguments
|
||||
* @throws ParseException if parser error
|
||||
* @throws SAXException if XML parse error
|
||||
* @throws IOException if IO error
|
||||
* @throws SQLException if database error
|
||||
* @throws RegistryExportException if export error
|
||||
* @throws ClassNotFoundException if no suitable DOM implementation
|
||||
* @throws InstantiationException if no suitable DOM implementation
|
||||
* @throws IllegalAccessException if no suitable DOM implementation
|
||||
*/
|
||||
public static void main(String[] args)
|
||||
throws ParseException, SQLException, IOException, RegistryExportException,
|
||||
ClassNotFoundException, InstantiationException, IllegalAccessException {
|
||||
throws ParseException, SQLException, IOException, SAXException, RegistryExportException {
|
||||
// create an options object and populate it
|
||||
CommandLineParser parser = new DefaultParser();
|
||||
CommandLineParser parser = new PosixParser();
|
||||
Options options = new Options();
|
||||
options.addOption("f", "file", true, "output xml file for registry");
|
||||
options.addOption("s", "schema", true, "the name of the schema to export");
|
||||
@@ -102,31 +95,32 @@ public class MetadataExporter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a registry to a file path
|
||||
* Save a registry to a filepath
|
||||
*
|
||||
* @param file file path
|
||||
* @param file filepath
|
||||
* @param schema schema definition to save
|
||||
* @throws SQLException if database error
|
||||
* @throws IOException if IO error
|
||||
* @throws SAXException if XML error
|
||||
* @throws RegistryExportException if export error
|
||||
* @throws ClassNotFoundException if no suitable DOM implementation
|
||||
* @throws InstantiationException if no suitable DOM implementation
|
||||
* @throws IllegalAccessException if no suitable DOM implementation
|
||||
*/
|
||||
public static void saveRegistry(String file, String schema)
|
||||
throws SQLException, IOException, RegistryExportException,
|
||||
ClassNotFoundException, InstantiationException, IllegalAccessException {
|
||||
throws SQLException, IOException, SAXException, RegistryExportException {
|
||||
// create a context
|
||||
Context context = new Context();
|
||||
context.turnOffAuthorisationSystem();
|
||||
|
||||
// Initialize an XML document.
|
||||
Document document = DOMImplementationRegistry.newInstance()
|
||||
.getDOMImplementation("XML 3.0")
|
||||
.createDocument(null, "dspace-dc-types", null);
|
||||
OutputFormat xmlFormat = new OutputFormat(Method.XML, "UTF-8", true);
|
||||
xmlFormat.setLineWidth(120);
|
||||
xmlFormat.setIndent(4);
|
||||
|
||||
XMLSerializer xmlSerializer = new XMLSerializer(new BufferedWriter(new FileWriter(file)), xmlFormat);
|
||||
// XMLSerializer xmlSerializer = new XMLSerializer(System.out, xmlFormat);
|
||||
xmlSerializer.startDocument();
|
||||
xmlSerializer.startElement("dspace-dc-types", null);
|
||||
|
||||
// Save the schema definition(s)
|
||||
saveSchema(context, document, schema);
|
||||
saveSchema(context, xmlSerializer, schema);
|
||||
|
||||
List<MetadataField> mdFields = null;
|
||||
|
||||
@@ -145,64 +139,55 @@ public class MetadataExporter {
|
||||
mdFields = metadataFieldService.findAll(context);
|
||||
}
|
||||
|
||||
// Compose the metadata fields
|
||||
// Output the metadata fields
|
||||
for (MetadataField mdField : mdFields) {
|
||||
saveType(context, document, mdField);
|
||||
saveType(context, xmlSerializer, mdField);
|
||||
}
|
||||
|
||||
// Serialize the completed document to the output file.
|
||||
try (Writer writer = new BufferedWriter(new FileWriter(file))) {
|
||||
DOMImplementationLS lsImplementation
|
||||
= (DOMImplementationLS) DOMImplementationRegistry.newInstance()
|
||||
.getDOMImplementation("LS");
|
||||
LSSerializer serializer = lsImplementation.createLSSerializer();
|
||||
DOMConfiguration configuration = serializer.getDomConfig();
|
||||
configuration.setParameter("format-pretty-print", true);
|
||||
LSOutput lsOutput = lsImplementation.createLSOutput();
|
||||
lsOutput.setEncoding("UTF-8");
|
||||
lsOutput.setCharacterStream(writer);
|
||||
serializer.write(document, lsOutput);
|
||||
}
|
||||
xmlSerializer.endElement("dspace-dc-types");
|
||||
xmlSerializer.endDocument();
|
||||
|
||||
// abort the context, as we shouldn't have changed it!!
|
||||
context.abort();
|
||||
}
|
||||
|
||||
/**
|
||||
* Compose the schema registry. If the parameter 'schema' is null or empty, save all schemas.
|
||||
* Serialize the schema registry. If the parameter 'schema' is null or empty, save all schemas
|
||||
*
|
||||
* @param context DSpace Context
|
||||
* @param document the document being built
|
||||
* @param xmlSerializer XML serializer
|
||||
* @param schema schema (may be null to save all)
|
||||
* @throws SQLException if database error
|
||||
* @throws SAXException if XML error
|
||||
* @throws RegistryExportException if export error
|
||||
*/
|
||||
public static void saveSchema(Context context, Document document, String schema)
|
||||
throws SQLException, RegistryExportException {
|
||||
public static void saveSchema(Context context, XMLSerializer xmlSerializer, String schema)
|
||||
throws SQLException, SAXException, RegistryExportException {
|
||||
if (schema != null && !"".equals(schema)) {
|
||||
// Find a single named schema
|
||||
MetadataSchema mdSchema = metadataSchemaService.find(context, schema);
|
||||
|
||||
saveSchema(document, mdSchema);
|
||||
saveSchema(xmlSerializer, mdSchema);
|
||||
} else {
|
||||
// Find all schemas
|
||||
List<MetadataSchema> mdSchemas = metadataSchemaService.findAll(context);
|
||||
|
||||
for (MetadataSchema mdSchema : mdSchemas) {
|
||||
saveSchema(document, mdSchema);
|
||||
saveSchema(xmlSerializer, mdSchema);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compose a single schema (namespace) registry entry
|
||||
* Serialize a single schema (namespace) registry entry
|
||||
*
|
||||
* @param document the output document being built.
|
||||
* @param mdSchema DSpace metadata schema
|
||||
* @param xmlSerializer XML serializer
|
||||
* @param mdSchema DSpace metadata schema
|
||||
* @throws SAXException if XML error
|
||||
* @throws RegistryExportException if export error
|
||||
*/
|
||||
private static void saveSchema(Document document, MetadataSchema mdSchema)
|
||||
throws RegistryExportException {
|
||||
private static void saveSchema(XMLSerializer xmlSerializer, MetadataSchema mdSchema)
|
||||
throws SAXException, RegistryExportException {
|
||||
// If we haven't got a schema, it's an error
|
||||
if (mdSchema == null) {
|
||||
throw new RegistryExportException("no schema to export");
|
||||
@@ -221,34 +206,35 @@ public class MetadataExporter {
|
||||
return;
|
||||
}
|
||||
|
||||
Element document_element = document.getDocumentElement();
|
||||
// Output the parent tag
|
||||
xmlSerializer.startElement("dc-schema", null);
|
||||
|
||||
// Compose the parent tag
|
||||
Element schema_element = document.createElement("dc-schema");
|
||||
document_element.appendChild(schema_element);
|
||||
// Output the schema name
|
||||
xmlSerializer.startElement("name", null);
|
||||
xmlSerializer.characters(name.toCharArray(), 0, name.length());
|
||||
xmlSerializer.endElement("name");
|
||||
|
||||
// Compose the schema name
|
||||
Element name_element = document.createElement("name");
|
||||
schema_element.appendChild(name_element);
|
||||
name_element.setTextContent(name);
|
||||
// Output the schema namespace
|
||||
xmlSerializer.startElement("namespace", null);
|
||||
xmlSerializer.characters(namespace.toCharArray(), 0, namespace.length());
|
||||
xmlSerializer.endElement("namespace");
|
||||
|
||||
// Compose the schema namespace
|
||||
Element namespace_element = document.createElement("namespace");
|
||||
schema_element.appendChild(namespace_element);
|
||||
namespace_element.setTextContent(namespace);
|
||||
xmlSerializer.endElement("dc-schema");
|
||||
}
|
||||
|
||||
/**
|
||||
* Compose a single metadata field registry entry to XML.
|
||||
* Serialize a single metadata field registry entry to xml
|
||||
*
|
||||
* @param context DSpace context
|
||||
* @param document the output document being built.
|
||||
* @param xmlSerializer xml serializer
|
||||
* @param mdField DSpace metadata field
|
||||
* @throws SAXException if XML error
|
||||
* @throws RegistryExportException if export error
|
||||
* @throws SQLException if database error
|
||||
* @throws IOException if IO error
|
||||
*/
|
||||
private static void saveType(Context context, Document document, MetadataField mdField)
|
||||
throws RegistryExportException, SQLException {
|
||||
private static void saveType(Context context, XMLSerializer xmlSerializer, MetadataField mdField)
|
||||
throws SAXException, RegistryExportException, SQLException, IOException {
|
||||
// If we haven't been given a field, it's an error
|
||||
if (mdField == null) {
|
||||
throw new RegistryExportException("no field to export");
|
||||
@@ -265,39 +251,38 @@ public class MetadataExporter {
|
||||
throw new RegistryExportException("incomplete field information");
|
||||
}
|
||||
|
||||
Element document_element = document.getDocumentElement();
|
||||
// Output the parent tag
|
||||
xmlSerializer.startElement("dc-type", null);
|
||||
|
||||
// Compose the parent tag
|
||||
Element dc_type = document.createElement("dc-type");
|
||||
document_element.appendChild(dc_type);
|
||||
// Output the schema name
|
||||
xmlSerializer.startElement("schema", null);
|
||||
xmlSerializer.characters(schemaName.toCharArray(), 0, schemaName.length());
|
||||
xmlSerializer.endElement("schema");
|
||||
|
||||
// Compose the schema name
|
||||
Element schema_element = document.createElement("schema");
|
||||
dc_type.appendChild(schema_element);
|
||||
schema_element.setTextContent(schemaName);
|
||||
// Output the element
|
||||
xmlSerializer.startElement("element", null);
|
||||
xmlSerializer.characters(element.toCharArray(), 0, element.length());
|
||||
xmlSerializer.endElement("element");
|
||||
|
||||
// Compose the element
|
||||
Element element_element = document.createElement("element");
|
||||
dc_type.appendChild(element_element);
|
||||
element_element.setTextContent(element);
|
||||
|
||||
// Compose the qualifier, if present
|
||||
// Output the qualifier, if present
|
||||
if (qualifier != null) {
|
||||
Element qualifier_element = document.createElement("qualifier");
|
||||
dc_type.appendChild(qualifier_element);
|
||||
qualifier_element.setTextContent(qualifier);
|
||||
xmlSerializer.startElement("qualifier", null);
|
||||
xmlSerializer.characters(qualifier.toCharArray(), 0, qualifier.length());
|
||||
xmlSerializer.endElement("qualifier");
|
||||
} else {
|
||||
dc_type.appendChild(document.createComment("unqualified"));
|
||||
xmlSerializer.comment("unqualified");
|
||||
}
|
||||
|
||||
// Compose the scope note, if present
|
||||
// Output the scope note, if present
|
||||
if (scopeNote != null) {
|
||||
Element scope_element = document.createElement("scope_note");
|
||||
dc_type.appendChild(scope_element);
|
||||
scope_element.setTextContent(scopeNote);
|
||||
xmlSerializer.startElement("scope_note", null);
|
||||
xmlSerializer.characters(scopeNote.toCharArray(), 0, scopeNote.length());
|
||||
xmlSerializer.endElement("scope_note");
|
||||
} else {
|
||||
dc_type.appendChild(document.createComment("no scope note"));
|
||||
xmlSerializer.comment("no scope note");
|
||||
}
|
||||
|
||||
xmlSerializer.endElement("dc-type");
|
||||
}
|
||||
|
||||
static Map<Integer, String> schemaMap = new HashMap<Integer, String>();
|
||||
@@ -332,7 +317,7 @@ public class MetadataExporter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Print the usage message to standard output
|
||||
* Print the usage message to stdout
|
||||
*/
|
||||
public static void usage() {
|
||||
String usage = "Use this class with the following options:\n" +
|
||||
|
@@ -11,20 +11,16 @@ import java.io.IOException;
|
||||
import java.sql.SQLException;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.transform.TransformerException;
|
||||
import javax.xml.xpath.XPath;
|
||||
import javax.xml.xpath.XPathConstants;
|
||||
import javax.xml.xpath.XPathExpressionException;
|
||||
import javax.xml.xpath.XPathFactory;
|
||||
|
||||
import org.apache.commons.cli.CommandLine;
|
||||
import org.apache.commons.cli.CommandLineParser;
|
||||
import org.apache.commons.cli.DefaultParser;
|
||||
import org.apache.commons.cli.Options;
|
||||
import org.apache.commons.cli.ParseException;
|
||||
import org.apache.commons.cli.PosixParser;
|
||||
import org.apache.xpath.XPathAPI;
|
||||
import org.dspace.authorize.AuthorizeException;
|
||||
import org.dspace.content.MetadataField;
|
||||
import org.dspace.content.MetadataSchema;
|
||||
import org.dspace.content.MetadataSchemaEnum;
|
||||
import org.dspace.content.NonUniqueMetadataException;
|
||||
import org.dspace.content.factory.ContentServiceFactory;
|
||||
import org.dspace.content.service.MetadataFieldService;
|
||||
@@ -84,7 +80,7 @@ public class MetadataImporter {
|
||||
* @throws SQLException if database error
|
||||
* @throws IOException if IO error
|
||||
* @throws TransformerException if transformer error
|
||||
* @throws ParserConfigurationException if configuration error
|
||||
* @throws ParserConfigurationException if config error
|
||||
* @throws AuthorizeException if authorization error
|
||||
* @throws SAXException if parser error
|
||||
* @throws NonUniqueMetadataException if duplicate metadata
|
||||
@@ -93,23 +89,26 @@ public class MetadataImporter {
|
||||
public static void main(String[] args)
|
||||
throws ParseException, SQLException, IOException, TransformerException,
|
||||
ParserConfigurationException, AuthorizeException, SAXException,
|
||||
NonUniqueMetadataException, RegistryImportException, XPathExpressionException {
|
||||
NonUniqueMetadataException, RegistryImportException {
|
||||
boolean forceUpdate = false;
|
||||
|
||||
// create an options object and populate it
|
||||
CommandLineParser parser = new DefaultParser();
|
||||
CommandLineParser parser = new PosixParser();
|
||||
Options options = new Options();
|
||||
options.addOption("f", "file", true, "source xml file for DC fields");
|
||||
options.addOption("u", "update", false, "update an existing schema");
|
||||
CommandLine line = parser.parse(options, args);
|
||||
|
||||
String file = null;
|
||||
if (line.hasOption('f')) {
|
||||
String file = line.getOptionValue('f');
|
||||
boolean forceUpdate = line.hasOption('u');
|
||||
loadRegistry(file, forceUpdate);
|
||||
file = line.getOptionValue('f');
|
||||
} else {
|
||||
usage();
|
||||
System.exit(1);
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
forceUpdate = line.hasOption('u');
|
||||
loadRegistry(file, forceUpdate);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -120,15 +119,15 @@ public class MetadataImporter {
|
||||
* @throws SQLException if database error
|
||||
* @throws IOException if IO error
|
||||
* @throws TransformerException if transformer error
|
||||
* @throws ParserConfigurationException if configuration error
|
||||
* @throws ParserConfigurationException if config error
|
||||
* @throws AuthorizeException if authorization error
|
||||
* @throws SAXException if parser error
|
||||
* @throws NonUniqueMetadataException if duplicate metadata
|
||||
* @throws RegistryImportException if import fails
|
||||
*/
|
||||
public static void loadRegistry(String file, boolean forceUpdate)
|
||||
throws SQLException, IOException, TransformerException, ParserConfigurationException, AuthorizeException,
|
||||
SAXException, NonUniqueMetadataException, RegistryImportException, XPathExpressionException {
|
||||
throws SQLException, IOException, TransformerException, ParserConfigurationException,
|
||||
AuthorizeException, SAXException, NonUniqueMetadataException, RegistryImportException {
|
||||
Context context = null;
|
||||
|
||||
try {
|
||||
@@ -140,9 +139,7 @@ public class MetadataImporter {
|
||||
Document document = RegistryImporter.loadXML(file);
|
||||
|
||||
// Get the nodes corresponding to types
|
||||
XPath xPath = XPathFactory.newInstance().newXPath();
|
||||
NodeList schemaNodes = (NodeList) xPath.compile("/dspace-dc-types/dc-schema")
|
||||
.evaluate(document, XPathConstants.NODESET);
|
||||
NodeList schemaNodes = XPathAPI.selectNodeList(document, "/dspace-dc-types/dc-schema");
|
||||
|
||||
// Add each one as a new format to the registry
|
||||
for (int i = 0; i < schemaNodes.getLength(); i++) {
|
||||
@@ -151,8 +148,7 @@ public class MetadataImporter {
|
||||
}
|
||||
|
||||
// Get the nodes corresponding to types
|
||||
NodeList typeNodes = (NodeList) xPath.compile("/dspace-dc-types/dc-type")
|
||||
.evaluate(document, XPathConstants.NODESET);
|
||||
NodeList typeNodes = XPathAPI.selectNodeList(document, "/dspace-dc-types/dc-type");
|
||||
|
||||
// Add each one as a new format to the registry
|
||||
for (int i = 0; i < typeNodes.getLength(); i++) {
|
||||
@@ -184,8 +180,8 @@ public class MetadataImporter {
|
||||
* @throws RegistryImportException if import fails
|
||||
*/
|
||||
private static void loadSchema(Context context, Node node, boolean updateExisting)
|
||||
throws SQLException, AuthorizeException, NonUniqueMetadataException, RegistryImportException,
|
||||
XPathExpressionException {
|
||||
throws SQLException, IOException, TransformerException,
|
||||
AuthorizeException, NonUniqueMetadataException, RegistryImportException {
|
||||
// Get the values
|
||||
String name = RegistryImporter.getElementData(node, "name");
|
||||
String namespace = RegistryImporter.getElementData(node, "namespace");
|
||||
@@ -230,7 +226,7 @@ public class MetadataImporter {
|
||||
/**
|
||||
* Process a node in the metadata registry XML file. The node must
|
||||
* be a "dc-type" node. If the type already exists, then it
|
||||
* will not be re-imported.
|
||||
* will not be reimported
|
||||
*
|
||||
* @param context DSpace context object
|
||||
* @param node the node in the DOM tree
|
||||
@@ -242,8 +238,8 @@ public class MetadataImporter {
|
||||
* @throws RegistryImportException if import fails
|
||||
*/
|
||||
private static void loadType(Context context, Node node)
|
||||
throws SQLException, IOException, AuthorizeException, NonUniqueMetadataException, RegistryImportException,
|
||||
XPathExpressionException {
|
||||
throws SQLException, IOException, TransformerException,
|
||||
AuthorizeException, NonUniqueMetadataException, RegistryImportException {
|
||||
// Get the values
|
||||
String schema = RegistryImporter.getElementData(node, "schema");
|
||||
String element = RegistryImporter.getElementData(node, "element");
|
||||
@@ -252,7 +248,7 @@ public class MetadataImporter {
|
||||
|
||||
// If the schema is not provided default to DC
|
||||
if (schema == null) {
|
||||
schema = MetadataSchemaEnum.DC.getName();
|
||||
schema = MetadataSchema.DC_SCHEMA;
|
||||
}
|
||||
|
||||
|
||||
|
@@ -1,140 +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.administer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.cli.ParseException;
|
||||
import org.apache.commons.lang.time.DateUtils;
|
||||
import org.dspace.authorize.AuthorizeException;
|
||||
import org.dspace.content.ProcessStatus;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.scripts.DSpaceRunnable;
|
||||
import org.dspace.scripts.Process;
|
||||
import org.dspace.scripts.factory.ScriptServiceFactory;
|
||||
import org.dspace.scripts.service.ProcessService;
|
||||
import org.dspace.services.ConfigurationService;
|
||||
import org.dspace.services.factory.DSpaceServicesFactory;
|
||||
import org.dspace.utils.DSpace;
|
||||
|
||||
/**
|
||||
* Script to cleanup the old processes in the specified state.
|
||||
*
|
||||
* @author Luca Giamminonni (luca.giamminonni at 4science.it)
|
||||
*
|
||||
*/
|
||||
public class ProcessCleaner extends DSpaceRunnable<ProcessCleanerConfiguration<ProcessCleaner>> {
|
||||
|
||||
private ConfigurationService configurationService;
|
||||
|
||||
private ProcessService processService;
|
||||
|
||||
|
||||
private boolean cleanCompleted = false;
|
||||
|
||||
private boolean cleanFailed = false;
|
||||
|
||||
private boolean cleanRunning = false;
|
||||
|
||||
private boolean help = false;
|
||||
|
||||
private Integer days;
|
||||
|
||||
|
||||
@Override
|
||||
public void setup() throws ParseException {
|
||||
|
||||
this.configurationService = DSpaceServicesFactory.getInstance().getConfigurationService();
|
||||
this.processService = ScriptServiceFactory.getInstance().getProcessService();
|
||||
|
||||
this.help = commandLine.hasOption('h');
|
||||
this.cleanFailed = commandLine.hasOption('f');
|
||||
this.cleanRunning = commandLine.hasOption('r');
|
||||
this.cleanCompleted = commandLine.hasOption('c') || (!cleanFailed && !cleanRunning);
|
||||
|
||||
this.days = configurationService.getIntProperty("process-cleaner.days", 14);
|
||||
|
||||
if (this.days <= 0) {
|
||||
throw new IllegalStateException("The number of days must be a positive integer.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void internalRun() throws Exception {
|
||||
|
||||
if (help) {
|
||||
printHelp();
|
||||
return;
|
||||
}
|
||||
|
||||
Context context = new Context();
|
||||
|
||||
try {
|
||||
context.turnOffAuthorisationSystem();
|
||||
performDeletion(context);
|
||||
} finally {
|
||||
context.restoreAuthSystemState();
|
||||
context.complete();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the processes based on the specified statuses and the configured days
|
||||
* from their creation.
|
||||
*/
|
||||
private void performDeletion(Context context) throws SQLException, IOException, AuthorizeException {
|
||||
|
||||
List<ProcessStatus> statuses = getProcessToDeleteStatuses();
|
||||
Date creationDate = calculateCreationDate();
|
||||
|
||||
handler.logInfo("Searching for processes with status: " + statuses);
|
||||
List<Process> processes = processService.findByStatusAndCreationTimeOlderThan(context, statuses, creationDate);
|
||||
handler.logInfo("Found " + processes.size() + " processes to be deleted");
|
||||
for (Process process : processes) {
|
||||
processService.delete(context, process);
|
||||
}
|
||||
|
||||
handler.logInfo("Process cleanup completed");
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of Process statuses do be deleted.
|
||||
*/
|
||||
private List<ProcessStatus> getProcessToDeleteStatuses() {
|
||||
List<ProcessStatus> statuses = new ArrayList<ProcessStatus>();
|
||||
if (cleanCompleted) {
|
||||
statuses.add(ProcessStatus.COMPLETED);
|
||||
}
|
||||
if (cleanFailed) {
|
||||
statuses.add(ProcessStatus.FAILED);
|
||||
}
|
||||
if (cleanRunning) {
|
||||
statuses.add(ProcessStatus.RUNNING);
|
||||
}
|
||||
return statuses;
|
||||
}
|
||||
|
||||
private Date calculateCreationDate() {
|
||||
return DateUtils.addDays(new Date(), -days);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public ProcessCleanerConfiguration<ProcessCleaner> getScriptConfiguration() {
|
||||
return new DSpace().getServiceManager()
|
||||
.getServiceByName("process-cleaner", ProcessCleanerConfiguration.class);
|
||||
}
|
||||
|
||||
}
|
@@ -1,18 +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.administer;
|
||||
|
||||
/**
|
||||
* The {@link ProcessCleaner} for CLI.
|
||||
*
|
||||
* @author Luca Giamminonni (luca.giamminonni at 4science.it)
|
||||
*
|
||||
*/
|
||||
public class ProcessCleanerCli extends ProcessCleaner {
|
||||
|
||||
}
|
@@ -1,18 +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.administer;
|
||||
|
||||
/**
|
||||
* The {@link ProcessCleanerConfiguration} for CLI.
|
||||
*
|
||||
* @author Luca Giamminonni (luca.giamminonni at 4science.it)
|
||||
*
|
||||
*/
|
||||
public class ProcessCleanerCliConfiguration extends ProcessCleanerConfiguration<ProcessCleanerCli> {
|
||||
|
||||
}
|
@@ -1,53 +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.administer;
|
||||
|
||||
import org.apache.commons.cli.Options;
|
||||
import org.dspace.scripts.configuration.ScriptConfiguration;
|
||||
|
||||
/**
|
||||
* The {@link ScriptConfiguration} for the {@link ProcessCleaner} script.
|
||||
*/
|
||||
public class ProcessCleanerConfiguration<T extends ProcessCleaner> extends ScriptConfiguration<T> {
|
||||
|
||||
private Class<T> dspaceRunnableClass;
|
||||
|
||||
@Override
|
||||
public Options getOptions() {
|
||||
if (options == null) {
|
||||
|
||||
Options options = new Options();
|
||||
|
||||
options.addOption("h", "help", false, "help");
|
||||
|
||||
options.addOption("r", "running", false, "delete the process with RUNNING status");
|
||||
options.getOption("r").setType(boolean.class);
|
||||
|
||||
options.addOption("f", "failed", false, "delete the process with FAILED status");
|
||||
options.getOption("f").setType(boolean.class);
|
||||
|
||||
options.addOption("c", "completed", false,
|
||||
"delete the process with COMPLETED status (default if no statuses are specified)");
|
||||
options.getOption("c").setType(boolean.class);
|
||||
|
||||
super.options = options;
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<T> getDspaceRunnableClass() {
|
||||
return dspaceRunnableClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDspaceRunnableClass(Class<T> dspaceRunnableClass) {
|
||||
this.dspaceRunnableClass = dspaceRunnableClass;
|
||||
}
|
||||
|
||||
}
|
@@ -13,11 +13,8 @@ import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.transform.TransformerException;
|
||||
import javax.xml.xpath.XPath;
|
||||
import javax.xml.xpath.XPathConstants;
|
||||
import javax.xml.xpath.XPathExpressionException;
|
||||
import javax.xml.xpath.XPathFactory;
|
||||
|
||||
import org.apache.xpath.XPathAPI;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
@@ -75,10 +72,9 @@ public class RegistryImporter {
|
||||
* @throws TransformerException if error
|
||||
*/
|
||||
public static String getElementData(Node parentElement, String childName)
|
||||
throws XPathExpressionException {
|
||||
throws TransformerException {
|
||||
// Grab the child node
|
||||
XPath xPath = XPathFactory.newInstance().newXPath();
|
||||
Node childNode = (Node) xPath.compile(childName).evaluate(parentElement, XPathConstants.NODE);
|
||||
Node childNode = XPathAPI.selectSingleNode(parentElement, childName);
|
||||
|
||||
if (childNode == null) {
|
||||
// No child node, so no values
|
||||
@@ -119,10 +115,9 @@ public class RegistryImporter {
|
||||
* @throws TransformerException if error
|
||||
*/
|
||||
public static String[] getRepeatedElementData(Node parentElement,
|
||||
String childName) throws XPathExpressionException {
|
||||
String childName) throws TransformerException {
|
||||
// Grab the child node
|
||||
XPath xPath = XPathFactory.newInstance().newXPath();
|
||||
NodeList childNodes = (NodeList) xPath.compile(childName).evaluate(parentElement, XPathConstants.NODESET);
|
||||
NodeList childNodes = XPathAPI.selectNodeList(parentElement, childName);
|
||||
|
||||
String[] data = new String[childNodes.getLength()];
|
||||
|
||||
|
@@ -16,18 +16,15 @@ import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.transform.TransformerException;
|
||||
import javax.xml.xpath.XPath;
|
||||
import javax.xml.xpath.XPathConstants;
|
||||
import javax.xml.xpath.XPathExpressionException;
|
||||
import javax.xml.xpath.XPathFactory;
|
||||
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.apache.xpath.XPathAPI;
|
||||
import org.dspace.authorize.AuthorizeException;
|
||||
import org.dspace.content.BitstreamFormat;
|
||||
import org.dspace.content.factory.ContentServiceFactory;
|
||||
import org.dspace.content.service.BitstreamFormatService;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.core.LogHelper;
|
||||
import org.dspace.core.LogManager;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
@@ -50,7 +47,7 @@ public class RegistryLoader {
|
||||
/**
|
||||
* log4j category
|
||||
*/
|
||||
private static Logger log = org.apache.logging.log4j.LogManager.getLogger(RegistryLoader.class);
|
||||
private static Logger log = Logger.getLogger(RegistryLoader.class);
|
||||
|
||||
protected static BitstreamFormatService bitstreamFormatService = ContentServiceFactory.getInstance()
|
||||
.getBitstreamFormatService();
|
||||
@@ -98,7 +95,7 @@ public class RegistryLoader {
|
||||
|
||||
System.exit(1);
|
||||
} catch (Exception e) {
|
||||
log.fatal(LogHelper.getHeader(context, "error_loading_registries",
|
||||
log.fatal(LogManager.getHeader(context, "error_loading_registries",
|
||||
""), e);
|
||||
|
||||
System.err.println("Error: \n - " + e.getMessage());
|
||||
@@ -125,13 +122,12 @@ public class RegistryLoader {
|
||||
*/
|
||||
public static void loadBitstreamFormats(Context context, String filename)
|
||||
throws SQLException, IOException, ParserConfigurationException,
|
||||
SAXException, TransformerException, AuthorizeException, XPathExpressionException {
|
||||
SAXException, TransformerException, AuthorizeException {
|
||||
Document document = loadXML(filename);
|
||||
|
||||
// Get the nodes corresponding to formats
|
||||
XPath xPath = XPathFactory.newInstance().newXPath();
|
||||
NodeList typeNodes = (NodeList) xPath.compile("dspace-bitstream-types/bitstream-type")
|
||||
.evaluate(document, XPathConstants.NODESET);
|
||||
NodeList typeNodes = XPathAPI.selectNodeList(document,
|
||||
"dspace-bitstream-types/bitstream-type");
|
||||
|
||||
// Add each one as a new format to the registry
|
||||
for (int i = 0; i < typeNodes.getLength(); i++) {
|
||||
@@ -139,7 +135,7 @@ public class RegistryLoader {
|
||||
loadFormat(context, n);
|
||||
}
|
||||
|
||||
log.info(LogHelper.getHeader(context, "load_bitstream_formats",
|
||||
log.info(LogManager.getHeader(context, "load_bitstream_formats",
|
||||
"number_loaded=" + typeNodes.getLength()));
|
||||
}
|
||||
|
||||
@@ -155,7 +151,8 @@ public class RegistryLoader {
|
||||
* @throws AuthorizeException if authorization error
|
||||
*/
|
||||
private static void loadFormat(Context context, Node node)
|
||||
throws SQLException, AuthorizeException, XPathExpressionException {
|
||||
throws SQLException, IOException, TransformerException,
|
||||
AuthorizeException {
|
||||
// Get the values
|
||||
String mimeType = getElementData(node, "mimetype");
|
||||
String shortDesc = getElementData(node, "short_description");
|
||||
@@ -234,10 +231,9 @@ public class RegistryLoader {
|
||||
* @throws TransformerException if transformer error
|
||||
*/
|
||||
private static String getElementData(Node parentElement, String childName)
|
||||
throws XPathExpressionException {
|
||||
throws TransformerException {
|
||||
// Grab the child node
|
||||
XPath xPath = XPathFactory.newInstance().newXPath();
|
||||
Node childNode = (Node) xPath.compile(childName).evaluate(parentElement, XPathConstants.NODE);
|
||||
Node childNode = XPathAPI.selectSingleNode(parentElement, childName);
|
||||
|
||||
if (childNode == null) {
|
||||
// No child node, so no values
|
||||
@@ -278,10 +274,9 @@ public class RegistryLoader {
|
||||
* @throws TransformerException if transformer error
|
||||
*/
|
||||
private static String[] getRepeatedElementData(Node parentElement,
|
||||
String childName) throws XPathExpressionException {
|
||||
String childName) throws TransformerException {
|
||||
// Grab the child node
|
||||
XPath xPath = XPathFactory.newInstance().newXPath();
|
||||
NodeList childNodes = (NodeList) xPath.compile(childName).evaluate(parentElement, XPathConstants.NODESET);
|
||||
NodeList childNodes = XPathAPI.selectNodeList(parentElement, childName);
|
||||
|
||||
String[] data = new String[childNodes.getLength()];
|
||||
|
||||
|
@@ -7,60 +7,34 @@
|
||||
*/
|
||||
package org.dspace.administer;
|
||||
|
||||
import static org.dspace.content.service.DSpaceObjectService.MD_COPYRIGHT_TEXT;
|
||||
import static org.dspace.content.service.DSpaceObjectService.MD_INTRODUCTORY_TEXT;
|
||||
import static org.dspace.content.service.DSpaceObjectService.MD_LICENSE;
|
||||
import static org.dspace.content.service.DSpaceObjectService.MD_NAME;
|
||||
import static org.dspace.content.service.DSpaceObjectService.MD_PROVENANCE_DESCRIPTION;
|
||||
import static org.dspace.content.service.DSpaceObjectService.MD_SHORT_DESCRIPTION;
|
||||
import static org.dspace.content.service.DSpaceObjectService.MD_SIDEBAR_TEXT;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.sql.SQLException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.transform.TransformerException;
|
||||
import javax.xml.xpath.XPath;
|
||||
import javax.xml.xpath.XPathConstants;
|
||||
import javax.xml.xpath.XPathExpressionException;
|
||||
import javax.xml.xpath.XPathFactory;
|
||||
|
||||
import org.apache.commons.cli.CommandLine;
|
||||
import org.apache.commons.cli.CommandLineParser;
|
||||
import org.apache.commons.cli.DefaultParser;
|
||||
import org.apache.commons.cli.HelpFormatter;
|
||||
import org.apache.commons.cli.Option;
|
||||
import org.apache.commons.cli.Options;
|
||||
import org.apache.commons.cli.ParseException;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.cli.PosixParser;
|
||||
import org.apache.xpath.XPathAPI;
|
||||
import org.dspace.authorize.AuthorizeException;
|
||||
import org.dspace.content.Collection;
|
||||
import org.dspace.content.Community;
|
||||
import org.dspace.content.Item;
|
||||
import org.dspace.content.MetadataFieldName;
|
||||
import org.dspace.content.MetadataSchemaEnum;
|
||||
import org.dspace.content.MetadataValue;
|
||||
import org.dspace.content.factory.ContentServiceFactory;
|
||||
import org.dspace.content.service.CollectionService;
|
||||
import org.dspace.content.service.CommunityService;
|
||||
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.handle.service.HandleService;
|
||||
import org.jdom2.Element;
|
||||
import org.jdom2.output.Format;
|
||||
import org.jdom2.output.XMLOutputter;
|
||||
import org.jdom.Element;
|
||||
import org.jdom.output.XMLOutputter;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
@@ -71,54 +45,44 @@ import org.xml.sax.SAXException;
|
||||
* an XML file.
|
||||
*
|
||||
* The XML file structure needs to be:
|
||||
* <pre>{@code
|
||||
* {@code
|
||||
* <import_structure>
|
||||
* <community>
|
||||
* <name>....</name>
|
||||
* <community>...</community>
|
||||
* <collection>
|
||||
* <name>....</name>
|
||||
* </collection>
|
||||
* </community>
|
||||
* <community>
|
||||
* <name>....</name>
|
||||
* <community>...</community>
|
||||
* <collection>
|
||||
* <name>....</name>
|
||||
* </collection>
|
||||
* </community>
|
||||
* </import_structure>
|
||||
* }</pre>
|
||||
*
|
||||
* <p>
|
||||
* It can be arbitrarily deep, and supports all the metadata elements
|
||||
* }
|
||||
* it can be arbitrarily deep, and supports all the metadata elements
|
||||
* that make up the community and collection metadata. See the system
|
||||
* documentation for more details.
|
||||
* documentation for more details
|
||||
*
|
||||
* @author Richard Jones
|
||||
*/
|
||||
|
||||
public class StructBuilder {
|
||||
/** Name of the root element for the document to be imported. */
|
||||
static final String INPUT_ROOT = "import_structure";
|
||||
|
||||
/*
|
||||
* Name of the root element for the document produced by importing.
|
||||
* Community and collection elements are annotated with their identifiers.
|
||||
/**
|
||||
* the output xml document which will contain updated information about the
|
||||
* imported structure
|
||||
*/
|
||||
static final String RESULT_ROOT = "imported_structure";
|
||||
private static org.jdom.Document xmlOutput = new org.jdom.Document(new Element("imported_structure"));
|
||||
|
||||
/**
|
||||
* A table to hold metadata for the collection being worked on.
|
||||
* a hashtable to hold metadata for the collection being worked on
|
||||
*/
|
||||
private static final Map<String, MetadataFieldName> collectionMap = new HashMap<>();
|
||||
private static Map<String, String> collectionMap = new HashMap<String, String>();
|
||||
|
||||
/**
|
||||
* A table to hold metadata for the community being worked on.
|
||||
* a hashtable to hold metadata for the community being worked on
|
||||
*/
|
||||
private static final Map<String, MetadataFieldName> communityMap = new HashMap<>();
|
||||
private static Map<String, String> communityMap = new HashMap<String, String>();
|
||||
|
||||
protected static final CommunityService communityService
|
||||
= ContentServiceFactory.getInstance().getCommunityService();
|
||||
protected static final CollectionService collectionService
|
||||
= ContentServiceFactory.getInstance().getCollectionService();
|
||||
protected static final EPersonService ePersonService
|
||||
= EPersonServiceFactory.getInstance().getEPersonService();
|
||||
protected static final HandleService handleService
|
||||
= HandleServiceFactory.getInstance().getHandleService();
|
||||
protected static CommunityService communityService = ContentServiceFactory.getInstance().getCommunityService();
|
||||
protected static CollectionService collectionService = ContentServiceFactory.getInstance().getCollectionService();
|
||||
protected static EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService();
|
||||
|
||||
/**
|
||||
* Default constructor
|
||||
@@ -127,384 +91,135 @@ public class StructBuilder {
|
||||
|
||||
/**
|
||||
* Main method to be run from the command line to import a structure into
|
||||
* DSpacee or export existing structure to a file.The command is of the form:
|
||||
* DSpace
|
||||
*
|
||||
* <p>{@code StructBuilder -f [XML source] -e [administrator email] -o [output file]}
|
||||
* This is of the form:
|
||||
*
|
||||
* <p>to import, or
|
||||
* {@code StructBuilder -f [xml source] -e [administrator email] -o [output file]}
|
||||
*
|
||||
* <p>{@code StructBuilder -x -e [administrator email] -o [output file]}</p>
|
||||
* The output file will contain exactly the same as the source xml document, but
|
||||
* with the handle for each imported item added as an attribute.
|
||||
*
|
||||
* <p>to export. The output will contain exactly the same as the source XML
|
||||
* document, but with the Handle for each imported item added as an attribute.
|
||||
*
|
||||
*
|
||||
* @param argv command line arguments.
|
||||
* @throws ParserConfigurationException passed through.
|
||||
* @throws SQLException passed through.
|
||||
* @throws FileNotFoundException if input or output could not be opened.
|
||||
* @throws TransformerException if the input document is invalid.
|
||||
* @throws XPathExpressionException passed through.
|
||||
* @param argv the command line arguments given
|
||||
* @throws Exception if an error occurs
|
||||
*/
|
||||
public static void main(String[] argv)
|
||||
throws ParserConfigurationException, SQLException,
|
||||
IOException, TransformerException, XPathExpressionException {
|
||||
// Define command line options.
|
||||
throws Exception {
|
||||
CommandLineParser parser = new PosixParser();
|
||||
|
||||
Options options = new Options();
|
||||
|
||||
options.addOption("h", "help", false, "Print this help message.");
|
||||
options.addOption("?", "help");
|
||||
options.addOption("x", "export", false, "Export the current structure as XML.");
|
||||
options.addOption("k", "keep-handles", false, "Apply Handles from input document.");
|
||||
options.addOption("f", "file", true, "file");
|
||||
options.addOption("e", "eperson", true, "eperson");
|
||||
options.addOption("o", "output", true, "output");
|
||||
|
||||
options.addOption(Option.builder("e").longOpt("eperson")
|
||||
.desc("User who is manipulating the repository's structure.")
|
||||
.hasArg().argName("eperson").required().build());
|
||||
CommandLine line = parser.parse(options, argv);
|
||||
|
||||
options.addOption(Option.builder("f").longOpt("file")
|
||||
.desc("File of new structure information.")
|
||||
.hasArg().argName("input").build());
|
||||
String file = null;
|
||||
String eperson = null;
|
||||
String output = null;
|
||||
|
||||
options.addOption(Option.builder("o").longOpt("output")
|
||||
.desc("File to receive the structure map ('-' for standard out).")
|
||||
.hasArg().argName("output").required().build());
|
||||
|
||||
// Parse the command line.
|
||||
CommandLineParser parser = new DefaultParser();
|
||||
CommandLine line = null;
|
||||
try {
|
||||
line = parser.parse(options, argv);
|
||||
} catch (ParseException ex) {
|
||||
System.err.println(ex.getMessage());
|
||||
usage(options);
|
||||
System.exit(1);
|
||||
if (line.hasOption('f')) {
|
||||
file = line.getOptionValue('f');
|
||||
}
|
||||
|
||||
// If the user asked for help, give it and exit.
|
||||
if (line.hasOption('h') || line.hasOption('?')) {
|
||||
giveHelp(options);
|
||||
if (line.hasOption('e')) {
|
||||
eperson = line.getOptionValue('e');
|
||||
}
|
||||
|
||||
if (line.hasOption('o')) {
|
||||
output = line.getOptionValue('o');
|
||||
}
|
||||
|
||||
if (output == null || eperson == null || file == null) {
|
||||
usage();
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
// Otherwise, analyze the command.
|
||||
// Must be import or export.
|
||||
if (!(line.hasOption('f') || line.hasOption('x'))) {
|
||||
giveHelp(options);
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
// Open the output stream.
|
||||
String output = line.getOptionValue('o');
|
||||
OutputStream outputStream;
|
||||
if ("-".equals(output)) {
|
||||
outputStream = System.out;
|
||||
} else {
|
||||
outputStream = new FileOutputStream(output);
|
||||
}
|
||||
|
||||
// create a context
|
||||
Context context = new Context();
|
||||
|
||||
// set the context.
|
||||
String eperson = line.getOptionValue('e');
|
||||
try {
|
||||
context.setCurrentUser(ePersonService.findByEmail(context, eperson));
|
||||
} catch (SQLException ex) {
|
||||
System.err.format("That user could not be found: %s%n", ex.getMessage());
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
// Export? Import?
|
||||
if (line.hasOption('x')) { // export
|
||||
exportStructure(context, outputStream);
|
||||
outputStream.close();
|
||||
} else { // Must be import
|
||||
String input = line.getOptionValue('f');
|
||||
if (null == input) {
|
||||
usage(options);
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
InputStream inputStream;
|
||||
if ("-".equals(input)) {
|
||||
inputStream = System.in;
|
||||
} else {
|
||||
inputStream = new FileInputStream(input);
|
||||
}
|
||||
|
||||
boolean keepHandles = options.hasOption("k");
|
||||
importStructure(context, inputStream, outputStream, keepHandles);
|
||||
|
||||
inputStream.close();
|
||||
outputStream.close();
|
||||
|
||||
// save changes from import
|
||||
context.complete();
|
||||
}
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Import new Community/Collection structure.
|
||||
*
|
||||
* @param context
|
||||
* @param input XML which describes the new communities and collections.
|
||||
* @param output input, annotated with the new objects' identifiers.
|
||||
* @param keepHandles true if Handles should be set from input.
|
||||
* @throws IOException
|
||||
* @throws ParserConfigurationException
|
||||
* @throws SAXException
|
||||
* @throws TransformerException
|
||||
* @throws SQLException
|
||||
*/
|
||||
static void importStructure(Context context, InputStream input,
|
||||
OutputStream output, boolean keepHandles)
|
||||
throws IOException, ParserConfigurationException, SQLException,
|
||||
TransformerException, XPathExpressionException {
|
||||
// set the context
|
||||
context.setCurrentUser(ePersonService.findByEmail(context, eperson));
|
||||
|
||||
// load the XML
|
||||
Document document = null;
|
||||
try {
|
||||
document = loadXML(input);
|
||||
} catch (IOException ex) {
|
||||
System.err.format("The input document could not be read: %s%n", ex.getMessage());
|
||||
System.exit(1);
|
||||
} catch (SAXException ex) {
|
||||
System.err.format("The input document could not be parsed: %s%n", ex.getMessage());
|
||||
System.exit(1);
|
||||
}
|
||||
Document document = loadXML(file);
|
||||
|
||||
// run the preliminary validation, to be sure that the the XML document
|
||||
// is properly structured.
|
||||
try {
|
||||
validate(document);
|
||||
} catch (XPathExpressionException ex) {
|
||||
System.err.format("The input document is invalid: %s%n", ex.getMessage());
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
// Check for 'identifier' attributes -- possibly output by this class.
|
||||
XPath xPath = XPathFactory.newInstance().newXPath();
|
||||
NodeList identifierNodes = (NodeList) xPath.compile("//*[@identifier]")
|
||||
.evaluate(document, XPathConstants.NODESET);
|
||||
if (identifierNodes.getLength() > 0) {
|
||||
if (!keepHandles) {
|
||||
System.err.println("The input document has 'identifier' attributes, which will be ignored.");
|
||||
} else {
|
||||
for (int i = 0; i < identifierNodes.getLength() ; i++) {
|
||||
String identifier = identifierNodes.item(i).getAttributes().item(0).getTextContent();
|
||||
if (handleService.resolveToURL(context, identifier) != null) {
|
||||
System.err.printf("The input document contains handle %s,"
|
||||
+ " which is in use already. Aborting...%n",
|
||||
identifier);
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// is properly structured
|
||||
validate(document);
|
||||
|
||||
// load the mappings into the member variable hashmaps
|
||||
communityMap.put("name", MD_NAME);
|
||||
communityMap.put("description", MD_SHORT_DESCRIPTION);
|
||||
communityMap.put("intro", MD_INTRODUCTORY_TEXT);
|
||||
communityMap.put("copyright", MD_COPYRIGHT_TEXT);
|
||||
communityMap.put("sidebar", MD_SIDEBAR_TEXT);
|
||||
communityMap.put("name", "name");
|
||||
communityMap.put("description", "short_description");
|
||||
communityMap.put("intro", "introductory_text");
|
||||
communityMap.put("copyright", "copyright_text");
|
||||
communityMap.put("sidebar", "side_bar_text");
|
||||
|
||||
collectionMap.put("name", MD_NAME);
|
||||
collectionMap.put("description", MD_SHORT_DESCRIPTION);
|
||||
collectionMap.put("intro", MD_INTRODUCTORY_TEXT);
|
||||
collectionMap.put("copyright", MD_COPYRIGHT_TEXT);
|
||||
collectionMap.put("sidebar", MD_SIDEBAR_TEXT);
|
||||
collectionMap.put("license", MD_LICENSE);
|
||||
collectionMap.put("provenance", MD_PROVENANCE_DESCRIPTION);
|
||||
collectionMap.put("name", "name");
|
||||
collectionMap.put("description", "short_description");
|
||||
collectionMap.put("intro", "introductory_text");
|
||||
collectionMap.put("copyright", "copyright_text");
|
||||
collectionMap.put("sidebar", "side_bar_text");
|
||||
collectionMap.put("license", "license");
|
||||
collectionMap.put("provenance", "provenance_description");
|
||||
|
||||
Element[] elements = new Element[]{};
|
||||
try {
|
||||
// get the top level community list
|
||||
NodeList first = (NodeList) xPath.compile("/import_structure/community")
|
||||
.evaluate(document, XPathConstants.NODESET);
|
||||
// get the top level community list
|
||||
NodeList first = XPathAPI.selectNodeList(document, "/import_structure/community");
|
||||
|
||||
// run the import starting with the top level communities
|
||||
elements = handleCommunities(context, first, null, keepHandles);
|
||||
} catch (TransformerException ex) {
|
||||
System.err.format("Input content not understood: %s%n", ex.getMessage());
|
||||
System.exit(1);
|
||||
} catch (AuthorizeException ex) {
|
||||
System.err.format("Not authorized: %s%n", ex.getMessage());
|
||||
System.exit(1);
|
||||
}
|
||||
// run the import starting with the top level communities
|
||||
Element[] elements = handleCommunities(context, first, null);
|
||||
|
||||
// generate the output
|
||||
final Element root = new Element(RESULT_ROOT);
|
||||
|
||||
for (Element element : elements) {
|
||||
root.addContent(element);
|
||||
Element root = xmlOutput.getRootElement();
|
||||
for (int i = 0; i < elements.length; i++) {
|
||||
root.addContent(elements[i]);
|
||||
}
|
||||
|
||||
// finally write the string into the output file.
|
||||
final org.jdom2.Document xmlOutput = new org.jdom2.Document(root);
|
||||
// finally write the string into the output file
|
||||
try {
|
||||
new XMLOutputter().output(xmlOutput, output);
|
||||
BufferedWriter out = new BufferedWriter(new FileWriter(output));
|
||||
out.write(new XMLOutputter().outputString(xmlOutput));
|
||||
out.close();
|
||||
} catch (IOException e) {
|
||||
System.out.printf("Unable to write to output file %s: %s%n",
|
||||
output, e.getMessage());
|
||||
System.exit(1);
|
||||
System.out.println("Unable to write to output file " + output);
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
context.complete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a single community, and its children, to the Document.
|
||||
*
|
||||
* @param community
|
||||
* @return a fragment representing this Community.
|
||||
* Output the usage information
|
||||
*/
|
||||
private static Element exportACommunity(Community community) {
|
||||
// Export this Community.
|
||||
Element element = new Element("community");
|
||||
element.setAttribute("identifier", community.getHandle());
|
||||
element.addContent(new Element("name").setText(community.getName()));
|
||||
element.addContent(new Element("description")
|
||||
.setText(communityService.getMetadataFirstValue(community,
|
||||
MetadataSchemaEnum.DC.getName(), "description", "abstract", Item.ANY)));
|
||||
element.addContent(new Element("intro")
|
||||
.setText(communityService.getMetadataFirstValue(community,
|
||||
MetadataSchemaEnum.DC.getName(), "description", null, Item.ANY)));
|
||||
element.addContent(new Element("copyright")
|
||||
.setText(communityService.getMetadataFirstValue(community,
|
||||
MetadataSchemaEnum.DC.getName(), "rights", null, Item.ANY)));
|
||||
element.addContent(new Element("sidebar")
|
||||
.setText(communityService.getMetadataFirstValue(community,
|
||||
MetadataSchemaEnum.DC.getName(), "description", "tableofcontents", Item.ANY)));
|
||||
|
||||
// Export this Community's Community children.
|
||||
for (Community subCommunity : community.getSubcommunities()) {
|
||||
element.addContent(exportACommunity(subCommunity));
|
||||
}
|
||||
|
||||
// Export this Community's Collection children.
|
||||
for (Collection collection : community.getCollections()) {
|
||||
element.addContent(exportACollection(collection));
|
||||
}
|
||||
|
||||
return element;
|
||||
private static void usage() {
|
||||
System.out.println("Usage: java StructBuilder -f <source XML file> -o <output file> -e <eperson email>");
|
||||
System.out.println(
|
||||
"Communities will be created from the top level, and a map of communities to handles will be returned in " +
|
||||
"the output file");
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a single Collection to the Document.
|
||||
*
|
||||
* @param collection
|
||||
* @return a fragment representing this Collection.
|
||||
*/
|
||||
private static Element exportACollection(Collection collection) {
|
||||
// Export this Collection.
|
||||
Element element = new Element("collection");
|
||||
element.setAttribute("identifier", collection.getHandle());
|
||||
element.addContent(new Element("name").setText(collection.getName()));
|
||||
element.addContent(new Element("description")
|
||||
.setText(collectionService.getMetadataFirstValue(collection,
|
||||
MetadataSchemaEnum.DC.getName(), "description", "abstract", Item.ANY)));
|
||||
element.addContent(new Element("intro")
|
||||
.setText(collectionService.getMetadataFirstValue(collection,
|
||||
MetadataSchemaEnum.DC.getName(), "description", null, Item.ANY)));
|
||||
element.addContent(new Element("copyright")
|
||||
.setText(collectionService.getMetadataFirstValue(collection,
|
||||
MetadataSchemaEnum.DC.getName(), "rights", null, Item.ANY)));
|
||||
element.addContent(new Element("sidebar")
|
||||
.setText(collectionService.getMetadataFirstValue(collection,
|
||||
MetadataSchemaEnum.DC.getName(), "description", "tableofcontents", Item.ANY)));
|
||||
element.addContent(new Element("license")
|
||||
.setText(collectionService.getMetadataFirstValue(collection,
|
||||
MetadataSchemaEnum.DC.getName(), "rights", "license", Item.ANY)));
|
||||
// Provenance is special: multivalued
|
||||
for (MetadataValue value : collectionService.getMetadata(collection,
|
||||
MetadataSchemaEnum.DC.getName(), "provenance", null, Item.ANY)) {
|
||||
element.addContent(new Element("provenance")
|
||||
.setText(value.getValue()));
|
||||
}
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write out the existing Community/Collection structure.
|
||||
*/
|
||||
static void exportStructure(Context context, OutputStream output) {
|
||||
// Build a document from the Community/Collection hierarchy.
|
||||
Element rootElement = new Element(INPUT_ROOT); // To be read by importStructure, perhaps
|
||||
|
||||
List<Community> communities = null;
|
||||
try {
|
||||
communities = communityService.findAllTop(context);
|
||||
} catch (SQLException ex) {
|
||||
System.out.printf("Unable to get the list of top-level communities: %s%n",
|
||||
ex.getMessage());
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
for (Community community : communities) {
|
||||
rootElement.addContent(exportACommunity(community));
|
||||
}
|
||||
|
||||
// Now write the structure out.
|
||||
org.jdom2.Document xmlOutput = new org.jdom2.Document(rootElement);
|
||||
try {
|
||||
XMLOutputter outputter = new XMLOutputter(Format.getPrettyFormat());
|
||||
outputter.output(xmlOutput, output);
|
||||
} catch (IOException e) {
|
||||
System.out.printf("Unable to write to output file %s: %s%n",
|
||||
output, e.getMessage());
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Output the usage information.
|
||||
*/
|
||||
private static void usage(Options options) {
|
||||
HelpFormatter helper = new HelpFormatter();
|
||||
try (PrintWriter writer = new PrintWriter(System.out);) {
|
||||
helper.printUsage(writer, 80/* FIXME Magic */,
|
||||
"structure-builder", options);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Help the user more.
|
||||
*/
|
||||
private static void giveHelp(Options options) {
|
||||
HelpFormatter formatter = new HelpFormatter();
|
||||
formatter.printHelp("struct-builder",
|
||||
"Import or export Community/Collection structure.",
|
||||
options,
|
||||
"When importing (-f), communities will be created from the "
|
||||
+ "top level, and a map of communities to handles will "
|
||||
+ "be returned in the output file. When exporting (-x),"
|
||||
+ "the current structure will be written to the map file.",
|
||||
true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the XML document. This method returns if the document is valid.
|
||||
* If validation fails it generates an error and ceases execution.
|
||||
* Validate the XML document. This method does not return, but if validation
|
||||
* fails it generates an error and ceases execution
|
||||
*
|
||||
* @param document the XML document object
|
||||
* @throws TransformerException if transformer error
|
||||
*/
|
||||
private static void validate(org.w3c.dom.Document document)
|
||||
throws XPathExpressionException {
|
||||
StringBuilder err = new StringBuilder();
|
||||
throws TransformerException {
|
||||
StringBuffer err = new StringBuffer();
|
||||
boolean trip = false;
|
||||
|
||||
err.append("The following errors were encountered parsing the source XML.\n");
|
||||
err.append("No changes have been made to the DSpace instance.\n\n");
|
||||
err.append("The following errors were encountered parsing the source XML\n");
|
||||
err.append("No changes have been made to the DSpace instance\n\n");
|
||||
|
||||
XPath xPath = XPathFactory.newInstance().newXPath();
|
||||
NodeList first = (NodeList) xPath.compile("/import_structure/community")
|
||||
.evaluate(document, XPathConstants.NODESET);
|
||||
NodeList first = XPathAPI.selectNodeList(document, "/import_structure/community");
|
||||
if (first.getLength() == 0) {
|
||||
err.append("-There are no top level communities in the source document.");
|
||||
err.append("-There are no top level communities in the source document");
|
||||
System.out.println(err.toString());
|
||||
System.exit(1);
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
String errs = validateCommunities(first, 1);
|
||||
@@ -515,13 +230,13 @@ public class StructBuilder {
|
||||
|
||||
if (trip) {
|
||||
System.out.println(err.toString());
|
||||
System.exit(1);
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the communities section of the XML document. This returns a string
|
||||
* containing any errors encountered, or null if there were no errors.
|
||||
* containing any errors encountered, or null if there were no errors
|
||||
*
|
||||
* @param communities the NodeList of communities to validate
|
||||
* @param level the level in the XML document that we are at, for the purposes
|
||||
@@ -530,25 +245,23 @@ public class StructBuilder {
|
||||
* no errors.
|
||||
*/
|
||||
private static String validateCommunities(NodeList communities, int level)
|
||||
throws XPathExpressionException {
|
||||
StringBuilder err = new StringBuilder();
|
||||
throws TransformerException {
|
||||
StringBuffer err = new StringBuffer();
|
||||
boolean trip = false;
|
||||
String errs = null;
|
||||
XPath xPath = XPathFactory.newInstance().newXPath();
|
||||
|
||||
for (int i = 0; i < communities.getLength(); i++) {
|
||||
Node n = communities.item(i);
|
||||
NodeList name = (NodeList) xPath.compile("name").evaluate(n, XPathConstants.NODESET);
|
||||
NodeList name = XPathAPI.selectNodeList(n, "name");
|
||||
if (name.getLength() != 1) {
|
||||
String pos = Integer.toString(i + 1);
|
||||
err.append("-The level ").append(level)
|
||||
.append(" community in position ").append(pos)
|
||||
.append(" does not contain exactly one name field.\n");
|
||||
err.append("-The level " + level + " community in position " + pos);
|
||||
err.append(" does not contain exactly one name field\n");
|
||||
trip = true;
|
||||
}
|
||||
|
||||
// validate sub communities
|
||||
NodeList subCommunities = (NodeList) xPath.compile("community").evaluate(n, XPathConstants.NODESET);
|
||||
NodeList subCommunities = XPathAPI.selectNodeList(n, "community");
|
||||
String comErrs = validateCommunities(subCommunities, level + 1);
|
||||
if (comErrs != null) {
|
||||
err.append(comErrs);
|
||||
@@ -556,7 +269,7 @@ public class StructBuilder {
|
||||
}
|
||||
|
||||
// validate collections
|
||||
NodeList collections = (NodeList) xPath.compile("collection").evaluate(n, XPathConstants.NODESET);
|
||||
NodeList collections = XPathAPI.selectNodeList(n, "collection");
|
||||
String colErrs = validateCollections(collections, level + 1);
|
||||
if (colErrs != null) {
|
||||
err.append(colErrs);
|
||||
@@ -573,27 +286,25 @@ public class StructBuilder {
|
||||
|
||||
/**
|
||||
* validate the collection section of the XML document. This generates a
|
||||
* string containing any errors encountered, or returns null if no errors.
|
||||
* string containing any errors encountered, or returns null if no errors
|
||||
*
|
||||
* @param collections a NodeList of collections to validate
|
||||
* @param level the level in the XML document for the purposes of error reporting
|
||||
* @return the errors to be generated by the calling method, or null if none
|
||||
*/
|
||||
private static String validateCollections(NodeList collections, int level)
|
||||
throws XPathExpressionException {
|
||||
StringBuilder err = new StringBuilder();
|
||||
throws TransformerException {
|
||||
StringBuffer err = new StringBuffer();
|
||||
boolean trip = false;
|
||||
String errs = null;
|
||||
XPath xPath = XPathFactory.newInstance().newXPath();
|
||||
|
||||
for (int i = 0; i < collections.getLength(); i++) {
|
||||
Node n = collections.item(i);
|
||||
NodeList name = (NodeList) xPath.compile("name").evaluate(n, XPathConstants.NODESET);
|
||||
NodeList name = XPathAPI.selectNodeList(n, "name");
|
||||
if (name.getLength() != 1) {
|
||||
String pos = Integer.toString(i + 1);
|
||||
err.append("-The level ").append(level)
|
||||
.append(" collection in position ").append(pos)
|
||||
.append(" does not contain exactly one name field.\n");
|
||||
err.append("-The level " + level + " collection in position " + pos);
|
||||
err.append(" does not contain exactly one name field\n");
|
||||
trip = true;
|
||||
}
|
||||
}
|
||||
@@ -606,17 +317,17 @@ public class StructBuilder {
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the XML document from input.
|
||||
* Load in the XML from file.
|
||||
*
|
||||
* @param input the filename to load from.
|
||||
* @return the DOM representation of the XML input.
|
||||
* @param filename the filename to load from
|
||||
* @return the DOM representation of the XML file
|
||||
*/
|
||||
private static org.w3c.dom.Document loadXML(InputStream input)
|
||||
private static org.w3c.dom.Document loadXML(String filename)
|
||||
throws IOException, ParserConfigurationException, SAXException {
|
||||
DocumentBuilder builder = DocumentBuilderFactory.newInstance()
|
||||
.newDocumentBuilder();
|
||||
|
||||
org.w3c.dom.Document document = builder.parse(input);
|
||||
org.w3c.dom.Document document = builder.parse(new File(filename));
|
||||
|
||||
return document;
|
||||
}
|
||||
@@ -627,7 +338,7 @@ public class StructBuilder {
|
||||
* @param node the node from which we want to extract the string value
|
||||
* @return the string value of the node
|
||||
*/
|
||||
private static String getStringValue(Node node) {
|
||||
public static String getStringValue(Node node) {
|
||||
String value = node.getNodeValue();
|
||||
|
||||
if (node.hasChildNodes()) {
|
||||
@@ -648,49 +359,43 @@ public class StructBuilder {
|
||||
* @param context the context of the request
|
||||
* @param communities a nodelist of communities to create along with their sub-structures
|
||||
* @param parent the parent community of the nodelist of communities to create
|
||||
* @param keepHandles use Handles from input.
|
||||
* @return an element array containing additional information regarding the
|
||||
* created communities (e.g. the handles they have been assigned)
|
||||
*/
|
||||
private static Element[] handleCommunities(Context context, NodeList communities,
|
||||
Community parent, boolean keepHandles)
|
||||
throws TransformerException, SQLException, AuthorizeException,
|
||||
XPathExpressionException {
|
||||
private static Element[] handleCommunities(Context context, NodeList communities, Community parent)
|
||||
throws TransformerException, SQLException, Exception {
|
||||
Element[] elements = new Element[communities.getLength()];
|
||||
XPath xPath = XPathFactory.newInstance().newXPath();
|
||||
|
||||
for (int i = 0; i < communities.getLength(); i++) {
|
||||
Node tn = communities.item(i);
|
||||
Node identifier = tn.getAttributes().getNamedItem("identifier");
|
||||
Community community;
|
||||
Element element = new Element("community");
|
||||
|
||||
// create the community or sub community
|
||||
Community community;
|
||||
if (null == identifier
|
||||
|| StringUtils.isBlank(identifier.getNodeValue())
|
||||
|| !keepHandles) {
|
||||
if (parent != null) {
|
||||
community = communityService.create(parent, context);
|
||||
} else {
|
||||
community = communityService.create(parent, context, identifier.getNodeValue());
|
||||
community = communityService.create(null, context);
|
||||
}
|
||||
|
||||
// default the short description to be an empty string
|
||||
communityService.setMetadataSingleValue(context, community,
|
||||
MD_SHORT_DESCRIPTION, null, " ");
|
||||
communityService.setMetadata(context, community, "short_description", " ");
|
||||
|
||||
// now update the metadata
|
||||
for (Map.Entry<String, MetadataFieldName> entry : communityMap.entrySet()) {
|
||||
NodeList nl = (NodeList) xPath.compile(entry.getKey()).evaluate(tn, XPathConstants.NODESET);
|
||||
Node tn = communities.item(i);
|
||||
for (Map.Entry<String, String> entry : communityMap.entrySet()) {
|
||||
NodeList nl = XPathAPI.selectNodeList(tn, entry.getKey());
|
||||
if (nl.getLength() == 1) {
|
||||
communityService.setMetadataSingleValue(context, community,
|
||||
entry.getValue(), null, getStringValue(nl.item(0)));
|
||||
communityService.setMetadata(context, community, entry.getValue(), getStringValue(nl.item(0)));
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: at the moment, if the community already exists by name
|
||||
// then this will throw an SQLException on a duplicate key
|
||||
// violation.
|
||||
// Ideally we'd skip this row and continue to create sub communities
|
||||
// and so forth where they don't exist, but it's proving difficult
|
||||
// then this will throw a PSQLException on a duplicate key
|
||||
// violation
|
||||
// Ideally we'd skip this row and continue to create sub
|
||||
// communities
|
||||
// and so forth where they don't exist, but it's proving
|
||||
// difficult
|
||||
// to isolate the community that already exists without hitting
|
||||
// the database directly.
|
||||
communityService.update(context, community);
|
||||
@@ -703,59 +408,43 @@ public class StructBuilder {
|
||||
// but it's here to keep it separate from the create process in
|
||||
// case
|
||||
// we want to move it or make it switchable later
|
||||
Element element = new Element("community");
|
||||
element.setAttribute("identifier", community.getHandle());
|
||||
|
||||
Element nameElement = new Element("name");
|
||||
nameElement.setText(communityService.getMetadataFirstValue(
|
||||
community, CommunityService.MD_NAME, Item.ANY));
|
||||
nameElement.setText(communityService.getMetadata(community, "name"));
|
||||
element.addContent(nameElement);
|
||||
|
||||
String fieldValue;
|
||||
|
||||
fieldValue = communityService.getMetadataFirstValue(community,
|
||||
CommunityService.MD_SHORT_DESCRIPTION, Item.ANY);
|
||||
if (fieldValue != null) {
|
||||
if (communityService.getMetadata(community, "short_description") != null) {
|
||||
Element descriptionElement = new Element("description");
|
||||
descriptionElement.setText(fieldValue);
|
||||
descriptionElement.setText(communityService.getMetadata(community, "short_description"));
|
||||
element.addContent(descriptionElement);
|
||||
}
|
||||
|
||||
fieldValue = communityService.getMetadataFirstValue(community,
|
||||
CommunityService.MD_INTRODUCTORY_TEXT, Item.ANY);
|
||||
if (fieldValue != null) {
|
||||
if (communityService.getMetadata(community, "introductory_text") != null) {
|
||||
Element introElement = new Element("intro");
|
||||
introElement.setText(fieldValue);
|
||||
introElement.setText(communityService.getMetadata(community, "introductory_text"));
|
||||
element.addContent(introElement);
|
||||
}
|
||||
|
||||
fieldValue = communityService.getMetadataFirstValue(community,
|
||||
CommunityService.MD_COPYRIGHT_TEXT, Item.ANY);
|
||||
if (fieldValue != null) {
|
||||
if (communityService.getMetadata(community, "copyright_text") != null) {
|
||||
Element copyrightElement = new Element("copyright");
|
||||
copyrightElement.setText(fieldValue);
|
||||
copyrightElement.setText(communityService.getMetadata(community, "copyright_text"));
|
||||
element.addContent(copyrightElement);
|
||||
}
|
||||
|
||||
fieldValue = communityService.getMetadataFirstValue(community,
|
||||
CommunityService.MD_SIDEBAR_TEXT, Item.ANY);
|
||||
if (fieldValue != null) {
|
||||
if (communityService.getMetadata(community, "side_bar_text") != null) {
|
||||
Element sidebarElement = new Element("sidebar");
|
||||
sidebarElement.setText(fieldValue);
|
||||
sidebarElement.setText(communityService.getMetadata(community, "side_bar_text"));
|
||||
element.addContent(sidebarElement);
|
||||
}
|
||||
|
||||
// handle sub communities
|
||||
NodeList subCommunities = (NodeList) xPath.compile("community")
|
||||
.evaluate(tn, XPathConstants.NODESET);
|
||||
Element[] subCommunityElements = handleCommunities(context,
|
||||
subCommunities, community, keepHandles);
|
||||
NodeList subCommunities = XPathAPI.selectNodeList(tn, "community");
|
||||
Element[] subCommunityElements = handleCommunities(context, subCommunities, community);
|
||||
|
||||
// handle collections
|
||||
NodeList collections = (NodeList) xPath.compile("collection")
|
||||
.evaluate(tn, XPathConstants.NODESET);
|
||||
Element[] collectionElements = handleCollections(context,
|
||||
collections, community, keepHandles);
|
||||
NodeList collections = XPathAPI.selectNodeList(tn, "collection");
|
||||
Element[] collectionElements = handleCollections(context, collections, community);
|
||||
|
||||
int j;
|
||||
for (j = 0; j < subCommunityElements.length; j++) {
|
||||
@@ -780,96 +469,67 @@ public class StructBuilder {
|
||||
* @return an Element array containing additional information about the
|
||||
* created collections (e.g. the handle)
|
||||
*/
|
||||
private static Element[] handleCollections(Context context,
|
||||
NodeList collections, Community parent, boolean keepHandles)
|
||||
throws SQLException, AuthorizeException, XPathExpressionException {
|
||||
private static Element[] handleCollections(Context context, NodeList collections, Community parent)
|
||||
throws TransformerException, SQLException, AuthorizeException, IOException, Exception {
|
||||
Element[] elements = new Element[collections.getLength()];
|
||||
XPath xPath = XPathFactory.newInstance().newXPath();
|
||||
|
||||
for (int i = 0; i < collections.getLength(); i++) {
|
||||
Node tn = collections.item(i);
|
||||
Node identifier = tn.getAttributes().getNamedItem("identifier");
|
||||
|
||||
// Create the Collection.
|
||||
Collection collection;
|
||||
if (null == identifier
|
||||
|| StringUtils.isBlank(identifier.getNodeValue())
|
||||
|| !keepHandles) {
|
||||
collection = collectionService.create(context, parent);
|
||||
} else {
|
||||
collection = collectionService.create(context, parent, identifier.getNodeValue());
|
||||
}
|
||||
Element element = new Element("collection");
|
||||
Collection collection = collectionService.create(context, parent);
|
||||
|
||||
// default the short description to the empty string
|
||||
collectionService.setMetadataSingleValue(context, collection,
|
||||
MD_SHORT_DESCRIPTION, null, " ");
|
||||
collectionService.setMetadata(context, collection, "short_description", " ");
|
||||
|
||||
// import the rest of the metadata
|
||||
for (Map.Entry<String, MetadataFieldName> entry : collectionMap.entrySet()) {
|
||||
NodeList nl = (NodeList) xPath.compile(entry.getKey()).evaluate(tn, XPathConstants.NODESET);
|
||||
Node tn = collections.item(i);
|
||||
for (Map.Entry<String, String> entry : collectionMap.entrySet()) {
|
||||
NodeList nl = XPathAPI.selectNodeList(tn, entry.getKey());
|
||||
if (nl.getLength() == 1) {
|
||||
collectionService.setMetadataSingleValue(context, collection,
|
||||
entry.getValue(), null, getStringValue(nl.item(0)));
|
||||
collectionService.setMetadata(context, collection, entry.getValue(), getStringValue(nl.item(0)));
|
||||
}
|
||||
}
|
||||
|
||||
collectionService.update(context, collection);
|
||||
|
||||
Element element = new Element("collection");
|
||||
element.setAttribute("identifier", collection.getHandle());
|
||||
|
||||
Element nameElement = new Element("name");
|
||||
nameElement.setText(collectionService.getMetadataFirstValue(collection,
|
||||
CollectionService.MD_NAME, Item.ANY));
|
||||
nameElement.setText(collectionService.getMetadata(collection, "name"));
|
||||
element.addContent(nameElement);
|
||||
|
||||
String fieldValue;
|
||||
|
||||
fieldValue = collectionService.getMetadataFirstValue(collection,
|
||||
CollectionService.MD_SHORT_DESCRIPTION, Item.ANY);
|
||||
if (fieldValue != null) {
|
||||
if (collectionService.getMetadata(collection, "short_description") != null) {
|
||||
Element descriptionElement = new Element("description");
|
||||
descriptionElement.setText(fieldValue);
|
||||
descriptionElement.setText(collectionService.getMetadata(collection, "short_description"));
|
||||
element.addContent(descriptionElement);
|
||||
}
|
||||
|
||||
fieldValue = collectionService.getMetadataFirstValue(collection,
|
||||
CollectionService.MD_INTRODUCTORY_TEXT, Item.ANY);
|
||||
if (fieldValue != null) {
|
||||
if (collectionService.getMetadata(collection, "introductory_text") != null) {
|
||||
Element introElement = new Element("intro");
|
||||
introElement.setText(fieldValue);
|
||||
introElement.setText(collectionService.getMetadata(collection, "introductory_text"));
|
||||
element.addContent(introElement);
|
||||
}
|
||||
|
||||
fieldValue = collectionService.getMetadataFirstValue(collection,
|
||||
CollectionService.MD_COPYRIGHT_TEXT, Item.ANY);
|
||||
if (fieldValue != null) {
|
||||
if (collectionService.getMetadata(collection, "copyright_text") != null) {
|
||||
Element copyrightElement = new Element("copyright");
|
||||
copyrightElement.setText(fieldValue);
|
||||
copyrightElement.setText(collectionService.getMetadata(collection, "copyright_text"));
|
||||
element.addContent(copyrightElement);
|
||||
}
|
||||
|
||||
fieldValue = collectionService.getMetadataFirstValue(collection,
|
||||
CollectionService.MD_SIDEBAR_TEXT, Item.ANY);
|
||||
if (fieldValue != null) {
|
||||
if (collectionService.getMetadata(collection, "side_bar_text") != null) {
|
||||
Element sidebarElement = new Element("sidebar");
|
||||
sidebarElement.setText(fieldValue);
|
||||
sidebarElement.setText(collectionService.getMetadata(collection, "side_bar_text"));
|
||||
element.addContent(sidebarElement);
|
||||
}
|
||||
|
||||
fieldValue = collectionService.getMetadataFirstValue(collection,
|
||||
CollectionService.MD_LICENSE, Item.ANY);
|
||||
if (fieldValue != null) {
|
||||
if (collectionService.getMetadata(collection, "license") != null) {
|
||||
Element sidebarElement = new Element("license");
|
||||
sidebarElement.setText(fieldValue);
|
||||
sidebarElement.setText(collectionService.getMetadata(collection, "license"));
|
||||
element.addContent(sidebarElement);
|
||||
}
|
||||
|
||||
fieldValue = collectionService.getMetadataFirstValue(collection,
|
||||
CollectionService.MD_PROVENANCE_DESCRIPTION, Item.ANY);
|
||||
if (fieldValue != null) {
|
||||
if (collectionService.getMetadata(collection, "provenance_description") != null) {
|
||||
Element sidebarElement = new Element("provenance");
|
||||
sidebarElement.setText(fieldValue);
|
||||
sidebarElement.setText(collectionService.getMetadata(collection, "provenance_description"));
|
||||
element.addContent(sidebarElement);
|
||||
}
|
||||
|
||||
@@ -878,4 +538,5 @@ public class StructBuilder {
|
||||
|
||||
return elements;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,54 +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.alerts;
|
||||
|
||||
/**
|
||||
* Enum representing the options for allowing sessions:
|
||||
* ALLOW_ALL_SESSIONS - Will allow all users to log in and continue their sessions
|
||||
* ALLOW_CURRENT_SESSIONS_ONLY - Will prevent non admin users from logging in, however logged-in users
|
||||
* will remain logged in
|
||||
* ALLOW_ADMIN_SESSIONS_ONLY - Only admin users can log in, non admin sessions will be interrupted
|
||||
*
|
||||
* NOTE: This functionality can be stored in the database, but no support is present right now to interrupt and prevent
|
||||
* sessions.
|
||||
*/
|
||||
public enum AllowSessionsEnum {
|
||||
ALLOW_ALL_SESSIONS("all"),
|
||||
ALLOW_CURRENT_SESSIONS_ONLY("current"),
|
||||
ALLOW_ADMIN_SESSIONS_ONLY("admin");
|
||||
|
||||
private String allowSessionsType;
|
||||
|
||||
AllowSessionsEnum(String allowSessionsType) {
|
||||
this.allowSessionsType = allowSessionsType;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return allowSessionsType;
|
||||
}
|
||||
|
||||
public static AllowSessionsEnum fromString(String alertAllowSessionType) {
|
||||
if (alertAllowSessionType == null) {
|
||||
return AllowSessionsEnum.ALLOW_ALL_SESSIONS;
|
||||
}
|
||||
|
||||
switch (alertAllowSessionType) {
|
||||
case "all":
|
||||
return AllowSessionsEnum.ALLOW_ALL_SESSIONS;
|
||||
case "current":
|
||||
return AllowSessionsEnum.ALLOW_CURRENT_SESSIONS_ONLY;
|
||||
case "admin" :
|
||||
return AllowSessionsEnum.ALLOW_ADMIN_SESSIONS_ONLY;
|
||||
default:
|
||||
throw new IllegalArgumentException("No corresponding enum value for provided string: "
|
||||
+ alertAllowSessionType);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -1,179 +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.alerts;
|
||||
|
||||
import java.util.Date;
|
||||
import javax.persistence.Cacheable;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.SequenceGenerator;
|
||||
import javax.persistence.Table;
|
||||
import javax.persistence.Temporal;
|
||||
import javax.persistence.TemporalType;
|
||||
|
||||
import org.apache.commons.lang3.builder.EqualsBuilder;
|
||||
import org.apache.commons.lang3.builder.HashCodeBuilder;
|
||||
import org.dspace.core.ReloadableEntity;
|
||||
import org.hibernate.annotations.CacheConcurrencyStrategy;
|
||||
|
||||
/**
|
||||
* Database object representing system-wide alerts
|
||||
*/
|
||||
@Entity
|
||||
@Cacheable
|
||||
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE, include = "non-lazy")
|
||||
@Table(name = "systemwidealert")
|
||||
public class SystemWideAlert implements ReloadableEntity<Integer> {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "alert_id_seq")
|
||||
@SequenceGenerator(name = "alert_id_seq", sequenceName = "alert_id_seq", allocationSize = 1)
|
||||
@Column(name = "alert_id", unique = true, nullable = false)
|
||||
private Integer alertId;
|
||||
|
||||
@Column(name = "message", nullable = false)
|
||||
private String message;
|
||||
|
||||
@Column(name = "allow_sessions")
|
||||
private String allowSessions;
|
||||
|
||||
@Column(name = "countdown_to")
|
||||
@Temporal(TemporalType.TIMESTAMP)
|
||||
private Date countdownTo;
|
||||
|
||||
@Column(name = "active")
|
||||
private boolean active;
|
||||
|
||||
protected SystemWideAlert() {
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the ID that the system-wide alert holds within the database
|
||||
*
|
||||
* @return The ID that the system-wide alert holds within the database
|
||||
*/
|
||||
@Override
|
||||
public Integer getID() {
|
||||
return alertId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the ID for the system-wide alert
|
||||
*
|
||||
* @param alertID The ID to set
|
||||
*/
|
||||
public void setID(final Integer alertID) {
|
||||
this.alertId = alertID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the message of the system-wide alert
|
||||
*
|
||||
* @return the message of the system-wide alert
|
||||
*/
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the message of the system-wide alert
|
||||
*
|
||||
* @param message The message to set
|
||||
*/
|
||||
public void setMessage(final String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve what kind of sessions are allowed while the system-wide alert is active
|
||||
*
|
||||
* @return what kind of sessions are allowed while the system-wide alert is active
|
||||
*/
|
||||
public AllowSessionsEnum getAllowSessions() {
|
||||
return AllowSessionsEnum.fromString(allowSessions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set what kind of sessions are allowed while the system-wide alert is active
|
||||
*
|
||||
* @param allowSessions Integer representing what kind of sessions are allowed
|
||||
*/
|
||||
public void setAllowSessions(AllowSessionsEnum allowSessions) {
|
||||
this.allowSessions = allowSessions.getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the date to which will be count down when the system-wide alert is active
|
||||
*
|
||||
* @return the date to which will be count down when the system-wide alert is active
|
||||
*/
|
||||
public Date getCountdownTo() {
|
||||
return countdownTo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the date to which will be count down when the system-wide alert is active
|
||||
*
|
||||
* @param countdownTo The date to which will be count down
|
||||
*/
|
||||
public void setCountdownTo(final Date countdownTo) {
|
||||
this.countdownTo = countdownTo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve whether the system-wide alert is active
|
||||
*
|
||||
* @return whether the system-wide alert is active
|
||||
*/
|
||||
public boolean isActive() {
|
||||
return active;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether the system-wide alert is active
|
||||
*
|
||||
* @param active Whether the system-wide alert is active
|
||||
*/
|
||||
public void setActive(final boolean active) {
|
||||
this.active = active;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return <code>true</code> if <code>other</code> is the same SystemWideAlert
|
||||
* as this object, <code>false</code> otherwise
|
||||
*
|
||||
* @param other object to compare to
|
||||
* @return <code>true</code> if object passed in represents the same
|
||||
* system-wide alert as this object
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
return (other instanceof SystemWideAlert &&
|
||||
new EqualsBuilder().append(this.getID(), ((SystemWideAlert) other).getID())
|
||||
.append(this.getMessage(), ((SystemWideAlert) other).getMessage())
|
||||
.append(this.getAllowSessions(), ((SystemWideAlert) other).getAllowSessions())
|
||||
.append(this.getCountdownTo(), ((SystemWideAlert) other).getCountdownTo())
|
||||
.append(this.isActive(), ((SystemWideAlert) other).isActive())
|
||||
.isEquals());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return new HashCodeBuilder(17, 37)
|
||||
.append(this.getID())
|
||||
.append(this.getMessage())
|
||||
.append(this.getAllowSessions())
|
||||
.append(this.getCountdownTo())
|
||||
.append(this.isActive())
|
||||
.toHashCode();
|
||||
}
|
||||
|
||||
}
|
@@ -1,129 +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.alerts;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.dspace.alerts.dao.SystemWideAlertDAO;
|
||||
import org.dspace.alerts.service.SystemWideAlertService;
|
||||
import org.dspace.authorize.AuthorizeException;
|
||||
import org.dspace.authorize.service.AuthorizeService;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.core.LogHelper;
|
||||
import org.dspace.eperson.EPerson;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
/**
|
||||
* The implementation for the {@link SystemWideAlertService} class
|
||||
*/
|
||||
public class SystemWideAlertServiceImpl implements SystemWideAlertService {
|
||||
|
||||
private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(SystemWideAlertService.class);
|
||||
|
||||
|
||||
@Autowired
|
||||
private SystemWideAlertDAO systemWideAlertDAO;
|
||||
|
||||
@Autowired
|
||||
private AuthorizeService authorizeService;
|
||||
|
||||
@Override
|
||||
public SystemWideAlert create(final Context context, final String message,
|
||||
final AllowSessionsEnum allowSessionsType,
|
||||
final Date countdownTo, final boolean active) throws SQLException,
|
||||
AuthorizeException {
|
||||
if (!authorizeService.isAdmin(context)) {
|
||||
throw new AuthorizeException(
|
||||
"Only administrators can create a system-wide alert");
|
||||
}
|
||||
SystemWideAlert systemWideAlert = new SystemWideAlert();
|
||||
systemWideAlert.setMessage(message);
|
||||
systemWideAlert.setAllowSessions(allowSessionsType);
|
||||
systemWideAlert.setCountdownTo(countdownTo);
|
||||
systemWideAlert.setActive(active);
|
||||
|
||||
SystemWideAlert createdAlert = systemWideAlertDAO.create(context, systemWideAlert);
|
||||
log.info(LogHelper.getHeader(context, "system_wide_alert_create",
|
||||
"System Wide Alert has been created with message: '" + message + "' and ID "
|
||||
+ createdAlert.getID() + " and allowSessionsType " + allowSessionsType +
|
||||
" and active set to " + active));
|
||||
|
||||
|
||||
return createdAlert;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SystemWideAlert find(final Context context, final int alertId) throws SQLException {
|
||||
return systemWideAlertDAO.findByID(context, SystemWideAlert.class, alertId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SystemWideAlert> findAll(final Context context) throws SQLException {
|
||||
return systemWideAlertDAO.findAll(context, SystemWideAlert.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SystemWideAlert> findAll(final Context context, final int limit, final int offset) throws SQLException {
|
||||
return systemWideAlertDAO.findAll(context, limit, offset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SystemWideAlert> findAllActive(final Context context, final int limit, final int offset)
|
||||
throws SQLException {
|
||||
return systemWideAlertDAO.findAllActive(context, limit, offset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(final Context context, final SystemWideAlert systemWideAlert)
|
||||
throws SQLException, IOException, AuthorizeException {
|
||||
if (!authorizeService.isAdmin(context)) {
|
||||
throw new AuthorizeException(
|
||||
"Only administrators can create a system-wide alert");
|
||||
}
|
||||
systemWideAlertDAO.delete(context, systemWideAlert);
|
||||
log.info(LogHelper.getHeader(context, "system_wide_alert_create",
|
||||
"System Wide Alert with ID " + systemWideAlert.getID() + " has been deleted"));
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(final Context context, final SystemWideAlert systemWideAlert)
|
||||
throws SQLException, AuthorizeException {
|
||||
if (!authorizeService.isAdmin(context)) {
|
||||
throw new AuthorizeException(
|
||||
"Only administrators can create a system-wide alert");
|
||||
}
|
||||
systemWideAlertDAO.save(context, systemWideAlert);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canNonAdminUserLogin(Context context) throws SQLException {
|
||||
List<SystemWideAlert> active = findAllActive(context, 1, 0);
|
||||
if (active == null || active.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
return active.get(0).getAllowSessions() == AllowSessionsEnum.ALLOW_ALL_SESSIONS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canUserMaintainSession(Context context, EPerson ePerson) throws SQLException {
|
||||
if (authorizeService.isAdmin(context, ePerson)) {
|
||||
return true;
|
||||
}
|
||||
List<SystemWideAlert> active = findAllActive(context, 1, 0);
|
||||
if (active == null || active.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
return active.get(0).getAllowSessions() != AllowSessionsEnum.ALLOW_ADMIN_SESSIONS_ONLY;
|
||||
}
|
||||
}
|
@@ -1,45 +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.alerts.dao;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
|
||||
import org.dspace.alerts.SystemWideAlert;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.core.GenericDAO;
|
||||
|
||||
/**
|
||||
* This is the Data Access Object for the {@link SystemWideAlert} object
|
||||
*/
|
||||
public interface SystemWideAlertDAO extends GenericDAO<SystemWideAlert> {
|
||||
|
||||
/**
|
||||
* Returns a list of all SystemWideAlert objects in the database
|
||||
*
|
||||
* @param context The relevant DSpace context
|
||||
* @param limit The limit for the amount of SystemWideAlerts returned
|
||||
* @param offset The offset for the Processes to be returned
|
||||
* @return The list of all SystemWideAlert objects in the Database
|
||||
* @throws SQLException If something goes wrong
|
||||
*/
|
||||
List<SystemWideAlert> findAll(Context context, int limit, int offset) throws SQLException;
|
||||
|
||||
/**
|
||||
* Returns a list of all active SystemWideAlert objects in the database
|
||||
*
|
||||
* @param context The relevant DSpace context
|
||||
* @param limit The limit for the amount of SystemWideAlerts returned
|
||||
* @param offset The offset for the Processes to be returned
|
||||
* @return The list of all SystemWideAlert objects in the Database
|
||||
* @throws SQLException If something goes wrong
|
||||
*/
|
||||
List<SystemWideAlert> findAllActive(Context context, int limit, int offset) throws SQLException;
|
||||
|
||||
|
||||
}
|
@@ -1,48 +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.alerts.dao.impl;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
import javax.persistence.criteria.CriteriaBuilder;
|
||||
import javax.persistence.criteria.CriteriaQuery;
|
||||
import javax.persistence.criteria.Root;
|
||||
|
||||
import org.dspace.alerts.SystemWideAlert;
|
||||
import org.dspace.alerts.SystemWideAlert_;
|
||||
import org.dspace.alerts.dao.SystemWideAlertDAO;
|
||||
import org.dspace.core.AbstractHibernateDAO;
|
||||
import org.dspace.core.Context;
|
||||
|
||||
/**
|
||||
* Implementation class for the {@link SystemWideAlertDAO}
|
||||
*/
|
||||
public class SystemWideAlertDAOImpl extends AbstractHibernateDAO<SystemWideAlert> implements SystemWideAlertDAO {
|
||||
|
||||
public List<SystemWideAlert> findAll(final Context context, final int limit, final int offset) throws SQLException {
|
||||
CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context);
|
||||
CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, SystemWideAlert.class);
|
||||
Root<SystemWideAlert> alertRoot = criteriaQuery.from(SystemWideAlert.class);
|
||||
criteriaQuery.select(alertRoot);
|
||||
|
||||
return list(context, criteriaQuery, false, SystemWideAlert.class, limit, offset);
|
||||
}
|
||||
|
||||
public List<SystemWideAlert> findAllActive(final Context context, final int limit, final int offset)
|
||||
throws SQLException {
|
||||
CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context);
|
||||
CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, SystemWideAlert.class);
|
||||
Root<SystemWideAlert> alertRoot = criteriaQuery.from(SystemWideAlert.class);
|
||||
criteriaQuery.select(alertRoot);
|
||||
criteriaQuery.where(criteriaBuilder.equal(alertRoot.get(SystemWideAlert_.active), true));
|
||||
|
||||
return list(context, criteriaQuery, false, SystemWideAlert.class, limit, offset);
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -1,118 +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.alerts.service;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import org.dspace.alerts.AllowSessionsEnum;
|
||||
import org.dspace.alerts.SystemWideAlert;
|
||||
import org.dspace.authorize.AuthorizeException;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.eperson.EPerson;
|
||||
|
||||
/**
|
||||
* An interface for the SystemWideAlertService with methods regarding the SystemWideAlert workload
|
||||
*/
|
||||
public interface SystemWideAlertService {
|
||||
|
||||
/**
|
||||
* This method will create a SystemWideAlert object in the database
|
||||
*
|
||||
* @param context The relevant DSpace context
|
||||
* @param message The message of the system-wide alert
|
||||
* @param allowSessionsType Which sessions need to be allowed for the system-wide alert
|
||||
* @param countdownTo The date to which to count down to when the system-wide alert is active
|
||||
* @param active Whether the system-wide alert os active
|
||||
* @return The created SystemWideAlert object
|
||||
* @throws SQLException If something goes wrong
|
||||
*/
|
||||
SystemWideAlert create(Context context, String message, AllowSessionsEnum allowSessionsType,
|
||||
Date countdownTo, boolean active
|
||||
) throws SQLException, AuthorizeException;
|
||||
|
||||
/**
|
||||
* This method will retrieve a SystemWideAlert object from the Database with the given ID
|
||||
*
|
||||
* @param context The relevant DSpace context
|
||||
* @param alertId The alert id on which we'll search for in the database
|
||||
* @return The system-wide alert that holds the given alert id
|
||||
* @throws SQLException If something goes wrong
|
||||
*/
|
||||
SystemWideAlert find(Context context, int alertId) throws SQLException;
|
||||
|
||||
/**
|
||||
* Returns a list of all SystemWideAlert objects in the database
|
||||
*
|
||||
* @param context The relevant DSpace context
|
||||
* @return The list of all SystemWideAlert objects in the Database
|
||||
* @throws SQLException If something goes wrong
|
||||
*/
|
||||
List<SystemWideAlert> findAll(Context context) throws SQLException;
|
||||
|
||||
/**
|
||||
* Returns a list of all SystemWideAlert objects in the database
|
||||
*
|
||||
* @param context The relevant DSpace context
|
||||
* @param limit The limit for the amount of system-wide alerts returned
|
||||
* @param offset The offset for the system-wide alerts to be returned
|
||||
* @return The list of all SystemWideAlert objects in the Database
|
||||
* @throws SQLException If something goes wrong
|
||||
*/
|
||||
List<SystemWideAlert> findAll(Context context, int limit, int offset) throws SQLException;
|
||||
|
||||
|
||||
/**
|
||||
* Returns a list of all active SystemWideAlert objects in the database
|
||||
*
|
||||
* @param context The relevant DSpace context
|
||||
* @return The list of all active SystemWideAlert objects in the database
|
||||
* @throws SQLException If something goes wrong
|
||||
*/
|
||||
List<SystemWideAlert> findAllActive(Context context, int limit, int offset) throws SQLException;
|
||||
|
||||
/**
|
||||
* This method will delete the given SystemWideAlert object from the database
|
||||
*
|
||||
* @param context The relevant DSpace context
|
||||
* @param systemWideAlert The SystemWideAlert object to be deleted
|
||||
* @throws SQLException If something goes wrong
|
||||
*/
|
||||
void delete(Context context, SystemWideAlert systemWideAlert)
|
||||
throws SQLException, IOException, AuthorizeException;
|
||||
|
||||
|
||||
/**
|
||||
* This method will be used to update the given SystemWideAlert object in the database
|
||||
*
|
||||
* @param context The relevant DSpace context
|
||||
* @param systemWideAlert The SystemWideAlert object to be updated
|
||||
* @throws SQLException If something goes wrong
|
||||
*/
|
||||
void update(Context context, SystemWideAlert systemWideAlert) throws SQLException, AuthorizeException;
|
||||
|
||||
|
||||
/**
|
||||
* Verifies if the user connected to the current context can retain its session
|
||||
*
|
||||
* @param context The relevant DSpace context
|
||||
* @return if the user connected to the current context can retain its session
|
||||
*/
|
||||
boolean canUserMaintainSession(Context context, EPerson ePerson) throws SQLException;
|
||||
|
||||
|
||||
/**
|
||||
* Verifies if a non admin user can log in
|
||||
*
|
||||
* @param context The relevant DSpace context
|
||||
* @return if a non admin user can log in
|
||||
*/
|
||||
boolean canNonAdminUserLogin(Context context) throws SQLException;
|
||||
}
|
@@ -1,689 +0,0 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
package org.dspace.app.bulkaccesscontrol;
|
||||
|
||||
import static org.apache.commons.collections4.CollectionUtils.isEmpty;
|
||||
import static org.apache.commons.collections4.CollectionUtils.isNotEmpty;
|
||||
import static org.dspace.authorize.ResourcePolicy.TYPE_CUSTOM;
|
||||
import static org.dspace.authorize.ResourcePolicy.TYPE_INHERITED;
|
||||
import static org.dspace.core.Constants.CONTENT_BUNDLE_NAME;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.sql.SQLException;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.TimeZone;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.apache.commons.cli.ParseException;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.dspace.app.bulkaccesscontrol.exception.BulkAccessControlException;
|
||||
import org.dspace.app.bulkaccesscontrol.model.AccessCondition;
|
||||
import org.dspace.app.bulkaccesscontrol.model.AccessConditionBitstream;
|
||||
import org.dspace.app.bulkaccesscontrol.model.AccessConditionItem;
|
||||
import org.dspace.app.bulkaccesscontrol.model.BulkAccessConditionConfiguration;
|
||||
import org.dspace.app.bulkaccesscontrol.model.BulkAccessControlInput;
|
||||
import org.dspace.app.bulkaccesscontrol.service.BulkAccessConditionConfigurationService;
|
||||
import org.dspace.app.mediafilter.factory.MediaFilterServiceFactory;
|
||||
import org.dspace.app.mediafilter.service.MediaFilterService;
|
||||
import org.dspace.app.util.DSpaceObjectUtilsImpl;
|
||||
import org.dspace.app.util.service.DSpaceObjectUtils;
|
||||
import org.dspace.authorize.AuthorizeException;
|
||||
import org.dspace.authorize.factory.AuthorizeServiceFactory;
|
||||
import org.dspace.authorize.service.ResourcePolicyService;
|
||||
import org.dspace.content.Bitstream;
|
||||
import org.dspace.content.Collection;
|
||||
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.core.Context;
|
||||
import org.dspace.discovery.DiscoverQuery;
|
||||
import org.dspace.discovery.SearchService;
|
||||
import org.dspace.discovery.SearchServiceException;
|
||||
import org.dspace.discovery.SearchUtils;
|
||||
import org.dspace.discovery.indexobject.IndexableItem;
|
||||
import org.dspace.eperson.EPerson;
|
||||
import org.dspace.eperson.factory.EPersonServiceFactory;
|
||||
import org.dspace.eperson.service.EPersonService;
|
||||
import org.dspace.scripts.DSpaceRunnable;
|
||||
import org.dspace.services.ConfigurationService;
|
||||
import org.dspace.services.factory.DSpaceServicesFactory;
|
||||
import org.dspace.submit.model.AccessConditionOption;
|
||||
import org.dspace.utils.DSpace;
|
||||
|
||||
/**
|
||||
* Implementation of {@link DSpaceRunnable} to perform a bulk access control via json file.
|
||||
*
|
||||
* @author Mohamed Eskander (mohamed.eskander at 4science.it)
|
||||
*
|
||||
*/
|
||||
public class BulkAccessControl extends DSpaceRunnable<BulkAccessControlScriptConfiguration<BulkAccessControl>> {
|
||||
|
||||
private DSpaceObjectUtils dSpaceObjectUtils;
|
||||
|
||||
private SearchService searchService;
|
||||
|
||||
private ItemService itemService;
|
||||
|
||||
private String filename;
|
||||
|
||||
private List<String> uuids;
|
||||
|
||||
private Context context;
|
||||
|
||||
private BulkAccessConditionConfigurationService bulkAccessConditionConfigurationService;
|
||||
|
||||
private ResourcePolicyService resourcePolicyService;
|
||||
|
||||
protected EPersonService epersonService;
|
||||
|
||||
private ConfigurationService configurationService;
|
||||
|
||||
private MediaFilterService mediaFilterService;
|
||||
|
||||
private Map<String, AccessConditionOption> itemAccessConditions;
|
||||
|
||||
private Map<String, AccessConditionOption> uploadAccessConditions;
|
||||
|
||||
private final String ADD_MODE = "add";
|
||||
|
||||
private final String REPLACE_MODE = "replace";
|
||||
|
||||
private boolean help = false;
|
||||
|
||||
protected String eperson = null;
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public void setup() throws ParseException {
|
||||
|
||||
this.searchService = SearchUtils.getSearchService();
|
||||
this.itemService = ContentServiceFactory.getInstance().getItemService();
|
||||
this.resourcePolicyService = AuthorizeServiceFactory.getInstance().getResourcePolicyService();
|
||||
this.epersonService = EPersonServiceFactory.getInstance().getEPersonService();
|
||||
this.configurationService = DSpaceServicesFactory.getInstance().getConfigurationService();
|
||||
mediaFilterService = MediaFilterServiceFactory.getInstance().getMediaFilterService();
|
||||
mediaFilterService.setLogHandler(handler);
|
||||
this.bulkAccessConditionConfigurationService = new DSpace().getServiceManager().getServiceByName(
|
||||
"bulkAccessConditionConfigurationService", BulkAccessConditionConfigurationService.class);
|
||||
this.dSpaceObjectUtils = new DSpace().getServiceManager().getServiceByName(
|
||||
DSpaceObjectUtilsImpl.class.getName(), DSpaceObjectUtilsImpl.class);
|
||||
|
||||
BulkAccessConditionConfiguration bulkAccessConditionConfiguration =
|
||||
bulkAccessConditionConfigurationService.getBulkAccessConditionConfiguration("default");
|
||||
|
||||
itemAccessConditions = bulkAccessConditionConfiguration
|
||||
.getItemAccessConditionOptions()
|
||||
.stream()
|
||||
.collect(Collectors.toMap(AccessConditionOption::getName, Function.identity()));
|
||||
|
||||
uploadAccessConditions = bulkAccessConditionConfiguration
|
||||
.getBitstreamAccessConditionOptions()
|
||||
.stream()
|
||||
.collect(Collectors.toMap(AccessConditionOption::getName, Function.identity()));
|
||||
|
||||
help = commandLine.hasOption('h');
|
||||
filename = commandLine.getOptionValue('f');
|
||||
uuids = commandLine.hasOption('u') ? Arrays.asList(commandLine.getOptionValues('u')) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void internalRun() throws Exception {
|
||||
|
||||
if (help) {
|
||||
printHelp();
|
||||
return;
|
||||
}
|
||||
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
mapper.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
BulkAccessControlInput accessControl;
|
||||
context = new Context(Context.Mode.BATCH_EDIT);
|
||||
setEPerson(context);
|
||||
|
||||
if (!isAuthorized(context)) {
|
||||
handler.logError("Current user is not eligible to execute script bulk-access-control");
|
||||
throw new AuthorizeException("Current user is not eligible to execute script bulk-access-control");
|
||||
}
|
||||
|
||||
if (uuids == null || uuids.size() == 0) {
|
||||
handler.logError("A target uuid must be provided with at least on uuid (run with -h flag for details)");
|
||||
throw new IllegalArgumentException("At least one target uuid must be provided");
|
||||
}
|
||||
|
||||
InputStream inputStream = handler.getFileStream(context, filename)
|
||||
.orElseThrow(() -> new IllegalArgumentException("Error reading file, the file couldn't be "
|
||||
+ "found for filename: " + filename));
|
||||
|
||||
try {
|
||||
accessControl = mapper.readValue(inputStream, BulkAccessControlInput.class);
|
||||
} catch (IOException e) {
|
||||
handler.logError("Error parsing json file " + e.getMessage());
|
||||
throw new IllegalArgumentException("Error parsing json file", e);
|
||||
}
|
||||
try {
|
||||
validate(accessControl);
|
||||
updateItemsAndBitstreamsPolices(accessControl);
|
||||
context.complete();
|
||||
} catch (Exception e) {
|
||||
handler.handleException(e);
|
||||
context.abort();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* check the validation of mapped json data, it must
|
||||
* provide item or bitstream information or both of them
|
||||
* and check the validation of item node if provided,
|
||||
* and check the validation of bitstream node if provided.
|
||||
*
|
||||
* @param accessControl mapped json data
|
||||
* @throws SQLException if something goes wrong in the database
|
||||
* @throws BulkAccessControlException if accessControl is invalid
|
||||
*/
|
||||
private void validate(BulkAccessControlInput accessControl) throws SQLException {
|
||||
|
||||
AccessConditionItem item = accessControl.getItem();
|
||||
AccessConditionBitstream bitstream = accessControl.getBitstream();
|
||||
|
||||
if (Objects.isNull(item) && Objects.isNull(bitstream)) {
|
||||
handler.logError("item or bitstream node must be provided");
|
||||
throw new BulkAccessControlException("item or bitstream node must be provided");
|
||||
}
|
||||
|
||||
if (Objects.nonNull(item)) {
|
||||
validateItemNode(item);
|
||||
}
|
||||
|
||||
if (Objects.nonNull(bitstream)) {
|
||||
validateBitstreamNode(bitstream);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* check the validation of item node, the item mode
|
||||
* must be provided with value 'add' or 'replace'
|
||||
* if mode equals to add so the information
|
||||
* of accessCondition must be provided,
|
||||
* also checking that accessConditions information are valid.
|
||||
*
|
||||
* @param item the item node
|
||||
* @throws BulkAccessControlException if item node is invalid
|
||||
*/
|
||||
private void validateItemNode(AccessConditionItem item) {
|
||||
String mode = item.getMode();
|
||||
List<AccessCondition> accessConditions = item.getAccessConditions();
|
||||
|
||||
if (StringUtils.isEmpty(mode)) {
|
||||
handler.logError("item mode node must be provided");
|
||||
throw new BulkAccessControlException("item mode node must be provided");
|
||||
} else if (!(StringUtils.equalsAny(mode, ADD_MODE, REPLACE_MODE))) {
|
||||
handler.logError("wrong value for item mode<" + mode + ">");
|
||||
throw new BulkAccessControlException("wrong value for item mode<" + mode + ">");
|
||||
} else if (ADD_MODE.equals(mode) && isEmpty(accessConditions)) {
|
||||
handler.logError("accessConditions of item must be provided with mode<" + ADD_MODE + ">");
|
||||
throw new BulkAccessControlException(
|
||||
"accessConditions of item must be provided with mode<" + ADD_MODE + ">");
|
||||
}
|
||||
|
||||
for (AccessCondition accessCondition : accessConditions) {
|
||||
validateAccessCondition(accessCondition);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* check the validation of bitstream node, the bitstream mode
|
||||
* must be provided with value 'add' or 'replace'
|
||||
* if mode equals to add so the information of accessConditions
|
||||
* must be provided,
|
||||
* also checking that constraint information is valid,
|
||||
* also checking that accessConditions information are valid.
|
||||
*
|
||||
* @param bitstream the bitstream node
|
||||
* @throws SQLException if something goes wrong in the database
|
||||
* @throws BulkAccessControlException if bitstream node is invalid
|
||||
*/
|
||||
private void validateBitstreamNode(AccessConditionBitstream bitstream) throws SQLException {
|
||||
String mode = bitstream.getMode();
|
||||
List<AccessCondition> accessConditions = bitstream.getAccessConditions();
|
||||
|
||||
if (StringUtils.isEmpty(mode)) {
|
||||
handler.logError("bitstream mode node must be provided");
|
||||
throw new BulkAccessControlException("bitstream mode node must be provided");
|
||||
} else if (!(StringUtils.equalsAny(mode, ADD_MODE, REPLACE_MODE))) {
|
||||
handler.logError("wrong value for bitstream mode<" + mode + ">");
|
||||
throw new BulkAccessControlException("wrong value for bitstream mode<" + mode + ">");
|
||||
} else if (ADD_MODE.equals(mode) && isEmpty(accessConditions)) {
|
||||
handler.logError("accessConditions of bitstream must be provided with mode<" + ADD_MODE + ">");
|
||||
throw new BulkAccessControlException(
|
||||
"accessConditions of bitstream must be provided with mode<" + ADD_MODE + ">");
|
||||
}
|
||||
|
||||
validateConstraint(bitstream);
|
||||
|
||||
for (AccessCondition accessCondition : bitstream.getAccessConditions()) {
|
||||
validateAccessCondition(accessCondition);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* check the validation of constraint node if provided,
|
||||
* constraint isn't supported when multiple uuids are provided
|
||||
* or when uuid isn't an Item
|
||||
*
|
||||
* @param bitstream the bitstream node
|
||||
* @throws SQLException if something goes wrong in the database
|
||||
* @throws BulkAccessControlException if constraint node is invalid
|
||||
*/
|
||||
private void validateConstraint(AccessConditionBitstream bitstream) throws SQLException {
|
||||
if (uuids.size() > 1 && containsConstraints(bitstream)) {
|
||||
handler.logError("constraint isn't supported when multiple uuids are provided");
|
||||
throw new BulkAccessControlException("constraint isn't supported when multiple uuids are provided");
|
||||
} else if (uuids.size() == 1 && containsConstraints(bitstream)) {
|
||||
DSpaceObject dso =
|
||||
dSpaceObjectUtils.findDSpaceObject(context, UUID.fromString(uuids.get(0)));
|
||||
|
||||
if (Objects.nonNull(dso) && dso.getType() != Constants.ITEM) {
|
||||
handler.logError("constraint is not supported when uuid isn't an Item");
|
||||
throw new BulkAccessControlException("constraint is not supported when uuid isn't an Item");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* check the validation of access condition,
|
||||
* the access condition name must equal to one of configured access conditions,
|
||||
* then call {@link AccessConditionOption#validateResourcePolicy(
|
||||
* Context, String, Date, Date)} if exception happens so, it's invalid.
|
||||
*
|
||||
* @param accessCondition the accessCondition
|
||||
* @throws BulkAccessControlException if the accessCondition is invalid
|
||||
*/
|
||||
private void validateAccessCondition(AccessCondition accessCondition) {
|
||||
|
||||
if (!itemAccessConditions.containsKey(accessCondition.getName())) {
|
||||
handler.logError("wrong access condition <" + accessCondition.getName() + ">");
|
||||
throw new BulkAccessControlException("wrong access condition <" + accessCondition.getName() + ">");
|
||||
}
|
||||
|
||||
try {
|
||||
itemAccessConditions.get(accessCondition.getName()).validateResourcePolicy(
|
||||
context, accessCondition.getName(), accessCondition.getStartDate(), accessCondition.getEndDate());
|
||||
} catch (Exception e) {
|
||||
handler.logError("invalid access condition, " + e.getMessage());
|
||||
handler.handleException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* find all items of provided {@link #uuids} from solr,
|
||||
* then update the resource policies of items
|
||||
* or bitstreams of items (only bitstreams of ORIGINAL bundles)
|
||||
* and derivative bitstreams, or both of them.
|
||||
*
|
||||
* @param accessControl the access control input
|
||||
* @throws SQLException if something goes wrong in the database
|
||||
* @throws SearchServiceException if a search error occurs
|
||||
* @throws AuthorizeException if an authorization error occurs
|
||||
*/
|
||||
private void updateItemsAndBitstreamsPolices(BulkAccessControlInput accessControl)
|
||||
throws SQLException, SearchServiceException, AuthorizeException {
|
||||
|
||||
int counter = 0;
|
||||
int start = 0;
|
||||
int limit = 20;
|
||||
|
||||
String query = buildSolrQuery(uuids);
|
||||
|
||||
Iterator<Item> itemIterator = findItems(query, start, limit);
|
||||
|
||||
while (itemIterator.hasNext()) {
|
||||
|
||||
Item item = context.reloadEntity(itemIterator.next());
|
||||
|
||||
if (Objects.nonNull(accessControl.getItem())) {
|
||||
updateItemPolicies(item, accessControl);
|
||||
}
|
||||
|
||||
if (Objects.nonNull(accessControl.getBitstream())) {
|
||||
updateBitstreamsPolicies(item, accessControl);
|
||||
}
|
||||
|
||||
context.commit();
|
||||
context.uncacheEntity(item);
|
||||
counter++;
|
||||
|
||||
if (counter == limit) {
|
||||
counter = 0;
|
||||
start += limit;
|
||||
itemIterator = findItems(query, start, limit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String buildSolrQuery(List<String> uuids) throws SQLException {
|
||||
String [] query = new String[uuids.size()];
|
||||
|
||||
for (int i = 0 ; i < query.length ; i++) {
|
||||
DSpaceObject dso = dSpaceObjectUtils.findDSpaceObject(context, UUID.fromString(uuids.get(i)));
|
||||
|
||||
if (dso.getType() == Constants.COMMUNITY) {
|
||||
query[i] = "location.comm:" + dso.getID();
|
||||
} else if (dso.getType() == Constants.COLLECTION) {
|
||||
query[i] = "location.coll:" + dso.getID();
|
||||
} else if (dso.getType() == Constants.ITEM) {
|
||||
query[i] = "search.resourceid:" + dso.getID();
|
||||
}
|
||||
}
|
||||
return StringUtils.joinWith(" OR ", query);
|
||||
}
|
||||
|
||||
private Iterator<Item> findItems(String query, int start, int limit)
|
||||
throws SearchServiceException {
|
||||
|
||||
DiscoverQuery discoverQuery = buildDiscoveryQuery(query, start, limit);
|
||||
|
||||
return searchService.search(context, discoverQuery)
|
||||
.getIndexableObjects()
|
||||
.stream()
|
||||
.map(indexableObject ->
|
||||
((IndexableItem) indexableObject).getIndexedObject())
|
||||
.collect(Collectors.toList())
|
||||
.iterator();
|
||||
}
|
||||
|
||||
private DiscoverQuery buildDiscoveryQuery(String query, int start, int limit) {
|
||||
DiscoverQuery discoverQuery = new DiscoverQuery();
|
||||
discoverQuery.setDSpaceObjectFilter(IndexableItem.TYPE);
|
||||
discoverQuery.setQuery(query);
|
||||
discoverQuery.setStart(start);
|
||||
discoverQuery.setMaxResults(limit);
|
||||
|
||||
return discoverQuery;
|
||||
}
|
||||
|
||||
/**
|
||||
* update the item resource policies,
|
||||
* when mode equals to 'replace' will remove
|
||||
* all current resource polices of types 'TYPE_CUSTOM'
|
||||
* and 'TYPE_INHERITED' then, set the new resource policies.
|
||||
*
|
||||
* @param item the item
|
||||
* @param accessControl the access control input
|
||||
* @throws SQLException if something goes wrong in the database
|
||||
* @throws AuthorizeException if an authorization error occurs
|
||||
*/
|
||||
private void updateItemPolicies(Item item, BulkAccessControlInput accessControl)
|
||||
throws SQLException, AuthorizeException {
|
||||
|
||||
AccessConditionItem acItem = accessControl.getItem();
|
||||
|
||||
if (REPLACE_MODE.equals(acItem.getMode())) {
|
||||
removeReadPolicies(item, TYPE_CUSTOM);
|
||||
removeReadPolicies(item, TYPE_INHERITED);
|
||||
}
|
||||
|
||||
setItemPolicies(item, accessControl);
|
||||
logInfo(acItem.getAccessConditions(), acItem.getMode(), item);
|
||||
}
|
||||
|
||||
/**
|
||||
* create the new resource policies of item.
|
||||
* then, call {@link ItemService#adjustItemPolicies(
|
||||
* Context, Item, Collection)} to adjust item's default policies.
|
||||
*
|
||||
* @param item the item
|
||||
* @param accessControl the access control input
|
||||
* @throws SQLException if something goes wrong in the database
|
||||
* @throws AuthorizeException if an authorization error occurs
|
||||
*/
|
||||
private void setItemPolicies(Item item, BulkAccessControlInput accessControl)
|
||||
throws SQLException, AuthorizeException {
|
||||
|
||||
accessControl
|
||||
.getItem()
|
||||
.getAccessConditions()
|
||||
.forEach(accessCondition -> createResourcePolicy(item, accessCondition,
|
||||
itemAccessConditions.get(accessCondition.getName())));
|
||||
|
||||
itemService.adjustItemPolicies(context, item, item.getOwningCollection(), false);
|
||||
}
|
||||
|
||||
/**
|
||||
* update the resource policies of all item's bitstreams
|
||||
* or bitstreams specified into constraint node,
|
||||
* and derivative bitstreams.
|
||||
*
|
||||
* <strong>NOTE:</strong> only bitstreams of ORIGINAL bundles
|
||||
*
|
||||
* @param item the item contains bitstreams
|
||||
* @param accessControl the access control input
|
||||
*/
|
||||
private void updateBitstreamsPolicies(Item item, BulkAccessControlInput accessControl) {
|
||||
AccessConditionBitstream.Constraint constraints = accessControl.getBitstream().getConstraints();
|
||||
|
||||
// look over all the bundles and force initialization of bitstreams collection
|
||||
// to avoid lazy initialization exception
|
||||
long count = item.getBundles()
|
||||
.stream()
|
||||
.flatMap(bundle ->
|
||||
bundle.getBitstreams().stream())
|
||||
.count();
|
||||
|
||||
item.getBundles(CONTENT_BUNDLE_NAME).stream()
|
||||
.flatMap(bundle -> bundle.getBitstreams().stream())
|
||||
.filter(bitstream -> constraints == null ||
|
||||
constraints.getUuid() == null ||
|
||||
constraints.getUuid().size() == 0 ||
|
||||
constraints.getUuid().contains(bitstream.getID().toString()))
|
||||
.forEach(bitstream -> updateBitstreamPolicies(bitstream, item, accessControl));
|
||||
}
|
||||
|
||||
/**
|
||||
* check that the bitstream node is existed,
|
||||
* and contains constraint node,
|
||||
* and constraint contains uuids.
|
||||
*
|
||||
* @param bitstream the bitstream node
|
||||
* @return true when uuids of constraint of bitstream is not empty,
|
||||
* otherwise false
|
||||
*/
|
||||
private boolean containsConstraints(AccessConditionBitstream bitstream) {
|
||||
return Objects.nonNull(bitstream) &&
|
||||
Objects.nonNull(bitstream.getConstraints()) &&
|
||||
isNotEmpty(bitstream.getConstraints().getUuid());
|
||||
}
|
||||
|
||||
/**
|
||||
* update the bitstream resource policies,
|
||||
* when mode equals to replace will remove
|
||||
* all current resource polices of types 'TYPE_CUSTOM'
|
||||
* and 'TYPE_INHERITED' then, set the new resource policies.
|
||||
*
|
||||
* @param bitstream the bitstream
|
||||
* @param item the item of bitstream
|
||||
* @param accessControl the access control input
|
||||
* @throws RuntimeException if something goes wrong in the database
|
||||
* or an authorization error occurs
|
||||
*/
|
||||
private void updateBitstreamPolicies(Bitstream bitstream, Item item, BulkAccessControlInput accessControl) {
|
||||
|
||||
AccessConditionBitstream acBitstream = accessControl.getBitstream();
|
||||
|
||||
if (REPLACE_MODE.equals(acBitstream.getMode())) {
|
||||
removeReadPolicies(bitstream, TYPE_CUSTOM);
|
||||
removeReadPolicies(bitstream, TYPE_INHERITED);
|
||||
}
|
||||
|
||||
try {
|
||||
setBitstreamPolicies(bitstream, item, accessControl);
|
||||
logInfo(acBitstream.getAccessConditions(), acBitstream.getMode(), bitstream);
|
||||
} catch (SQLException | AuthorizeException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* remove dspace object's read policies.
|
||||
*
|
||||
* @param dso the dspace object
|
||||
* @param type resource policy type
|
||||
* @throws BulkAccessControlException if something goes wrong
|
||||
* in the database or an authorization error occurs
|
||||
*/
|
||||
private void removeReadPolicies(DSpaceObject dso, String type) {
|
||||
try {
|
||||
resourcePolicyService.removePolicies(context, dso, type, Constants.READ);
|
||||
} catch (SQLException | AuthorizeException e) {
|
||||
throw new BulkAccessControlException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* create the new resource policies of bitstream.
|
||||
* then, call {@link ItemService#adjustItemPolicies(
|
||||
* Context, Item, Collection)} to adjust bitstream's default policies.
|
||||
* and also update the resource policies of its derivative bitstreams.
|
||||
*
|
||||
* @param bitstream the bitstream
|
||||
* @param item the item of bitstream
|
||||
* @param accessControl the access control input
|
||||
* @throws SQLException if something goes wrong in the database
|
||||
* @throws AuthorizeException if an authorization error occurs
|
||||
*/
|
||||
private void setBitstreamPolicies(Bitstream bitstream, Item item, BulkAccessControlInput accessControl)
|
||||
throws SQLException, AuthorizeException {
|
||||
|
||||
accessControl.getBitstream()
|
||||
.getAccessConditions()
|
||||
.forEach(accessCondition -> createResourcePolicy(bitstream, accessCondition,
|
||||
uploadAccessConditions.get(accessCondition.getName())));
|
||||
|
||||
itemService.adjustBitstreamPolicies(context, item, item.getOwningCollection(), bitstream);
|
||||
mediaFilterService.updatePoliciesOfDerivativeBitstreams(context, item, bitstream);
|
||||
}
|
||||
|
||||
/**
|
||||
* create the resource policy from the information
|
||||
* comes from the access condition.
|
||||
*
|
||||
* @param obj the dspace object
|
||||
* @param accessCondition the access condition
|
||||
* @param accessConditionOption the access condition option
|
||||
* @throws BulkAccessControlException if an exception occurs
|
||||
*/
|
||||
private void createResourcePolicy(DSpaceObject obj, AccessCondition accessCondition,
|
||||
AccessConditionOption accessConditionOption) {
|
||||
|
||||
String name = accessCondition.getName();
|
||||
String description = accessCondition.getDescription();
|
||||
Date startDate = accessCondition.getStartDate();
|
||||
Date endDate = accessCondition.getEndDate();
|
||||
|
||||
try {
|
||||
accessConditionOption.createResourcePolicy(context, obj, name, description, startDate, endDate);
|
||||
} catch (Exception e) {
|
||||
throw new BulkAccessControlException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the eperson in the context
|
||||
*
|
||||
* @param context the context
|
||||
* @throws SQLException if database error
|
||||
*/
|
||||
protected void setEPerson(Context context) throws SQLException {
|
||||
EPerson myEPerson = epersonService.find(context, this.getEpersonIdentifier());
|
||||
|
||||
if (myEPerson == null) {
|
||||
handler.logError("EPerson cannot be found: " + this.getEpersonIdentifier());
|
||||
throw new UnsupportedOperationException("EPerson cannot be found: " + this.getEpersonIdentifier());
|
||||
}
|
||||
|
||||
context.setCurrentUser(myEPerson);
|
||||
}
|
||||
|
||||
private void logInfo(List<AccessCondition> accessConditions, String mode, DSpaceObject dso) {
|
||||
String type = dso.getClass().getSimpleName();
|
||||
|
||||
if (REPLACE_MODE.equals(mode) && isEmpty(accessConditions)) {
|
||||
handler.logInfo("Cleaning " + type + " {" + dso.getID() + "} policies");
|
||||
handler.logInfo("Inheriting policies from owning Collection in " + type + " {" + dso.getID() + "}");
|
||||
return;
|
||||
}
|
||||
|
||||
StringBuilder message = new StringBuilder();
|
||||
message.append(mode.equals(ADD_MODE) ? "Adding " : "Replacing ")
|
||||
.append(type)
|
||||
.append(" {")
|
||||
.append(dso.getID())
|
||||
.append("} policy")
|
||||
.append(mode.equals(ADD_MODE) ? " with " : " to ")
|
||||
.append("access conditions:");
|
||||
|
||||
AppendAccessConditionsInfo(message, accessConditions);
|
||||
|
||||
handler.logInfo(message.toString());
|
||||
|
||||
if (REPLACE_MODE.equals(mode) && isAppendModeEnabled()) {
|
||||
handler.logInfo("Inheriting policies from owning Collection in " + type + " {" + dso.getID() + "}");
|
||||
}
|
||||
}
|
||||
|
||||
private void AppendAccessConditionsInfo(StringBuilder message, List<AccessCondition> accessConditions) {
|
||||
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
|
||||
message.append("{");
|
||||
|
||||
for (int i = 0; i < accessConditions.size(); i++) {
|
||||
message.append(accessConditions.get(i).getName());
|
||||
|
||||
Optional.ofNullable(accessConditions.get(i).getStartDate())
|
||||
.ifPresent(date -> message.append(", start_date=" + dateFormat.format(date)));
|
||||
|
||||
Optional.ofNullable(accessConditions.get(i).getEndDate())
|
||||
.ifPresent(date -> message.append(", end_date=" + dateFormat.format(date)));
|
||||
|
||||
if (i != accessConditions.size() - 1) {
|
||||
message.append(", ");
|
||||
}
|
||||
}
|
||||
|
||||
message.append("}");
|
||||
}
|
||||
|
||||
private boolean isAppendModeEnabled() {
|
||||
return configurationService.getBooleanProperty("core.authorization.installitem.inheritance-read.append-mode");
|
||||
}
|
||||
|
||||
protected boolean isAuthorized(Context context) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public BulkAccessControlScriptConfiguration<BulkAccessControl> getScriptConfiguration() {
|
||||
return new DSpace().getServiceManager()
|
||||
.getServiceByName("bulk-access-control", BulkAccessControlScriptConfiguration.class);
|
||||
}
|
||||
|
||||
}
|
@@ -1,66 +0,0 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
package org.dspace.app.bulkaccesscontrol;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.Arrays;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.eperson.EPerson;
|
||||
import org.dspace.scripts.DSpaceCommandLineParameter;
|
||||
|
||||
/**
|
||||
* Extension of {@link BulkAccessControl} for CLI.
|
||||
*
|
||||
* @author Mohamed Eskander (mohamed.eskander at 4science.it)
|
||||
*
|
||||
*/
|
||||
public class BulkAccessControlCli extends BulkAccessControl {
|
||||
|
||||
@Override
|
||||
protected void setEPerson(Context context) throws SQLException {
|
||||
EPerson myEPerson;
|
||||
eperson = commandLine.getOptionValue('e');
|
||||
|
||||
if (eperson == null) {
|
||||
handler.logError("An eperson to do the the Bulk Access Control must be specified " +
|
||||
"(run with -h flag for details)");
|
||||
throw new UnsupportedOperationException("An eperson to do the Bulk Access Control must be specified");
|
||||
}
|
||||
|
||||
if (StringUtils.contains(eperson, '@')) {
|
||||
myEPerson = epersonService.findByEmail(context, eperson);
|
||||
} else {
|
||||
myEPerson = epersonService.find(context, UUID.fromString(eperson));
|
||||
}
|
||||
|
||||
if (myEPerson == null) {
|
||||
handler.logError("EPerson cannot be found: " + eperson + " (run with -h flag for details)");
|
||||
throw new UnsupportedOperationException("EPerson cannot be found: " + eperson);
|
||||
}
|
||||
|
||||
context.setCurrentUser(myEPerson);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isAuthorized(Context context) {
|
||||
|
||||
if (context.getCurrentUser() == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return getScriptConfiguration().isAllowedToExecute(context,
|
||||
Arrays.stream(commandLine.getOptions())
|
||||
.map(option ->
|
||||
new DSpaceCommandLineParameter("-" + option.getOpt(), option.getValue()))
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
}
|
@@ -1,42 +0,0 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
package org.dspace.app.bulkaccesscontrol;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.apache.commons.cli.Options;
|
||||
|
||||
/**
|
||||
* Extension of {@link BulkAccessControlScriptConfiguration} for CLI.
|
||||
*
|
||||
* @author Mohamed Eskander (mohamed.eskander at 4science.it)
|
||||
*
|
||||
*/
|
||||
public class BulkAccessControlCliScriptConfiguration<T extends BulkAccessControlCli>
|
||||
extends BulkAccessControlScriptConfiguration<T> {
|
||||
|
||||
@Override
|
||||
public Options getOptions() {
|
||||
Options options = new Options();
|
||||
|
||||
options.addOption("u", "uuid", true, "target uuids of communities/collections/items");
|
||||
options.getOption("u").setType(String.class);
|
||||
options.getOption("u").setRequired(true);
|
||||
|
||||
options.addOption("f", "file", true, "source json file");
|
||||
options.getOption("f").setType(InputStream.class);
|
||||
options.getOption("f").setRequired(true);
|
||||
|
||||
options.addOption("e", "eperson", true, "email of EPerson used to perform actions");
|
||||
options.getOption("e").setRequired(true);
|
||||
|
||||
options.addOption("h", "help", false, "help");
|
||||
|
||||
return options;
|
||||
}
|
||||
}
|
@@ -1,110 +0,0 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
package org.dspace.app.bulkaccesscontrol;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.cli.Options;
|
||||
import org.dspace.app.util.DSpaceObjectUtilsImpl;
|
||||
import org.dspace.app.util.service.DSpaceObjectUtils;
|
||||
import org.dspace.content.DSpaceObject;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.scripts.DSpaceCommandLineParameter;
|
||||
import org.dspace.scripts.configuration.ScriptConfiguration;
|
||||
import org.dspace.utils.DSpace;
|
||||
|
||||
/**
|
||||
* Script configuration for {@link BulkAccessControl}.
|
||||
*
|
||||
* @author Mohamed Eskander (mohamed.eskander at 4science.it)
|
||||
*
|
||||
* @param <T> the {@link BulkAccessControl} type
|
||||
*/
|
||||
public class BulkAccessControlScriptConfiguration<T extends BulkAccessControl> extends ScriptConfiguration<T> {
|
||||
|
||||
private Class<T> dspaceRunnableClass;
|
||||
|
||||
@Override
|
||||
public boolean isAllowedToExecute(Context context, List<DSpaceCommandLineParameter> commandLineParameters) {
|
||||
|
||||
try {
|
||||
if (Objects.isNull(commandLineParameters)) {
|
||||
return authorizeService.isAdmin(context) || authorizeService.isComColAdmin(context)
|
||||
|| authorizeService.isItemAdmin(context);
|
||||
} else {
|
||||
List<String> dspaceObjectIDs =
|
||||
commandLineParameters.stream()
|
||||
.filter(parameter -> "-u".equals(parameter.getName()))
|
||||
.map(DSpaceCommandLineParameter::getValue)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
DSpaceObjectUtils dSpaceObjectUtils = new DSpace().getServiceManager().getServiceByName(
|
||||
DSpaceObjectUtilsImpl.class.getName(), DSpaceObjectUtilsImpl.class);
|
||||
|
||||
for (String dspaceObjectID : dspaceObjectIDs) {
|
||||
|
||||
DSpaceObject dso = dSpaceObjectUtils.findDSpaceObject(context, UUID.fromString(dspaceObjectID));
|
||||
|
||||
if (Objects.isNull(dso)) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
if (!authorizeService.isAdmin(context, dso)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Options getOptions() {
|
||||
if (options == null) {
|
||||
Options options = new Options();
|
||||
|
||||
options.addOption("u", "uuid", true, "target uuids of communities/collections/items");
|
||||
options.getOption("u").setType(String.class);
|
||||
options.getOption("u").setRequired(true);
|
||||
|
||||
options.addOption("f", "file", true, "source json file");
|
||||
options.getOption("f").setType(InputStream.class);
|
||||
options.getOption("f").setRequired(true);
|
||||
|
||||
options.addOption("h", "help", false, "help");
|
||||
|
||||
super.options = options;
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<T> getDspaceRunnableClass() {
|
||||
return dspaceRunnableClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic setter for the dspaceRunnableClass
|
||||
*
|
||||
* @param dspaceRunnableClass The dspaceRunnableClass to be set on this
|
||||
* BulkImportScriptConfiguration
|
||||
*/
|
||||
@Override
|
||||
public void setDspaceRunnableClass(Class<T> dspaceRunnableClass) {
|
||||
this.dspaceRunnableClass = dspaceRunnableClass;
|
||||
}
|
||||
|
||||
}
|
@@ -1,48 +0,0 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
package org.dspace.app.bulkaccesscontrol.exception;
|
||||
|
||||
/**
|
||||
* Exception for errors that occurs during the bulk access control
|
||||
*
|
||||
* @author Mohamed Eskander (mohamed.eskander at 4science.it)
|
||||
*
|
||||
*/
|
||||
public class BulkAccessControlException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = -74730626862418515L;
|
||||
|
||||
/**
|
||||
* Constructor with error message and cause.
|
||||
*
|
||||
* @param message the error message
|
||||
* @param cause the error cause
|
||||
*/
|
||||
public BulkAccessControlException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor with error message.
|
||||
*
|
||||
* @param message the error message
|
||||
*/
|
||||
public BulkAccessControlException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor with error cause.
|
||||
*
|
||||
* @param cause the error cause
|
||||
*/
|
||||
public BulkAccessControlException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
}
|
@@ -1,59 +0,0 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
package org.dspace.app.bulkaccesscontrol.model;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import org.dspace.app.bulkaccesscontrol.BulkAccessControl;
|
||||
import org.dspace.util.MultiFormatDateDeserializer;
|
||||
|
||||
/**
|
||||
* Class that model the values of an Access Condition as expressed in the {@link BulkAccessControl} input file
|
||||
*
|
||||
* @author Mohamed Eskander (mohamed.eskander at 4science.it)
|
||||
*/
|
||||
public class AccessCondition {
|
||||
|
||||
private String name;
|
||||
|
||||
private String description;
|
||||
|
||||
@JsonDeserialize(using = MultiFormatDateDeserializer.class)
|
||||
private Date startDate;
|
||||
|
||||
@JsonDeserialize(using = MultiFormatDateDeserializer.class)
|
||||
private Date endDate;
|
||||
|
||||
public AccessCondition() {
|
||||
}
|
||||
|
||||
public AccessCondition(String name, String description, Date startDate, Date endDate) {
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.startDate = startDate;
|
||||
this.endDate = endDate;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public Date getStartDate() {
|
||||
return startDate;
|
||||
}
|
||||
|
||||
public Date getEndDate() {
|
||||
return endDate;
|
||||
}
|
||||
|
||||
}
|
@@ -1,69 +0,0 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
package org.dspace.app.bulkaccesscontrol.model;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.dspace.app.bulkaccesscontrol.BulkAccessControl;
|
||||
|
||||
/**
|
||||
* Class that model the value of bitstream node
|
||||
* from json file of the {@link BulkAccessControl}
|
||||
*
|
||||
* @author Mohamed Eskander (mohamed.eskander at 4science.it)
|
||||
*/
|
||||
public class AccessConditionBitstream {
|
||||
|
||||
private String mode;
|
||||
|
||||
private Constraint constraints;
|
||||
|
||||
private List<AccessCondition> accessConditions;
|
||||
|
||||
public String getMode() {
|
||||
return mode;
|
||||
}
|
||||
|
||||
public void setMode(String mode) {
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
public Constraint getConstraints() {
|
||||
return constraints;
|
||||
}
|
||||
|
||||
public void setConstraints(Constraint constraints) {
|
||||
this.constraints = constraints;
|
||||
}
|
||||
|
||||
public List<AccessCondition> getAccessConditions() {
|
||||
if (accessConditions == null) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
return accessConditions;
|
||||
}
|
||||
|
||||
public void setAccessConditions(List<AccessCondition> accessConditions) {
|
||||
this.accessConditions = accessConditions;
|
||||
}
|
||||
|
||||
public class Constraint {
|
||||
|
||||
private List<String> uuid;
|
||||
|
||||
public List<String> getUuid() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
public void setUuid(List<String> uuid) {
|
||||
this.uuid = uuid;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -1,45 +0,0 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
package org.dspace.app.bulkaccesscontrol.model;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.dspace.app.bulkaccesscontrol.BulkAccessControl;
|
||||
|
||||
/**
|
||||
* Class that model the value of item node
|
||||
* from json file of the {@link BulkAccessControl}
|
||||
*
|
||||
* @author Mohamed Eskander (mohamed.eskander at 4science.it)
|
||||
*/
|
||||
public class AccessConditionItem {
|
||||
|
||||
String mode;
|
||||
|
||||
List<AccessCondition> accessConditions;
|
||||
|
||||
public String getMode() {
|
||||
return mode;
|
||||
}
|
||||
|
||||
public void setMode(String mode) {
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
public List<AccessCondition> getAccessConditions() {
|
||||
if (accessConditions == null) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
return accessConditions;
|
||||
}
|
||||
|
||||
public void setAccessConditions(List<AccessCondition> accessConditions) {
|
||||
this.accessConditions = accessConditions;
|
||||
}
|
||||
}
|
@@ -1,50 +0,0 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
package org.dspace.app.bulkaccesscontrol.model;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.dspace.submit.model.AccessConditionOption;
|
||||
|
||||
/**
|
||||
* A collection of conditions to be met when bulk access condition.
|
||||
*
|
||||
* @author Mohamed Eskander (mohamed.eskander at 4science.it)
|
||||
*/
|
||||
public class BulkAccessConditionConfiguration {
|
||||
|
||||
private String name;
|
||||
private List<AccessConditionOption> itemAccessConditionOptions;
|
||||
private List<AccessConditionOption> bitstreamAccessConditionOptions;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public List<AccessConditionOption> getItemAccessConditionOptions() {
|
||||
return itemAccessConditionOptions;
|
||||
}
|
||||
|
||||
public void setItemAccessConditionOptions(
|
||||
List<AccessConditionOption> itemAccessConditionOptions) {
|
||||
this.itemAccessConditionOptions = itemAccessConditionOptions;
|
||||
}
|
||||
|
||||
public List<AccessConditionOption> getBitstreamAccessConditionOptions() {
|
||||
return bitstreamAccessConditionOptions;
|
||||
}
|
||||
|
||||
public void setBitstreamAccessConditionOptions(
|
||||
List<AccessConditionOption> bitstreamAccessConditionOptions) {
|
||||
this.bitstreamAccessConditionOptions = bitstreamAccessConditionOptions;
|
||||
}
|
||||
}
|
@@ -1,72 +0,0 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
package org.dspace.app.bulkaccesscontrol.model;
|
||||
|
||||
import org.dspace.app.bulkaccesscontrol.BulkAccessControl;
|
||||
|
||||
/**
|
||||
* Class that model the content of the JSON file used as input for the {@link BulkAccessControl}
|
||||
*
|
||||
* <code> <br/>
|
||||
* { <br/>
|
||||
* item: { <br/>
|
||||
* mode: "replace", <br/>
|
||||
* accessConditions: [ <br/>
|
||||
* { <br/>
|
||||
* "name": "openaccess" <br/>
|
||||
* } <br/>
|
||||
* ] <br/>
|
||||
* }, <br/>
|
||||
* bitstream: { <br/>
|
||||
* constraints: { <br/>
|
||||
* uuid: [bit-uuid1, bit-uuid2, ..., bit-uuidN], <br/>
|
||||
* }, <br/>
|
||||
* mode: "add", <br/>
|
||||
* accessConditions: [ <br/>
|
||||
* { <br/>
|
||||
* "name": "embargo", <br/>
|
||||
* "startDate": "2024-06-24T23:59:59.999+0000" <br/>
|
||||
* } <br/>
|
||||
* ] <br/>
|
||||
* } <br/>
|
||||
* }
|
||||
* </code>
|
||||
*
|
||||
* @author Mohamed Eskander (mohamed.eskander at 4science.it)
|
||||
*/
|
||||
public class BulkAccessControlInput {
|
||||
|
||||
AccessConditionItem item;
|
||||
|
||||
AccessConditionBitstream bitstream;
|
||||
|
||||
public BulkAccessControlInput() {
|
||||
}
|
||||
|
||||
public BulkAccessControlInput(AccessConditionItem item,
|
||||
AccessConditionBitstream bitstream) {
|
||||
this.item = item;
|
||||
this.bitstream = bitstream;
|
||||
}
|
||||
|
||||
public AccessConditionItem getItem() {
|
||||
return item;
|
||||
}
|
||||
|
||||
public void setItem(AccessConditionItem item) {
|
||||
this.item = item;
|
||||
}
|
||||
|
||||
public AccessConditionBitstream getBitstream() {
|
||||
return bitstream;
|
||||
}
|
||||
|
||||
public void setBitstream(AccessConditionBitstream bitstream) {
|
||||
this.bitstream = bitstream;
|
||||
}
|
||||
}
|
@@ -1,45 +0,0 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
package org.dspace.app.bulkaccesscontrol.service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.dspace.app.bulkaccesscontrol.model.BulkAccessConditionConfiguration;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
/**
|
||||
* Simple bean to manage different Bulk Access Condition configurations
|
||||
*
|
||||
* @author Mohamed Eskander (mohamed.eskander at 4science.it)
|
||||
*/
|
||||
public class BulkAccessConditionConfigurationService {
|
||||
|
||||
@Autowired
|
||||
private List<BulkAccessConditionConfiguration> bulkAccessConditionConfigurations;
|
||||
|
||||
public List<BulkAccessConditionConfiguration> getBulkAccessConditionConfigurations() {
|
||||
if (CollectionUtils.isEmpty(bulkAccessConditionConfigurations)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
return bulkAccessConditionConfigurations;
|
||||
}
|
||||
|
||||
public BulkAccessConditionConfiguration getBulkAccessConditionConfiguration(String name) {
|
||||
return getBulkAccessConditionConfigurations().stream()
|
||||
.filter(x -> name.equals(x.getName()))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
public void setBulkAccessConditionConfigurations(
|
||||
List<BulkAccessConditionConfiguration> bulkAccessConditionConfigurations) {
|
||||
this.bulkAccessConditionConfigurations = bulkAccessConditionConfigurations;
|
||||
}
|
||||
}
|
@@ -19,7 +19,6 @@ import org.dspace.content.Item;
|
||||
* @author Stuart Lewis
|
||||
*/
|
||||
public class BulkEditChange {
|
||||
|
||||
/**
|
||||
* The item these changes relate to
|
||||
*/
|
||||
|
@@ -8,10 +8,14 @@
|
||||
package org.dspace.app.bulkedit;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStream;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
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;
|
||||
@@ -23,8 +27,6 @@ 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;
|
||||
import org.dspace.authority.service.AuthorityValueService;
|
||||
@@ -32,7 +34,6 @@ import org.dspace.content.Collection;
|
||||
import org.dspace.content.Item;
|
||||
import org.dspace.content.MetadataField;
|
||||
import org.dspace.content.MetadataSchema;
|
||||
import org.dspace.content.MetadataSchemaEnum;
|
||||
import org.dspace.content.MetadataValue;
|
||||
import org.dspace.content.authority.Choices;
|
||||
import org.dspace.content.factory.ContentServiceFactory;
|
||||
@@ -138,18 +139,18 @@ public class DSpaceCSV implements Serializable {
|
||||
/**
|
||||
* Create a new instance, reading the lines in from file
|
||||
*
|
||||
* @param inputStream the input stream to read from
|
||||
* @param f The file to read from
|
||||
* @param c The DSpace Context
|
||||
* @throws Exception thrown if there is an error reading or processing the file
|
||||
*/
|
||||
public DSpaceCSV(InputStream inputStream, Context c) throws Exception {
|
||||
public DSpaceCSV(File f, Context c) throws Exception {
|
||||
// Initialise the class
|
||||
init();
|
||||
|
||||
// Open the CSV file
|
||||
BufferedReader input = null;
|
||||
try {
|
||||
input = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
|
||||
input = new BufferedReader(new InputStreamReader(new FileInputStream(f), "UTF-8"));
|
||||
|
||||
// Read the heading line
|
||||
String head = input.readLine();
|
||||
@@ -159,7 +160,7 @@ public class DSpaceCSV implements Serializable {
|
||||
columnCounter++;
|
||||
|
||||
// Remove surrounding quotes if there are any
|
||||
if (element.startsWith("\"") && element.endsWith("\"")) {
|
||||
if ((element.startsWith("\"")) && (element.endsWith("\""))) {
|
||||
element = element.substring(1, element.length() - 1);
|
||||
}
|
||||
|
||||
@@ -167,36 +168,21 @@ public class DSpaceCSV implements Serializable {
|
||||
if ("collection".equals(element)) {
|
||||
// Store the heading
|
||||
headings.add(element);
|
||||
} else if ("rowName".equals(element)) {
|
||||
// Store the heading
|
||||
headings.add(element);
|
||||
} else if ("action".equals(element)) { // Store the action
|
||||
// Store the heading
|
||||
headings.add(element);
|
||||
} else if (!"id".equals(element)) {
|
||||
String authorityPrefix = "";
|
||||
if (StringUtils.startsWith(element, "[authority]")) {
|
||||
element = StringUtils.substringAfter(element, "[authority]");
|
||||
AuthorityValue authorityValueType = authorityValueService.getAuthorityValueType(element);
|
||||
if (authorityValueType != null) {
|
||||
String authorityType = authorityValueType.getAuthorityType();
|
||||
authorityPrefix = element.substring(0, authorityType.length() + 1);
|
||||
element = element.substring(authorityPrefix.length());
|
||||
}
|
||||
AuthorityValue authorityValueType = authorityValueService.getAuthorityValueType(element);
|
||||
if (authorityValueType != null) {
|
||||
String authorityType = authorityValueType.getAuthorityType();
|
||||
authorityPrefix = element.substring(0, authorityType.length() + 1);
|
||||
element = element.substring(authorityPrefix.length());
|
||||
}
|
||||
|
||||
// Verify that the heading is valid in the metadata registry
|
||||
String[] clean = element.split("\\[");
|
||||
String[] parts = clean[0].split("\\.");
|
||||
// Check language if present, if it's ANY then throw an exception
|
||||
if (clean.length > 1 && clean[1].equals(Item.ANY + "]")) {
|
||||
throw new MetadataImportInvalidHeadingException("Language ANY (*) was found in the heading " +
|
||||
"of the metadata value to import, " +
|
||||
"this should never be the case",
|
||||
MetadataImportInvalidHeadingException.ENTRY,
|
||||
columnCounter);
|
||||
|
||||
}
|
||||
|
||||
if (parts.length < 2) {
|
||||
throw new MetadataImportInvalidHeadingException(element,
|
||||
@@ -212,32 +198,19 @@ public class DSpaceCSV implements Serializable {
|
||||
}
|
||||
|
||||
// Check that the scheme exists
|
||||
if (!StringUtils.equals(metadataSchema, MetadataSchemaEnum.RELATION.getName())) {
|
||||
MetadataSchema foundSchema = metadataSchemaService.find(c, metadataSchema);
|
||||
if (foundSchema == null) {
|
||||
throw new MetadataImportInvalidHeadingException(clean[0],
|
||||
MetadataImportInvalidHeadingException
|
||||
.SCHEMA,
|
||||
columnCounter);
|
||||
}
|
||||
|
||||
// Check that the metadata element exists in the schema
|
||||
MetadataField foundField = metadataFieldService
|
||||
.findByElement(c, foundSchema, metadataElement, metadataQualifier);
|
||||
if (foundField == null) {
|
||||
throw new MetadataImportInvalidHeadingException(clean[0],
|
||||
MetadataImportInvalidHeadingException
|
||||
.ELEMENT,
|
||||
columnCounter);
|
||||
}
|
||||
MetadataSchema foundSchema = metadataSchemaService.find(c, metadataSchema);
|
||||
if (foundSchema == null) {
|
||||
throw new MetadataImportInvalidHeadingException(clean[0],
|
||||
MetadataImportInvalidHeadingException.SCHEMA,
|
||||
columnCounter);
|
||||
}
|
||||
|
||||
// Verify there isn’t already a header that is the same; if it already exists,
|
||||
// throw MetadataImportInvalidHeadingException
|
||||
String header = authorityPrefix + element;
|
||||
if (headings.contains(header)) {
|
||||
throw new MetadataImportInvalidHeadingException("Duplicate heading found: " + header,
|
||||
MetadataImportInvalidHeadingException.ENTRY,
|
||||
// Check that the metadata element exists in the schema
|
||||
MetadataField foundField = metadataFieldService
|
||||
.findByElement(c, foundSchema, metadataElement, metadataQualifier);
|
||||
if (foundField == null) {
|
||||
throw new MetadataImportInvalidHeadingException(clean[0],
|
||||
MetadataImportInvalidHeadingException.ELEMENT,
|
||||
columnCounter);
|
||||
}
|
||||
|
||||
@@ -324,7 +297,7 @@ public class DSpaceCSV implements Serializable {
|
||||
// Specify default values
|
||||
String[] defaultValues =
|
||||
new String[] {
|
||||
"dc.date.accessioned", "dc.date.available", "dc.date.updated", "dc.description.provenance"
|
||||
"dc.date.accessioned, dc.date.available, dc.date.updated, dc.description.provenance"
|
||||
};
|
||||
String[] toIgnoreArray =
|
||||
DSpaceServicesFactory.getInstance()
|
||||
@@ -355,15 +328,15 @@ public class DSpaceCSV implements Serializable {
|
||||
/**
|
||||
* Set the value separator for multiple values stored in one csv value.
|
||||
*
|
||||
* Is set in {@code bulkedit.cfg} as {@code valueseparator}.
|
||||
* Is set in bulkedit.cfg as valueseparator
|
||||
*
|
||||
* If not set, defaults to double pipe '||'.
|
||||
* If not set, defaults to double pipe '||'
|
||||
*/
|
||||
private void setValueSeparator() {
|
||||
// Get the value separator
|
||||
valueSeparator = DSpaceServicesFactory.getInstance().getConfigurationService()
|
||||
.getProperty("bulkedit.valueseparator");
|
||||
if ((valueSeparator != null) && !valueSeparator.trim().isEmpty()) {
|
||||
if ((valueSeparator != null) && (!"".equals(valueSeparator.trim()))) {
|
||||
valueSeparator = valueSeparator.trim();
|
||||
} else {
|
||||
valueSeparator = "||";
|
||||
@@ -378,7 +351,7 @@ public class DSpaceCSV implements Serializable {
|
||||
/**
|
||||
* Set the field separator use to separate fields in the csv.
|
||||
*
|
||||
* Is set in {@code bulkedit.cfg} as {@code fieldseparator}.
|
||||
* Is set in bulkedit.cfg as fieldseparator
|
||||
*
|
||||
* If not set, defaults to comma ','.
|
||||
*
|
||||
@@ -389,7 +362,7 @@ public class DSpaceCSV implements Serializable {
|
||||
// Get the value separator
|
||||
fieldSeparator = DSpaceServicesFactory.getInstance().getConfigurationService()
|
||||
.getProperty("bulkedit.fieldseparator");
|
||||
if ((fieldSeparator != null) && !fieldSeparator.trim().isEmpty()) {
|
||||
if ((fieldSeparator != null) && (!"".equals(fieldSeparator.trim()))) {
|
||||
fieldSeparator = fieldSeparator.trim();
|
||||
if ("tab".equals(fieldSeparator)) {
|
||||
fieldSeparator = "\t";
|
||||
@@ -413,15 +386,15 @@ public class DSpaceCSV implements Serializable {
|
||||
/**
|
||||
* Set the authority separator for value with authority data.
|
||||
*
|
||||
* Is set in {@code dspace.cfg} as {@code bulkedit.authorityseparator}.
|
||||
* Is set in dspace.cfg as bulkedit.authorityseparator
|
||||
*
|
||||
* If not set, defaults to double colon '::'.
|
||||
* If not set, defaults to double colon '::'
|
||||
*/
|
||||
private void setAuthoritySeparator() {
|
||||
// Get the value separator
|
||||
authoritySeparator = DSpaceServicesFactory.getInstance().getConfigurationService()
|
||||
.getProperty("bulkedit.authorityseparator");
|
||||
if ((authoritySeparator != null) && !authoritySeparator.trim().isEmpty()) {
|
||||
if ((authoritySeparator != null) && (!"".equals(authoritySeparator.trim()))) {
|
||||
authoritySeparator = authoritySeparator.trim();
|
||||
} else {
|
||||
authoritySeparator = "::";
|
||||
@@ -526,7 +499,7 @@ public class DSpaceCSV implements Serializable {
|
||||
int i = 0;
|
||||
for (String part : bits) {
|
||||
int bitcounter = part.length() - part.replaceAll("\"", "").length();
|
||||
if (part.startsWith("\"") && (!part.endsWith("\"") || ((bitcounter & 1) == 1))) {
|
||||
if ((part.startsWith("\"")) && ((!part.endsWith("\"")) || ((bitcounter & 1) == 1))) {
|
||||
found = true;
|
||||
String add = bits.get(i) + fieldSeparator + bits.get(i + 1);
|
||||
bits.remove(i);
|
||||
@@ -542,7 +515,7 @@ public class DSpaceCSV implements Serializable {
|
||||
// Deal with quotes around the elements
|
||||
int i = 0;
|
||||
for (String part : bits) {
|
||||
if (part.startsWith("\"") && part.endsWith("\"")) {
|
||||
if ((part.startsWith("\"")) && (part.endsWith("\""))) {
|
||||
part = part.substring(1, part.length() - 1);
|
||||
bits.set(i, part);
|
||||
}
|
||||
@@ -582,7 +555,7 @@ public class DSpaceCSV implements Serializable {
|
||||
for (String part : bits) {
|
||||
if (i > 0) {
|
||||
// Is this a last empty item?
|
||||
if (last && (i == headings.size())) {
|
||||
if ((last) && (i == headings.size())) {
|
||||
part = "";
|
||||
}
|
||||
|
||||
@@ -595,7 +568,7 @@ public class DSpaceCSV implements Serializable {
|
||||
csvLine.add(headings.get(i - 1), null);
|
||||
String[] elements = part.split(escapedValueSeparator);
|
||||
for (String element : elements) {
|
||||
if ((element != null) && !element.isEmpty()) {
|
||||
if ((element != null) && (!"".equals(element))) {
|
||||
csvLine.add(headings.get(i - 1), element);
|
||||
}
|
||||
}
|
||||
@@ -641,24 +614,30 @@ public class DSpaceCSV implements Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and returns an InputStream from the CSV Lines in this DSpaceCSV
|
||||
* @return The InputStream created from the CSVLines in this DSpaceCSV
|
||||
* 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
|
||||
*/
|
||||
public InputStream getInputStream() {
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
public final void save(String filename) throws IOException {
|
||||
// Save the file
|
||||
BufferedWriter out = new BufferedWriter(
|
||||
new OutputStreamWriter(
|
||||
new FileOutputStream(filename), "UTF-8"));
|
||||
for (String csvLine : getCSVLinesAsStringArray()) {
|
||||
stringBuilder.append(csvLine).append("\n");
|
||||
out.write(csvLine + "\n");
|
||||
}
|
||||
return IOUtils.toInputStream(stringBuilder.toString(), StandardCharsets.UTF_8);
|
||||
out.flush();
|
||||
out.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Is it okay to export this value? When exportAll is set to false, we don't export
|
||||
* Is it Ok to export this value? When exportAll is set to false, we don't export
|
||||
* some of the metadata elements.
|
||||
*
|
||||
* The list can be configured via the key ignore-on-export in {@code bulkedit.cfg}.
|
||||
* The list can be configured via the key ignore-on-export in bulkedit.cfg
|
||||
*
|
||||
* @param md The MetadataField to examine
|
||||
* @param md The Metadatum to examine
|
||||
* @return Whether or not it is OK to export this element
|
||||
*/
|
||||
protected boolean okToExport(MetadataField md) {
|
||||
@@ -667,8 +646,12 @@ public class DSpaceCSV implements Serializable {
|
||||
if (md.getQualifier() != null) {
|
||||
key += "." + md.getQualifier();
|
||||
}
|
||||
if (ignore.get(key) != null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Must be OK, so don't ignore
|
||||
return ignore.get(key) == null;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -1,115 +0,0 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
package org.dspace.app.bulkedit;
|
||||
|
||||
import java.sql.SQLException;
|
||||
|
||||
import org.apache.commons.cli.ParseException;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.dspace.content.MetadataField;
|
||||
import org.dspace.content.factory.ContentServiceFactory;
|
||||
import org.dspace.content.service.MetadataFieldService;
|
||||
import org.dspace.content.service.MetadataValueService;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.scripts.DSpaceRunnable;
|
||||
import org.dspace.services.ConfigurationService;
|
||||
import org.dspace.services.factory.DSpaceServicesFactory;
|
||||
import org.dspace.utils.DSpace;
|
||||
|
||||
/**
|
||||
* {@link DSpaceRunnable} implementation to delete all the values of the given
|
||||
* metadata field.
|
||||
*
|
||||
* @author Luca Giamminonni (luca.giamminonni at 4science.it)
|
||||
*
|
||||
*/
|
||||
public class MetadataDeletion extends DSpaceRunnable<MetadataDeletionScriptConfiguration<MetadataDeletion>> {
|
||||
|
||||
private MetadataValueService metadataValueService;
|
||||
|
||||
private MetadataFieldService metadataFieldService;
|
||||
|
||||
private ConfigurationService configurationService;
|
||||
|
||||
private String metadataField;
|
||||
|
||||
private boolean list;
|
||||
|
||||
@Override
|
||||
public void internalRun() throws Exception {
|
||||
|
||||
if (list) {
|
||||
listErasableMetadata();
|
||||
return;
|
||||
}
|
||||
|
||||
Context context = new Context();
|
||||
|
||||
try {
|
||||
context.turnOffAuthorisationSystem();
|
||||
performMetadataValuesDeletion(context);
|
||||
} finally {
|
||||
context.restoreAuthSystemState();
|
||||
context.complete();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void listErasableMetadata() {
|
||||
String[] erasableMetadata = getErasableMetadata();
|
||||
if (ArrayUtils.isEmpty(erasableMetadata)) {
|
||||
handler.logInfo("No fields has been configured to be cleared via bulk deletion");
|
||||
} else {
|
||||
handler.logInfo("The fields that can be bulk deleted are: " + String.join(", ", erasableMetadata));
|
||||
}
|
||||
}
|
||||
|
||||
private void performMetadataValuesDeletion(Context context) throws SQLException {
|
||||
|
||||
MetadataField field = metadataFieldService.findByString(context, metadataField, '.');
|
||||
if (field == null) {
|
||||
throw new IllegalArgumentException("No metadata field found with name " + metadataField);
|
||||
}
|
||||
|
||||
if (!ArrayUtils.contains(getErasableMetadata(), metadataField)) {
|
||||
throw new IllegalArgumentException("The given metadata field cannot be bulk deleted");
|
||||
}
|
||||
|
||||
handler.logInfo(String.format("Deleting the field '%s' from all objects", metadataField));
|
||||
|
||||
metadataValueService.deleteByMetadataField(context, field);
|
||||
}
|
||||
|
||||
private String[] getErasableMetadata() {
|
||||
return configurationService.getArrayProperty("bulkedit.allow-bulk-deletion");
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public MetadataDeletionScriptConfiguration<MetadataDeletion> getScriptConfiguration() {
|
||||
return new DSpace().getServiceManager()
|
||||
.getServiceByName("metadata-deletion", MetadataDeletionScriptConfiguration.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setup() throws ParseException {
|
||||
|
||||
metadataValueService = ContentServiceFactory.getInstance().getMetadataValueService();
|
||||
metadataFieldService = ContentServiceFactory.getInstance().getMetadataFieldService();
|
||||
configurationService = DSpaceServicesFactory.getInstance().getConfigurationService();
|
||||
|
||||
metadataField = commandLine.getOptionValue('m');
|
||||
list = commandLine.hasOption('l');
|
||||
|
||||
if (!list && metadataField == null) {
|
||||
throw new ParseException("One of the following parameters is required: -m or -l");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -1,18 +0,0 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
package org.dspace.app.bulkedit;
|
||||
|
||||
/**
|
||||
* The {@link MetadataDeletion} for CLI.
|
||||
*
|
||||
* @author Luca Giamminonni (luca.giamminonni at 4science.it)
|
||||
*
|
||||
*/
|
||||
public class MetadataDeletionCli extends MetadataDeletion {
|
||||
|
||||
}
|
@@ -1,18 +0,0 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
package org.dspace.app.bulkedit;
|
||||
|
||||
/**
|
||||
* Script configuration for {@link MetadataDeletionCli}.
|
||||
*
|
||||
* @author Luca Giamminonni (luca.giamminonni at 4science.it)
|
||||
*
|
||||
*/
|
||||
public class MetadataDeletionCliScriptConfiguration extends MetadataDeletionScriptConfiguration<MetadataDeletionCli> {
|
||||
|
||||
}
|
@@ -1,49 +0,0 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
package org.dspace.app.bulkedit;
|
||||
|
||||
import org.apache.commons.cli.Options;
|
||||
import org.dspace.scripts.configuration.ScriptConfiguration;
|
||||
|
||||
/**
|
||||
* The {@link ScriptConfiguration} for the {@link MetadataDeletion} script.
|
||||
*/
|
||||
public class MetadataDeletionScriptConfiguration<T extends MetadataDeletion> extends ScriptConfiguration<T> {
|
||||
|
||||
private Class<T> dspaceRunnableClass;
|
||||
|
||||
@Override
|
||||
public Options getOptions() {
|
||||
if (options == null) {
|
||||
|
||||
Options options = new Options();
|
||||
|
||||
options.addOption("m", "metadata", true, "metadata field name");
|
||||
|
||||
options.addOption("l", "list", false, "lists the metadata fields that can be deleted");
|
||||
|
||||
super.options = options;
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<T> getDspaceRunnableClass() {
|
||||
return dspaceRunnableClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic setter for the dspaceRunnableClass
|
||||
* @param dspaceRunnableClass The dspaceRunnableClass to be set on this MetadataDeletionScriptConfiguration
|
||||
*/
|
||||
@Override
|
||||
public void setDspaceRunnableClass(Class<T> dspaceRunnableClass) {
|
||||
this.dspaceRunnableClass = dspaceRunnableClass;
|
||||
}
|
||||
|
||||
}
|
@@ -8,115 +8,271 @@
|
||||
package org.dspace.app.bulkedit;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.UUID;
|
||||
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.lang3.StringUtils;
|
||||
import org.dspace.app.util.factory.UtilServiceFactory;
|
||||
import org.dspace.app.util.service.DSpaceObjectUtils;
|
||||
import org.apache.commons.cli.PosixParser;
|
||||
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.MetadataDSpaceCsvExportService;
|
||||
import org.dspace.content.service.ItemService;
|
||||
import org.dspace.core.Constants;
|
||||
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 extends DSpaceRunnable<MetadataExportScriptConfiguration> {
|
||||
public class MetadataExport {
|
||||
/**
|
||||
* The items to export
|
||||
*/
|
||||
protected Iterator<Item> toExport;
|
||||
|
||||
private boolean help = false;
|
||||
private String filename = null;
|
||||
private String identifier = null;
|
||||
private boolean exportAllMetadata = false;
|
||||
private boolean exportAllItems = false;
|
||||
protected ItemService itemService;
|
||||
|
||||
private static final String EXPORT_CSV = "exportCSV";
|
||||
protected Context context;
|
||||
|
||||
private MetadataDSpaceCsvExportService metadataDSpaceCsvExportService = new DSpace().getServiceManager()
|
||||
.getServicesByType(MetadataDSpaceCsvExportService.class).get(0);
|
||||
/**
|
||||
* Whether to export all metadata, or just normally edited metadata
|
||||
*/
|
||||
protected boolean exportAll;
|
||||
|
||||
private EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService();
|
||||
protected MetadataExport() {
|
||||
itemService = ContentServiceFactory.getInstance().getItemService();
|
||||
}
|
||||
|
||||
private DSpaceObjectUtils dSpaceObjectUtils = UtilServiceFactory.getInstance().getDSpaceObjectUtils();
|
||||
/**
|
||||
* 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();
|
||||
|
||||
@Override
|
||||
public void internalRun() throws Exception {
|
||||
// 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();
|
||||
|
||||
if (help) {
|
||||
logHelpInfo();
|
||||
printHelp();
|
||||
return;
|
||||
}
|
||||
Context context = new Context();
|
||||
context.turnOffAuthorisationSystem();
|
||||
try {
|
||||
context.setCurrentUser(ePersonService.find(context, this.getEpersonIdentifier()));
|
||||
} catch (SQLException e) {
|
||||
handler.handleException(e);
|
||||
// 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);
|
||||
}
|
||||
DSpaceCSV dSpaceCSV = metadataDSpaceCsvExportService
|
||||
.handleExport(context, exportAllItems, exportAllMetadata, identifier,
|
||||
handler);
|
||||
handler.writeFilestream(context, filename, dSpaceCSV.getInputStream(), EXPORT_CSV);
|
||||
context.restoreAuthSystemState();
|
||||
context.complete();
|
||||
}
|
||||
|
||||
protected void logHelpInfo() {
|
||||
handler.logInfo("\nfull export: metadata-export");
|
||||
handler.logInfo("partial export: metadata-export -i handle/UUID");
|
||||
}
|
||||
|
||||
@Override
|
||||
public MetadataExportScriptConfiguration getScriptConfiguration() {
|
||||
return new DSpace().getServiceManager().getServiceByName("metadata-export",
|
||||
MetadataExportScriptConfiguration.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setup() throws ParseException {
|
||||
|
||||
if (commandLine.hasOption('h')) {
|
||||
help = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!commandLine.hasOption('i')) {
|
||||
exportAllItems = true;
|
||||
}
|
||||
identifier = 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(identifier)) {
|
||||
dso = HandleServiceFactory.getInstance().getHandleService().resolveToObject(context, identifier);
|
||||
if (dso == null) {
|
||||
dso = dSpaceObjectUtils.findDSpaceObject(context, UUID.fromString(identifier));
|
||||
}
|
||||
} else {
|
||||
dso = ContentServiceFactory.getInstance().getSiteService().findSite(context);
|
||||
/**
|
||||
* 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(" ");
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
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) {
|
||||
throw new ParseException("An identifier was given that wasn't able to be parsed to a DSpaceObject");
|
||||
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);
|
||||
}
|
||||
return dso.getID().toString() + ".csv";
|
||||
} catch (SQLException e) {
|
||||
handler.handleException("Something went wrong trying to retrieve DSO for identifier: " + identifier, e);
|
||||
}
|
||||
return null;
|
||||
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
|
@@ -1,33 +0,0 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
package org.dspace.app.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");
|
||||
}
|
||||
}
|
@@ -1,26 +0,0 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
package org.dspace.app.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;
|
||||
}
|
||||
}
|
@@ -1,50 +0,0 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
package org.dspace.app.bulkedit;
|
||||
|
||||
import org.apache.commons.cli.Options;
|
||||
import org.dspace.scripts.configuration.ScriptConfiguration;
|
||||
|
||||
/**
|
||||
* The {@link ScriptConfiguration} for the {@link MetadataExport} script
|
||||
*/
|
||||
public class MetadataExportScriptConfiguration<T extends MetadataExport> extends ScriptConfiguration<T> {
|
||||
|
||||
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 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.addOption("a", "all", false,
|
||||
"include all metadata fields that are not normally changed (e.g. provenance)");
|
||||
options.addOption("h", "help", false, "help");
|
||||
|
||||
|
||||
super.options = options;
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
}
|
@@ -1,170 +0,0 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
|
||||
package org.dspace.app.bulkedit;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.apache.commons.cli.ParseException;
|
||||
import org.dspace.content.Item;
|
||||
import org.dspace.content.MetadataDSpaceCsvExportServiceImpl;
|
||||
import org.dspace.content.factory.ContentServiceFactory;
|
||||
import org.dspace.content.service.CollectionService;
|
||||
import org.dspace.content.service.CommunityService;
|
||||
import org.dspace.content.service.MetadataDSpaceCsvExportService;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.discovery.DiscoverQuery;
|
||||
import org.dspace.discovery.IndexableObject;
|
||||
import org.dspace.discovery.SearchService;
|
||||
import org.dspace.discovery.SearchUtils;
|
||||
import org.dspace.discovery.configuration.DiscoveryConfiguration;
|
||||
import org.dspace.discovery.configuration.DiscoveryConfigurationService;
|
||||
import org.dspace.discovery.indexobject.IndexableCollection;
|
||||
import org.dspace.discovery.indexobject.IndexableCommunity;
|
||||
import org.dspace.discovery.utils.DiscoverQueryBuilder;
|
||||
import org.dspace.discovery.utils.parameter.QueryBuilderSearchFilter;
|
||||
import org.dspace.eperson.factory.EPersonServiceFactory;
|
||||
import org.dspace.eperson.service.EPersonService;
|
||||
import org.dspace.scripts.DSpaceRunnable;
|
||||
import org.dspace.sort.SortOption;
|
||||
import org.dspace.utils.DSpace;
|
||||
|
||||
/**
|
||||
* Metadata exporter to allow the batch export of metadata from a discovery search into a file
|
||||
*
|
||||
*/
|
||||
public class MetadataExportSearch extends DSpaceRunnable<MetadataExportSearchScriptConfiguration> {
|
||||
private static final String EXPORT_CSV = "exportCSV";
|
||||
private boolean help = false;
|
||||
private String identifier;
|
||||
private String discoveryConfigName;
|
||||
private String[] filterQueryStrings;
|
||||
private boolean hasScope = false;
|
||||
private String query;
|
||||
|
||||
private SearchService searchService;
|
||||
private MetadataDSpaceCsvExportService metadataDSpaceCsvExportService;
|
||||
private EPersonService ePersonService;
|
||||
private DiscoveryConfigurationService discoveryConfigurationService;
|
||||
private CommunityService communityService;
|
||||
private CollectionService collectionService;
|
||||
private DiscoverQueryBuilder queryBuilder;
|
||||
|
||||
@Override
|
||||
public MetadataExportSearchScriptConfiguration getScriptConfiguration() {
|
||||
return new DSpace().getServiceManager()
|
||||
.getServiceByName("metadata-export-search", MetadataExportSearchScriptConfiguration.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setup() throws ParseException {
|
||||
searchService = SearchUtils.getSearchService();
|
||||
metadataDSpaceCsvExportService = new DSpace().getServiceManager()
|
||||
.getServiceByName(
|
||||
MetadataDSpaceCsvExportServiceImpl.class.getCanonicalName(),
|
||||
MetadataDSpaceCsvExportService.class
|
||||
);
|
||||
ePersonService = EPersonServiceFactory.getInstance().getEPersonService();
|
||||
discoveryConfigurationService = SearchUtils.getConfigurationService();
|
||||
communityService = ContentServiceFactory.getInstance().getCommunityService();
|
||||
collectionService = ContentServiceFactory.getInstance().getCollectionService();
|
||||
queryBuilder = SearchUtils.getQueryBuilder();
|
||||
|
||||
if (commandLine.hasOption('h')) {
|
||||
help = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (commandLine.hasOption('q')) {
|
||||
query = commandLine.getOptionValue('q');
|
||||
}
|
||||
|
||||
if (commandLine.hasOption('s')) {
|
||||
hasScope = true;
|
||||
identifier = commandLine.getOptionValue('s');
|
||||
}
|
||||
|
||||
if (commandLine.hasOption('c')) {
|
||||
discoveryConfigName = commandLine.getOptionValue('c');
|
||||
}
|
||||
|
||||
if (commandLine.hasOption('f')) {
|
||||
filterQueryStrings = commandLine.getOptionValues('f');
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void internalRun() throws Exception {
|
||||
if (help) {
|
||||
loghelpinfo();
|
||||
printHelp();
|
||||
return;
|
||||
}
|
||||
handler.logDebug("starting search export");
|
||||
|
||||
IndexableObject dso = null;
|
||||
Context context = new Context();
|
||||
context.setCurrentUser(ePersonService.find(context, this.getEpersonIdentifier()));
|
||||
|
||||
if (hasScope) {
|
||||
dso = resolveScope(context, identifier);
|
||||
}
|
||||
|
||||
DiscoveryConfiguration discoveryConfiguration =
|
||||
discoveryConfigurationService.getDiscoveryConfiguration(discoveryConfigName);
|
||||
|
||||
List<QueryBuilderSearchFilter> queryBuilderSearchFilters = new ArrayList<>();
|
||||
|
||||
handler.logDebug("processing filter queries");
|
||||
if (filterQueryStrings != null) {
|
||||
for (String filterQueryString: filterQueryStrings) {
|
||||
String field = filterQueryString.split(",", 2)[0];
|
||||
String operator = filterQueryString.split("(,|=)", 3)[1];
|
||||
String value = filterQueryString.split("=", 2)[1];
|
||||
QueryBuilderSearchFilter queryBuilderSearchFilter =
|
||||
new QueryBuilderSearchFilter(field, operator, value);
|
||||
queryBuilderSearchFilters.add(queryBuilderSearchFilter);
|
||||
}
|
||||
}
|
||||
handler.logDebug("building query");
|
||||
DiscoverQuery discoverQuery =
|
||||
queryBuilder.buildQuery(context, dso, discoveryConfiguration, query, queryBuilderSearchFilters,
|
||||
"Item", 10, Long.getLong("0"), null, SortOption.DESCENDING);
|
||||
handler.logDebug("creating iterator");
|
||||
|
||||
Iterator<Item> itemIterator = searchService.iteratorSearch(context, dso, discoverQuery);
|
||||
handler.logDebug("creating dspacecsv");
|
||||
DSpaceCSV dSpaceCSV = metadataDSpaceCsvExportService.export(context, itemIterator, true);
|
||||
handler.logDebug("writing to file " + getFileNameOrExportFile());
|
||||
handler.writeFilestream(context, getFileNameOrExportFile(), dSpaceCSV.getInputStream(), EXPORT_CSV);
|
||||
context.restoreAuthSystemState();
|
||||
context.complete();
|
||||
|
||||
}
|
||||
|
||||
protected void loghelpinfo() {
|
||||
handler.logInfo("metadata-export");
|
||||
}
|
||||
|
||||
protected String getFileNameOrExportFile() {
|
||||
return "metadataExportSearch.csv";
|
||||
}
|
||||
|
||||
public IndexableObject resolveScope(Context context, String id) throws SQLException {
|
||||
UUID uuid = UUID.fromString(id);
|
||||
IndexableObject scopeObj = new IndexableCommunity(communityService.find(context, uuid));
|
||||
if (scopeObj.getIndexedObject() == null) {
|
||||
scopeObj = new IndexableCollection(collectionService.find(context, uuid));
|
||||
}
|
||||
return scopeObj;
|
||||
}
|
||||
}
|
@@ -1,20 +0,0 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
|
||||
package org.dspace.app.bulkedit;
|
||||
|
||||
/**
|
||||
* The cli version of the {@link MetadataExportSearch} script
|
||||
*/
|
||||
public class MetadataExportSearchCli extends MetadataExportSearch {
|
||||
|
||||
@Override
|
||||
protected String getFileNameOrExportFile() {
|
||||
return commandLine.getOptionValue('n');
|
||||
}
|
||||
}
|
@@ -1,26 +0,0 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
|
||||
package org.dspace.app.bulkedit;
|
||||
|
||||
import org.apache.commons.cli.Options;
|
||||
|
||||
/**
|
||||
* This is the CLI version of the {@link MetadataExportSearchScriptConfiguration} class that handles the
|
||||
* configuration for the {@link MetadataExportSearchCli} script
|
||||
*/
|
||||
public class MetadataExportSearchCliScriptConfiguration
|
||||
extends MetadataExportSearchScriptConfiguration<MetadataExportSearchCli> {
|
||||
|
||||
@Override
|
||||
public Options getOptions() {
|
||||
Options options = super.getOptions();
|
||||
options.addOption("n", "filename", true, "the filename to export to");
|
||||
return super.getOptions();
|
||||
}
|
||||
}
|
@@ -1,56 +0,0 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
|
||||
package org.dspace.app.bulkedit;
|
||||
|
||||
import org.apache.commons.cli.Options;
|
||||
import org.dspace.scripts.configuration.ScriptConfiguration;
|
||||
|
||||
/**
|
||||
* The {@link ScriptConfiguration} for the {@link MetadataExportSearch} script
|
||||
*/
|
||||
public class MetadataExportSearchScriptConfiguration<T extends MetadataExportSearch> extends ScriptConfiguration<T> {
|
||||
|
||||
private Class<T> dspaceRunnableclass;
|
||||
|
||||
@Override
|
||||
public Class<T> getDspaceRunnableClass() {
|
||||
return dspaceRunnableclass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDspaceRunnableClass(Class<T> dspaceRunnableClass) {
|
||||
this.dspaceRunnableclass = dspaceRunnableClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Options getOptions() {
|
||||
if (options == null) {
|
||||
Options options = new Options();
|
||||
options.addOption("q", "query", true,
|
||||
"The discovery search string to will be used to match records. Not URL encoded");
|
||||
options.getOption("q").setType(String.class);
|
||||
options.addOption("s", "scope", true,
|
||||
"UUID of a specific DSpace container (site, community or collection) to which the search has to be " +
|
||||
"limited");
|
||||
options.getOption("s").setType(String.class);
|
||||
options.addOption("c", "configuration", true,
|
||||
"The name of a Discovery configuration that should be used by this search");
|
||||
options.getOption("c").setType(String.class);
|
||||
options.addOption("f", "filter", true,
|
||||
"Advanced search filter that has to be used to filter the result set, with syntax `<:filter-name>," +
|
||||
"<:filter-operator>=<:filter-value>`. Not URL encoded. For example `author," +
|
||||
"authority=5df05073-3be7-410d-8166-e254369e4166` or `title,contains=sample text`");
|
||||
options.getOption("f").setType(String.class);
|
||||
options.addOption("h", "help", false, "help");
|
||||
|
||||
super.options = options;
|
||||
}
|
||||
return options;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@@ -1,68 +0,0 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
package org.dspace.app.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!");
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,26 +0,0 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
package org.dspace.app.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").setRequired(true);
|
||||
super.options = options;
|
||||
return options;
|
||||
}
|
||||
}
|
@@ -1,59 +0,0 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
package org.dspace.app.bulkedit;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.apache.commons.cli.Options;
|
||||
import org.dspace.scripts.configuration.ScriptConfiguration;
|
||||
|
||||
/**
|
||||
* The {@link ScriptConfiguration} for the {@link MetadataImport} script
|
||||
*/
|
||||
public class MetadataImportScriptConfiguration<T extends MetadataImport> extends ScriptConfiguration<T> {
|
||||
|
||||
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 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.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("v", "validate-only", false,
|
||||
"validate - just validate the csv, don't run the import");
|
||||
options.addOption("t", "template", false,
|
||||
"template - when adding new items, use the collection template (if it exists)");
|
||||
options.addOption("h", "help", false, "help");
|
||||
|
||||
super.options = options;
|
||||
}
|
||||
return options;
|
||||
}
|
||||
}
|
@@ -17,13 +17,13 @@ import java.util.UUID;
|
||||
|
||||
import org.apache.commons.cli.CommandLine;
|
||||
import org.apache.commons.cli.CommandLineParser;
|
||||
import org.apache.commons.cli.DefaultParser;
|
||||
import org.apache.commons.cli.HelpFormatter;
|
||||
import org.apache.commons.cli.Option;
|
||||
import org.apache.commons.cli.OptionBuilder;
|
||||
import org.apache.commons.cli.Options;
|
||||
import org.apache.commons.cli.ParseException;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.commons.cli.PosixParser;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.dspace.checker.BitstreamDispatcher;
|
||||
import org.dspace.checker.CheckerCommand;
|
||||
import org.dspace.checker.HandleDispatcher;
|
||||
@@ -48,7 +48,7 @@ import org.dspace.core.Utils;
|
||||
* @author Nathan Sarr
|
||||
*/
|
||||
public final class ChecksumChecker {
|
||||
private static final Logger LOG = LogManager.getLogger(ChecksumChecker.class);
|
||||
private static final Logger LOG = Logger.getLogger(ChecksumChecker.class);
|
||||
|
||||
private static final BitstreamService bitstreamService = ContentServiceFactory.getInstance().getBitstreamService();
|
||||
|
||||
@@ -86,7 +86,7 @@ public final class ChecksumChecker {
|
||||
*/
|
||||
public static void main(String[] args) throws SQLException {
|
||||
// set up command line parser
|
||||
CommandLineParser parser = new DefaultParser();
|
||||
CommandLineParser parser = new PosixParser();
|
||||
CommandLine line = null;
|
||||
|
||||
// create an options object and populate it
|
||||
@@ -101,21 +101,19 @@ public final class ChecksumChecker {
|
||||
options.addOption("a", "handle", true, "Specify a handle to check");
|
||||
options.addOption("v", "verbose", false, "Report all processing");
|
||||
|
||||
Option option;
|
||||
OptionBuilder.withArgName("bitstream-ids").hasArgs().withDescription(
|
||||
"Space separated list of bitstream ids");
|
||||
Option useBitstreamIds = OptionBuilder.create('b');
|
||||
|
||||
option = Option.builder("b")
|
||||
.longOpt("bitstream-ids")
|
||||
.hasArgs()
|
||||
.desc("Space separated list of bitstream ids")
|
||||
.build();
|
||||
options.addOption(option);
|
||||
options.addOption(useBitstreamIds);
|
||||
|
||||
option = Option.builder("p")
|
||||
.longOpt("prune")
|
||||
.optionalArg(true)
|
||||
.desc("Prune old results (optionally using specified properties file for configuration)")
|
||||
.build();
|
||||
options.addOption(option);
|
||||
options.addOption("p", "prune", false, "Prune configuration file");
|
||||
options.addOption(OptionBuilder
|
||||
.withArgName("prune")
|
||||
.hasOptionalArgs(1)
|
||||
.withDescription(
|
||||
"Prune old results (optionally using specified properties file for configuration)")
|
||||
.create('p'));
|
||||
|
||||
try {
|
||||
line = parser.parse(options, args);
|
||||
|
@@ -1,32 +0,0 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
package org.dspace.app.exception;
|
||||
|
||||
/**
|
||||
* This class provides an exception to be used when trying to save a resource
|
||||
* that already exists.
|
||||
*
|
||||
* @author Luca Giamminonni (luca.giamminonni at 4science.it)
|
||||
*
|
||||
*/
|
||||
public class ResourceAlreadyExistsException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Create a ResourceAlreadyExistsException with a message and the already
|
||||
* existing resource.
|
||||
*
|
||||
* @param message the error message
|
||||
*/
|
||||
public ResourceAlreadyExistsException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -13,8 +13,11 @@ import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.apache.commons.cli.ParseException;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
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.dspace.authorize.AuthorizeException;
|
||||
import org.dspace.content.Collection;
|
||||
import org.dspace.content.DSpaceObject;
|
||||
@@ -33,223 +36,224 @@ import org.dspace.harvest.HarvestingException;
|
||||
import org.dspace.harvest.OAIHarvester;
|
||||
import org.dspace.harvest.factory.HarvestServiceFactory;
|
||||
import org.dspace.harvest.service.HarvestedCollectionService;
|
||||
import org.dspace.scripts.DSpaceRunnable;
|
||||
import org.dspace.utils.DSpace;
|
||||
|
||||
/**
|
||||
* Test class for harvested collections.
|
||||
*
|
||||
* @author Alexey Maslov
|
||||
*/
|
||||
public class Harvest extends DSpaceRunnable<HarvestScriptConfiguration> {
|
||||
public class Harvest {
|
||||
private static Context context;
|
||||
|
||||
private HarvestedCollectionService harvestedCollectionService;
|
||||
protected EPersonService ePersonService;
|
||||
private CollectionService collectionService;
|
||||
private static final HarvestedCollectionService harvestedCollectionService =
|
||||
HarvestServiceFactory.getInstance().getHarvestedCollectionService();
|
||||
private static final EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService();
|
||||
private static final CollectionService collectionService =
|
||||
ContentServiceFactory.getInstance().getCollectionService();
|
||||
|
||||
private boolean help;
|
||||
private String command = null;
|
||||
private String collection = null;
|
||||
private String oaiSource = null;
|
||||
private String oaiSetID = null;
|
||||
private String metadataKey = null;
|
||||
private int harvestType = 0;
|
||||
public static void main(String[] argv) throws Exception {
|
||||
// create an options object and populate it
|
||||
CommandLineParser parser = new PosixParser();
|
||||
|
||||
protected Context context;
|
||||
Options options = new Options();
|
||||
|
||||
options.addOption("p", "purge", false, "delete all items in the collection");
|
||||
options.addOption("r", "run", false, "run the standard harvest procedure");
|
||||
options.addOption("g", "ping", false, "test the OAI server and set");
|
||||
options.addOption("o", "once", false, "run the harvest procedure with specified parameters");
|
||||
options.addOption("s", "setup", false, "Set the collection up for harvesting");
|
||||
options.addOption("S", "start", false, "start the harvest loop");
|
||||
options.addOption("R", "reset", false, "reset harvest status on all collections");
|
||||
options.addOption("P", "purge", false, "purge all harvestable collections");
|
||||
|
||||
|
||||
public HarvestScriptConfiguration getScriptConfiguration() {
|
||||
return new DSpace().getServiceManager()
|
||||
.getServiceByName("harvest", HarvestScriptConfiguration.class);
|
||||
}
|
||||
options.addOption("e", "eperson", true,
|
||||
"eperson");
|
||||
options.addOption("c", "collection", true,
|
||||
"harvesting collection (handle or id)");
|
||||
options.addOption("t", "type", true,
|
||||
"type of harvesting (0 for none)");
|
||||
options.addOption("a", "address", true,
|
||||
"address of the OAI-PMH server");
|
||||
options.addOption("i", "oai_set_id", true,
|
||||
"id of the PMH set representing the harvested collection");
|
||||
options.addOption("m", "metadata_format", true,
|
||||
"the name of the desired metadata format for harvesting, resolved to namespace and " +
|
||||
"crosswalk in dspace.cfg");
|
||||
|
||||
public void setup() throws ParseException {
|
||||
harvestedCollectionService =
|
||||
HarvestServiceFactory.getInstance().getHarvestedCollectionService();
|
||||
ePersonService = EPersonServiceFactory.getInstance().getEPersonService();
|
||||
collectionService =
|
||||
ContentServiceFactory.getInstance().getCollectionService();
|
||||
options.addOption("h", "help", false, "help");
|
||||
|
||||
assignCurrentUserInContext();
|
||||
CommandLine line = parser.parse(options, argv);
|
||||
|
||||
help = commandLine.hasOption('h');
|
||||
String command = null;
|
||||
String eperson = null;
|
||||
String collection = null;
|
||||
String oaiSource = null;
|
||||
String oaiSetID = null;
|
||||
String metadataKey = null;
|
||||
int harvestType = 0;
|
||||
|
||||
if (line.hasOption('h')) {
|
||||
HelpFormatter myhelp = new HelpFormatter();
|
||||
myhelp.printHelp("Harvest\n", options);
|
||||
System.out.println("\nPING OAI server: Harvest -g -a oai_source -i oai_set_id");
|
||||
System.out.println(
|
||||
"RUNONCE harvest with arbitrary options: Harvest -o -e eperson -c collection -t harvest_type -a " +
|
||||
"oai_source -i oai_set_id -m metadata_format");
|
||||
System.out.println(
|
||||
"SETUP a collection for harvesting: Harvest -s -c collection -t harvest_type -a oai_source -i " +
|
||||
"oai_set_id -m metadata_format");
|
||||
System.out.println("RUN harvest once: Harvest -r -e eperson -c collection");
|
||||
System.out.println("START harvest scheduler: Harvest -S");
|
||||
System.out.println("RESET all harvest status: Harvest -R");
|
||||
System.out.println("PURGE a collection of items and settings: Harvest -p -e eperson -c collection");
|
||||
System.out.println("PURGE all harvestable collections: Harvest -P -e eperson");
|
||||
|
||||
|
||||
if (commandLine.hasOption('s')) {
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
if (line.hasOption('s')) {
|
||||
command = "config";
|
||||
}
|
||||
if (commandLine.hasOption('p')) {
|
||||
if (line.hasOption('p')) {
|
||||
command = "purge";
|
||||
}
|
||||
if (commandLine.hasOption('r')) {
|
||||
if (line.hasOption('r')) {
|
||||
command = "run";
|
||||
}
|
||||
if (commandLine.hasOption('g')) {
|
||||
if (line.hasOption('g')) {
|
||||
command = "ping";
|
||||
}
|
||||
if (commandLine.hasOption('S')) {
|
||||
if (line.hasOption('o')) {
|
||||
command = "runOnce";
|
||||
}
|
||||
if (line.hasOption('S')) {
|
||||
command = "start";
|
||||
}
|
||||
if (commandLine.hasOption('R')) {
|
||||
if (line.hasOption('R')) {
|
||||
command = "reset";
|
||||
}
|
||||
if (commandLine.hasOption('P')) {
|
||||
if (line.hasOption('P')) {
|
||||
command = "purgeAll";
|
||||
}
|
||||
if (commandLine.hasOption('o')) {
|
||||
command = "reimport";
|
||||
|
||||
|
||||
if (line.hasOption('e')) {
|
||||
eperson = line.getOptionValue('e');
|
||||
}
|
||||
if (commandLine.hasOption('c')) {
|
||||
collection = commandLine.getOptionValue('c');
|
||||
if (line.hasOption('c')) {
|
||||
collection = line.getOptionValue('c');
|
||||
}
|
||||
if (commandLine.hasOption('t')) {
|
||||
harvestType = Integer.parseInt(commandLine.getOptionValue('t'));
|
||||
if (line.hasOption('t')) {
|
||||
harvestType = Integer.parseInt(line.getOptionValue('t'));
|
||||
} else {
|
||||
harvestType = 0;
|
||||
}
|
||||
if (commandLine.hasOption('a')) {
|
||||
oaiSource = commandLine.getOptionValue('a');
|
||||
if (line.hasOption('a')) {
|
||||
oaiSource = line.getOptionValue('a');
|
||||
}
|
||||
if (commandLine.hasOption('i')) {
|
||||
oaiSetID = commandLine.getOptionValue('i');
|
||||
if (line.hasOption('i')) {
|
||||
oaiSetID = line.getOptionValue('i');
|
||||
}
|
||||
if (commandLine.hasOption('m')) {
|
||||
metadataKey = commandLine.getOptionValue('m');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
|
||||
public void internalRun() throws Exception {
|
||||
if (help) {
|
||||
printHelp();
|
||||
handler.logInfo("PING OAI server: Harvest -g -a oai_source -i oai_set_id");
|
||||
handler.logInfo(
|
||||
"SETUP a collection for harvesting: Harvest -s -c collection -t harvest_type -a oai_source -i " +
|
||||
"oai_set_id -m metadata_format");
|
||||
handler.logInfo("RUN harvest once: Harvest -r -e eperson -c collection");
|
||||
handler.logInfo("START harvest scheduler: Harvest -S");
|
||||
handler.logInfo("RESET all harvest status: Harvest -R");
|
||||
handler.logInfo("PURGE a collection of items and settings: Harvest -p -e eperson -c collection");
|
||||
handler.logInfo("PURGE all harvestable collections: Harvest -P -e eperson");
|
||||
|
||||
return;
|
||||
if (line.hasOption('m')) {
|
||||
metadataKey = line.getOptionValue('m');
|
||||
}
|
||||
|
||||
if (StringUtils.isBlank(command)) {
|
||||
handler.logError("No parameters specified (run with -h flag for details)");
|
||||
throw new UnsupportedOperationException("No command specified");
|
||||
|
||||
// Instantiate our class
|
||||
Harvest harvester = new Harvest();
|
||||
harvester.context = new Context(Context.Mode.BATCH_EDIT);
|
||||
|
||||
|
||||
// Check our options
|
||||
if (command == null) {
|
||||
System.out
|
||||
.println("Error - no parameters specified (run with -h flag for details)");
|
||||
System.exit(1);
|
||||
} else if ("run".equals(command)) {
|
||||
// Run a single harvest cycle on a collection using saved settings.
|
||||
if (collection == null || context.getCurrentUser() == null) {
|
||||
handler.logError("A target collection and eperson must be provided (run with -h flag for details)");
|
||||
throw new UnsupportedOperationException("A target collection and eperson must be provided");
|
||||
if (collection == null || eperson == null) {
|
||||
System.out
|
||||
.println("Error - a target collection and eperson must be provided");
|
||||
System.out.println(" (run with -h flag for details)");
|
||||
System.exit(1);
|
||||
}
|
||||
runHarvest(context, collection);
|
||||
|
||||
harvester.runHarvest(collection, eperson);
|
||||
} else if ("start".equals(command)) {
|
||||
// start the harvest loop
|
||||
startHarvester();
|
||||
} else if ("reset".equals(command)) {
|
||||
// reset harvesting status
|
||||
resetHarvesting(context);
|
||||
resetHarvesting();
|
||||
} else if ("purgeAll".equals(command)) {
|
||||
// purge all collections that are set up for harvesting (obviously for testing purposes only)
|
||||
if (context.getCurrentUser() == null) {
|
||||
handler.logError("An eperson must be provided (run with -h flag for details)");
|
||||
throw new UnsupportedOperationException("An eperson must be provided");
|
||||
if (eperson == null) {
|
||||
System.out
|
||||
.println("Error - an eperson must be provided");
|
||||
System.out.println(" (run with -h flag for details)");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
List<HarvestedCollection> harvestedCollections = harvestedCollectionService.findAll(context);
|
||||
for (HarvestedCollection harvestedCollection : harvestedCollections) {
|
||||
handler.logInfo(
|
||||
"Purging the following collections (deleting items and resetting harvest status): " +
|
||||
harvestedCollection
|
||||
.getCollection().getID().toString());
|
||||
purgeCollection(context, harvestedCollection.getCollection().getID().toString());
|
||||
System.out.println(
|
||||
"Purging the following collections (deleting items and resetting harvest status): " +
|
||||
harvestedCollection
|
||||
.getCollection().getID().toString());
|
||||
harvester.purgeCollection(harvestedCollection.getCollection().getID().toString(), eperson);
|
||||
}
|
||||
context.complete();
|
||||
} else if ("purge".equals(command)) {
|
||||
// Delete all items in a collection. Useful for testing fresh harvests.
|
||||
if (collection == null || context.getCurrentUser() == null) {
|
||||
handler.logError("A target collection and eperson must be provided (run with -h flag for details)");
|
||||
throw new UnsupportedOperationException("A target collection and eperson must be provided");
|
||||
if (collection == null || eperson == null) {
|
||||
System.out
|
||||
.println("Error - a target collection and eperson must be provided");
|
||||
System.out.println(" (run with -h flag for details)");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
purgeCollection(context, collection);
|
||||
context.complete();
|
||||
|
||||
} else if ("reimport".equals(command)) {
|
||||
// Delete all items in a collection. Useful for testing fresh harvests.
|
||||
if (collection == null || context.getCurrentUser() == null) {
|
||||
handler.logError("A target collection and eperson must be provided (run with -h flag for details)");
|
||||
throw new UnsupportedOperationException("A target collection and eperson must be provided");
|
||||
}
|
||||
purgeCollection(context, collection);
|
||||
runHarvest(context, collection);
|
||||
harvester.purgeCollection(collection, eperson);
|
||||
context.complete();
|
||||
|
||||
//TODO: implement this... remove all items and remember to unset "last-harvested" settings
|
||||
} else if ("config".equals(command)) {
|
||||
// Configure a collection with the three main settings
|
||||
if (collection == null) {
|
||||
handler.logError("A target collection must be provided (run with -h flag for details)");
|
||||
throw new UnsupportedOperationException("A target collection must be provided");
|
||||
System.out.println("Error - a target collection must be provided");
|
||||
System.out.println(" (run with -h flag for details)");
|
||||
System.exit(1);
|
||||
}
|
||||
if (oaiSource == null || oaiSetID == null) {
|
||||
handler.logError(
|
||||
"Both the OAI server address and OAI set id must be specified (run with -h flag for details)");
|
||||
throw new UnsupportedOperationException("Both the OAI server address and OAI set id must be specified");
|
||||
System.out.println("Error - both the OAI server address and OAI set id must be specified");
|
||||
System.out.println(" (run with -h flag for details)");
|
||||
System.exit(1);
|
||||
}
|
||||
if (metadataKey == null) {
|
||||
handler.logError(
|
||||
"A metadata key (commonly the prefix) must be specified for this collection (run with -h flag" +
|
||||
" for details)");
|
||||
throw new UnsupportedOperationException(
|
||||
"A metadata key (commonly the prefix) must be specified for this collection");
|
||||
System.out
|
||||
.println("Error - a metadata key (commonly the prefix) must be specified for this collection");
|
||||
System.out.println(" (run with -h flag for details)");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
configureCollection(context, collection, harvestType, oaiSource, oaiSetID, metadataKey);
|
||||
harvester.configureCollection(collection, harvestType, oaiSource, oaiSetID, metadataKey);
|
||||
} else if ("ping".equals(command)) {
|
||||
if (oaiSource == null || oaiSetID == null) {
|
||||
handler.logError(
|
||||
"Both the OAI server address and OAI set id must be specified (run with -h flag for details)");
|
||||
throw new UnsupportedOperationException("Both the OAI server address and OAI set id must be specified");
|
||||
System.out.println("Error - both the OAI server address and OAI set id must be specified");
|
||||
System.out.println(" (run with -h flag for details)");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
pingResponder(oaiSource, oaiSetID, metadataKey);
|
||||
} else {
|
||||
handler.logError(
|
||||
"Your command '" + command + "' was not recognized properly (run with -h flag for details)");
|
||||
throw new UnsupportedOperationException("Your command '" + command + "' was not recognized properly");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Resolve the ID into a collection and check to see if its harvesting options are set. If so, return
|
||||
* the collection, if not, bail out.
|
||||
*/
|
||||
private Collection resolveCollection(Context context, String collectionID) {
|
||||
private Collection resolveCollection(String collectionID) {
|
||||
|
||||
DSpaceObject dso;
|
||||
Collection targetCollection = null;
|
||||
@@ -268,15 +272,16 @@ public class Harvest extends DSpaceRunnable<HarvestScriptConfiguration> {
|
||||
targetCollection = (Collection) dso;
|
||||
}
|
||||
} else {
|
||||
// not a handle, try and treat it as an collection database UUID
|
||||
handler.logInfo("Looking up by UUID: " + collectionID + ", " + "in context: " + context);
|
||||
// not a handle, try and treat it as an integer collection database ID
|
||||
System.out.println("Looking up by id: " + collectionID + ", parsed as '" + Integer
|
||||
.parseInt(collectionID) + "', " + "in context: " + context);
|
||||
targetCollection = collectionService.find(context, UUID.fromString(collectionID));
|
||||
}
|
||||
}
|
||||
// was the collection valid?
|
||||
if (targetCollection == null) {
|
||||
handler.logError("Cannot resolve " + collectionID + " to collection");
|
||||
throw new UnsupportedOperationException("Cannot resolve " + collectionID + " to collection");
|
||||
System.out.println("Cannot resolve " + collectionID + " to collection");
|
||||
System.exit(1);
|
||||
}
|
||||
} catch (SQLException se) {
|
||||
se.printStackTrace();
|
||||
@@ -286,12 +291,12 @@ public class Harvest extends DSpaceRunnable<HarvestScriptConfiguration> {
|
||||
}
|
||||
|
||||
|
||||
private void configureCollection(Context context, String collectionID, int type, String oaiSource, String oaiSetId,
|
||||
private void configureCollection(String collectionID, int type, String oaiSource, String oaiSetId,
|
||||
String mdConfigId) {
|
||||
handler.logInfo("Running: configure collection");
|
||||
System.out.println("Running: configure collection");
|
||||
|
||||
Collection collection = resolveCollection(context, collectionID);
|
||||
handler.logInfo(String.valueOf(collection.getID()));
|
||||
Collection collection = resolveCollection(collectionID);
|
||||
System.out.println(collection.getID());
|
||||
|
||||
try {
|
||||
HarvestedCollection hc = harvestedCollectionService.find(context, collection);
|
||||
@@ -306,8 +311,9 @@ public class Harvest extends DSpaceRunnable<HarvestScriptConfiguration> {
|
||||
context.restoreAuthSystemState();
|
||||
context.complete();
|
||||
} catch (Exception e) {
|
||||
handler.logError("Changes could not be committed");
|
||||
handler.handleException(e);
|
||||
System.out.println("Changes could not be committed");
|
||||
e.printStackTrace();
|
||||
System.exit(1);
|
||||
} finally {
|
||||
if (context != null) {
|
||||
context.restoreAuthSystemState();
|
||||
@@ -318,15 +324,18 @@ public class Harvest extends DSpaceRunnable<HarvestScriptConfiguration> {
|
||||
|
||||
/**
|
||||
* Purges a collection of all harvest-related data and settings. All items in the collection will be deleted.
|
||||
* @param collectionID
|
||||
*
|
||||
* @param collectionID
|
||||
* @param email
|
||||
*/
|
||||
private void purgeCollection(Context context, String collectionID) {
|
||||
handler.logInfo(
|
||||
"Purging collection of all items and resetting last_harvested and harvest_message: " + collectionID);
|
||||
Collection collection = resolveCollection(context, collectionID);
|
||||
private void purgeCollection(String collectionID, String email) {
|
||||
System.out.println(
|
||||
"Purging collection of all items and resetting last_harvested and harvest_message: " + collectionID);
|
||||
Collection collection = resolveCollection(collectionID);
|
||||
|
||||
try {
|
||||
EPerson eperson = ePersonService.findByEmail(context, email);
|
||||
context.setCurrentUser(eperson);
|
||||
context.turnOffAuthorisationSystem();
|
||||
|
||||
ItemService itemService = ContentServiceFactory.getInstance().getItemService();
|
||||
@@ -335,7 +344,7 @@ public class Harvest extends DSpaceRunnable<HarvestScriptConfiguration> {
|
||||
while (it.hasNext()) {
|
||||
i++;
|
||||
Item item = it.next();
|
||||
handler.logInfo("Deleting: " + item.getHandle());
|
||||
System.out.println("Deleting: " + item.getHandle());
|
||||
collectionService.removeItem(context, collection, item);
|
||||
context.uncacheEntity(item);// Dispatch events every 50 items
|
||||
if (i % 50 == 0) {
|
||||
@@ -355,8 +364,9 @@ public class Harvest extends DSpaceRunnable<HarvestScriptConfiguration> {
|
||||
context.restoreAuthSystemState();
|
||||
context.dispatchEvents();
|
||||
} catch (Exception e) {
|
||||
handler.logError("Changes could not be committed");
|
||||
handler.handleException(e);
|
||||
System.out.println("Changes could not be committed");
|
||||
e.printStackTrace();
|
||||
System.exit(1);
|
||||
} finally {
|
||||
context.restoreAuthSystemState();
|
||||
}
|
||||
@@ -366,42 +376,50 @@ public class Harvest extends DSpaceRunnable<HarvestScriptConfiguration> {
|
||||
/**
|
||||
* Run a single harvest cycle on the specified collection under the authorization of the supplied EPerson
|
||||
*/
|
||||
private void runHarvest(Context context, String collectionID) {
|
||||
handler.logInfo("Running: a harvest cycle on " + collectionID);
|
||||
private void runHarvest(String collectionID, String email) {
|
||||
System.out.println("Running: a harvest cycle on " + collectionID);
|
||||
|
||||
handler.logInfo("Initializing the harvester... ");
|
||||
System.out.print("Initializing the harvester... ");
|
||||
OAIHarvester harvester = null;
|
||||
try {
|
||||
Collection collection = resolveCollection(context, collectionID);
|
||||
Collection collection = resolveCollection(collectionID);
|
||||
HarvestedCollection hc = harvestedCollectionService.find(context, collection);
|
||||
harvester = new OAIHarvester(context, collection, hc);
|
||||
handler.logInfo("Initialized the harvester successfully");
|
||||
System.out.println("success. ");
|
||||
} catch (HarvestingException hex) {
|
||||
handler.logError("Initializing the harvester failed.");
|
||||
System.out.print("failed. ");
|
||||
System.out.println(hex.getMessage());
|
||||
throw new IllegalStateException("Unable to harvest", hex);
|
||||
} catch (SQLException se) {
|
||||
handler.logError("Initializing the harvester failed.");
|
||||
System.out.print("failed. ");
|
||||
System.out.println(se.getMessage());
|
||||
throw new IllegalStateException("Unable to access database", se);
|
||||
}
|
||||
|
||||
try {
|
||||
// Harvest will not work for an anonymous user
|
||||
handler.logInfo("Harvest started... ");
|
||||
EPerson eperson = ePersonService.findByEmail(context, email);
|
||||
System.out.println("Harvest started... ");
|
||||
context.setCurrentUser(eperson);
|
||||
harvester.runHarvest();
|
||||
context.complete();
|
||||
} catch (SQLException | AuthorizeException | IOException e) {
|
||||
} catch (SQLException e) {
|
||||
throw new IllegalStateException("Failed to run harvester", e);
|
||||
} catch (AuthorizeException e) {
|
||||
throw new IllegalStateException("Failed to run harvester", e);
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException("Failed to run harvester", e);
|
||||
}
|
||||
|
||||
handler.logInfo("Harvest complete. ");
|
||||
System.out.println("Harvest complete. ");
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets harvest_status and harvest_start_time flags for all collections that have a row in the
|
||||
* harvested_collections table
|
||||
*/
|
||||
private void resetHarvesting(Context context) {
|
||||
handler.logInfo("Resetting harvest status flag on all collections... ");
|
||||
private static void resetHarvesting() {
|
||||
System.out.print("Resetting harvest status flag on all collections... ");
|
||||
|
||||
try {
|
||||
List<HarvestedCollection> harvestedCollections = harvestedCollectionService.findAll(context);
|
||||
@@ -411,21 +429,21 @@ public class Harvest extends DSpaceRunnable<HarvestScriptConfiguration> {
|
||||
harvestedCollection.setHarvestStatus(HarvestedCollection.STATUS_READY);
|
||||
harvestedCollectionService.update(context, harvestedCollection);
|
||||
}
|
||||
handler.logInfo("Reset harvest status flag successfully");
|
||||
System.out.println("success. ");
|
||||
} catch (Exception ex) {
|
||||
handler.logError("Resetting harvest status flag failed");
|
||||
handler.handleException(ex);
|
||||
System.out.println("failed. ");
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts up the harvest scheduler. Terminating this process will stop the scheduler.
|
||||
*/
|
||||
private void startHarvester() {
|
||||
private static void startHarvester() {
|
||||
try {
|
||||
handler.logInfo("Starting harvest loop... ");
|
||||
System.out.print("Starting harvest loop... ");
|
||||
HarvestServiceFactory.getInstance().getHarvestSchedulingService().startNewScheduler();
|
||||
handler.logInfo("running. ");
|
||||
System.out.println("running. ");
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
@@ -438,31 +456,29 @@ public class Harvest extends DSpaceRunnable<HarvestScriptConfiguration> {
|
||||
* @param set name of an item set.
|
||||
* @param metadataFormat local prefix name, or null for "dc".
|
||||
*/
|
||||
private void pingResponder(String server, String set, String metadataFormat) {
|
||||
private static void pingResponder(String server, String set, String metadataFormat) {
|
||||
List<String> errors;
|
||||
|
||||
handler.logInfo("Testing basic PMH access: ");
|
||||
errors = harvestedCollectionService.verifyOAIharvester(server, set,
|
||||
(null != metadataFormat) ? metadataFormat : "dc", false);
|
||||
System.out.print("Testing basic PMH access: ");
|
||||
errors = OAIHarvester.verifyOAIharvester(server, set,
|
||||
(null != metadataFormat) ? metadataFormat : "dc", false);
|
||||
if (errors.isEmpty()) {
|
||||
handler.logInfo("OK");
|
||||
System.out.println("OK");
|
||||
} else {
|
||||
for (String error : errors) {
|
||||
handler.logError(error);
|
||||
System.err.println(error);
|
||||
}
|
||||
}
|
||||
|
||||
handler.logInfo("Testing ORE support: ");
|
||||
errors = harvestedCollectionService.verifyOAIharvester(server, set,
|
||||
(null != metadataFormat) ? metadataFormat : "dc", true);
|
||||
System.out.print("Testing ORE support: ");
|
||||
errors = OAIHarvester.verifyOAIharvester(server, set,
|
||||
(null != metadataFormat) ? metadataFormat : "dc", true);
|
||||
if (errors.isEmpty()) {
|
||||
handler.logInfo("OK");
|
||||
System.out.println("OK");
|
||||
} else {
|
||||
for (String error : errors) {
|
||||
handler.logError(error);
|
||||
System.err.println(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@@ -1,45 +0,0 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
package org.dspace.app.harvest;
|
||||
|
||||
import java.sql.SQLException;
|
||||
|
||||
import org.apache.commons.cli.ParseException;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.eperson.EPerson;
|
||||
|
||||
public class HarvestCli extends Harvest {
|
||||
|
||||
/**
|
||||
* This is the overridden instance of the {@link Harvest#assignCurrentUserInContext()} method in the parent class
|
||||
* {@link Harvest}.
|
||||
* 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
|
||||
*/
|
||||
@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 {
|
||||
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);
|
||||
}
|
||||
this.context.setCurrentUser(ePerson);
|
||||
} catch (SQLException e) {
|
||||
throw new IllegalArgumentException("SQLException trying to find user with email: " + ePersonEmail);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -1,22 +0,0 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
package org.dspace.app.harvest;
|
||||
|
||||
import org.apache.commons.cli.Options;
|
||||
|
||||
|
||||
public class HarvestCliScriptConfiguration extends HarvestScriptConfiguration {
|
||||
|
||||
public Options getOptions() {
|
||||
Options options = super.getOptions();
|
||||
options.addOption("e", "eperson", true,
|
||||
"eperson");
|
||||
|
||||
return options;
|
||||
}
|
||||
}
|
@@ -1,56 +0,0 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
package org.dspace.app.harvest;
|
||||
|
||||
import org.apache.commons.cli.Options;
|
||||
import org.dspace.scripts.configuration.ScriptConfiguration;
|
||||
|
||||
|
||||
public class HarvestScriptConfiguration<T extends Harvest> extends ScriptConfiguration<T> {
|
||||
|
||||
private Class<T> dspaceRunnableClass;
|
||||
|
||||
@Override
|
||||
public Class<T> getDspaceRunnableClass() {
|
||||
return dspaceRunnableClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDspaceRunnableClass(Class<T> dspaceRunnableClass) {
|
||||
this.dspaceRunnableClass = dspaceRunnableClass;
|
||||
}
|
||||
|
||||
|
||||
public Options getOptions() {
|
||||
Options options = new Options();
|
||||
options.addOption("p", "purge", false, "delete all items in the collection");
|
||||
options.addOption("r", "run", false, "run the standard harvest procedure");
|
||||
options.addOption("g", "ping", false, "test the OAI server and set");
|
||||
options.addOption("s", "setup", false, "Set the collection up for harvesting");
|
||||
options.addOption("S", "start", false, "start the harvest loop");
|
||||
options.addOption("R", "reset", false, "reset harvest status on all collections");
|
||||
options.addOption("P", "purgeCollections", false, "purge all harvestable collections");
|
||||
options.addOption("o", "reimport", false, "reimport all items in the collection, " +
|
||||
"this is equivalent to -p -r, purging all items in a collection and reimporting them");
|
||||
options.addOption("c", "collection", true,
|
||||
"harvesting collection (handle or id)");
|
||||
options.addOption("t", "type", true,
|
||||
"type of harvesting (0 for none)");
|
||||
options.addOption("a", "address", true,
|
||||
"address of the OAI-PMH server");
|
||||
options.addOption("i", "oai_set_id", true,
|
||||
"id of the PMH set representing the harvested collection");
|
||||
options.addOption("m", "metadata_format", true,
|
||||
"the name of the desired metadata format for harvesting, resolved to namespace and " +
|
||||
"crosswalk in dspace.cfg");
|
||||
|
||||
options.addOption("h", "help", false, "help");
|
||||
|
||||
return options;
|
||||
}
|
||||
}
|
@@ -1,264 +0,0 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
package org.dspace.app.itemexport;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Path;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.apache.commons.cli.ParseException;
|
||||
import org.apache.commons.io.file.PathUtils;
|
||||
import org.dspace.app.itemexport.factory.ItemExportServiceFactory;
|
||||
import org.dspace.app.itemexport.service.ItemExportService;
|
||||
import org.dspace.content.Collection;
|
||||
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.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;
|
||||
|
||||
/**
|
||||
* Item exporter to create simple AIPs for DSpace content. Currently exports
|
||||
* individual items, or entire collections. For instructions on use, see
|
||||
* printUsage() method.
|
||||
* <P>
|
||||
* ItemExport creates the simple AIP package that the importer also uses. It
|
||||
* consists of:
|
||||
* <P>
|
||||
* /exportdir/42/ (one directory per item) / dublin_core.xml - qualified dublin
|
||||
* core in RDF schema / contents - text file, listing one file per line / file1
|
||||
* - files contained in the item / file2 / ...
|
||||
* <P>
|
||||
* issues -doesn't handle special characters in metadata (needs to turn {@code &'s} into
|
||||
* {@code &}, etc.)
|
||||
* <P>
|
||||
* Modified by David Little, UCSD Libraries 12/21/04 to allow the registration
|
||||
* of files (bitstreams) into DSpace.
|
||||
*
|
||||
* @author David Little
|
||||
* @author Jay Paz
|
||||
*/
|
||||
public class ItemExport extends DSpaceRunnable<ItemExportScriptConfiguration> {
|
||||
|
||||
public static final String TEMP_DIR = "exportSAF";
|
||||
public static final String ZIP_NAME = "exportSAFZip";
|
||||
public static final String ZIP_FILENAME = "saf-export";
|
||||
public static final String ZIP_EXT = "zip";
|
||||
|
||||
protected String typeString = null;
|
||||
protected String destDirName = null;
|
||||
protected String idString = null;
|
||||
protected int seqStart = -1;
|
||||
protected int type = -1;
|
||||
protected Item item = null;
|
||||
protected Collection collection = null;
|
||||
protected boolean migrate = false;
|
||||
protected boolean zip = false;
|
||||
protected String zipFileName = "";
|
||||
protected boolean excludeBitstreams = false;
|
||||
protected boolean help = false;
|
||||
|
||||
protected static HandleService handleService = HandleServiceFactory.getInstance().getHandleService();
|
||||
protected static ItemService itemService = ContentServiceFactory.getInstance().getItemService();
|
||||
protected static CollectionService collectionService = ContentServiceFactory.getInstance().getCollectionService();
|
||||
protected static final EPersonService epersonService =
|
||||
EPersonServiceFactory.getInstance().getEPersonService();
|
||||
|
||||
@Override
|
||||
public ItemExportScriptConfiguration getScriptConfiguration() {
|
||||
return new DSpace().getServiceManager()
|
||||
.getServiceByName("export", ItemExportScriptConfiguration.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setup() throws ParseException {
|
||||
help = commandLine.hasOption('h');
|
||||
|
||||
if (commandLine.hasOption('t')) { // type
|
||||
typeString = commandLine.getOptionValue('t');
|
||||
|
||||
if ("ITEM".equals(typeString)) {
|
||||
type = Constants.ITEM;
|
||||
} else if ("COLLECTION".equals(typeString)) {
|
||||
type = Constants.COLLECTION;
|
||||
}
|
||||
}
|
||||
|
||||
if (commandLine.hasOption('i')) { // id
|
||||
idString = commandLine.getOptionValue('i');
|
||||
}
|
||||
|
||||
setNumber();
|
||||
|
||||
if (commandLine.hasOption('m')) { // number
|
||||
migrate = true;
|
||||
}
|
||||
|
||||
if (commandLine.hasOption('x')) {
|
||||
excludeBitstreams = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void internalRun() throws Exception {
|
||||
if (help) {
|
||||
printHelp();
|
||||
return;
|
||||
}
|
||||
|
||||
validate();
|
||||
|
||||
Context context = new Context();
|
||||
context.turnOffAuthorisationSystem();
|
||||
|
||||
if (type == Constants.ITEM) {
|
||||
// first, is myIDString a handle?
|
||||
if (idString.indexOf('/') != -1) {
|
||||
item = (Item) handleService.resolveToObject(context, idString);
|
||||
|
||||
if ((item == null) || (item.getType() != Constants.ITEM)) {
|
||||
item = null;
|
||||
}
|
||||
} else {
|
||||
item = itemService.find(context, UUID.fromString(idString));
|
||||
}
|
||||
|
||||
if (item == null) {
|
||||
handler.logError("The item cannot be found: " + idString + " (run with -h flag for details)");
|
||||
throw new UnsupportedOperationException("The item cannot be found: " + idString);
|
||||
}
|
||||
} else {
|
||||
if (idString.indexOf('/') != -1) {
|
||||
// has a / must be a handle
|
||||
collection = (Collection) handleService.resolveToObject(context,
|
||||
idString);
|
||||
|
||||
// ensure it's a collection
|
||||
if ((collection == null)
|
||||
|| (collection.getType() != Constants.COLLECTION)) {
|
||||
collection = null;
|
||||
}
|
||||
} else {
|
||||
collection = collectionService.find(context, UUID.fromString(idString));
|
||||
}
|
||||
|
||||
if (collection == null) {
|
||||
handler.logError("The collection cannot be found: " + idString + " (run with -h flag for details)");
|
||||
throw new UnsupportedOperationException("The collection cannot be found: " + idString);
|
||||
}
|
||||
}
|
||||
|
||||
ItemExportService itemExportService = ItemExportServiceFactory.getInstance()
|
||||
.getItemExportService();
|
||||
try {
|
||||
itemExportService.setHandler(handler);
|
||||
process(context, itemExportService);
|
||||
context.complete();
|
||||
} catch (Exception e) {
|
||||
context.abort();
|
||||
throw new Exception(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the options
|
||||
*/
|
||||
protected void validate() {
|
||||
if (type == -1) {
|
||||
handler.logError("The type must be either COLLECTION or ITEM (run with -h flag for details)");
|
||||
throw new UnsupportedOperationException("The type must be either COLLECTION or ITEM");
|
||||
}
|
||||
|
||||
if (idString == null) {
|
||||
handler.logError("The ID must be set to either a database ID or a handle (run with -h flag for details)");
|
||||
throw new UnsupportedOperationException("The ID must be set to either a database ID or a handle");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the export
|
||||
* @param context
|
||||
* @throws Exception
|
||||
*/
|
||||
protected void process(Context context, ItemExportService itemExportService) throws Exception {
|
||||
setEPerson(context);
|
||||
setDestDirName(context, itemExportService);
|
||||
setZip(context);
|
||||
|
||||
Iterator<Item> items;
|
||||
if (item != null) {
|
||||
List<Item> myItems = new ArrayList<>();
|
||||
myItems.add(item);
|
||||
items = myItems.iterator();
|
||||
} else {
|
||||
handler.logInfo("Exporting from collection: " + idString);
|
||||
items = itemService.findByCollection(context, collection);
|
||||
}
|
||||
itemExportService.exportAsZip(context, items, destDirName, zipFileName,
|
||||
seqStart, migrate, excludeBitstreams);
|
||||
|
||||
File zip = new File(destDirName + System.getProperty("file.separator") + zipFileName);
|
||||
try (InputStream is = new FileInputStream(zip)) {
|
||||
// write input stream on handler
|
||||
handler.writeFilestream(context, ZIP_FILENAME + "." + ZIP_EXT, is, ZIP_NAME);
|
||||
} finally {
|
||||
PathUtils.deleteDirectory(Path.of(destDirName));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the destination directory option
|
||||
*/
|
||||
protected void setDestDirName(Context context, ItemExportService itemExportService) throws Exception {
|
||||
destDirName = itemExportService.getExportWorkDirectory() + File.separator + TEMP_DIR;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the zip option
|
||||
*/
|
||||
protected void setZip(Context context) {
|
||||
zip = true;
|
||||
zipFileName = ZIP_FILENAME + "-" + context.getCurrentUser().getID() + "." + ZIP_EXT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the number option
|
||||
*/
|
||||
protected void setNumber() {
|
||||
seqStart = 1;
|
||||
if (commandLine.hasOption('n')) { // number
|
||||
seqStart = Integer.parseInt(commandLine.getOptionValue('n'));
|
||||
}
|
||||
}
|
||||
|
||||
private void setEPerson(Context context) throws SQLException {
|
||||
EPerson myEPerson = epersonService.find(context, this.getEpersonIdentifier());
|
||||
|
||||
// check eperson
|
||||
if (myEPerson == null) {
|
||||
handler.logError("EPerson cannot be found: " + this.getEpersonIdentifier());
|
||||
throw new UnsupportedOperationException("EPerson cannot be found: " + this.getEpersonIdentifier());
|
||||
}
|
||||
|
||||
context.setCurrentUser(myEPerson);
|
||||
}
|
||||
}
|
@@ -1,96 +0,0 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
package org.dspace.app.itemexport;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.dspace.app.itemexport.service.ItemExportService;
|
||||
import org.dspace.content.Item;
|
||||
import org.dspace.core.Context;
|
||||
|
||||
/**
|
||||
* CLI variant for the {@link ItemExport} class.
|
||||
* This was done to specify the specific behaviors for the CLI.
|
||||
*
|
||||
* @author Francesco Pio Scognamiglio (francescopio.scognamiglio at 4science.com)
|
||||
*/
|
||||
public class ItemExportCLI extends ItemExport {
|
||||
|
||||
@Override
|
||||
protected void validate() {
|
||||
super.validate();
|
||||
|
||||
setDestDirName();
|
||||
|
||||
if (destDirName == null) {
|
||||
handler.logError("The destination directory must be set (run with -h flag for details)");
|
||||
throw new UnsupportedOperationException("The destination directory must be set");
|
||||
}
|
||||
|
||||
if (seqStart == -1) {
|
||||
handler.logError("The sequence start number must be set (run with -h flag for details)");
|
||||
throw new UnsupportedOperationException("The sequence start number must be set");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void process(Context context, ItemExportService itemExportService) throws Exception {
|
||||
setZip(context);
|
||||
|
||||
if (zip) {
|
||||
Iterator<Item> items;
|
||||
if (item != null) {
|
||||
List<Item> myItems = new ArrayList<>();
|
||||
myItems.add(item);
|
||||
items = myItems.iterator();
|
||||
} else {
|
||||
handler.logInfo("Exporting from collection: " + idString);
|
||||
items = itemService.findByCollection(context, collection);
|
||||
}
|
||||
itemExportService.exportAsZip(context, items, destDirName, zipFileName,
|
||||
seqStart, migrate, excludeBitstreams);
|
||||
} else {
|
||||
if (item != null) {
|
||||
// it's only a single item
|
||||
itemExportService
|
||||
.exportItem(context, Collections.singletonList(item).iterator(), destDirName,
|
||||
seqStart, migrate, excludeBitstreams);
|
||||
} else {
|
||||
handler.logInfo("Exporting from collection: " + idString);
|
||||
|
||||
// it's a collection, so do a bunch of items
|
||||
Iterator<Item> i = itemService.findByCollection(context, collection);
|
||||
itemExportService.exportItem(context, i, destDirName, seqStart, migrate, excludeBitstreams);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void setDestDirName() {
|
||||
if (commandLine.hasOption('d')) { // dest
|
||||
destDirName = commandLine.getOptionValue('d');
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setZip(Context context) {
|
||||
if (commandLine.hasOption('z')) {
|
||||
zip = true;
|
||||
zipFileName = commandLine.getOptionValue('z');
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setNumber() {
|
||||
if (commandLine.hasOption('n')) { // number
|
||||
seqStart = Integer.parseInt(commandLine.getOptionValue('n'));
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,56 +0,0 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
package org.dspace.app.itemexport;
|
||||
|
||||
import org.apache.commons.cli.Option;
|
||||
import org.apache.commons.cli.Options;
|
||||
import org.dspace.scripts.configuration.ScriptConfiguration;
|
||||
|
||||
/**
|
||||
* The {@link ScriptConfiguration} for the {@link ItemExportCLI} script
|
||||
*
|
||||
* @author Francesco Pio Scognamiglio (francescopio.scognamiglio at 4science.com)
|
||||
*/
|
||||
public class ItemExportCLIScriptConfiguration extends ItemExportScriptConfiguration<ItemExportCLI> {
|
||||
|
||||
@Override
|
||||
public Options getOptions() {
|
||||
Options options = new Options();
|
||||
|
||||
options.addOption(Option.builder("t").longOpt("type")
|
||||
.desc("type: COLLECTION or ITEM")
|
||||
.hasArg().required().build());
|
||||
options.addOption(Option.builder("i").longOpt("id")
|
||||
.desc("ID or handle of thing to export")
|
||||
.hasArg().required().build());
|
||||
options.addOption(Option.builder("d").longOpt("dest")
|
||||
.desc("destination where you want items to go")
|
||||
.hasArg().required().build());
|
||||
options.addOption(Option.builder("n").longOpt("number")
|
||||
.desc("sequence number to begin exporting items with")
|
||||
.hasArg().required().build());
|
||||
options.addOption(Option.builder("z").longOpt("zip")
|
||||
.desc("export as zip file (specify filename e.g. export.zip)")
|
||||
.hasArg().required(false).build());
|
||||
options.addOption(Option.builder("m").longOpt("migrate")
|
||||
.desc("export for migration (remove handle and metadata that will be re-created in new system)")
|
||||
.hasArg(false).required(false).build());
|
||||
|
||||
// as pointed out by Peter Dietz this provides similar functionality to export metadata
|
||||
// but it is needed since it directly exports to Simple Archive Format (SAF)
|
||||
options.addOption(Option.builder("x").longOpt("exclude-bitstreams")
|
||||
.desc("do not export bitstreams")
|
||||
.hasArg(false).required(false).build());
|
||||
|
||||
options.addOption(Option.builder("h").longOpt("help")
|
||||
.desc("help")
|
||||
.hasArg(false).required(false).build());
|
||||
|
||||
return options;
|
||||
}
|
||||
}
|
@@ -0,0 +1,246 @@
|
||||
/**
|
||||
* 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.itemexport;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
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.dspace.app.itemexport.factory.ItemExportServiceFactory;
|
||||
import org.dspace.app.itemexport.service.ItemExportService;
|
||||
import org.dspace.content.Collection;
|
||||
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.handle.factory.HandleServiceFactory;
|
||||
import org.dspace.handle.service.HandleService;
|
||||
|
||||
/**
|
||||
* Item exporter to create simple AIPs for DSpace content. Currently exports
|
||||
* individual items, or entire collections. For instructions on use, see
|
||||
* printUsage() method.
|
||||
* <P>
|
||||
* ItemExport creates the simple AIP package that the importer also uses. It
|
||||
* consists of:
|
||||
* <P>
|
||||
* /exportdir/42/ (one directory per item) / dublin_core.xml - qualified dublin
|
||||
* core in RDF schema / contents - text file, listing one file per line / file1
|
||||
* - files contained in the item / file2 / ...
|
||||
* <P>
|
||||
* issues -doesn't handle special characters in metadata (needs to turn {@code &'s} into
|
||||
* {@code &}, etc.)
|
||||
* <P>
|
||||
* Modified by David Little, UCSD Libraries 12/21/04 to allow the registration
|
||||
* of files (bitstreams) into DSpace.
|
||||
*
|
||||
* @author David Little
|
||||
* @author Jay Paz
|
||||
*/
|
||||
public class ItemExportCLITool {
|
||||
|
||||
protected static ItemExportService itemExportService = ItemExportServiceFactory.getInstance()
|
||||
.getItemExportService();
|
||||
protected static HandleService handleService = HandleServiceFactory.getInstance().getHandleService();
|
||||
protected static ItemService itemService = ContentServiceFactory.getInstance().getItemService();
|
||||
protected static CollectionService collectionService = ContentServiceFactory.getInstance().getCollectionService();
|
||||
|
||||
/**
|
||||
* Default constructor
|
||||
*/
|
||||
private ItemExportCLITool() { }
|
||||
|
||||
/*
|
||||
*
|
||||
*/
|
||||
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("t", "type", true, "type: COLLECTION or ITEM");
|
||||
options.addOption("i", "id", true, "ID or handle of thing to export");
|
||||
options.addOption("d", "dest", true,
|
||||
"destination where you want items to go");
|
||||
options.addOption("m", "migrate", false,
|
||||
"export for migration (remove handle and metadata that will be re-created in new system)");
|
||||
options.addOption("n", "number", true,
|
||||
"sequence number to begin exporting items with");
|
||||
options.addOption("z", "zip", true, "export as zip file (specify filename e.g. export.zip)");
|
||||
options.addOption("h", "help", false, "help");
|
||||
|
||||
// as pointed out by Peter Dietz this provides similar functionality to export metadata
|
||||
// but it is needed since it directly exports to Simple Archive Format (SAF)
|
||||
options.addOption("x", "exclude-bitstreams", false, "do not export bitstreams");
|
||||
|
||||
CommandLine line = parser.parse(options, argv);
|
||||
|
||||
String typeString = null;
|
||||
String destDirName = null;
|
||||
String myIDString = null;
|
||||
int seqStart = -1;
|
||||
int myType = -1;
|
||||
|
||||
Item myItem = null;
|
||||
Collection mycollection = null;
|
||||
|
||||
if (line.hasOption('h')) {
|
||||
HelpFormatter myhelp = new HelpFormatter();
|
||||
myhelp.printHelp("ItemExport\n", options);
|
||||
System.out
|
||||
.println("\nfull collection: ItemExport -t COLLECTION -i ID -d dest -n number");
|
||||
System.out
|
||||
.println("singleitem: ItemExport -t ITEM -i ID -d dest -n number");
|
||||
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
if (line.hasOption('t')) { // type
|
||||
typeString = line.getOptionValue('t');
|
||||
|
||||
if ("ITEM".equals(typeString)) {
|
||||
myType = Constants.ITEM;
|
||||
} else if ("COLLECTION".equals(typeString)) {
|
||||
myType = Constants.COLLECTION;
|
||||
}
|
||||
}
|
||||
|
||||
if (line.hasOption('i')) { // id
|
||||
myIDString = line.getOptionValue('i');
|
||||
}
|
||||
|
||||
if (line.hasOption('d')) { // dest
|
||||
destDirName = line.getOptionValue('d');
|
||||
}
|
||||
|
||||
if (line.hasOption('n')) { // number
|
||||
seqStart = Integer.parseInt(line.getOptionValue('n'));
|
||||
}
|
||||
|
||||
boolean migrate = false;
|
||||
if (line.hasOption('m')) { // number
|
||||
migrate = true;
|
||||
}
|
||||
|
||||
boolean zip = false;
|
||||
String zipFileName = "";
|
||||
if (line.hasOption('z')) {
|
||||
zip = true;
|
||||
zipFileName = line.getOptionValue('z');
|
||||
}
|
||||
|
||||
boolean excludeBitstreams = false;
|
||||
if (line.hasOption('x')) {
|
||||
excludeBitstreams = true;
|
||||
}
|
||||
|
||||
// now validate the args
|
||||
if (myType == -1) {
|
||||
System.out
|
||||
.println("type must be either COLLECTION or ITEM (-h for help)");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
if (destDirName == null) {
|
||||
System.out
|
||||
.println("destination directory must be set (-h for help)");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
if (seqStart == -1) {
|
||||
System.out
|
||||
.println("sequence start number must be set (-h for help)");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
if (myIDString == null) {
|
||||
System.out
|
||||
.println("ID must be set to either a database ID or a handle (-h for help)");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
Context c = new Context(Context.Mode.READ_ONLY);
|
||||
c.turnOffAuthorisationSystem();
|
||||
|
||||
if (myType == Constants.ITEM) {
|
||||
// first, is myIDString a handle?
|
||||
if (myIDString.indexOf('/') != -1) {
|
||||
myItem = (Item) handleService.resolveToObject(c, myIDString);
|
||||
|
||||
if ((myItem == null) || (myItem.getType() != Constants.ITEM)) {
|
||||
myItem = null;
|
||||
}
|
||||
} else {
|
||||
myItem = itemService.find(c, UUID.fromString(myIDString));
|
||||
}
|
||||
|
||||
if (myItem == null) {
|
||||
System.out
|
||||
.println("Error, item cannot be found: " + myIDString);
|
||||
}
|
||||
} else {
|
||||
if (myIDString.indexOf('/') != -1) {
|
||||
// has a / must be a handle
|
||||
mycollection = (Collection) handleService.resolveToObject(c,
|
||||
myIDString);
|
||||
|
||||
// ensure it's a collection
|
||||
if ((mycollection == null)
|
||||
|| (mycollection.getType() != Constants.COLLECTION)) {
|
||||
mycollection = null;
|
||||
}
|
||||
} else if (myIDString != null) {
|
||||
mycollection = collectionService.find(c, UUID.fromString(myIDString));
|
||||
}
|
||||
|
||||
if (mycollection == null) {
|
||||
System.out.println("Error, collection cannot be found: "
|
||||
+ myIDString);
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (zip) {
|
||||
Iterator<Item> items;
|
||||
if (myItem != null) {
|
||||
List<Item> myItems = new ArrayList<>();
|
||||
myItems.add(myItem);
|
||||
items = myItems.iterator();
|
||||
} else {
|
||||
System.out.println("Exporting from collection: " + myIDString);
|
||||
items = itemService.findByCollection(c, mycollection);
|
||||
}
|
||||
itemExportService.exportAsZip(c, items, destDirName, zipFileName, seqStart, migrate, excludeBitstreams);
|
||||
} else {
|
||||
if (myItem != null) {
|
||||
// it's only a single item
|
||||
itemExportService
|
||||
.exportItem(c, Collections.singletonList(myItem).iterator(), destDirName, seqStart, migrate,
|
||||
excludeBitstreams);
|
||||
} else {
|
||||
System.out.println("Exporting from collection: " + myIDString);
|
||||
|
||||
// it's a collection, so do a bunch of items
|
||||
Iterator<Item> i = itemService.findByCollection(c, mycollection);
|
||||
itemExportService.exportItem(c, i, destDirName, seqStart, migrate, excludeBitstreams);
|
||||
}
|
||||
}
|
||||
|
||||
c.complete();
|
||||
}
|
||||
}
|
@@ -1,62 +0,0 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
package org.dspace.app.itemexport;
|
||||
|
||||
import org.apache.commons.cli.Option;
|
||||
import org.apache.commons.cli.Options;
|
||||
import org.dspace.scripts.configuration.ScriptConfiguration;
|
||||
|
||||
/**
|
||||
* The {@link ScriptConfiguration} for the {@link ItemExport} script
|
||||
*
|
||||
* @author Francesco Pio Scognamiglio (francescopio.scognamiglio at 4science.com)
|
||||
*/
|
||||
public class ItemExportScriptConfiguration<T extends ItemExport> extends ScriptConfiguration<T> {
|
||||
|
||||
private Class<T> dspaceRunnableClass;
|
||||
|
||||
@Override
|
||||
public Class<T> getDspaceRunnableClass() {
|
||||
return dspaceRunnableClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDspaceRunnableClass(Class<T> dspaceRunnableClass) {
|
||||
this.dspaceRunnableClass = dspaceRunnableClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Options getOptions() {
|
||||
Options options = new Options();
|
||||
|
||||
options.addOption(Option.builder("t").longOpt("type")
|
||||
.desc("type: COLLECTION or ITEM")
|
||||
.hasArg().required().build());
|
||||
options.addOption(Option.builder("i").longOpt("id")
|
||||
.desc("ID or handle of thing to export")
|
||||
.hasArg().required().build());
|
||||
options.addOption(Option.builder("n").longOpt("number")
|
||||
.desc("sequence number to begin exporting items with")
|
||||
.hasArg().required(false).build());
|
||||
options.addOption(Option.builder("m").longOpt("migrate")
|
||||
.desc("export for migration (remove handle and metadata that will be re-created in new system)")
|
||||
.hasArg(false).required(false).build());
|
||||
|
||||
// as pointed out by Peter Dietz this provides similar functionality to export metadata
|
||||
// but it is needed since it directly exports to Simple Archive Format (SAF)
|
||||
options.addOption(Option.builder("x").longOpt("exclude-bitstreams")
|
||||
.desc("do not export bitstreams")
|
||||
.hasArg(false).required(false).build());
|
||||
|
||||
options.addOption(Option.builder("h").longOpt("help")
|
||||
.desc("help")
|
||||
.hasArg(false).required(false).build());
|
||||
|
||||
return options;
|
||||
}
|
||||
}
|
@@ -16,7 +16,6 @@ import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.sql.SQLException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
@@ -33,8 +32,8 @@ import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
import javax.mail.MessagingException;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.dspace.app.itemexport.service.ItemExportService;
|
||||
import org.dspace.content.Bitstream;
|
||||
import org.dspace.content.Bundle;
|
||||
@@ -43,43 +42,38 @@ import org.dspace.content.Community;
|
||||
import org.dspace.content.DSpaceObject;
|
||||
import org.dspace.content.Item;
|
||||
import org.dspace.content.MetadataField;
|
||||
import org.dspace.content.MetadataSchemaEnum;
|
||||
import org.dspace.content.MetadataSchema;
|
||||
import org.dspace.content.MetadataValue;
|
||||
import org.dspace.content.service.BitstreamService;
|
||||
import org.dspace.content.service.CommunityService;
|
||||
import org.dspace.content.service.ItemService;
|
||||
import org.dspace.core.ConfigurationManager;
|
||||
import org.dspace.core.Constants;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.core.Email;
|
||||
import org.dspace.core.I18nUtil;
|
||||
import org.dspace.core.LogHelper;
|
||||
import org.dspace.core.LogManager;
|
||||
import org.dspace.core.Utils;
|
||||
import org.dspace.eperson.EPerson;
|
||||
import org.dspace.eperson.service.EPersonService;
|
||||
import org.dspace.handle.service.HandleService;
|
||||
import org.dspace.scripts.handler.DSpaceRunnableHandler;
|
||||
import org.dspace.services.ConfigurationService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
/**
|
||||
* Item exporter to create simple AIPs for DSpace content. Currently exports
|
||||
* individual items, or entire collections. For instructions on use, see
|
||||
* printUsage() method.
|
||||
* <p>
|
||||
* <P>
|
||||
* ItemExport creates the simple AIP package that the importer also uses. It
|
||||
* consists of:
|
||||
* <pre>{@code
|
||||
* /exportdir/42/ (one directory per item)
|
||||
* / dublin_core.xml - qualified dublin core in RDF schema
|
||||
* / contents - text file, listing one file per line
|
||||
* / file1 - files contained in the item
|
||||
* / file2
|
||||
* / ...
|
||||
* }</pre>
|
||||
* <p>
|
||||
* <P>
|
||||
* /exportdir/42/ (one directory per item) / dublin_core.xml - qualified dublin
|
||||
* core in RDF schema / contents - text file, listing one file per line / file1
|
||||
* - files contained in the item / file2 / ...
|
||||
* <P>
|
||||
* issues -doesn't handle special characters in metadata (needs to turn {@code &'s} into
|
||||
* {@code &}, etc.)
|
||||
* <p>
|
||||
* <P>
|
||||
* Modified by David Little, UCSD Libraries 12/21/04 to allow the registration
|
||||
* of files (bitstreams) into DSpace.
|
||||
*
|
||||
@@ -99,15 +93,12 @@ public class ItemExportServiceImpl implements ItemExportService {
|
||||
protected ItemService itemService;
|
||||
@Autowired(required = true)
|
||||
protected HandleService handleService;
|
||||
@Autowired(required = true)
|
||||
protected ConfigurationService configurationService;
|
||||
|
||||
|
||||
/**
|
||||
* log4j logger
|
||||
*/
|
||||
private final Logger log = org.apache.logging.log4j.LogManager.getLogger();
|
||||
|
||||
private DSpaceRunnableHandler handler;
|
||||
private Logger log = Logger.getLogger(ItemExportServiceImpl.class);
|
||||
|
||||
protected ItemExportServiceImpl() {
|
||||
|
||||
@@ -132,11 +123,11 @@ public class ItemExportServiceImpl implements ItemExportService {
|
||||
}
|
||||
}
|
||||
|
||||
logInfo("Beginning export");
|
||||
System.out.println("Beginning export");
|
||||
|
||||
while (i.hasNext()) {
|
||||
if (SUBDIR_LIMIT > 0 && ++counter == SUBDIR_LIMIT) {
|
||||
subdir = Integer.toString(subDirSuffix++);
|
||||
subdir = Integer.valueOf(subDirSuffix++).toString();
|
||||
fullPath = destDirName + File.separatorChar + subdir;
|
||||
counter = 0;
|
||||
|
||||
@@ -145,7 +136,7 @@ public class ItemExportServiceImpl implements ItemExportService {
|
||||
}
|
||||
}
|
||||
|
||||
logInfo("Exporting item to " + mySequenceNumber);
|
||||
System.out.println("Exporting item to " + mySequenceNumber);
|
||||
Item item = i.next();
|
||||
exportItem(c, item, fullPath, mySequenceNumber, migrate, excludeBitstreams);
|
||||
c.uncacheEntity(item);
|
||||
@@ -161,7 +152,7 @@ public class ItemExportServiceImpl implements ItemExportService {
|
||||
// now create a subdirectory
|
||||
File itemDir = new File(destDir + "/" + seqStart);
|
||||
|
||||
logInfo("Exporting Item " + myItem.getID() +
|
||||
System.out.println("Exporting Item " + myItem.getID() +
|
||||
(myItem.getHandle() != null ? ", handle " + myItem.getHandle() : "") +
|
||||
" to " + itemDir);
|
||||
|
||||
@@ -174,7 +165,6 @@ public class ItemExportServiceImpl implements ItemExportService {
|
||||
// make it this far, now start exporting
|
||||
writeMetadata(c, myItem, itemDir, migrate);
|
||||
writeBitstreams(c, myItem, itemDir, excludeBitstreams);
|
||||
writeCollections(myItem, itemDir);
|
||||
if (!migrate) {
|
||||
writeHandle(c, myItem, itemDir);
|
||||
}
|
||||
@@ -199,7 +189,7 @@ public class ItemExportServiceImpl implements ItemExportService {
|
||||
*/
|
||||
protected void writeMetadata(Context c, Item i, File destDir, boolean migrate)
|
||||
throws Exception {
|
||||
Set<String> schemas = new HashSet<>();
|
||||
Set<String> schemas = new HashSet<String>();
|
||||
List<MetadataValue> dcValues = itemService.getMetadata(i, Item.ANY, Item.ANY, Item.ANY, Item.ANY);
|
||||
for (MetadataValue metadataValue : dcValues) {
|
||||
schemas.add(metadataValue.getMetadataField().getMetadataSchema().getName());
|
||||
@@ -224,7 +214,7 @@ public class ItemExportServiceImpl implements ItemExportService {
|
||||
protected void writeMetadata(Context c, String schema, Item i,
|
||||
File destDir, boolean migrate) throws Exception {
|
||||
String filename;
|
||||
if (schema.equals(MetadataSchemaEnum.DC.getName())) {
|
||||
if (schema.equals(MetadataSchema.DC_SCHEMA)) {
|
||||
filename = "dublin_core.xml";
|
||||
} else {
|
||||
filename = "metadata_" + schema + ".xml";
|
||||
@@ -232,7 +222,7 @@ public class ItemExportServiceImpl implements ItemExportService {
|
||||
|
||||
File outFile = new File(destDir, filename);
|
||||
|
||||
logInfo("Attempting to create file " + outFile);
|
||||
System.out.println("Attempting to create file " + outFile);
|
||||
|
||||
if (outFile.createNewFile()) {
|
||||
BufferedOutputStream out = new BufferedOutputStream(
|
||||
@@ -275,14 +265,15 @@ public class ItemExportServiceImpl implements ItemExportService {
|
||||
+ Utils.addEntities(dcv.getValue()) + "</dcvalue>\n")
|
||||
.getBytes("UTF-8");
|
||||
|
||||
if (!migrate ||
|
||||
if ((!migrate) ||
|
||||
(migrate && !(
|
||||
("date".equals(metadataField.getElement()) && "issued".equals(qualifier)) ||
|
||||
("date".equals(metadataField.getElement()) && "accessioned".equals(qualifier)) ||
|
||||
("date".equals(metadataField.getElement()) && "available".equals(qualifier)) ||
|
||||
("identifier".equals(metadataField.getElement()) && "uri".equals(qualifier) &&
|
||||
(dcv.getValue() != null && dcv.getValue().startsWith(
|
||||
handleService.getCanonicalPrefix() + handleService.getPrefix() + "/"))) ||
|
||||
(dcv.getValue() != null && dcv.getValue().startsWith("http://hdl.handle.net/" +
|
||||
handleService
|
||||
.getPrefix() + "/"))) ||
|
||||
("description".equals(metadataField.getElement()) && "provenance".equals(qualifier)) ||
|
||||
("format".equals(metadataField.getElement()) && "extent".equals(qualifier)) ||
|
||||
("format".equals(metadataField.getElement()) && "mimetype".equals(qualifier))))) {
|
||||
@@ -300,10 +291,10 @@ public class ItemExportServiceImpl implements ItemExportService {
|
||||
}
|
||||
|
||||
// When migrating, only keep date.issued if it is different to date.accessioned
|
||||
if (migrate &&
|
||||
if ((migrate) &&
|
||||
(dateIssued != null) &&
|
||||
(dateAccessioned != null) &&
|
||||
!dateIssued.equals(dateAccessioned)) {
|
||||
(!dateIssued.equals(dateAccessioned))) {
|
||||
utf8 = (" <dcvalue element=\"date\" "
|
||||
+ "qualifier=\"issued\">"
|
||||
+ Utils.addEntities(dateIssued) + "</dcvalue>\n")
|
||||
@@ -338,7 +329,7 @@ public class ItemExportServiceImpl implements ItemExportService {
|
||||
File outFile = new File(destDir, filename);
|
||||
|
||||
if (outFile.createNewFile()) {
|
||||
PrintWriter out = new PrintWriter(new FileWriter(outFile, StandardCharsets.UTF_8));
|
||||
PrintWriter out = new PrintWriter(new FileWriter(outFile));
|
||||
|
||||
out.println(i.getHandle());
|
||||
|
||||
@@ -350,33 +341,6 @@ public class ItemExportServiceImpl implements ItemExportService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the 'collections' file. List handles of all Collections which
|
||||
* contain this Item. The "owning" Collection is listed first.
|
||||
*
|
||||
* @param item list collections holding this Item.
|
||||
* @param destDir write the file here.
|
||||
* @throws IOException if the file cannot be created or written.
|
||||
*/
|
||||
protected void writeCollections(Item item, File destDir)
|
||||
throws IOException {
|
||||
File outFile = new File(destDir, "collections");
|
||||
if (outFile.createNewFile()) {
|
||||
try (PrintWriter out = new PrintWriter(new FileWriter(outFile))) {
|
||||
String ownerHandle = item.getOwningCollection().getHandle();
|
||||
out.println(ownerHandle);
|
||||
for (Collection collection : item.getCollections()) {
|
||||
String collectionHandle = collection.getHandle();
|
||||
if (!collectionHandle.equals(ownerHandle)) {
|
||||
out.println(collectionHandle);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new IOException("Cannot create 'collections' in " + destDir);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create both the bitstreams and the contents file. Any bitstreams that
|
||||
* were originally registered will be marked in the contents file as such.
|
||||
@@ -395,7 +359,7 @@ public class ItemExportServiceImpl implements ItemExportService {
|
||||
File outFile = new File(destDir, "contents");
|
||||
|
||||
if (outFile.createNewFile()) {
|
||||
PrintWriter out = new PrintWriter(new FileWriter(outFile, StandardCharsets.UTF_8));
|
||||
PrintWriter out = new PrintWriter(new FileWriter(outFile));
|
||||
|
||||
List<Bundle> bundles = i.getBundles();
|
||||
|
||||
@@ -433,7 +397,7 @@ public class ItemExportServiceImpl implements ItemExportService {
|
||||
File fdirs = new File(destDir + File.separator
|
||||
+ dirs);
|
||||
if (!fdirs.exists() && !fdirs.mkdirs()) {
|
||||
logError("Unable to create destination directory");
|
||||
log.error("Unable to create destination directory");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -490,33 +454,26 @@ public class ItemExportServiceImpl implements ItemExportService {
|
||||
|
||||
File wkDir = new File(workDir);
|
||||
if (!wkDir.exists() && !wkDir.mkdirs()) {
|
||||
logError("Unable to create working directory");
|
||||
log.error("Unable to create working direcory");
|
||||
}
|
||||
|
||||
File dnDir = new File(destDirName);
|
||||
if (!dnDir.exists() && !dnDir.mkdirs()) {
|
||||
logError("Unable to create destination directory");
|
||||
log.error("Unable to create destination directory");
|
||||
}
|
||||
|
||||
try {
|
||||
// export the items using normal export method (this exports items to our workDir)
|
||||
exportItem(context, items, workDir, seqStart, migrate, excludeBitstreams);
|
||||
// export the items using normal export method
|
||||
exportItem(context, items, workDir, seqStart, migrate, excludeBitstreams);
|
||||
|
||||
// now zip up the workDir directory created above
|
||||
zip(workDir, destDirName + System.getProperty("file.separator") + zipFileName);
|
||||
} finally {
|
||||
// Cleanup workDir created above, if it still exists
|
||||
if (wkDir.exists()) {
|
||||
deleteDirectory(wkDir);
|
||||
}
|
||||
}
|
||||
// now zip up the export directory created above
|
||||
zip(workDir, destDirName + System.getProperty("file.separator") + zipFileName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createDownloadableExport(DSpaceObject dso,
|
||||
Context context, boolean migrate) throws Exception {
|
||||
EPerson eperson = context.getCurrentUser();
|
||||
ArrayList<DSpaceObject> list = new ArrayList<>(1);
|
||||
ArrayList<DSpaceObject> list = new ArrayList<DSpaceObject>(1);
|
||||
list.add(dso);
|
||||
processDownloadableExport(list, context, eperson == null ? null
|
||||
: eperson.getEmail(), migrate);
|
||||
@@ -533,7 +490,7 @@ public class ItemExportServiceImpl implements ItemExportService {
|
||||
@Override
|
||||
public void createDownloadableExport(DSpaceObject dso,
|
||||
Context context, String additionalEmail, boolean migrate) throws Exception {
|
||||
ArrayList<DSpaceObject> list = new ArrayList<>(1);
|
||||
ArrayList<DSpaceObject> list = new ArrayList<DSpaceObject>(1);
|
||||
list.add(dso);
|
||||
processDownloadableExport(list, context, additionalEmail, migrate);
|
||||
}
|
||||
@@ -590,7 +547,7 @@ public class ItemExportServiceImpl implements ItemExportService {
|
||||
List<Bitstream> bitstreams = bundle.getBitstreams();
|
||||
for (Bitstream bitstream : bitstreams) {
|
||||
// add up the size
|
||||
size += bitstream.getSizeBytes();
|
||||
size += bitstream.getSize();
|
||||
}
|
||||
}
|
||||
items.add(item.getID());
|
||||
@@ -617,7 +574,7 @@ public class ItemExportServiceImpl implements ItemExportService {
|
||||
List<Bitstream> bitstreams = bundle.getBitstreams();
|
||||
for (Bitstream bitstream : bitstreams) {
|
||||
// add up the size
|
||||
size += bitstream.getSizeBytes();
|
||||
size += bitstream.getSize();
|
||||
}
|
||||
}
|
||||
items.add(item.getID());
|
||||
@@ -636,7 +593,7 @@ public class ItemExportServiceImpl implements ItemExportService {
|
||||
List<Bitstream> bitstreams = bundle.getBitstreams();
|
||||
for (Bitstream bitstream : bitstreams) {
|
||||
// add up the size
|
||||
size += bitstream.getSizeBytes();
|
||||
size += bitstream.getSize();
|
||||
}
|
||||
}
|
||||
ArrayList<UUID> items = new ArrayList<>();
|
||||
@@ -649,7 +606,7 @@ public class ItemExportServiceImpl implements ItemExportService {
|
||||
|
||||
// check the size of all the bitstreams against the configuration file
|
||||
// entry if it exists
|
||||
String megaBytes = configurationService
|
||||
String megaBytes = ConfigurationManager
|
||||
.getProperty("org.dspace.app.itemexport.max.size");
|
||||
if (megaBytes != null) {
|
||||
float maxSize = 0;
|
||||
@@ -671,9 +628,11 @@ public class ItemExportServiceImpl implements ItemExportService {
|
||||
Thread go = new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
Context context = new Context();
|
||||
Context context = null;
|
||||
Iterator<Item> iitems = null;
|
||||
try {
|
||||
// create a new dspace context
|
||||
context = new Context();
|
||||
// ignore auths
|
||||
context.turnOffAuthorisationSystem();
|
||||
|
||||
@@ -685,14 +644,14 @@ public class ItemExportServiceImpl implements ItemExportService {
|
||||
String downloadDir = getExportDownloadDirectory(eperson);
|
||||
File dnDir = new File(downloadDir);
|
||||
if (!dnDir.exists() && !dnDir.mkdirs()) {
|
||||
logError("Unable to create download directory");
|
||||
log.error("Unable to create download directory");
|
||||
}
|
||||
|
||||
Iterator<String> iter = itemsMap.keySet().iterator();
|
||||
while (iter.hasNext()) {
|
||||
String keyName = iter.next();
|
||||
List<UUID> uuids = itemsMap.get(keyName);
|
||||
List<Item> items = new ArrayList<>();
|
||||
List<Item> items = new ArrayList<Item>();
|
||||
for (UUID uuid : uuids) {
|
||||
items.add(itemService.find(context, uuid));
|
||||
}
|
||||
@@ -704,7 +663,7 @@ public class ItemExportServiceImpl implements ItemExportService {
|
||||
|
||||
File wkDir = new File(workDir);
|
||||
if (!wkDir.exists() && !wkDir.mkdirs()) {
|
||||
logError("Unable to create working directory");
|
||||
log.error("Unable to create working directory");
|
||||
}
|
||||
|
||||
|
||||
@@ -772,7 +731,7 @@ public class ItemExportServiceImpl implements ItemExportService {
|
||||
@Override
|
||||
public String getExportDownloadDirectory(EPerson ePerson)
|
||||
throws Exception {
|
||||
String downloadDir = configurationService
|
||||
String downloadDir = ConfigurationManager
|
||||
.getProperty("org.dspace.app.itemexport.download.dir");
|
||||
if (downloadDir == null) {
|
||||
throw new Exception(
|
||||
@@ -789,14 +748,13 @@ public class ItemExportServiceImpl implements ItemExportService {
|
||||
|
||||
@Override
|
||||
public String getExportWorkDirectory() throws Exception {
|
||||
String exportDir = configurationService
|
||||
String exportDir = ConfigurationManager
|
||||
.getProperty("org.dspace.app.itemexport.work.dir");
|
||||
if (exportDir == null) {
|
||||
throw new Exception(
|
||||
"A dspace.cfg entry for 'org.dspace.app.itemexport.work.dir' does not exist.");
|
||||
}
|
||||
// clean work dir path from duplicate separators
|
||||
return StringUtils.replace(exportDir, File.separator + File.separator, File.separator);
|
||||
return exportDir;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -896,7 +854,7 @@ public class ItemExportServiceImpl implements ItemExportService {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<String> fileNames = new ArrayList<>();
|
||||
List<String> fileNames = new ArrayList<String>();
|
||||
|
||||
for (String fileName : downloadDir.list()) {
|
||||
if (fileName.contains("export") && fileName.endsWith(".zip")) {
|
||||
@@ -913,18 +871,18 @@ public class ItemExportServiceImpl implements ItemExportService {
|
||||
|
||||
@Override
|
||||
public void deleteOldExportArchives(EPerson eperson) throws Exception {
|
||||
int hours = configurationService
|
||||
int hours = ConfigurationManager
|
||||
.getIntProperty("org.dspace.app.itemexport.life.span.hours");
|
||||
Calendar now = Calendar.getInstance();
|
||||
now.setTime(new Date());
|
||||
now.add(Calendar.HOUR, -hours);
|
||||
now.add(Calendar.HOUR, (-hours));
|
||||
File downloadDir = new File(getExportDownloadDirectory(eperson));
|
||||
if (downloadDir.exists()) {
|
||||
File[] files = downloadDir.listFiles();
|
||||
for (File file : files) {
|
||||
if (file.lastModified() < now.getTimeInMillis()) {
|
||||
if (!file.delete()) {
|
||||
logError("Unable to delete export file");
|
||||
log.error("Unable to delete export file");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -934,11 +892,11 @@ public class ItemExportServiceImpl implements ItemExportService {
|
||||
|
||||
@Override
|
||||
public void deleteOldExportArchives() throws Exception {
|
||||
int hours = configurationService.getIntProperty("org.dspace.app.itemexport.life.span.hours");
|
||||
int hours = ConfigurationManager.getIntProperty("org.dspace.app.itemexport.life.span.hours");
|
||||
Calendar now = Calendar.getInstance();
|
||||
now.setTime(new Date());
|
||||
now.add(Calendar.HOUR, -hours);
|
||||
File downloadDir = new File(configurationService.getProperty("org.dspace.app.itemexport.download.dir"));
|
||||
now.add(Calendar.HOUR, (-hours));
|
||||
File downloadDir = new File(ConfigurationManager.getProperty("org.dspace.app.itemexport.download.dir"));
|
||||
if (downloadDir.exists()) {
|
||||
// Get a list of all the sub-directories, potentially one for each ePerson.
|
||||
File[] dirs = downloadDir.listFiles();
|
||||
@@ -948,7 +906,7 @@ public class ItemExportServiceImpl implements ItemExportService {
|
||||
for (File file : files) {
|
||||
if (file.lastModified() < now.getTimeInMillis()) {
|
||||
if (!file.delete()) {
|
||||
logError("Unable to delete old files");
|
||||
log.error("Unable to delete old files");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -956,7 +914,7 @@ public class ItemExportServiceImpl implements ItemExportService {
|
||||
// If the directory is now empty then we delete it too.
|
||||
if (dir.listFiles().length == 0) {
|
||||
if (!dir.delete()) {
|
||||
logError("Unable to delete directory");
|
||||
log.error("Unable to delete directory");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -972,29 +930,29 @@ public class ItemExportServiceImpl implements ItemExportService {
|
||||
Locale supportedLocale = I18nUtil.getEPersonLocale(eperson);
|
||||
Email email = Email.getEmail(I18nUtil.getEmailFilename(supportedLocale, "export_success"));
|
||||
email.addRecipient(eperson.getEmail());
|
||||
email.addArgument(configurationService.getProperty("dspace.ui.url") + "/exportdownload/" + fileName);
|
||||
email.addArgument(configurationService.getProperty("org.dspace.app.itemexport.life.span.hours"));
|
||||
email.addArgument(ConfigurationManager.getProperty("dspace.url") + "/exportdownload/" + fileName);
|
||||
email.addArgument(ConfigurationManager.getProperty("org.dspace.app.itemexport.life.span.hours"));
|
||||
|
||||
email.send();
|
||||
} catch (Exception e) {
|
||||
logWarn(LogHelper.getHeader(context, "emailSuccessMessage", "cannot notify user of export"), e);
|
||||
log.warn(LogManager.getHeader(context, "emailSuccessMessage", "cannot notify user of export"), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void emailErrorMessage(EPerson eperson, String error)
|
||||
throws MessagingException {
|
||||
logWarn("An error occurred during item export, the user will be notified. " + error);
|
||||
log.warn("An error occurred during item export, the user will be notified. " + error);
|
||||
try {
|
||||
Locale supportedLocale = I18nUtil.getEPersonLocale(eperson);
|
||||
Email email = Email.getEmail(I18nUtil.getEmailFilename(supportedLocale, "export_error"));
|
||||
email.addRecipient(eperson.getEmail());
|
||||
email.addArgument(error);
|
||||
email.addArgument(configurationService.getProperty("dspace.ui.url") + "/feedback");
|
||||
email.addArgument(ConfigurationManager.getProperty("dspace.url") + "/feedback");
|
||||
|
||||
email.send();
|
||||
} catch (Exception e) {
|
||||
logWarn("error during item export error notification", e);
|
||||
log.warn("error during item export error notification", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1009,7 +967,7 @@ public class ItemExportServiceImpl implements ItemExportService {
|
||||
}
|
||||
File targetFile = new File(tempFileName);
|
||||
if (!targetFile.createNewFile()) {
|
||||
logWarn("Target file already exists: " + targetFile.getName());
|
||||
log.warn("Target file already exists: " + targetFile.getName());
|
||||
}
|
||||
|
||||
FileOutputStream fos = new FileOutputStream(tempFileName);
|
||||
@@ -1025,7 +983,7 @@ public class ItemExportServiceImpl implements ItemExportService {
|
||||
|
||||
deleteDirectory(cpFile);
|
||||
if (!targetFile.renameTo(new File(target))) {
|
||||
logError("Unable to rename file");
|
||||
log.error("Unable to rename file");
|
||||
}
|
||||
} finally {
|
||||
if (cpZipOutputStream != null) {
|
||||
@@ -1058,11 +1016,8 @@ public class ItemExportServiceImpl implements ItemExportService {
|
||||
return;
|
||||
}
|
||||
String strAbsPath = cpFile.getPath();
|
||||
int startIndex = strSource.length();
|
||||
if (!StringUtils.endsWith(strSource, File.separator)) {
|
||||
startIndex++;
|
||||
}
|
||||
String strZipEntryName = strAbsPath.substring(startIndex, strAbsPath.length());
|
||||
String strZipEntryName = strAbsPath.substring(strSource
|
||||
.length() + 1, strAbsPath.length());
|
||||
|
||||
// byte[] b = new byte[ (int)(cpFile.length()) ];
|
||||
|
||||
@@ -1101,7 +1056,7 @@ public class ItemExportServiceImpl implements ItemExportService {
|
||||
deleteDirectory(file);
|
||||
} else {
|
||||
if (!file.delete()) {
|
||||
logError("Unable to delete file: " + file.getName());
|
||||
log.error("Unable to delete file: " + file.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1110,64 +1065,4 @@ public class ItemExportServiceImpl implements ItemExportService {
|
||||
return (path.delete());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHandler(DSpaceRunnableHandler handler) {
|
||||
this.handler = handler;
|
||||
}
|
||||
|
||||
private void logInfo(String message) {
|
||||
logInfo(message, null);
|
||||
}
|
||||
|
||||
private void logInfo(String message, Exception e) {
|
||||
if (handler != null) {
|
||||
handler.logInfo(message);
|
||||
return;
|
||||
}
|
||||
|
||||
if (e != null) {
|
||||
log.info(message, e);
|
||||
} else {
|
||||
log.info(message);
|
||||
}
|
||||
}
|
||||
|
||||
private void logWarn(String message) {
|
||||
logWarn(message, null);
|
||||
}
|
||||
|
||||
private void logWarn(String message, Exception e) {
|
||||
if (handler != null) {
|
||||
handler.logWarning(message);
|
||||
return;
|
||||
}
|
||||
|
||||
if (e != null) {
|
||||
log.warn(message, e);
|
||||
} else {
|
||||
log.warn(message);
|
||||
}
|
||||
}
|
||||
|
||||
private void logError(String message) {
|
||||
logError(message, null);
|
||||
}
|
||||
|
||||
private void logError(String message, Exception e) {
|
||||
if (handler != null) {
|
||||
if (e != null) {
|
||||
handler.logError(message, e);
|
||||
} else {
|
||||
handler.logError(message);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (e != null) {
|
||||
log.error(message, e);
|
||||
} else {
|
||||
log.error(message);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -17,7 +17,6 @@ import org.dspace.content.DSpaceObject;
|
||||
import org.dspace.content.Item;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.eperson.EPerson;
|
||||
import org.dspace.scripts.handler.DSpaceRunnableHandler;
|
||||
|
||||
/**
|
||||
* Item exporter to create simple AIPs for DSpace content. Currently exports
|
||||
@@ -268,10 +267,4 @@ public interface ItemExportService {
|
||||
*/
|
||||
public void zip(String strSource, String target) throws Exception;
|
||||
|
||||
/**
|
||||
* Set the DSpace Runnable Handler
|
||||
* @param handler
|
||||
*/
|
||||
public void setHandler(DSpaceRunnableHandler handler);
|
||||
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user