mirror of
https://github.com/DSpace/DSpace.git
synced 2025-10-07 01:54:22 +00:00
Merge branch 'main' into w2p-100414_Missing-search-result-statistics-7.0
This commit is contained in:
@@ -6,6 +6,5 @@ dspace/modules/*/target/
|
||||
Dockerfile.*
|
||||
dspace/src/main/docker/dspace-postgres-pgcrypto
|
||||
dspace/src/main/docker/dspace-postgres-pgcrypto-curl
|
||||
dspace/src/main/docker/solr
|
||||
dspace/src/main/docker/README.md
|
||||
dspace/src/main/docker-compose/
|
||||
|
10
.github/pull_request_template.md
vendored
10
.github/pull_request_template.md
vendored
@@ -1,7 +1,7 @@
|
||||
## References
|
||||
_Add references/links to any related issues or PRs. These may include:_
|
||||
* Fixes #[issue-number]
|
||||
* Related to [REST Contract](https://github.com/DSpace/Rest7Contract)
|
||||
* 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).
|
||||
@@ -22,5 +22,7 @@ _This checklist provides a reminder of what we are going to look for when review
|
||||
- [ ] My PR passes Checkstyle validation based on the [Code Style Guide](https://wiki.lyrasis.org/display/DSPACE/Code+Style+Guide).
|
||||
- [ ] My PR includes Javadoc for _all new (or modified) public methods and classes_. It also includes Javadoc for large or complex private methods.
|
||||
- [ ] My PR passes all tests and includes new/updated Unit or Integration Tests based on the [Code Testing Guide](https://wiki.lyrasis.org/display/DSPACE/Code+Testing+Guide).
|
||||
- [ ] If my PR includes new, third-party dependencies (in any `pom.xml`), I've made sure their licenses align with the [DSpace BSD License](https://github.com/DSpace/DSpace/blob/main/LICENSE) based on the [Licensing of Contributions](https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines#CodeContributionGuidelines-LicensingofContributions) documentation.
|
||||
- [ ] If my PR modifies the REST API, I've linked to the REST Contract page (or open PR) related to this change.
|
||||
- [ ] 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).
|
||||
|
70
.github/workflows/build.yml
vendored
70
.github/workflows/build.yml
vendored
@@ -6,49 +6,57 @@ 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
|
||||
# Suppress all Maven "downloading" messages in Travis logs (see https://stackoverflow.com/a/35653426)
|
||||
# This also slightly speeds builds, as there is less logging
|
||||
MAVEN_OPTS: "-Xmx1024M -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn"
|
||||
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"
|
||||
mvnflags: "-DskipUnitTests=false -Pdspace-rest"
|
||||
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"
|
||||
mvnflags: "-DskipIntegrationTests=false -Denforcer.skip=true -Dcheckstyle.skip=true -Dlicense.skip=true -Dxml.skip=true"
|
||||
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@v1
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# https://github.com/actions/setup-java
|
||||
- name: Install JDK 11
|
||||
uses: actions/setup-java@v1
|
||||
- name: Install JDK ${{ matrix.java }}
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: 11
|
||||
java-version: ${{ matrix.java }}
|
||||
distribution: 'temurin'
|
||||
|
||||
# https://github.com/actions/cache
|
||||
- name: Cache Maven dependencies
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
# Cache entire ~/.m2/repository
|
||||
path: ~/.m2/repository
|
||||
@@ -60,18 +68,50 @@ jobs:
|
||||
- name: Run Maven ${{ matrix.type }}
|
||||
env:
|
||||
TEST_FLAGS: ${{ matrix.mvnflags }}
|
||||
run: mvn install -B -V -P-assembly -Pcoverage-report $TEST_FLAGS
|
||||
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@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ matrix.type }} results
|
||||
path: ${{ matrix.resultsdir }}
|
||||
retention-days: 7
|
||||
|
||||
# https://github.com/codecov/codecov-action
|
||||
# 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@v3
|
||||
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@v3
|
||||
|
||||
# Download artifacts from previous 'tests' job
|
||||
- name: Download coverage artifacts
|
||||
uses: actions/download-artifact@v3
|
||||
|
||||
# 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: codecov/codecov-action@v1
|
||||
uses: Wandalen/wretry.action@v1.0.36
|
||||
with:
|
||||
action: codecov/codecov-action@v3
|
||||
# Try upload 5 times max
|
||||
attempt_limit: 5
|
||||
# Run again in 30 seconds
|
||||
attempt_delay: 30000
|
||||
|
59
.github/workflows/codescan.yml
vendored
Normal file
59
.github/workflows/codescan.yml
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
# 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 branch. Also run once a week.
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
# 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@v3
|
||||
|
||||
# https://github.com/actions/setup-java
|
||||
- name: Install JDK
|
||||
uses: actions/setup-java@v3
|
||||
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
|
198
.github/workflows/docker.yml
vendored
Normal file
198
.github/workflows/docker.yml
vendored
Normal file
@@ -0,0 +1,198 @@
|
||||
# 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
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- 'dspace-**'
|
||||
tags:
|
||||
- 'dspace-**'
|
||||
pull_request:
|
||||
|
||||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
# Ensure this job never runs on forked repos. It's only executed for 'dspace/dspace'
|
||||
if: github.repository == 'dspace/dspace'
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
# 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 'dspace-7_x' 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.
|
||||
IMAGE_TAGS: |
|
||||
type=raw,value=dspace-7_x,enable=${{ endsWith(github.ref, github.event.repository.default_branch) }}
|
||||
type=ref,event=branch,enable=${{ !endsWith(github.ref, github.event.repository.default_branch) }}
|
||||
type=ref,event=tag
|
||||
# Define default tag "flavor" for docker/metadata-action per
|
||||
# https://github.com/docker/metadata-action#flavor-input
|
||||
# We turn off 'latest' tag by default.
|
||||
TAGS_FLAVOR: |
|
||||
latest=false
|
||||
# Architectures / Platforms for which we will build Docker images
|
||||
# If this is a PR, we ONLY build for AMD64. For PRs we only do a sanity check test to ensure Docker builds work.
|
||||
# If this is NOT a PR (e.g. a tag or merge commit), also build for ARM64. NOTE: The ARM64 build takes MUCH
|
||||
# longer (around 45mins or so) which is why we only run it when pushing a new Docker image.
|
||||
PLATFORMS: linux/amd64${{ github.event_name != 'pull_request' && ', linux/arm64' || '' }}
|
||||
|
||||
steps:
|
||||
# https://github.com/actions/checkout
|
||||
- name: Checkout codebase
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# https://github.com/docker/setup-buildx-action
|
||||
- name: Setup Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
# https://github.com/docker/setup-qemu-action
|
||||
- name: Set up QEMU emulation to build for multiple architectures
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
# https://github.com/docker/login-action
|
||||
- name: Login to DockerHub
|
||||
# Only login if not a PR, as PRs only trigger a Docker build and not a push
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
|
||||
|
||||
####################################################
|
||||
# Build/Push the 'dspace/dspace-dependencies' image
|
||||
####################################################
|
||||
# https://github.com/docker/metadata-action
|
||||
# Get Metadata for docker_build_deps step below
|
||||
- name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace-dependencies' image
|
||||
id: meta_build_deps
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: dspace/dspace-dependencies
|
||||
tags: ${{ env.IMAGE_TAGS }}
|
||||
flavor: ${{ env.TAGS_FLAVOR }}
|
||||
|
||||
# https://github.com/docker/build-push-action
|
||||
- name: Build and push 'dspace-dependencies' image
|
||||
id: docker_build_deps
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile.dependencies
|
||||
platforms: ${{ env.PLATFORMS }}
|
||||
# For pull requests, we run the Docker build (to ensure no PR changes break the build),
|
||||
# but we ONLY do an image push to DockerHub if it's NOT a PR
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
# Use tags / labels provided by 'docker/metadata-action' above
|
||||
tags: ${{ steps.meta_build_deps.outputs.tags }}
|
||||
labels: ${{ steps.meta_build_deps.outputs.labels }}
|
||||
|
||||
#######################################
|
||||
# Build/Push the 'dspace/dspace' image
|
||||
#######################################
|
||||
# Get Metadata for docker_build step below
|
||||
- name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace' image
|
||||
id: meta_build
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: dspace/dspace
|
||||
tags: ${{ env.IMAGE_TAGS }}
|
||||
flavor: ${{ env.TAGS_FLAVOR }}
|
||||
|
||||
- name: Build and push 'dspace' image
|
||||
id: docker_build
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
platforms: ${{ env.PLATFORMS }}
|
||||
# For pull requests, we run the Docker build (to ensure no PR changes break the build),
|
||||
# but we ONLY do an image push to DockerHub if it's NOT a PR
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
# Use tags / labels provided by 'docker/metadata-action' above
|
||||
tags: ${{ steps.meta_build.outputs.tags }}
|
||||
labels: ${{ steps.meta_build.outputs.labels }}
|
||||
|
||||
#####################################################
|
||||
# Build/Push the 'dspace/dspace' image ('-test' tag)
|
||||
#####################################################
|
||||
# Get Metadata for docker_build_test step below
|
||||
- name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace-test' image
|
||||
id: meta_build_test
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: dspace/dspace
|
||||
tags: ${{ env.IMAGE_TAGS }}
|
||||
# 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.
|
||||
flavor: ${{ env.TAGS_FLAVOR }}
|
||||
suffix=-test
|
||||
|
||||
- name: Build and push 'dspace-test' image
|
||||
id: docker_build_test
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile.test
|
||||
platforms: ${{ env.PLATFORMS }}
|
||||
# For pull requests, we run the Docker build (to ensure no PR changes break the build),
|
||||
# but we ONLY do an image push to DockerHub if it's NOT a PR
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
# Use tags / labels provided by 'docker/metadata-action' above
|
||||
tags: ${{ steps.meta_build_test.outputs.tags }}
|
||||
labels: ${{ steps.meta_build_test.outputs.labels }}
|
||||
|
||||
###########################################
|
||||
# Build/Push the 'dspace/dspace-cli' image
|
||||
###########################################
|
||||
# Get Metadata for docker_build_test step below
|
||||
- name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace-cli' image
|
||||
id: meta_build_cli
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: dspace/dspace-cli
|
||||
tags: ${{ env.IMAGE_TAGS }}
|
||||
flavor: ${{ env.TAGS_FLAVOR }}
|
||||
|
||||
- name: Build and push 'dspace-cli' image
|
||||
id: docker_build_cli
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile.cli
|
||||
platforms: ${{ env.PLATFORMS }}
|
||||
# For pull requests, we run the Docker build (to ensure no PR changes break the build),
|
||||
# but we ONLY do an image push to DockerHub if it's NOT a PR
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
# Use tags / labels provided by 'docker/metadata-action' above
|
||||
tags: ${{ steps.meta_build_cli.outputs.tags }}
|
||||
labels: ${{ steps.meta_build_cli.outputs.labels }}
|
||||
|
||||
###########################################
|
||||
# Build/Push the 'dspace/dspace-solr' image
|
||||
###########################################
|
||||
# Get Metadata for docker_build_solr step below
|
||||
- name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace-solr' image
|
||||
id: meta_build_solr
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: dspace/dspace-solr
|
||||
tags: ${{ env.IMAGE_TAGS }}
|
||||
flavor: ${{ env.TAGS_FLAVOR }}
|
||||
|
||||
- name: Build and push 'dspace-solr' image
|
||||
id: docker_build_solr
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
file: ./dspace/src/main/docker/dspace-solr/Dockerfile
|
||||
platforms: ${{ env.PLATFORMS }}
|
||||
# For pull requests, we run the Docker build (to ensure no PR changes break the build),
|
||||
# but we ONLY do an image push to DockerHub if it's NOT a PR
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
# Use tags / labels provided by 'docker/metadata-action' above
|
||||
tags: ${{ steps.meta_build_solr.outputs.tags }}
|
||||
labels: ${{ steps.meta_build_solr.outputs.labels }}
|
17
.github/workflows/issue_opened.yml
vendored
17
.github/workflows/issue_opened.yml
vendored
@@ -5,25 +5,22 @@ 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/marketplace/actions/create-project-card-action
|
||||
- name: Add issue to project board
|
||||
# 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: technote-space/create-project-card-action@v1
|
||||
uses: actions/add-to-project@v0.5.0
|
||||
# Note, the authentication token below is an ORG level Secret.
|
||||
# It must be created/recreated manually via a personal access token with "public_repo" and "admin:org" permissions
|
||||
# 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.ORG_PROJECT_TOKEN }}
|
||||
PROJECT: DSpace Backlog
|
||||
COLUMN: Triage
|
||||
CHECK_ORG_PROJECT: true
|
||||
# Ignore errors.
|
||||
continue-on-error: true
|
||||
github-token: ${{ secrets.TRIAGE_PROJECT_TOKEN }}
|
||||
project-url: https://github.com/orgs/DSpace/projects/24
|
||||
|
27
.github/workflows/label_merge_conflicts.yml
vendored
27
.github/workflows/label_merge_conflicts.yml
vendored
@@ -5,21 +5,32 @@ name: Check for merge conflicts
|
||||
# NOTE: This means merge conflicts are only checked for when a PR is merged to main.
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
branches: [ main ]
|
||||
# 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/mschilde/auto-label-merge-conflicts/
|
||||
# See: https://github.com/prince-chrismc/label-merge-conflicts-action
|
||||
- name: Auto-label PRs with merge conflicts
|
||||
uses: mschilde/auto-label-merge-conflicts@v2.0
|
||||
uses: prince-chrismc/label-merge-conflicts-action@v3
|
||||
# Add "merge conflict" label if a merge conflict is detected. Remove it when resolved.
|
||||
# Note, the authentication token is created automatically
|
||||
# See: https://docs.github.com/en/actions/configuring-and-managing-workflows/authenticating-with-the-github_token
|
||||
with:
|
||||
CONFLICT_LABEL_NAME: 'merge conflict'
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
# Ignore errors
|
||||
continue-on-error: true
|
||||
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!
|
||||
|
4
.gitignore
vendored
4
.gitignore
vendored
@@ -42,3 +42,7 @@ nb-configuration.xml
|
||||
|
||||
##Ignore JRebel project configuration
|
||||
rebel.xml
|
||||
|
||||
|
||||
## Ignore jenv configuration
|
||||
.java-version
|
||||
|
45
CONTRIBUTING.md
Normal file
45
CONTRIBUTING.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# 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.
|
48
Dockerfile
48
Dockerfile
@@ -1,63 +1,67 @@
|
||||
# This image will be published as dspace/dspace
|
||||
# See https://github.com/DSpace/DSpace/tree/main/dspace/src/main/docker for usage details
|
||||
#
|
||||
# This version is JDK11 compatible
|
||||
# - tomcat:8-jdk11
|
||||
# - ANT 1.10.7
|
||||
# - maven:3-jdk-11 (see dspace-dependencies)
|
||||
# - 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
|
||||
|
||||
# Step 1 - Run Maven Build
|
||||
FROM dspace/dspace-dependencies:dspace-7_x as build
|
||||
ARG TARGET_DIR=dspace-installer
|
||||
WORKDIR /app
|
||||
|
||||
# The dspace-install directory will be written to /install
|
||||
# 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 into the workdir (excluding .dockerignore contents)
|
||||
# Copy the DSpace source code (from local machine) into the workdir (excluding .dockerignore contents)
|
||||
ADD --chown=dspace . /app/
|
||||
COPY dspace/src/main/docker/local.cfg /app/local.cfg
|
||||
|
||||
# Build DSpace (note: this build doesn't include the optional, deprecated "dspace-rest" webapp)
|
||||
# Copy the dspace-install directory to /install. Clean up the build to keep the docker image small
|
||||
RUN mvn package && \
|
||||
# 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 tomcat:8-jdk11 as ant_build
|
||||
FROM openjdk:${JDK_VERSION}-slim 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.7
|
||||
ENV ANT_VERSION 1.10.12
|
||||
ENV ANT_HOME /tmp/ant-$ANT_VERSION
|
||||
ENV PATH $ANT_HOME/bin:$PATH
|
||||
|
||||
# Need wget to install ant
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends wget \
|
||||
&& apt-get purge -y --auto-remove \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
# Download and install 'ant'
|
||||
RUN mkdir $ANT_HOME && \
|
||||
wget -qO- "https://archive.apache.org/dist/ant/binaries/apache-ant-$ANT_VERSION-bin.tar.gz" | tar -zx --strip-components=1 -C $ANT_HOME
|
||||
|
||||
# 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 tomcat:8-jdk11
|
||||
FROM 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' containger to /dspace in this container
|
||||
COPY --from=ant_build /dspace $DSPACE_INSTALL
|
||||
# Expose Tomcat port and AJP port
|
||||
EXPOSE 8080 8009
|
||||
|
||||
# Give java extra memory (2GB)
|
||||
ENV JAVA_OPTS=-Xmx2000m
|
||||
|
||||
# Run the "server" webapp off the /server path (e.g. http://localhost:8080/server/)
|
||||
# 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 URL in dspace/src/main/docker/local.cfg
|
||||
# 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,53 +1,54 @@
|
||||
# This image will be published as dspace/dspace-cli
|
||||
# See https://github.com/DSpace/DSpace/tree/main/dspace/src/main/docker for usage details
|
||||
#
|
||||
# This version is JDK11 compatible
|
||||
# - openjdk:11
|
||||
# - ANT 1.10.7
|
||||
# - maven:3-jdk-11 (see dspace-dependencies)
|
||||
# - 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
|
||||
|
||||
# Step 1 - Run Maven Build
|
||||
FROM dspace/dspace-dependencies:dspace-7_x as build
|
||||
ARG TARGET_DIR=dspace-installer
|
||||
WORKDIR /app
|
||||
|
||||
# The dspace-install directory will be written to /install
|
||||
# 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 into the workdir (excluding .dockerignore contents)
|
||||
# Copy the DSpace source code (from local machine) into the workdir (excluding .dockerignore contents)
|
||||
ADD --chown=dspace . /app/
|
||||
COPY dspace/src/main/docker/local.cfg /app/local.cfg
|
||||
|
||||
# Build DSpace. Copy the dspace-install directory to /install. Clean up the build to keep the docker image small
|
||||
RUN mvn package && \
|
||||
# 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 openjdk:11 as ant_build
|
||||
FROM openjdk:${JDK_VERSION}-slim 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.7
|
||||
ENV ANT_VERSION 1.10.12
|
||||
ENV ANT_HOME /tmp/ant-$ANT_VERSION
|
||||
ENV PATH $ANT_HOME/bin:$PATH
|
||||
|
||||
# Need wget to install ant
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends wget \
|
||||
&& apt-get purge -y --auto-remove \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
# Download and install 'ant'
|
||||
RUN mkdir $ANT_HOME && \
|
||||
wget -qO- "https://archive.apache.org/dist/ant/binaries/apache-ant-$ANT_VERSION-bin.tar.gz" | tar -zx --strip-components=1 -C $ANT_HOME
|
||||
|
||||
# Run necessary 'ant' deploy scripts
|
||||
RUN ant init_installation update_configs update_code
|
||||
|
||||
# Step 3 - Run jdk
|
||||
# Create a new tomcat image that does not retain the the build directory contents
|
||||
FROM openjdk:11
|
||||
FROM openjdk:${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
|
||||
|
@@ -1,27 +1,36 @@
|
||||
# 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 version is JDK11 compatible
|
||||
# - maven:3-jdk-11
|
||||
|
||||
# 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 - Run Maven Build
|
||||
FROM maven:3-jdk-11 as build
|
||||
FROM maven:3-openjdk-${JDK_VERSION}-slim as build
|
||||
ARG TARGET_DIR=dspace-installer
|
||||
WORKDIR /app
|
||||
|
||||
# Create the 'dspace' user account & home directory
|
||||
RUN useradd dspace \
|
||||
&& mkdir /home/dspace \
|
||||
&& mkdir -p /home/dspace \
|
||||
&& chown -Rv dspace: /home/dspace
|
||||
RUN chown -Rv dspace: /app
|
||||
# Need git to support buildnumber-maven-plugin, which lets us know what version of DSpace is being run.
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends git \
|
||||
&& apt-get purge -y --auto-remove \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Switch to dspace user & run below commands as that user
|
||||
USER dspace
|
||||
|
||||
# Copy the DSpace source code into the workdir (excluding .dockerignore contents)
|
||||
# Copy the DSpace source code (from local machine) into the workdir (excluding .dockerignore contents)
|
||||
ADD --chown=dspace . /app/
|
||||
COPY dspace/src/main/docker/local.cfg /app/local.cfg
|
||||
|
||||
# Trigger the installation of all maven dependencies
|
||||
RUN mvn package
|
||||
# Trigger the installation of all maven dependencies (hide download progress messages)
|
||||
RUN mvn --no-transfer-progress package
|
||||
|
||||
# Clear the contents of the /app directory (including all maven builds), so no artifacts remain.
|
||||
# This ensures when dspace:dspace is built, it will just the Maven local cache (.m2) for dependencies
|
||||
# This ensures when dspace:dspace is built, it will use the Maven local cache (~/.m2) for dependencies
|
||||
USER root
|
||||
RUN rm -rf /app/*
|
||||
|
@@ -1,77 +1,82 @@
|
||||
# This image will be published as dspace/dspace
|
||||
# See https://github.com/DSpace/DSpace/tree/main/dspace/src/main/docker for usage details
|
||||
#
|
||||
# This version is JDK11 compatible
|
||||
# - tomcat:8-jdk11
|
||||
# - ANT 1.10.7
|
||||
# - maven:3-jdk-11 (see dspace-dependencies)
|
||||
# - 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
|
||||
|
||||
# Step 1 - Run Maven Build
|
||||
FROM dspace/dspace-dependencies:dspace-7_x as build
|
||||
ARG TARGET_DIR=dspace-installer
|
||||
WORKDIR /app
|
||||
|
||||
# The dspace-install directory will be written to /install
|
||||
# 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 into the workdir (excluding .dockerignore contents)
|
||||
# Copy the DSpace source code (from local machine) into the workdir (excluding .dockerignore contents)
|
||||
ADD --chown=dspace . /app/
|
||||
COPY dspace/src/main/docker/local.cfg /app/local.cfg
|
||||
|
||||
# Build DSpace (including the optional, deprecated "dspace-rest" webapp)
|
||||
# Copy the dspace-install directory to /install. Clean up the build to keep the docker image small
|
||||
RUN mvn package -Pdspace-rest && \
|
||||
# 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 tomcat:8-jdk11 as ant_build
|
||||
FROM openjdk:${JDK_VERSION}-slim 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.7
|
||||
ENV ANT_VERSION 1.10.12
|
||||
ENV ANT_HOME /tmp/ant-$ANT_VERSION
|
||||
ENV PATH $ANT_HOME/bin:$PATH
|
||||
|
||||
# Need wget to install ant
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends wget \
|
||||
&& apt-get purge -y --auto-remove \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
# Download and install 'ant'
|
||||
RUN mkdir $ANT_HOME && \
|
||||
wget -qO- "https://archive.apache.org/dist/ant/binaries/apache-ant-$ANT_VERSION-bin.tar.gz" | tar -zx --strip-components=1 -C $ANT_HOME
|
||||
|
||||
# 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 tomcat:8-jdk11
|
||||
FROM 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
|
||||
# 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
|
||||
|
||||
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
|
||||
|
||||
# Run the "server" webapp off the /server path (e.g. http://localhost:8080/server/)
|
||||
# and the v6.x (deprecated) REST API off the "/rest" path
|
||||
# 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 URL in dspace/src/main/docker/local.cfg
|
||||
# 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
|
||||
|
20
LICENSE
20
LICENSE
@@ -1,4 +1,4 @@
|
||||
DSpace source code BSD License:
|
||||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2002-2021, LYRASIS. All rights reserved.
|
||||
|
||||
@@ -13,13 +13,12 @@ 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 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.
|
||||
- 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.
|
||||
|
||||
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,
|
||||
@@ -29,11 +28,4 @@ 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.
|
||||
|
||||
|
||||
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.
|
||||
DAMAGE.
|
File diff suppressed because it is too large
Load Diff
10
NOTICE
10
NOTICE
@@ -1,3 +1,13 @@
|
||||
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 Notices
|
||||
=================
|
||||
|
||||
|
18
README.md
18
README.md
@@ -35,7 +35,7 @@ Documentation for each release may be viewed online or downloaded via our [Docum
|
||||
The latest DSpace Installation instructions are available at:
|
||||
https://wiki.lyrasis.org/display/DSDOC7x/Installing+DSpace
|
||||
|
||||
Please be aware that, as a Java web application, DSpace requires a database (PostgreSQL or Oracle)
|
||||
Please be aware that, as a Java web application, DSpace requires a database (PostgreSQL)
|
||||
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.
|
||||
|
||||
@@ -48,18 +48,7 @@ See [Running DSpace 7 with Docker Compose](dspace/src/main/docker-compose/README
|
||||
|
||||
## Contributing
|
||||
|
||||
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.lyrasis.org/display/DSPACE/How+to+Contribute+to+DSpace): How to contribute in general (via code, documentation, bug reports, expertise, etc)
|
||||
* [Code Contribution Guidelines](https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines): How to give back code or contribute features, bug fixes, etc.
|
||||
* [DSpace Community Advisory Team (DCAT)](https://wiki.lyrasis.org/display/cmtygp/DSpace+Community+Advisory+Team): If you are not a developer, we also have an interest group specifically for repository managers. The DCAT group meets virtually, once a month, and sends open invitations to join their meetings via the [DCAT mailing list](https://groups.google.com/d/forum/DSpaceCommunityAdvisoryTeam).
|
||||
|
||||
We also encourage GitHub Pull Requests (PRs) at any time. Please see our [Development with Git](https://wiki.lyrasis.org/display/DSPACE/Development+with+Git) guide for more info.
|
||||
|
||||
In addition, a listing of all known contributors to DSpace software can be
|
||||
found online at: https://wiki.lyrasis.org/display/DSPACE/DSpaceContributors
|
||||
See [Contributing documentation](CONTRIBUTING.md)
|
||||
|
||||
## Getting Help
|
||||
|
||||
@@ -136,3 +125,6 @@ run automatically by [GitHub Actions](https://github.com/DSpace/DSpace/actions?q
|
||||
|
||||
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.
|
||||
|
@@ -92,9 +92,7 @@ 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="scope" value="public"/> -->
|
||||
<!-- TODO: Above rule has been disabled because of large amount of missing public method Javadocs -->
|
||||
<property name="scope" value="nothing"/>
|
||||
<property name="scope" value="public"/>
|
||||
<!-- Allow params, throws and return tags to be optional -->
|
||||
<property name="allowMissingParamTags" value="true"/>
|
||||
<property name="allowMissingReturnTag" value="true"/>
|
||||
|
@@ -7,10 +7,23 @@ services:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.cli
|
||||
#environment:
|
||||
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:
|
||||
- ./dspace/src/main/docker-compose/local.cfg:/dspace/config/local.cfg
|
||||
# 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
|
||||
networks:
|
||||
|
@@ -4,12 +4,30 @@ networks:
|
||||
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' in your local.cfg.
|
||||
# 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'
|
||||
image: "${DOCKER_OWNER:-dspace}/dspace:${DSPACE_VER:-dspace-7_x-test}"
|
||||
build:
|
||||
context: .
|
||||
@@ -23,11 +41,16 @@ services:
|
||||
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
|
||||
- ./dspace/src/main/docker-compose/local.cfg:/dspace/config/local.cfg
|
||||
# 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
|
||||
@@ -58,8 +81,12 @@ services:
|
||||
# DSpace Solr container
|
||||
dspacesolr:
|
||||
container_name: dspacesolr
|
||||
# Uses official Solr image at https://hub.docker.com/_/solr/
|
||||
image: solr:8.8
|
||||
image: "${DOCKER_OWNER:-dspace}/dspace-solr:${DSPACE_VER:-dspace-7_x}"
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./dspace/src/main/docker/dspace-solr/Dockerfile
|
||||
args:
|
||||
SOLR_VERSION: "${SOLR_VER:-8.11}"
|
||||
networks:
|
||||
dspacenet:
|
||||
ports:
|
||||
@@ -69,23 +96,25 @@ services:
|
||||
tty: true
|
||||
working_dir: /var/solr/data
|
||||
volumes:
|
||||
# Mount our local Solr core configs so that they are available as Solr configsets on container
|
||||
- ./dspace/solr/authority:/opt/solr/server/solr/configsets/authority
|
||||
- ./dspace/solr/oai:/opt/solr/server/solr/configsets/oai
|
||||
- ./dspace/solr/search:/opt/solr/server/solr/configsets/search
|
||||
- ./dspace/solr/statistics:/opt/solr/server/solr/configsets/statistics
|
||||
# Keep Solr data directory between reboots
|
||||
- solr_data:/var/solr/data
|
||||
# Initialize all DSpace Solr cores using the mounted local configsets (see above), then start Solr
|
||||
# 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:
|
||||
|
@@ -12,7 +12,7 @@
|
||||
<parent>
|
||||
<groupId>org.dspace</groupId>
|
||||
<artifactId>dspace-parent</artifactId>
|
||||
<version>7.0</version>
|
||||
<version>7.6-SNAPSHOT</version>
|
||||
<relativePath>..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -334,18 +334,47 @@
|
||||
</profiles>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.logging.log4j</groupId>
|
||||
<artifactId>log4j-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hibernate</groupId>
|
||||
<artifactId>hibernate-ehcache</artifactId>
|
||||
<artifactId>hibernate-core</artifactId>
|
||||
<exclusions>
|
||||
<!-- Newer version pulled in via Jersey below -->
|
||||
<exclusion>
|
||||
<groupId>org.javassist</groupId>
|
||||
<artifactId>javassist</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hibernate</groupId>
|
||||
<artifactId>hibernate-jcache</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.ehcache</groupId>
|
||||
<artifactId>ehcache</artifactId>
|
||||
<version>${ehcache.version}</version>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-cache
|
||||
Caching dependencies for sherpa service. -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-cache</artifactId>
|
||||
<version>${spring-boot.version}</version>
|
||||
<exclusions>
|
||||
<!-- Newer version pulled in via Jersey below -->
|
||||
<exclusion>
|
||||
<groupId>org.javassist</groupId>
|
||||
<artifactId>javassist</artifactId>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-logging</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.cache</groupId>
|
||||
<artifactId>cache-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hibernate</groupId>
|
||||
<artifactId>hibernate-jpamodelgen</artifactId>
|
||||
@@ -358,7 +387,7 @@
|
||||
<dependency>
|
||||
<groupId>org.hibernate.javax.persistence</groupId>
|
||||
<artifactId>hibernate-jpa-2.1-api</artifactId>
|
||||
<version>1.0.0.Final</version>
|
||||
<version>1.0.2.Final</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -379,7 +408,7 @@
|
||||
<groupId>org.ow2.asm</groupId>
|
||||
<artifactId>asm-commons</artifactId>
|
||||
</exclusion>
|
||||
<!-- Newer version of Bouncycastle brought in via solr-cell -->
|
||||
<!-- Newer version of Bouncycastle brought in via Tika -->
|
||||
<exclusion>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcpkix-jdk15on</artifactId>
|
||||
@@ -388,39 +417,6 @@
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk15on</artifactId>
|
||||
</exclusion>
|
||||
<!-- Newer version of Jetty in our parent POM & via Solr -->
|
||||
<exclusion>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-alpn-java-server</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-deploy</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-servlet</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-servlets</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-webapp</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-xml</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.eclipse.jetty.http2</groupId>
|
||||
<artifactId>http2-common</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.eclipse.jetty.http2</groupId>
|
||||
<artifactId>http2-server</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<!-- Jetty is needed to run Handle Server -->
|
||||
@@ -481,8 +477,8 @@
|
||||
<artifactId>commons-validator</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.mail</groupId>
|
||||
<artifactId>mail</artifactId>
|
||||
<groupId>com.sun.mail</groupId>
|
||||
<artifactId>javax.mail</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
@@ -505,7 +501,7 @@
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jdom</groupId>
|
||||
<artifactId>jdom</artifactId>
|
||||
<artifactId>jdom2</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.pdfbox</groupId>
|
||||
@@ -515,22 +511,11 @@
|
||||
<groupId>org.apache.pdfbox</groupId>
|
||||
<artifactId>fontbox</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.poi</groupId>
|
||||
<artifactId>poi-scratchpad</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>xalan</groupId>
|
||||
<artifactId>xalan</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>xerces</groupId>
|
||||
<artifactId>xercesImpl</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.ibm.icu</groupId>
|
||||
<artifactId>icu4j</artifactId>
|
||||
</dependency>
|
||||
<!-- Codebase at https://github.com/DSpace/oclc-harvester2 -->
|
||||
<dependency>
|
||||
<groupId>org.dspace</groupId>
|
||||
<artifactId>oclc-harvester2</artifactId>
|
||||
@@ -558,13 +543,6 @@
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-inline</artifactId>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<!-- Different version provided by hibernate-ehcache -->
|
||||
<exclusion>
|
||||
<groupId>net.bytebuddy</groupId>
|
||||
<artifactId>byte-buddy</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
@@ -573,101 +551,49 @@
|
||||
</dependency>
|
||||
<!-- Used for RSS / ATOM syndication feeds -->
|
||||
<dependency>
|
||||
<groupId>org.rometools</groupId>
|
||||
<groupId>com.rometools</groupId>
|
||||
<artifactId>rome</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.rometools</groupId>
|
||||
<artifactId>rome-modules</artifactId>
|
||||
<version>1.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jbibtex</groupId>
|
||||
<artifactId>jbibtex</artifactId>
|
||||
<version>1.0.10</version>
|
||||
<version>1.0.20</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpcore</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpmime</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- SolrJ is used to communicate with Solr throughout the dspace-api -->
|
||||
<dependency>
|
||||
<groupId>org.apache.solr</groupId>
|
||||
<artifactId>solr-solrj</artifactId>
|
||||
<version>${solr.client.version}</version>
|
||||
<exclusions>
|
||||
<!-- Newer Jetty version brought in via Parent POM -->
|
||||
<exclusion>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-http</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-io</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-util</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<!-- Solr Core is needed for Integration Tests (to run a MockSolrServer) -->
|
||||
<!-- Solr Core is only needed for Integration Tests (to run a MockSolrServer) -->
|
||||
<!-- The following Solr / Lucene dependencies also support integration tests -->
|
||||
<dependency>
|
||||
<groupId>org.apache.solr</groupId>
|
||||
<artifactId>solr-core</artifactId>
|
||||
<scope>test</scope>
|
||||
<version>${solr.client.version}</version>
|
||||
<exclusions>
|
||||
<!-- Newer version brought in by opencsv -->
|
||||
<exclusion>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-text</artifactId>
|
||||
</exclusion>
|
||||
<!-- Newer Jetty version brought in via Parent POM -->
|
||||
<exclusion>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-http</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-io</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-util</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.solr</groupId>
|
||||
<artifactId>solr-cell</artifactId>
|
||||
<exclusions>
|
||||
<!-- Newer version brought in by opencsv -->
|
||||
<exclusion>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-text</artifactId>
|
||||
</exclusion>
|
||||
<!-- Newer Jetty version brought in via Parent POM -->
|
||||
<exclusion>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-http</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-io</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-util</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.lucene</groupId>
|
||||
<artifactId>lucene-core</artifactId>
|
||||
</dependency>
|
||||
<!-- Used for full-text indexing with Solr -->
|
||||
<dependency>
|
||||
<groupId>org.apache.tika</groupId>
|
||||
<artifactId>tika-parsers</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.lucene</groupId>
|
||||
<artifactId>lucene-analyzers-icu</artifactId>
|
||||
@@ -683,9 +609,15 @@
|
||||
<artifactId>lucene-analyzers-stempel</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Tika is used to extract full text from documents in order to index in Solr -->
|
||||
<dependency>
|
||||
<groupId>org.apache.xmlbeans</groupId>
|
||||
<artifactId>xmlbeans</artifactId>
|
||||
<groupId>org.apache.tika</groupId>
|
||||
<artifactId>tika-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.tika</groupId>
|
||||
<artifactId>tika-parsers-standard-package</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -709,13 +641,6 @@
|
||||
<version>1.1.1</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Gson: Java to Json conversion -->
|
||||
<dependency>
|
||||
<groupId>com.google.code.gson</groupId>
|
||||
<artifactId>gson</artifactId>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
@@ -743,7 +668,7 @@
|
||||
<dependency>
|
||||
<groupId>org.flywaydb</groupId>
|
||||
<artifactId>flyway-core</artifactId>
|
||||
<version>6.5.5</version>
|
||||
<version>8.4.4</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Google Analytics -->
|
||||
@@ -799,44 +724,6 @@
|
||||
<artifactId>jaxb-runtime</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Apache Axiom -->
|
||||
<dependency>
|
||||
<groupId>org.apache.ws.commons.axiom</groupId>
|
||||
<artifactId>axiom-impl</artifactId>
|
||||
<version>${axiom.version}</version>
|
||||
<exclusions>
|
||||
<!-- Exclude Geronimo as it is NOT necessary when using javax.activation (which we use)
|
||||
See: https://ws.apache.org/axiom/userguide/ch04.html#d0e732 -->
|
||||
<exclusion>
|
||||
<groupId>org.apache.geronimo.specs</groupId>
|
||||
<artifactId>*</artifactId>
|
||||
</exclusion>
|
||||
<!-- Exclude Woodstox, as later version provided by Solr dependencies -->
|
||||
<exclusion>
|
||||
<groupId>org.codehaus.woodstox</groupId>
|
||||
<artifactId>woodstox-core-asl</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.ws.commons.axiom</groupId>
|
||||
<artifactId>axiom-api</artifactId>
|
||||
<version>${axiom.version}</version>
|
||||
<exclusions>
|
||||
<!-- Exclude Geronimo as it is NOT necessary when using javax.activation (which we use)
|
||||
See: https://ws.apache.org/axiom/userguide/ch04.html#d0e732 -->
|
||||
<exclusion>
|
||||
<groupId>org.apache.geronimo.specs</groupId>
|
||||
<artifactId>*</artifactId>
|
||||
</exclusion>
|
||||
<!-- Exclude Woodstox, as later version provided by Solr dependencies -->
|
||||
<exclusion>
|
||||
<groupId>org.codehaus.woodstox</groupId>
|
||||
<artifactId>woodstox-core-asl</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<!-- Jersey / JAX-RS client (javax.ws.rs.*) dependencies needed to integrate with external sources/services -->
|
||||
<dependency>
|
||||
<groupId>org.glassfish.jersey.core</groupId>
|
||||
@@ -855,7 +742,7 @@
|
||||
<dependency>
|
||||
<groupId>com.amazonaws</groupId>
|
||||
<artifactId>aws-java-sdk-s3</artifactId>
|
||||
<version>1.10.50</version>
|
||||
<version>1.12.261</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -889,37 +776,127 @@
|
||||
<dependency>
|
||||
<groupId>org.json</groupId>
|
||||
<artifactId>json</artifactId>
|
||||
<version>20180130</version>
|
||||
<version>20230227</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Useful for testing command-line tools -->
|
||||
<dependency>
|
||||
<groupId>com.github.stefanbirkner</groupId>
|
||||
<artifactId>system-rules</artifactId>
|
||||
<version>1.19.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Used for Solr core export/import -->
|
||||
<dependency>
|
||||
<groupId>com.opencsv</groupId>
|
||||
<artifactId>opencsv</artifactId>
|
||||
<version>5.2</version>
|
||||
<version>5.6</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Email templating -->
|
||||
<dependency>
|
||||
<groupId>org.apache.velocity</groupId>
|
||||
<artifactId>velocity-engine-core</artifactId>
|
||||
<version>2.0</version>
|
||||
<type>jar</type>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.xmlunit</groupId>
|
||||
<artifactId>xmlunit-core</artifactId>
|
||||
<version>2.6.3</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.bcel</groupId>
|
||||
<artifactId>bcel</artifactId>
|
||||
<version>6.4.0</version>
|
||||
<version>6.6.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- required for openaire api integration -->
|
||||
<dependency>
|
||||
<groupId>eu.openaire</groupId>
|
||||
<artifactId>funders-model</artifactId>
|
||||
<version>2.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.mock-server</groupId>
|
||||
<artifactId>mockserver-junit-rule</artifactId>
|
||||
<version>5.11.2</version>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<!-- Exclude snakeyaml to avoid conflicts with: spring-boot-starter-cache -->
|
||||
<exclusion>
|
||||
<groupId>org.yaml</groupId>
|
||||
<artifactId>snakeyaml</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<!-- for mockserver -->
|
||||
<!-- Solve dependency convergence issues related to
|
||||
'mockserver-junit-rule' by selecting the versions we want to use. -->
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-buffer</artifactId>
|
||||
<version>4.1.68.Final</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-transport</artifactId>
|
||||
<version>4.1.68.Final</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-common</artifactId>
|
||||
<version>4.1.68.Final</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-handler</artifactId>
|
||||
<version>4.1.68.Final</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-codec</artifactId>
|
||||
<version>4.1.68.Final</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.velocity</groupId>
|
||||
<artifactId>velocity-engine-core</artifactId>
|
||||
<version>2.3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.xmlunit</groupId>
|
||||
<artifactId>xmlunit-core</artifactId>
|
||||
<version>2.8.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.java-json-tools</groupId>
|
||||
<artifactId>json-schema-validator</artifactId>
|
||||
<version>2.2.14</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>jakarta.xml.bind</groupId>
|
||||
<artifactId>jakarta.xml.bind-api</artifactId>
|
||||
<version>2.3.3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.validation</groupId>
|
||||
<artifactId>validation-api</artifactId>
|
||||
<version>2.0.1.Final</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.swagger</groupId>
|
||||
<artifactId>swagger-core</artifactId>
|
||||
<version>1.6.2</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
</project>
|
||||
|
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* 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
|
||||
* @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;
|
||||
}
|
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
@@ -0,0 +1,159 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
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 embargos and restrictions.
|
||||
*
|
||||
* If the item is null, simply returns the "unknown" value.
|
||||
*
|
||||
* @param context the DSpace context
|
||||
* @param item the item to embargo
|
||||
* @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 caculateAccessStatusForDso(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 caculateAccessStatusForDso(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;
|
||||
}
|
||||
}
|
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
package org.dspace.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;
|
||||
}
|
||||
}
|
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* 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;
|
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* 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
|
||||
* @throws SQLException An exception that provides information on a database access error or other errors.
|
||||
*/
|
||||
public String getAccessStatus(Context context, Item item) throws SQLException;
|
||||
}
|
@@ -14,6 +14,7 @@ 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.dspace.core.Context;
|
||||
@@ -54,14 +55,14 @@ 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 {
|
||||
throws Exception {
|
||||
CommandLineParser parser = new DefaultParser();
|
||||
Options options = new Options();
|
||||
|
||||
@@ -69,19 +70,41 @@ public final class 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 = parser.parse(options, argv);
|
||||
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);
|
||||
|
||||
}
|
||||
|
||||
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"));
|
||||
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;
|
||||
} else {
|
||||
ca.negotiateAdministratorDetails();
|
||||
ca.negotiateAdministratorDetails(line);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,7 +114,7 @@ public final class CreateAdministrator {
|
||||
* @throws Exception if error
|
||||
*/
|
||||
protected CreateAdministrator()
|
||||
throws Exception {
|
||||
throws Exception {
|
||||
context = new Context();
|
||||
groupService = EPersonServiceFactory.getInstance().getGroupService();
|
||||
ePersonService = EPersonServiceFactory.getInstance().getEPersonService();
|
||||
@@ -103,20 +126,20 @@ public final class CreateAdministrator {
|
||||
*
|
||||
* @throws Exception if error
|
||||
*/
|
||||
protected void negotiateAdministratorDetails()
|
||||
throws Exception {
|
||||
protected void negotiateAdministratorDetails(CommandLine line)
|
||||
throws Exception {
|
||||
Console console = System.console();
|
||||
|
||||
System.out.println("Creating an initial administrator account");
|
||||
|
||||
boolean dataOK = false;
|
||||
|
||||
String email = null;
|
||||
String firstName = null;
|
||||
String lastName = null;
|
||||
char[] password1 = null;
|
||||
char[] password2 = null;
|
||||
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');
|
||||
|
||||
while (!dataOK) {
|
||||
System.out.print("E-mail address: ");
|
||||
@@ -147,8 +170,6 @@ public final class CreateAdministrator {
|
||||
if (lastName != null) {
|
||||
lastName = lastName.trim();
|
||||
}
|
||||
|
||||
ConfigurationService cfg = DSpaceServicesFactory.getInstance().getConfigurationService();
|
||||
if (cfg.hasProperty("webui.supported.locales")) {
|
||||
System.out.println("Select one of the following languages: "
|
||||
+ cfg.getProperty("webui.supported.locales"));
|
||||
@@ -163,46 +184,59 @@ public final class CreateAdministrator {
|
||||
}
|
||||
}
|
||||
|
||||
System.out.println("Password will not display on screen.");
|
||||
System.out.print("Password: ");
|
||||
System.out.print("Is the above data correct? (y or n): ");
|
||||
System.out.flush();
|
||||
|
||||
password1 = console.readPassword();
|
||||
String s = console.readLine();
|
||||
|
||||
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;
|
||||
}
|
||||
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(password1));
|
||||
createAdministrator(email, firstName, lastName, language, String.valueOf(password));
|
||||
|
||||
//Cleaning arrays that held password
|
||||
Arrays.fill(password1, ' ');
|
||||
Arrays.fill(password2, ' ');
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
@@ -213,8 +247,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();
|
||||
|
@@ -11,13 +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.xpath.XPathAPI;
|
||||
import org.dspace.authorize.AuthorizeException;
|
||||
import org.dspace.content.MetadataField;
|
||||
import org.dspace.content.MetadataSchema;
|
||||
@@ -81,7 +84,7 @@ public class MetadataImporter {
|
||||
* @throws SQLException if database error
|
||||
* @throws IOException if IO error
|
||||
* @throws TransformerException if transformer error
|
||||
* @throws ParserConfigurationException if config error
|
||||
* @throws ParserConfigurationException if configuration error
|
||||
* @throws AuthorizeException if authorization error
|
||||
* @throws SAXException if parser error
|
||||
* @throws NonUniqueMetadataException if duplicate metadata
|
||||
@@ -90,8 +93,7 @@ public class MetadataImporter {
|
||||
public static void main(String[] args)
|
||||
throws ParseException, SQLException, IOException, TransformerException,
|
||||
ParserConfigurationException, AuthorizeException, SAXException,
|
||||
NonUniqueMetadataException, RegistryImportException {
|
||||
boolean forceUpdate = false;
|
||||
NonUniqueMetadataException, RegistryImportException, XPathExpressionException {
|
||||
|
||||
// create an options object and populate it
|
||||
CommandLineParser parser = new DefaultParser();
|
||||
@@ -100,16 +102,14 @@ public class MetadataImporter {
|
||||
options.addOption("u", "update", false, "update an existing schema");
|
||||
CommandLine line = parser.parse(options, args);
|
||||
|
||||
String file = null;
|
||||
if (line.hasOption('f')) {
|
||||
file = line.getOptionValue('f');
|
||||
String file = line.getOptionValue('f');
|
||||
boolean forceUpdate = line.hasOption('u');
|
||||
loadRegistry(file, forceUpdate);
|
||||
} else {
|
||||
usage();
|
||||
System.exit(0);
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
forceUpdate = line.hasOption('u');
|
||||
loadRegistry(file, forceUpdate);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -120,15 +120,15 @@ public class MetadataImporter {
|
||||
* @throws SQLException if database error
|
||||
* @throws IOException if IO error
|
||||
* @throws TransformerException if transformer error
|
||||
* @throws ParserConfigurationException if config error
|
||||
* @throws ParserConfigurationException if configuration 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 {
|
||||
throws SQLException, IOException, TransformerException, ParserConfigurationException, AuthorizeException,
|
||||
SAXException, NonUniqueMetadataException, RegistryImportException, XPathExpressionException {
|
||||
Context context = null;
|
||||
|
||||
try {
|
||||
@@ -140,7 +140,9 @@ public class MetadataImporter {
|
||||
Document document = RegistryImporter.loadXML(file);
|
||||
|
||||
// Get the nodes corresponding to types
|
||||
NodeList schemaNodes = XPathAPI.selectNodeList(document, "/dspace-dc-types/dc-schema");
|
||||
XPath xPath = XPathFactory.newInstance().newXPath();
|
||||
NodeList schemaNodes = (NodeList) xPath.compile("/dspace-dc-types/dc-schema")
|
||||
.evaluate(document, XPathConstants.NODESET);
|
||||
|
||||
// Add each one as a new format to the registry
|
||||
for (int i = 0; i < schemaNodes.getLength(); i++) {
|
||||
@@ -149,7 +151,8 @@ public class MetadataImporter {
|
||||
}
|
||||
|
||||
// Get the nodes corresponding to types
|
||||
NodeList typeNodes = XPathAPI.selectNodeList(document, "/dspace-dc-types/dc-type");
|
||||
NodeList typeNodes = (NodeList) xPath.compile("/dspace-dc-types/dc-type")
|
||||
.evaluate(document, XPathConstants.NODESET);
|
||||
|
||||
// Add each one as a new format to the registry
|
||||
for (int i = 0; i < typeNodes.getLength(); i++) {
|
||||
@@ -181,8 +184,8 @@ public class MetadataImporter {
|
||||
* @throws RegistryImportException if import fails
|
||||
*/
|
||||
private static void loadSchema(Context context, Node node, boolean updateExisting)
|
||||
throws SQLException, IOException, TransformerException,
|
||||
AuthorizeException, NonUniqueMetadataException, RegistryImportException {
|
||||
throws SQLException, AuthorizeException, NonUniqueMetadataException, RegistryImportException,
|
||||
XPathExpressionException {
|
||||
// Get the values
|
||||
String name = RegistryImporter.getElementData(node, "name");
|
||||
String namespace = RegistryImporter.getElementData(node, "namespace");
|
||||
@@ -227,7 +230,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 reimported
|
||||
* will not be re-imported.
|
||||
*
|
||||
* @param context DSpace context object
|
||||
* @param node the node in the DOM tree
|
||||
@@ -239,8 +242,8 @@ public class MetadataImporter {
|
||||
* @throws RegistryImportException if import fails
|
||||
*/
|
||||
private static void loadType(Context context, Node node)
|
||||
throws SQLException, IOException, TransformerException,
|
||||
AuthorizeException, NonUniqueMetadataException, RegistryImportException {
|
||||
throws SQLException, IOException, AuthorizeException, NonUniqueMetadataException, RegistryImportException,
|
||||
XPathExpressionException {
|
||||
// Get the values
|
||||
String schema = RegistryImporter.getElementData(node, "schema");
|
||||
String element = RegistryImporter.getElementData(node, "element");
|
||||
|
@@ -0,0 +1,140 @@
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
}
|
@@ -5,9 +5,14 @@
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
package org.dspace.administer;
|
||||
|
||||
/**
|
||||
* Implementation of the core Caching service.
|
||||
* The {@link ProcessCleaner} for CLI.
|
||||
*
|
||||
* @author Luca Giamminonni (luca.giamminonni at 4science.it)
|
||||
*
|
||||
*/
|
||||
public class ProcessCleanerCli extends ProcessCleaner {
|
||||
|
||||
package org.dspace.services.caching;
|
||||
}
|
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* 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> {
|
||||
|
||||
}
|
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
package org.dspace.administer;
|
||||
|
||||
import java.sql.SQLException;
|
||||
|
||||
import org.apache.commons.cli.Options;
|
||||
import org.dspace.authorize.service.AuthorizeService;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.scripts.configuration.ScriptConfiguration;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
/**
|
||||
* The {@link ScriptConfiguration} for the {@link ProcessCleaner} script.
|
||||
*/
|
||||
public class ProcessCleanerConfiguration<T extends ProcessCleaner> extends ScriptConfiguration<T> {
|
||||
|
||||
@Autowired
|
||||
private AuthorizeService authorizeService;
|
||||
|
||||
private Class<T> dspaceRunnableClass;
|
||||
|
||||
@Override
|
||||
public boolean isAllowedToExecute(Context context) {
|
||||
try {
|
||||
return authorizeService.isAdmin(context);
|
||||
} catch (SQLException e) {
|
||||
throw new RuntimeException("SQLException occurred when checking if the current user is an admin", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Options getOptions() {
|
||||
if (options == null) {
|
||||
|
||||
Options options = new Options();
|
||||
|
||||
options.addOption("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,8 +13,11 @@ 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;
|
||||
@@ -72,9 +75,10 @@ public class RegistryImporter {
|
||||
* @throws TransformerException if error
|
||||
*/
|
||||
public static String getElementData(Node parentElement, String childName)
|
||||
throws TransformerException {
|
||||
throws XPathExpressionException {
|
||||
// Grab the child node
|
||||
Node childNode = XPathAPI.selectSingleNode(parentElement, childName);
|
||||
XPath xPath = XPathFactory.newInstance().newXPath();
|
||||
Node childNode = (Node) xPath.compile(childName).evaluate(parentElement, XPathConstants.NODE);
|
||||
|
||||
if (childNode == null) {
|
||||
// No child node, so no values
|
||||
@@ -115,9 +119,10 @@ public class RegistryImporter {
|
||||
* @throws TransformerException if error
|
||||
*/
|
||||
public static String[] getRepeatedElementData(Node parentElement,
|
||||
String childName) throws TransformerException {
|
||||
String childName) throws XPathExpressionException {
|
||||
// Grab the child node
|
||||
NodeList childNodes = XPathAPI.selectNodeList(parentElement, childName);
|
||||
XPath xPath = XPathFactory.newInstance().newXPath();
|
||||
NodeList childNodes = (NodeList) xPath.compile(childName).evaluate(parentElement, XPathConstants.NODESET);
|
||||
|
||||
String[] data = new String[childNodes.getLength()];
|
||||
|
||||
|
@@ -16,15 +16,18 @@ 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.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.LogManager;
|
||||
import org.dspace.core.LogHelper;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
@@ -95,7 +98,7 @@ public class RegistryLoader {
|
||||
|
||||
System.exit(1);
|
||||
} catch (Exception e) {
|
||||
log.fatal(LogManager.getHeader(context, "error_loading_registries",
|
||||
log.fatal(LogHelper.getHeader(context, "error_loading_registries",
|
||||
""), e);
|
||||
|
||||
System.err.println("Error: \n - " + e.getMessage());
|
||||
@@ -122,12 +125,13 @@ public class RegistryLoader {
|
||||
*/
|
||||
public static void loadBitstreamFormats(Context context, String filename)
|
||||
throws SQLException, IOException, ParserConfigurationException,
|
||||
SAXException, TransformerException, AuthorizeException {
|
||||
SAXException, TransformerException, AuthorizeException, XPathExpressionException {
|
||||
Document document = loadXML(filename);
|
||||
|
||||
// Get the nodes corresponding to formats
|
||||
NodeList typeNodes = XPathAPI.selectNodeList(document,
|
||||
"dspace-bitstream-types/bitstream-type");
|
||||
XPath xPath = XPathFactory.newInstance().newXPath();
|
||||
NodeList typeNodes = (NodeList) xPath.compile("dspace-bitstream-types/bitstream-type")
|
||||
.evaluate(document, XPathConstants.NODESET);
|
||||
|
||||
// Add each one as a new format to the registry
|
||||
for (int i = 0; i < typeNodes.getLength(); i++) {
|
||||
@@ -135,7 +139,7 @@ public class RegistryLoader {
|
||||
loadFormat(context, n);
|
||||
}
|
||||
|
||||
log.info(LogManager.getHeader(context, "load_bitstream_formats",
|
||||
log.info(LogHelper.getHeader(context, "load_bitstream_formats",
|
||||
"number_loaded=" + typeNodes.getLength()));
|
||||
}
|
||||
|
||||
@@ -151,8 +155,7 @@ public class RegistryLoader {
|
||||
* @throws AuthorizeException if authorization error
|
||||
*/
|
||||
private static void loadFormat(Context context, Node node)
|
||||
throws SQLException, IOException, TransformerException,
|
||||
AuthorizeException {
|
||||
throws SQLException, AuthorizeException, XPathExpressionException {
|
||||
// Get the values
|
||||
String mimeType = getElementData(node, "mimetype");
|
||||
String shortDesc = getElementData(node, "short_description");
|
||||
@@ -231,9 +234,10 @@ public class RegistryLoader {
|
||||
* @throws TransformerException if transformer error
|
||||
*/
|
||||
private static String getElementData(Node parentElement, String childName)
|
||||
throws TransformerException {
|
||||
throws XPathExpressionException {
|
||||
// Grab the child node
|
||||
Node childNode = XPathAPI.selectSingleNode(parentElement, childName);
|
||||
XPath xPath = XPathFactory.newInstance().newXPath();
|
||||
Node childNode = (Node) xPath.compile(childName).evaluate(parentElement, XPathConstants.NODE);
|
||||
|
||||
if (childNode == null) {
|
||||
// No child node, so no values
|
||||
@@ -274,9 +278,10 @@ public class RegistryLoader {
|
||||
* @throws TransformerException if transformer error
|
||||
*/
|
||||
private static String[] getRepeatedElementData(Node parentElement,
|
||||
String childName) throws TransformerException {
|
||||
String childName) throws XPathExpressionException {
|
||||
// Grab the child node
|
||||
NodeList childNodes = XPathAPI.selectNodeList(parentElement, childName);
|
||||
XPath xPath = XPathFactory.newInstance().newXPath();
|
||||
NodeList childNodes = (NodeList) xPath.compile(childName).evaluate(parentElement, XPathConstants.NODESET);
|
||||
|
||||
String[] data = new String[childNodes.getLength()];
|
||||
|
||||
|
@@ -30,6 +30,10 @@ 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;
|
||||
@@ -38,7 +42,7 @@ 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.xpath.XPathAPI;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.dspace.authorize.AuthorizeException;
|
||||
import org.dspace.content.Collection;
|
||||
import org.dspace.content.Community;
|
||||
@@ -52,9 +56,11 @@ 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.jdom.Element;
|
||||
import org.jdom.output.Format;
|
||||
import org.jdom.output.XMLOutputter;
|
||||
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.w3c.dom.Document;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
@@ -76,6 +82,7 @@ import org.xml.sax.SAXException;
|
||||
* </community>
|
||||
* </import_structure>
|
||||
* }</pre>
|
||||
*
|
||||
* <p>
|
||||
* It can be arbitrarily deep, and supports all the metadata elements
|
||||
* that make up the community and collection metadata. See the system
|
||||
@@ -104,12 +111,14 @@ public class StructBuilder {
|
||||
*/
|
||||
private static final Map<String, MetadataFieldName> communityMap = new HashMap<>();
|
||||
|
||||
protected static CommunityService communityService
|
||||
protected static final CommunityService communityService
|
||||
= ContentServiceFactory.getInstance().getCommunityService();
|
||||
protected static CollectionService collectionService
|
||||
protected static final CollectionService collectionService
|
||||
= ContentServiceFactory.getInstance().getCollectionService();
|
||||
protected static EPersonService ePersonService
|
||||
protected static final EPersonService ePersonService
|
||||
= EPersonServiceFactory.getInstance().getEPersonService();
|
||||
protected static final HandleService handleService
|
||||
= HandleServiceFactory.getInstance().getHandleService();
|
||||
|
||||
/**
|
||||
* Default constructor
|
||||
@@ -135,16 +144,18 @@ public class StructBuilder {
|
||||
* @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.
|
||||
*/
|
||||
public static void main(String[] argv)
|
||||
throws ParserConfigurationException, SQLException,
|
||||
FileNotFoundException, IOException, TransformerException {
|
||||
throws ParserConfigurationException, SQLException,
|
||||
IOException, TransformerException, XPathExpressionException {
|
||||
// Define command line options.
|
||||
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(Option.builder("e").longOpt("eperson")
|
||||
.desc("User who is manipulating the repository's structure.")
|
||||
@@ -206,6 +217,7 @@ public class StructBuilder {
|
||||
// Export? Import?
|
||||
if (line.hasOption('x')) { // export
|
||||
exportStructure(context, outputStream);
|
||||
outputStream.close();
|
||||
} else { // Must be import
|
||||
String input = line.getOptionValue('f');
|
||||
if (null == input) {
|
||||
@@ -220,7 +232,12 @@ public class StructBuilder {
|
||||
inputStream = new FileInputStream(input);
|
||||
}
|
||||
|
||||
importStructure(context, inputStream, outputStream);
|
||||
boolean keepHandles = options.hasOption("k");
|
||||
importStructure(context, inputStream, outputStream, keepHandles);
|
||||
|
||||
inputStream.close();
|
||||
outputStream.close();
|
||||
|
||||
// save changes from import
|
||||
context.complete();
|
||||
}
|
||||
@@ -233,14 +250,17 @@ public class StructBuilder {
|
||||
* @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)
|
||||
throws IOException, ParserConfigurationException, SQLException, TransformerException {
|
||||
static void importStructure(Context context, InputStream input,
|
||||
OutputStream output, boolean keepHandles)
|
||||
throws IOException, ParserConfigurationException, SQLException,
|
||||
TransformerException, XPathExpressionException {
|
||||
|
||||
// load the XML
|
||||
Document document = null;
|
||||
@@ -258,15 +278,29 @@ public class StructBuilder {
|
||||
// is properly structured.
|
||||
try {
|
||||
validate(document);
|
||||
} catch (TransformerException ex) {
|
||||
} 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.
|
||||
NodeList identifierNodes = XPathAPI.selectNodeList(document, "//*[@identifier]");
|
||||
XPath xPath = XPathFactory.newInstance().newXPath();
|
||||
NodeList identifierNodes = (NodeList) xPath.compile("//*[@identifier]")
|
||||
.evaluate(document, XPathConstants.NODESET);
|
||||
if (identifierNodes.getLength() > 0) {
|
||||
System.err.println("The input document has 'identifier' attributes, which will be ignored.");
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// load the mappings into the member variable hashmaps
|
||||
@@ -287,10 +321,11 @@ public class StructBuilder {
|
||||
Element[] elements = new Element[]{};
|
||||
try {
|
||||
// get the top level community list
|
||||
NodeList first = XPathAPI.selectNodeList(document, "/import_structure/community");
|
||||
NodeList first = (NodeList) xPath.compile("/import_structure/community")
|
||||
.evaluate(document, XPathConstants.NODESET);
|
||||
|
||||
// run the import starting with the top level communities
|
||||
elements = handleCommunities(context, first, null);
|
||||
elements = handleCommunities(context, first, null, keepHandles);
|
||||
} catch (TransformerException ex) {
|
||||
System.err.format("Input content not understood: %s%n", ex.getMessage());
|
||||
System.exit(1);
|
||||
@@ -307,7 +342,7 @@ public class StructBuilder {
|
||||
}
|
||||
|
||||
// finally write the string into the output file.
|
||||
final org.jdom.Document xmlOutput = new org.jdom.Document(root);
|
||||
final org.jdom2.Document xmlOutput = new org.jdom2.Document(root);
|
||||
try {
|
||||
new XMLOutputter().output(xmlOutput, output);
|
||||
} catch (IOException e) {
|
||||
@@ -411,7 +446,7 @@ public class StructBuilder {
|
||||
}
|
||||
|
||||
// Now write the structure out.
|
||||
org.jdom.Document xmlOutput = new org.jdom.Document(rootElement);
|
||||
org.jdom2.Document xmlOutput = new org.jdom2.Document(rootElement);
|
||||
try {
|
||||
XMLOutputter outputter = new XMLOutputter(Format.getPrettyFormat());
|
||||
outputter.output(xmlOutput, output);
|
||||
@@ -456,14 +491,16 @@ public class StructBuilder {
|
||||
* @throws TransformerException if transformer error
|
||||
*/
|
||||
private static void validate(org.w3c.dom.Document document)
|
||||
throws TransformerException {
|
||||
throws XPathExpressionException {
|
||||
StringBuilder err = new StringBuilder();
|
||||
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");
|
||||
|
||||
NodeList first = XPathAPI.selectNodeList(document, "/import_structure/community");
|
||||
XPath xPath = XPathFactory.newInstance().newXPath();
|
||||
NodeList first = (NodeList) xPath.compile("/import_structure/community")
|
||||
.evaluate(document, XPathConstants.NODESET);
|
||||
if (first.getLength() == 0) {
|
||||
err.append("-There are no top level communities in the source document.");
|
||||
System.out.println(err.toString());
|
||||
@@ -493,14 +530,15 @@ public class StructBuilder {
|
||||
* no errors.
|
||||
*/
|
||||
private static String validateCommunities(NodeList communities, int level)
|
||||
throws TransformerException {
|
||||
throws XPathExpressionException {
|
||||
StringBuilder err = new StringBuilder();
|
||||
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 = XPathAPI.selectNodeList(n, "name");
|
||||
NodeList name = (NodeList) xPath.compile("name").evaluate(n, XPathConstants.NODESET);
|
||||
if (name.getLength() != 1) {
|
||||
String pos = Integer.toString(i + 1);
|
||||
err.append("-The level ").append(level)
|
||||
@@ -510,7 +548,7 @@ public class StructBuilder {
|
||||
}
|
||||
|
||||
// validate sub communities
|
||||
NodeList subCommunities = XPathAPI.selectNodeList(n, "community");
|
||||
NodeList subCommunities = (NodeList) xPath.compile("community").evaluate(n, XPathConstants.NODESET);
|
||||
String comErrs = validateCommunities(subCommunities, level + 1);
|
||||
if (comErrs != null) {
|
||||
err.append(comErrs);
|
||||
@@ -518,7 +556,7 @@ public class StructBuilder {
|
||||
}
|
||||
|
||||
// validate collections
|
||||
NodeList collections = XPathAPI.selectNodeList(n, "collection");
|
||||
NodeList collections = (NodeList) xPath.compile("collection").evaluate(n, XPathConstants.NODESET);
|
||||
String colErrs = validateCollections(collections, level + 1);
|
||||
if (colErrs != null) {
|
||||
err.append(colErrs);
|
||||
@@ -542,14 +580,15 @@ public class StructBuilder {
|
||||
* @return the errors to be generated by the calling method, or null if none
|
||||
*/
|
||||
private static String validateCollections(NodeList collections, int level)
|
||||
throws TransformerException {
|
||||
throws XPathExpressionException {
|
||||
StringBuilder err = new StringBuilder();
|
||||
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 = XPathAPI.selectNodeList(n, "name");
|
||||
NodeList name = (NodeList) xPath.compile("name").evaluate(n, XPathConstants.NODESET);
|
||||
if (name.getLength() != 1) {
|
||||
String pos = Integer.toString(i + 1);
|
||||
err.append("-The level ").append(level)
|
||||
@@ -609,22 +648,29 @@ 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)
|
||||
throws TransformerException, SQLException, AuthorizeException {
|
||||
private static Element[] handleCommunities(Context context, NodeList communities,
|
||||
Community parent, boolean keepHandles)
|
||||
throws TransformerException, SQLException, AuthorizeException,
|
||||
XPathExpressionException {
|
||||
Element[] elements = new Element[communities.getLength()];
|
||||
XPath xPath = XPathFactory.newInstance().newXPath();
|
||||
|
||||
for (int i = 0; i < communities.getLength(); i++) {
|
||||
Community community;
|
||||
Element element = new Element("community");
|
||||
Node tn = communities.item(i);
|
||||
Node identifier = tn.getAttributes().getNamedItem("identifier");
|
||||
|
||||
// create the community or sub community
|
||||
if (parent != null) {
|
||||
Community community;
|
||||
if (null == identifier
|
||||
|| StringUtils.isBlank(identifier.getNodeValue())
|
||||
|| !keepHandles) {
|
||||
community = communityService.create(parent, context);
|
||||
} else {
|
||||
community = communityService.create(null, context);
|
||||
community = communityService.create(parent, context, identifier.getNodeValue());
|
||||
}
|
||||
|
||||
// default the short description to be an empty string
|
||||
@@ -632,9 +678,8 @@ public class StructBuilder {
|
||||
MD_SHORT_DESCRIPTION, null, " ");
|
||||
|
||||
// now update the metadata
|
||||
Node tn = communities.item(i);
|
||||
for (Map.Entry<String, MetadataFieldName> entry : communityMap.entrySet()) {
|
||||
NodeList nl = XPathAPI.selectNodeList(tn, entry.getKey());
|
||||
NodeList nl = (NodeList) xPath.compile(entry.getKey()).evaluate(tn, XPathConstants.NODESET);
|
||||
if (nl.getLength() == 1) {
|
||||
communityService.setMetadataSingleValue(context, community,
|
||||
entry.getValue(), null, getStringValue(nl.item(0)));
|
||||
@@ -658,6 +703,7 @@ 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");
|
||||
@@ -700,12 +746,16 @@ public class StructBuilder {
|
||||
}
|
||||
|
||||
// handle sub communities
|
||||
NodeList subCommunities = XPathAPI.selectNodeList(tn, "community");
|
||||
Element[] subCommunityElements = handleCommunities(context, subCommunities, community);
|
||||
NodeList subCommunities = (NodeList) xPath.compile("community")
|
||||
.evaluate(tn, XPathConstants.NODESET);
|
||||
Element[] subCommunityElements = handleCommunities(context,
|
||||
subCommunities, community, keepHandles);
|
||||
|
||||
// handle collections
|
||||
NodeList collections = XPathAPI.selectNodeList(tn, "collection");
|
||||
Element[] collectionElements = handleCollections(context, collections, community);
|
||||
NodeList collections = (NodeList) xPath.compile("collection")
|
||||
.evaluate(tn, XPathConstants.NODESET);
|
||||
Element[] collectionElements = handleCollections(context,
|
||||
collections, community, keepHandles);
|
||||
|
||||
int j;
|
||||
for (j = 0; j < subCommunityElements.length; j++) {
|
||||
@@ -730,22 +780,33 @@ 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)
|
||||
throws TransformerException, SQLException, AuthorizeException {
|
||||
private static Element[] handleCollections(Context context,
|
||||
NodeList collections, Community parent, boolean keepHandles)
|
||||
throws SQLException, AuthorizeException, XPathExpressionException {
|
||||
Element[] elements = new Element[collections.getLength()];
|
||||
XPath xPath = XPathFactory.newInstance().newXPath();
|
||||
|
||||
for (int i = 0; i < collections.getLength(); i++) {
|
||||
Element element = new Element("collection");
|
||||
Collection collection = collectionService.create(context, parent);
|
||||
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());
|
||||
}
|
||||
|
||||
// default the short description to the empty string
|
||||
collectionService.setMetadataSingleValue(context, collection,
|
||||
MD_SHORT_DESCRIPTION, Item.ANY, " ");
|
||||
|
||||
// import the rest of the metadata
|
||||
Node tn = collections.item(i);
|
||||
for (Map.Entry<String, MetadataFieldName> entry : collectionMap.entrySet()) {
|
||||
NodeList nl = XPathAPI.selectNodeList(tn, entry.getKey());
|
||||
NodeList nl = (NodeList) xPath.compile(entry.getKey()).evaluate(tn, XPathConstants.NODESET);
|
||||
if (nl.getLength() == 1) {
|
||||
collectionService.setMetadataSingleValue(context, collection,
|
||||
entry.getValue(), null, getStringValue(nl.item(0)));
|
||||
@@ -754,6 +815,7 @@ public class StructBuilder {
|
||||
|
||||
collectionService.update(context, collection);
|
||||
|
||||
Element element = new Element("collection");
|
||||
element.setAttribute("identifier", collection.getHandle());
|
||||
|
||||
Element nameElement = new Element("name");
|
||||
|
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
179
dspace-api/src/main/java/org/dspace/alerts/SystemWideAlert.java
Normal file
179
dspace-api/src/main/java/org/dspace/alerts/SystemWideAlert.java
Normal file
@@ -0,0 +1,179 @@
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,129 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
|
||||
}
|
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -0,0 +1,118 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
@@ -138,7 +138,7 @@ public class DSpaceCSV implements Serializable {
|
||||
/**
|
||||
* Create a new instance, reading the lines in from file
|
||||
*
|
||||
* @param inputStream the inputstream to read from
|
||||
* @param inputStream the input stream to read from
|
||||
* @param c The DSpace Context
|
||||
* @throws Exception thrown if there is an error reading or processing the file
|
||||
*/
|
||||
@@ -159,7 +159,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);
|
||||
}
|
||||
|
||||
@@ -337,15 +337,15 @@ public class DSpaceCSV implements Serializable {
|
||||
/**
|
||||
* Set the value separator for multiple values stored in one csv value.
|
||||
*
|
||||
* Is set in bulkedit.cfg as valueseparator
|
||||
* Is set in {@code bulkedit.cfg} as {@code 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) && (!"".equals(valueSeparator.trim()))) {
|
||||
if ((valueSeparator != null) && !valueSeparator.trim().isEmpty()) {
|
||||
valueSeparator = valueSeparator.trim();
|
||||
} else {
|
||||
valueSeparator = "||";
|
||||
@@ -360,7 +360,7 @@ public class DSpaceCSV implements Serializable {
|
||||
/**
|
||||
* Set the field separator use to separate fields in the csv.
|
||||
*
|
||||
* Is set in bulkedit.cfg as fieldseparator
|
||||
* Is set in {@code bulkedit.cfg} as {@code fieldseparator}.
|
||||
*
|
||||
* If not set, defaults to comma ','.
|
||||
*
|
||||
@@ -371,7 +371,7 @@ public class DSpaceCSV implements Serializable {
|
||||
// Get the value separator
|
||||
fieldSeparator = DSpaceServicesFactory.getInstance().getConfigurationService()
|
||||
.getProperty("bulkedit.fieldseparator");
|
||||
if ((fieldSeparator != null) && (!"".equals(fieldSeparator.trim()))) {
|
||||
if ((fieldSeparator != null) && !fieldSeparator.trim().isEmpty()) {
|
||||
fieldSeparator = fieldSeparator.trim();
|
||||
if ("tab".equals(fieldSeparator)) {
|
||||
fieldSeparator = "\t";
|
||||
@@ -395,15 +395,15 @@ public class DSpaceCSV implements Serializable {
|
||||
/**
|
||||
* Set the authority separator for value with authority data.
|
||||
*
|
||||
* Is set in dspace.cfg as bulkedit.authorityseparator
|
||||
* Is set in {@code dspace.cfg} as {@code 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) && (!"".equals(authoritySeparator.trim()))) {
|
||||
if ((authoritySeparator != null) && !authoritySeparator.trim().isEmpty()) {
|
||||
authoritySeparator = authoritySeparator.trim();
|
||||
} else {
|
||||
authoritySeparator = "::";
|
||||
@@ -508,7 +508,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);
|
||||
@@ -524,7 +524,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);
|
||||
}
|
||||
@@ -564,7 +564,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 = "";
|
||||
}
|
||||
|
||||
@@ -577,7 +577,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) && (!"".equals(element))) {
|
||||
if ((element != null) && !element.isEmpty()) {
|
||||
csvLine.add(headings.get(i - 1), element);
|
||||
}
|
||||
}
|
||||
@@ -629,18 +629,18 @@ public class DSpaceCSV implements Serializable {
|
||||
public InputStream getInputStream() {
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
for (String csvLine : getCSVLinesAsStringArray()) {
|
||||
stringBuilder.append(csvLine + "\n");
|
||||
stringBuilder.append(csvLine).append("\n");
|
||||
}
|
||||
return IOUtils.toInputStream(stringBuilder.toString(), StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is it Ok to export this value? When exportAll is set to false, we don't export
|
||||
* Is it okay 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 bulkedit.cfg
|
||||
* The list can be configured via the key ignore-on-export in {@code bulkedit.cfg}.
|
||||
*
|
||||
* @param md The Metadatum to examine
|
||||
* @param md The MetadataField to examine
|
||||
* @return Whether or not it is OK to export this element
|
||||
*/
|
||||
protected boolean okToExport(MetadataField md) {
|
||||
@@ -649,12 +649,8 @@ 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 true;
|
||||
return ignore.get(key) == null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -41,10 +41,8 @@ public class MetadataDeletionScriptConfiguration<T extends MetadataDeletion> ext
|
||||
Options options = new Options();
|
||||
|
||||
options.addOption("m", "metadata", true, "metadata field name");
|
||||
options.getOption("m").setType(String.class);
|
||||
|
||||
options.addOption("l", "list", false, "lists the metadata fields that can be deleted");
|
||||
options.getOption("l").setType(boolean.class);
|
||||
|
||||
super.options = options;
|
||||
}
|
||||
|
@@ -54,12 +54,9 @@ public class MetadataExportScriptConfiguration<T extends MetadataExport> extends
|
||||
Options options = new Options();
|
||||
|
||||
options.addOption("i", "id", true, "ID or handle of thing to export (item, collection, or community)");
|
||||
options.getOption("i").setType(String.class);
|
||||
options.addOption("a", "all", false,
|
||||
"include all metadata fields that are not normally changed (e.g. provenance)");
|
||||
options.getOption("a").setType(boolean.class);
|
||||
options.addOption("h", "help", false, "help");
|
||||
options.getOption("h").setType(boolean.class);
|
||||
|
||||
|
||||
super.options = options;
|
||||
|
@@ -0,0 +1,170 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* 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');
|
||||
}
|
||||
}
|
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
|
||||
package org.dspace.app.bulkedit;
|
||||
|
||||
import 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();
|
||||
}
|
||||
}
|
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* 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.core.Context;
|
||||
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 boolean isAllowedToExecute(Context context) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
@@ -25,6 +25,7 @@ import javax.annotation.Nullable;
|
||||
import org.apache.commons.cli.ParseException;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.dspace.app.util.RelationshipUtils;
|
||||
import org.dspace.authority.AuthorityValue;
|
||||
import org.dspace.authority.factory.AuthorityServiceFactory;
|
||||
import org.dspace.authority.service.AuthorityValueService;
|
||||
@@ -53,7 +54,7 @@ import org.dspace.content.service.RelationshipTypeService;
|
||||
import org.dspace.content.service.WorkspaceItemService;
|
||||
import org.dspace.core.Constants;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.core.LogManager;
|
||||
import org.dspace.core.LogHelper;
|
||||
import org.dspace.eperson.EPerson;
|
||||
import org.dspace.eperson.factory.EPersonServiceFactory;
|
||||
import org.dspace.handle.factory.HandleServiceFactory;
|
||||
@@ -597,18 +598,19 @@ public class MetadataImport extends DSpaceRunnable<MetadataImportScriptConfigura
|
||||
changes.add(whatHasChanged);
|
||||
}
|
||||
|
||||
if (change) {
|
||||
//only clear cache if changes have been made.
|
||||
c.uncacheEntity(wsItem);
|
||||
c.uncacheEntity(wfItem);
|
||||
c.uncacheEntity(item);
|
||||
if (change && (rowCount % configurationService.getIntProperty("bulkedit.change.commit.count", 100) == 0)) {
|
||||
c.commit();
|
||||
handler.logInfo(LogHelper.getHeader(c, "metadata_import_commit", "lineNumber=" + rowCount));
|
||||
}
|
||||
populateRefAndRowMap(line, item == null ? null : item.getID());
|
||||
// keep track of current rows processed
|
||||
rowCount++;
|
||||
}
|
||||
if (change) {
|
||||
c.commit();
|
||||
}
|
||||
|
||||
c.setMode(originalMode);
|
||||
c.setMode(Context.Mode.READ_ONLY);
|
||||
|
||||
|
||||
// Return the changes
|
||||
@@ -640,7 +642,7 @@ public class MetadataImport extends DSpaceRunnable<MetadataImportScriptConfigura
|
||||
all += part + ",";
|
||||
}
|
||||
all = all.substring(0, all.length());
|
||||
log.debug(LogManager.getHeader(c, "metadata_import",
|
||||
log.debug(LogHelper.getHeader(c, "metadata_import",
|
||||
"item_id=" + item.getID() + ",fromCSV=" + all));
|
||||
|
||||
// Don't compare collections or actions or rowNames
|
||||
@@ -677,7 +679,7 @@ public class MetadataImport extends DSpaceRunnable<MetadataImportScriptConfigura
|
||||
qualifier = qualifier.substring(0, qualifier.indexOf('['));
|
||||
}
|
||||
}
|
||||
log.debug(LogManager.getHeader(c, "metadata_import",
|
||||
log.debug(LogHelper.getHeader(c, "metadata_import",
|
||||
"item_id=" + item.getID() + ",fromCSV=" + all +
|
||||
",looking_for_schema=" + schema +
|
||||
",looking_for_element=" + element +
|
||||
@@ -697,7 +699,7 @@ public class MetadataImport extends DSpaceRunnable<MetadataImportScriptConfigura
|
||||
.getConfidence() : Choices.CF_ACCEPTED);
|
||||
}
|
||||
i++;
|
||||
log.debug(LogManager.getHeader(c, "metadata_import",
|
||||
log.debug(LogHelper.getHeader(c, "metadata_import",
|
||||
"item_id=" + item.getID() + ",fromCSV=" + all +
|
||||
",found=" + dcv.getValue()));
|
||||
}
|
||||
@@ -748,7 +750,7 @@ public class MetadataImport extends DSpaceRunnable<MetadataImportScriptConfigura
|
||||
// column "dc.contributor.author" so don't remove it
|
||||
if ((value != null) && (!"".equals(value)) && (!contains(value, fromCSV)) && fromAuthority == null) {
|
||||
// Remove it
|
||||
log.debug(LogManager.getHeader(c, "metadata_import",
|
||||
log.debug(LogHelper.getHeader(c, "metadata_import",
|
||||
"item_id=" + item.getID() + ",fromCSV=" + all +
|
||||
",removing_schema=" + schema +
|
||||
",removing_element=" + element +
|
||||
@@ -924,11 +926,10 @@ public class MetadataImport extends DSpaceRunnable<MetadataImportScriptConfigura
|
||||
rightItem = item;
|
||||
}
|
||||
|
||||
// Create the relationship
|
||||
int leftPlace = relationshipService.findNextLeftPlaceByLeftItem(c, leftItem);
|
||||
int rightPlace = relationshipService.findNextRightPlaceByRightItem(c, rightItem);
|
||||
Relationship persistedRelationship = relationshipService.create(c, leftItem, rightItem,
|
||||
foundRelationshipType, leftPlace, rightPlace);
|
||||
// Create the relationship, appending to the end
|
||||
Relationship persistedRelationship = relationshipService.create(
|
||||
c, leftItem, rightItem, foundRelationshipType, -1, -1
|
||||
);
|
||||
relationshipService.update(c, persistedRelationship);
|
||||
}
|
||||
|
||||
@@ -1793,36 +1794,7 @@ public class MetadataImport extends DSpaceRunnable<MetadataImportScriptConfigura
|
||||
*/
|
||||
private RelationshipType matchRelationshipType(List<RelationshipType> relTypes,
|
||||
String targetType, String originType, String originTypeName) {
|
||||
RelationshipType foundRelationshipType = null;
|
||||
if (originTypeName.split("\\.").length > 1) {
|
||||
originTypeName = originTypeName.split("\\.")[1];
|
||||
}
|
||||
for (RelationshipType relationshipType : relTypes) {
|
||||
// Is origin type leftward or righward
|
||||
boolean isLeft = false;
|
||||
if (relationshipType.getLeftType().getLabel().equalsIgnoreCase(originType)) {
|
||||
isLeft = true;
|
||||
}
|
||||
if (isLeft) {
|
||||
// Validate typeName reference
|
||||
if (!relationshipType.getLeftwardType().equalsIgnoreCase(originTypeName)) {
|
||||
continue;
|
||||
}
|
||||
if (relationshipType.getLeftType().getLabel().equalsIgnoreCase(originType) &&
|
||||
relationshipType.getRightType().getLabel().equalsIgnoreCase(targetType)) {
|
||||
foundRelationshipType = relationshipType;
|
||||
}
|
||||
} else {
|
||||
if (!relationshipType.getRightwardType().equalsIgnoreCase(originTypeName)) {
|
||||
continue;
|
||||
}
|
||||
if (relationshipType.getLeftType().getLabel().equalsIgnoreCase(targetType) &&
|
||||
relationshipType.getRightType().getLabel().equalsIgnoreCase(originType)) {
|
||||
foundRelationshipType = relationshipType;
|
||||
}
|
||||
}
|
||||
}
|
||||
return foundRelationshipType;
|
||||
return RelationshipUtils.matchRelationshipType(relTypes, targetType, originType, originTypeName);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -19,7 +19,6 @@ public class MetadataImportCliScriptConfiguration extends MetadataImportScriptCo
|
||||
public Options getOptions() {
|
||||
Options options = super.getOptions();
|
||||
options.addOption("e", "email", true, "email address or user id of user (required if adding new items)");
|
||||
options.getOption("e").setType(String.class);
|
||||
options.getOption("e").setRequired(true);
|
||||
super.options = options;
|
||||
return options;
|
||||
|
@@ -59,20 +59,14 @@ public class MetadataImportScriptConfiguration<T extends MetadataImport> extends
|
||||
options.getOption("f").setRequired(true);
|
||||
options.addOption("s", "silent", false,
|
||||
"silent operation - doesn't request confirmation of changes USE WITH CAUTION");
|
||||
options.getOption("s").setType(boolean.class);
|
||||
options.addOption("w", "workflow", false, "workflow - when adding new items, use collection workflow");
|
||||
options.getOption("w").setType(boolean.class);
|
||||
options.addOption("n", "notify", false,
|
||||
"notify - when adding new items using a workflow, send notification emails");
|
||||
options.getOption("n").setType(boolean.class);
|
||||
options.addOption("v", "validate-only", false,
|
||||
"validate - just validate the csv, don't run the import");
|
||||
options.getOption("v").setType(boolean.class);
|
||||
options.addOption("t", "template", false,
|
||||
"template - when adding new items, use the collection template (if it exists)");
|
||||
options.getOption("t").setType(boolean.class);
|
||||
options.addOption("h", "help", false, "help");
|
||||
options.getOption("h").setType(boolean.class);
|
||||
|
||||
super.options = options;
|
||||
}
|
||||
|
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* 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,11 +13,8 @@ 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.DefaultParser;
|
||||
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.authorize.AuthorizeException;
|
||||
import org.dspace.content.Collection;
|
||||
import org.dspace.content.DSpaceObject;
|
||||
@@ -36,224 +33,223 @@ 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 {
|
||||
private static Context context;
|
||||
public class Harvest extends DSpaceRunnable<HarvestScriptConfiguration> {
|
||||
|
||||
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 HarvestedCollectionService harvestedCollectionService;
|
||||
protected EPersonService ePersonService;
|
||||
private CollectionService collectionService;
|
||||
|
||||
public static void main(String[] argv) throws Exception {
|
||||
// create an options object and populate it
|
||||
CommandLineParser parser = new DefaultParser();
|
||||
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;
|
||||
|
||||
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");
|
||||
protected Context context;
|
||||
|
||||
|
||||
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 HarvestScriptConfiguration getScriptConfiguration() {
|
||||
return new DSpace().getServiceManager()
|
||||
.getServiceByName("harvest", HarvestScriptConfiguration.class);
|
||||
}
|
||||
|
||||
options.addOption("h", "help", false, "help");
|
||||
public void setup() throws ParseException {
|
||||
harvestedCollectionService =
|
||||
HarvestServiceFactory.getInstance().getHarvestedCollectionService();
|
||||
ePersonService = EPersonServiceFactory.getInstance().getEPersonService();
|
||||
collectionService =
|
||||
ContentServiceFactory.getInstance().getCollectionService();
|
||||
|
||||
CommandLine line = parser.parse(options, argv);
|
||||
assignCurrentUserInContext();
|
||||
|
||||
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");
|
||||
help = commandLine.hasOption('h');
|
||||
|
||||
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
if (line.hasOption('s')) {
|
||||
if (commandLine.hasOption('s')) {
|
||||
command = "config";
|
||||
}
|
||||
if (line.hasOption('p')) {
|
||||
if (commandLine.hasOption('p')) {
|
||||
command = "purge";
|
||||
}
|
||||
if (line.hasOption('r')) {
|
||||
if (commandLine.hasOption('r')) {
|
||||
command = "run";
|
||||
}
|
||||
if (line.hasOption('g')) {
|
||||
if (commandLine.hasOption('g')) {
|
||||
command = "ping";
|
||||
}
|
||||
if (line.hasOption('o')) {
|
||||
command = "runOnce";
|
||||
}
|
||||
if (line.hasOption('S')) {
|
||||
if (commandLine.hasOption('S')) {
|
||||
command = "start";
|
||||
}
|
||||
if (line.hasOption('R')) {
|
||||
if (commandLine.hasOption('R')) {
|
||||
command = "reset";
|
||||
}
|
||||
if (line.hasOption('P')) {
|
||||
if (commandLine.hasOption('P')) {
|
||||
command = "purgeAll";
|
||||
}
|
||||
|
||||
|
||||
if (line.hasOption('e')) {
|
||||
eperson = line.getOptionValue('e');
|
||||
if (commandLine.hasOption('o')) {
|
||||
command = "reimport";
|
||||
}
|
||||
if (line.hasOption('c')) {
|
||||
collection = line.getOptionValue('c');
|
||||
if (commandLine.hasOption('c')) {
|
||||
collection = commandLine.getOptionValue('c');
|
||||
}
|
||||
if (line.hasOption('t')) {
|
||||
harvestType = Integer.parseInt(line.getOptionValue('t'));
|
||||
if (commandLine.hasOption('t')) {
|
||||
harvestType = Integer.parseInt(commandLine.getOptionValue('t'));
|
||||
} else {
|
||||
harvestType = 0;
|
||||
}
|
||||
if (line.hasOption('a')) {
|
||||
oaiSource = line.getOptionValue('a');
|
||||
if (commandLine.hasOption('a')) {
|
||||
oaiSource = commandLine.getOptionValue('a');
|
||||
}
|
||||
if (line.hasOption('i')) {
|
||||
oaiSetID = line.getOptionValue('i');
|
||||
if (commandLine.hasOption('i')) {
|
||||
oaiSetID = commandLine.getOptionValue('i');
|
||||
}
|
||||
if (line.hasOption('m')) {
|
||||
metadataKey = line.getOptionValue('m');
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
// 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);
|
||||
if (StringUtils.isBlank(command)) {
|
||||
handler.logError("No parameters specified (run with -h flag for details)");
|
||||
throw new UnsupportedOperationException("No command specified");
|
||||
} else if ("run".equals(command)) {
|
||||
// Run a single harvest cycle on a collection using saved settings.
|
||||
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);
|
||||
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");
|
||||
}
|
||||
|
||||
harvester.runHarvest(collection, eperson);
|
||||
runHarvest(context, collection);
|
||||
} else if ("start".equals(command)) {
|
||||
// start the harvest loop
|
||||
startHarvester();
|
||||
} else if ("reset".equals(command)) {
|
||||
// reset harvesting status
|
||||
resetHarvesting();
|
||||
resetHarvesting(context);
|
||||
} else if ("purgeAll".equals(command)) {
|
||||
// purge all collections that are set up for harvesting (obviously for testing purposes only)
|
||||
if (eperson == null) {
|
||||
System.out
|
||||
.println("Error - an eperson must be provided");
|
||||
System.out.println(" (run with -h flag for details)");
|
||||
System.exit(1);
|
||||
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");
|
||||
}
|
||||
|
||||
List<HarvestedCollection> harvestedCollections = harvestedCollectionService.findAll(context);
|
||||
for (HarvestedCollection harvestedCollection : harvestedCollections) {
|
||||
System.out.println(
|
||||
"Purging the following collections (deleting items and resetting harvest status): " +
|
||||
harvestedCollection
|
||||
.getCollection().getID().toString());
|
||||
harvester.purgeCollection(harvestedCollection.getCollection().getID().toString(), eperson);
|
||||
handler.logInfo(
|
||||
"Purging the following collections (deleting items and resetting harvest status): " +
|
||||
harvestedCollection
|
||||
.getCollection().getID().toString());
|
||||
purgeCollection(context, harvestedCollection.getCollection().getID().toString());
|
||||
}
|
||||
context.complete();
|
||||
} else if ("purge".equals(command)) {
|
||||
// Delete all items in a collection. Useful for testing fresh harvests.
|
||||
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);
|
||||
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");
|
||||
}
|
||||
|
||||
harvester.purgeCollection(collection, eperson);
|
||||
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);
|
||||
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) {
|
||||
System.out.println("Error - a target collection must be provided");
|
||||
System.out.println(" (run with -h flag for details)");
|
||||
System.exit(1);
|
||||
handler.logError("A target collection must be provided (run with -h flag for details)");
|
||||
throw new UnsupportedOperationException("A target collection must be provided");
|
||||
}
|
||||
if (oaiSource == null || oaiSetID == null) {
|
||||
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);
|
||||
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");
|
||||
}
|
||||
if (metadataKey == null) {
|
||||
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);
|
||||
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");
|
||||
}
|
||||
|
||||
harvester.configureCollection(collection, harvestType, oaiSource, oaiSetID, metadataKey);
|
||||
configureCollection(context, collection, harvestType, oaiSource, oaiSetID, metadataKey);
|
||||
} else if ("ping".equals(command)) {
|
||||
if (oaiSource == null || oaiSetID == null) {
|
||||
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);
|
||||
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");
|
||||
}
|
||||
|
||||
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(String collectionID) {
|
||||
private Collection resolveCollection(Context context, String collectionID) {
|
||||
|
||||
DSpaceObject dso;
|
||||
Collection targetCollection = null;
|
||||
@@ -273,14 +269,14 @@ public class Harvest {
|
||||
}
|
||||
} else {
|
||||
// not a handle, try and treat it as an collection database UUID
|
||||
System.out.println("Looking up by UUID: " + collectionID + ", " + "in context: " + context);
|
||||
handler.logInfo("Looking up by UUID: " + collectionID + ", " + "in context: " + context);
|
||||
targetCollection = collectionService.find(context, UUID.fromString(collectionID));
|
||||
}
|
||||
}
|
||||
// was the collection valid?
|
||||
if (targetCollection == null) {
|
||||
System.out.println("Cannot resolve " + collectionID + " to collection");
|
||||
System.exit(1);
|
||||
handler.logError("Cannot resolve " + collectionID + " to collection");
|
||||
throw new UnsupportedOperationException("Cannot resolve " + collectionID + " to collection");
|
||||
}
|
||||
} catch (SQLException se) {
|
||||
se.printStackTrace();
|
||||
@@ -290,12 +286,12 @@ public class Harvest {
|
||||
}
|
||||
|
||||
|
||||
private void configureCollection(String collectionID, int type, String oaiSource, String oaiSetId,
|
||||
private void configureCollection(Context context, String collectionID, int type, String oaiSource, String oaiSetId,
|
||||
String mdConfigId) {
|
||||
System.out.println("Running: configure collection");
|
||||
handler.logInfo("Running: configure collection");
|
||||
|
||||
Collection collection = resolveCollection(collectionID);
|
||||
System.out.println(collection.getID());
|
||||
Collection collection = resolveCollection(context, collectionID);
|
||||
handler.logInfo(String.valueOf(collection.getID()));
|
||||
|
||||
try {
|
||||
HarvestedCollection hc = harvestedCollectionService.find(context, collection);
|
||||
@@ -310,9 +306,8 @@ public class Harvest {
|
||||
context.restoreAuthSystemState();
|
||||
context.complete();
|
||||
} catch (Exception e) {
|
||||
System.out.println("Changes could not be committed");
|
||||
e.printStackTrace();
|
||||
System.exit(1);
|
||||
handler.logError("Changes could not be committed");
|
||||
handler.handleException(e);
|
||||
} finally {
|
||||
if (context != null) {
|
||||
context.restoreAuthSystemState();
|
||||
@@ -323,18 +318,15 @@ public class Harvest {
|
||||
|
||||
/**
|
||||
* 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(String collectionID, String email) {
|
||||
System.out.println(
|
||||
"Purging collection of all items and resetting last_harvested and harvest_message: " + collectionID);
|
||||
Collection collection = resolveCollection(collectionID);
|
||||
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);
|
||||
|
||||
try {
|
||||
EPerson eperson = ePersonService.findByEmail(context, email);
|
||||
context.setCurrentUser(eperson);
|
||||
context.turnOffAuthorisationSystem();
|
||||
|
||||
ItemService itemService = ContentServiceFactory.getInstance().getItemService();
|
||||
@@ -343,7 +335,7 @@ public class Harvest {
|
||||
while (it.hasNext()) {
|
||||
i++;
|
||||
Item item = it.next();
|
||||
System.out.println("Deleting: " + item.getHandle());
|
||||
handler.logInfo("Deleting: " + item.getHandle());
|
||||
collectionService.removeItem(context, collection, item);
|
||||
context.uncacheEntity(item);// Dispatch events every 50 items
|
||||
if (i % 50 == 0) {
|
||||
@@ -363,9 +355,8 @@ public class Harvest {
|
||||
context.restoreAuthSystemState();
|
||||
context.dispatchEvents();
|
||||
} catch (Exception e) {
|
||||
System.out.println("Changes could not be committed");
|
||||
e.printStackTrace();
|
||||
System.exit(1);
|
||||
handler.logError("Changes could not be committed");
|
||||
handler.handleException(e);
|
||||
} finally {
|
||||
context.restoreAuthSystemState();
|
||||
}
|
||||
@@ -375,46 +366,42 @@ public class Harvest {
|
||||
/**
|
||||
* Run a single harvest cycle on the specified collection under the authorization of the supplied EPerson
|
||||
*/
|
||||
private void runHarvest(String collectionID, String email) {
|
||||
System.out.println("Running: a harvest cycle on " + collectionID);
|
||||
private void runHarvest(Context context, String collectionID) {
|
||||
handler.logInfo("Running: a harvest cycle on " + collectionID);
|
||||
|
||||
System.out.print("Initializing the harvester... ");
|
||||
handler.logInfo("Initializing the harvester... ");
|
||||
OAIHarvester harvester = null;
|
||||
try {
|
||||
Collection collection = resolveCollection(collectionID);
|
||||
Collection collection = resolveCollection(context, collectionID);
|
||||
HarvestedCollection hc = harvestedCollectionService.find(context, collection);
|
||||
harvester = new OAIHarvester(context, collection, hc);
|
||||
System.out.println("success. ");
|
||||
handler.logInfo("Initialized the harvester successfully");
|
||||
} catch (HarvestingException hex) {
|
||||
System.out.print("failed. ");
|
||||
System.out.println(hex.getMessage());
|
||||
handler.logError("Initializing the harvester failed.");
|
||||
throw new IllegalStateException("Unable to harvest", hex);
|
||||
} catch (SQLException se) {
|
||||
System.out.print("failed. ");
|
||||
System.out.println(se.getMessage());
|
||||
handler.logError("Initializing the harvester failed.");
|
||||
throw new IllegalStateException("Unable to access database", se);
|
||||
}
|
||||
|
||||
try {
|
||||
// Harvest will not work for an anonymous user
|
||||
EPerson eperson = ePersonService.findByEmail(context, email);
|
||||
System.out.println("Harvest started... ");
|
||||
context.setCurrentUser(eperson);
|
||||
handler.logInfo("Harvest started... ");
|
||||
harvester.runHarvest();
|
||||
context.complete();
|
||||
} catch (SQLException | AuthorizeException | IOException e) {
|
||||
throw new IllegalStateException("Failed to run harvester", e);
|
||||
}
|
||||
|
||||
System.out.println("Harvest complete. ");
|
||||
handler.logInfo("Harvest complete. ");
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets harvest_status and harvest_start_time flags for all collections that have a row in the
|
||||
* harvested_collections table
|
||||
*/
|
||||
private static void resetHarvesting() {
|
||||
System.out.print("Resetting harvest status flag on all collections... ");
|
||||
private void resetHarvesting(Context context) {
|
||||
handler.logInfo("Resetting harvest status flag on all collections... ");
|
||||
|
||||
try {
|
||||
List<HarvestedCollection> harvestedCollections = harvestedCollectionService.findAll(context);
|
||||
@@ -424,21 +411,21 @@ public class Harvest {
|
||||
harvestedCollection.setHarvestStatus(HarvestedCollection.STATUS_READY);
|
||||
harvestedCollectionService.update(context, harvestedCollection);
|
||||
}
|
||||
System.out.println("success. ");
|
||||
handler.logInfo("Reset harvest status flag successfully");
|
||||
} catch (Exception ex) {
|
||||
System.out.println("failed. ");
|
||||
ex.printStackTrace();
|
||||
handler.logError("Resetting harvest status flag failed");
|
||||
handler.handleException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts up the harvest scheduler. Terminating this process will stop the scheduler.
|
||||
*/
|
||||
private static void startHarvester() {
|
||||
private void startHarvester() {
|
||||
try {
|
||||
System.out.print("Starting harvest loop... ");
|
||||
handler.logInfo("Starting harvest loop... ");
|
||||
HarvestServiceFactory.getInstance().getHarvestSchedulingService().startNewScheduler();
|
||||
System.out.println("running. ");
|
||||
handler.logInfo("running. ");
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
@@ -451,29 +438,31 @@ public class Harvest {
|
||||
* @param set name of an item set.
|
||||
* @param metadataFormat local prefix name, or null for "dc".
|
||||
*/
|
||||
private static void pingResponder(String server, String set, String metadataFormat) {
|
||||
private void pingResponder(String server, String set, String metadataFormat) {
|
||||
List<String> errors;
|
||||
|
||||
System.out.print("Testing basic PMH access: ");
|
||||
handler.logInfo("Testing basic PMH access: ");
|
||||
errors = harvestedCollectionService.verifyOAIharvester(server, set,
|
||||
(null != metadataFormat) ? metadataFormat : "dc", false);
|
||||
(null != metadataFormat) ? metadataFormat : "dc", false);
|
||||
if (errors.isEmpty()) {
|
||||
System.out.println("OK");
|
||||
handler.logInfo("OK");
|
||||
} else {
|
||||
for (String error : errors) {
|
||||
System.err.println(error);
|
||||
handler.logError(error);
|
||||
}
|
||||
}
|
||||
|
||||
System.out.print("Testing ORE support: ");
|
||||
handler.logInfo("Testing ORE support: ");
|
||||
errors = harvestedCollectionService.verifyOAIharvester(server, set,
|
||||
(null != metadataFormat) ? metadataFormat : "dc", true);
|
||||
(null != metadataFormat) ? metadataFormat : "dc", true);
|
||||
if (errors.isEmpty()) {
|
||||
System.out.println("OK");
|
||||
handler.logInfo("OK");
|
||||
} else {
|
||||
for (String error : errors) {
|
||||
System.err.println(error);
|
||||
handler.logError(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
package org.dspace.app.harvest;
|
||||
|
||||
import java.sql.SQLException;
|
||||
|
||||
import org.apache.commons.cli.Options;
|
||||
import org.dspace.authorize.service.AuthorizeService;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.scripts.configuration.ScriptConfiguration;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
|
||||
public class HarvestScriptConfiguration<T extends Harvest> extends ScriptConfiguration<T> {
|
||||
@Autowired
|
||||
private AuthorizeService authorizeService;
|
||||
|
||||
private Class<T> dspaceRunnableClass;
|
||||
|
||||
@Override
|
||||
public Class<T> getDspaceRunnableClass() {
|
||||
return dspaceRunnableClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDspaceRunnableClass(Class<T> dspaceRunnableClass) {
|
||||
this.dspaceRunnableClass = dspaceRunnableClass;
|
||||
}
|
||||
|
||||
public boolean isAllowedToExecute(final Context context) {
|
||||
try {
|
||||
return authorizeService.isAdmin(context);
|
||||
} catch (SQLException e) {
|
||||
throw new RuntimeException("SQLException occurred when checking if the current user is an admin", e);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
@@ -0,0 +1,264 @@
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* 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'));
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
@@ -1,246 +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 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.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 DefaultParser();
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
* 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.sql.SQLException;
|
||||
|
||||
import org.apache.commons.cli.Option;
|
||||
import org.apache.commons.cli.Options;
|
||||
import org.dspace.authorize.service.AuthorizeService;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.scripts.configuration.ScriptConfiguration;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
/**
|
||||
* The {@link ScriptConfiguration} for the {@link ItemExport} script
|
||||
*
|
||||
* @author Francesco Pio Scognamiglio (francescopio.scognamiglio at 4science.com)
|
||||
*/
|
||||
public class ItemExportScriptConfiguration<T extends ItemExport> extends ScriptConfiguration<T> {
|
||||
|
||||
@Autowired
|
||||
private AuthorizeService authorizeService;
|
||||
|
||||
private Class<T> dspaceRunnableClass;
|
||||
|
||||
@Override
|
||||
public Class<T> getDspaceRunnableClass() {
|
||||
return dspaceRunnableClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDspaceRunnableClass(Class<T> dspaceRunnableClass) {
|
||||
this.dspaceRunnableClass = dspaceRunnableClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAllowedToExecute(final Context context) {
|
||||
try {
|
||||
return authorizeService.isAdmin(context);
|
||||
} catch (SQLException e) {
|
||||
throw new RuntimeException("SQLException occurred when checking if the current user is an admin", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Options getOptions() {
|
||||
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,6 +16,7 @@ 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;
|
||||
@@ -51,11 +52,12 @@ import org.dspace.core.Constants;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.core.Email;
|
||||
import org.dspace.core.I18nUtil;
|
||||
import org.dspace.core.LogManager;
|
||||
import org.dspace.core.LogHelper;
|
||||
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;
|
||||
|
||||
@@ -63,17 +65,21 @@ 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:
|
||||
* <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>
|
||||
* <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>
|
||||
* 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.
|
||||
*
|
||||
@@ -96,11 +102,12 @@ public class ItemExportServiceImpl implements ItemExportService {
|
||||
@Autowired(required = true)
|
||||
protected ConfigurationService configurationService;
|
||||
|
||||
|
||||
/**
|
||||
* log4j logger
|
||||
*/
|
||||
private final Logger log = org.apache.logging.log4j.LogManager.getLogger(ItemExportServiceImpl.class);
|
||||
private final Logger log = org.apache.logging.log4j.LogManager.getLogger();
|
||||
|
||||
private DSpaceRunnableHandler handler;
|
||||
|
||||
protected ItemExportServiceImpl() {
|
||||
|
||||
@@ -125,11 +132,11 @@ public class ItemExportServiceImpl implements ItemExportService {
|
||||
}
|
||||
}
|
||||
|
||||
System.out.println("Beginning export");
|
||||
logInfo("Beginning export");
|
||||
|
||||
while (i.hasNext()) {
|
||||
if (SUBDIR_LIMIT > 0 && ++counter == SUBDIR_LIMIT) {
|
||||
subdir = Integer.valueOf(subDirSuffix++).toString();
|
||||
subdir = Integer.toString(subDirSuffix++);
|
||||
fullPath = destDirName + File.separatorChar + subdir;
|
||||
counter = 0;
|
||||
|
||||
@@ -138,7 +145,7 @@ public class ItemExportServiceImpl implements ItemExportService {
|
||||
}
|
||||
}
|
||||
|
||||
System.out.println("Exporting item to " + mySequenceNumber);
|
||||
logInfo("Exporting item to " + mySequenceNumber);
|
||||
Item item = i.next();
|
||||
exportItem(c, item, fullPath, mySequenceNumber, migrate, excludeBitstreams);
|
||||
c.uncacheEntity(item);
|
||||
@@ -154,7 +161,7 @@ public class ItemExportServiceImpl implements ItemExportService {
|
||||
// now create a subdirectory
|
||||
File itemDir = new File(destDir + "/" + seqStart);
|
||||
|
||||
System.out.println("Exporting Item " + myItem.getID() +
|
||||
logInfo("Exporting Item " + myItem.getID() +
|
||||
(myItem.getHandle() != null ? ", handle " + myItem.getHandle() : "") +
|
||||
" to " + itemDir);
|
||||
|
||||
@@ -167,6 +174,7 @@ 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);
|
||||
}
|
||||
@@ -191,7 +199,7 @@ public class ItemExportServiceImpl implements ItemExportService {
|
||||
*/
|
||||
protected void writeMetadata(Context c, Item i, File destDir, boolean migrate)
|
||||
throws Exception {
|
||||
Set<String> schemas = new HashSet<String>();
|
||||
Set<String> schemas = new HashSet<>();
|
||||
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 +232,7 @@ public class ItemExportServiceImpl implements ItemExportService {
|
||||
|
||||
File outFile = new File(destDir, filename);
|
||||
|
||||
System.out.println("Attempting to create file " + outFile);
|
||||
logInfo("Attempting to create file " + outFile);
|
||||
|
||||
if (outFile.createNewFile()) {
|
||||
BufferedOutputStream out = new BufferedOutputStream(
|
||||
@@ -267,7 +275,7 @@ 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)) ||
|
||||
@@ -292,10 +300,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")
|
||||
@@ -330,7 +338,7 @@ public class ItemExportServiceImpl implements ItemExportService {
|
||||
File outFile = new File(destDir, filename);
|
||||
|
||||
if (outFile.createNewFile()) {
|
||||
PrintWriter out = new PrintWriter(new FileWriter(outFile));
|
||||
PrintWriter out = new PrintWriter(new FileWriter(outFile, StandardCharsets.UTF_8));
|
||||
|
||||
out.println(i.getHandle());
|
||||
|
||||
@@ -342,6 +350,33 @@ 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.
|
||||
@@ -360,7 +395,7 @@ public class ItemExportServiceImpl implements ItemExportService {
|
||||
File outFile = new File(destDir, "contents");
|
||||
|
||||
if (outFile.createNewFile()) {
|
||||
PrintWriter out = new PrintWriter(new FileWriter(outFile));
|
||||
PrintWriter out = new PrintWriter(new FileWriter(outFile, StandardCharsets.UTF_8));
|
||||
|
||||
List<Bundle> bundles = i.getBundles();
|
||||
|
||||
@@ -398,7 +433,7 @@ public class ItemExportServiceImpl implements ItemExportService {
|
||||
File fdirs = new File(destDir + File.separator
|
||||
+ dirs);
|
||||
if (!fdirs.exists() && !fdirs.mkdirs()) {
|
||||
log.error("Unable to create destination directory");
|
||||
logError("Unable to create destination directory");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -455,12 +490,12 @@ public class ItemExportServiceImpl implements ItemExportService {
|
||||
|
||||
File wkDir = new File(workDir);
|
||||
if (!wkDir.exists() && !wkDir.mkdirs()) {
|
||||
log.error("Unable to create working direcory");
|
||||
logError("Unable to create working direcory");
|
||||
}
|
||||
|
||||
File dnDir = new File(destDirName);
|
||||
if (!dnDir.exists() && !dnDir.mkdirs()) {
|
||||
log.error("Unable to create destination directory");
|
||||
logError("Unable to create destination directory");
|
||||
}
|
||||
|
||||
// export the items using normal export method
|
||||
@@ -474,7 +509,7 @@ public class ItemExportServiceImpl implements ItemExportService {
|
||||
public void createDownloadableExport(DSpaceObject dso,
|
||||
Context context, boolean migrate) throws Exception {
|
||||
EPerson eperson = context.getCurrentUser();
|
||||
ArrayList<DSpaceObject> list = new ArrayList<DSpaceObject>(1);
|
||||
ArrayList<DSpaceObject> list = new ArrayList<>(1);
|
||||
list.add(dso);
|
||||
processDownloadableExport(list, context, eperson == null ? null
|
||||
: eperson.getEmail(), migrate);
|
||||
@@ -491,7 +526,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<DSpaceObject>(1);
|
||||
ArrayList<DSpaceObject> list = new ArrayList<>(1);
|
||||
list.add(dso);
|
||||
processDownloadableExport(list, context, additionalEmail, migrate);
|
||||
}
|
||||
@@ -629,11 +664,9 @@ public class ItemExportServiceImpl implements ItemExportService {
|
||||
Thread go = new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
Context context = null;
|
||||
Context context = new Context();
|
||||
Iterator<Item> iitems = null;
|
||||
try {
|
||||
// create a new dspace context
|
||||
context = new Context();
|
||||
// ignore auths
|
||||
context.turnOffAuthorisationSystem();
|
||||
|
||||
@@ -645,14 +678,14 @@ public class ItemExportServiceImpl implements ItemExportService {
|
||||
String downloadDir = getExportDownloadDirectory(eperson);
|
||||
File dnDir = new File(downloadDir);
|
||||
if (!dnDir.exists() && !dnDir.mkdirs()) {
|
||||
log.error("Unable to create download directory");
|
||||
logError("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<Item>();
|
||||
List<Item> items = new ArrayList<>();
|
||||
for (UUID uuid : uuids) {
|
||||
items.add(itemService.find(context, uuid));
|
||||
}
|
||||
@@ -664,7 +697,7 @@ public class ItemExportServiceImpl implements ItemExportService {
|
||||
|
||||
File wkDir = new File(workDir);
|
||||
if (!wkDir.exists() && !wkDir.mkdirs()) {
|
||||
log.error("Unable to create working directory");
|
||||
logError("Unable to create working directory");
|
||||
}
|
||||
|
||||
|
||||
@@ -755,7 +788,8 @@ public class ItemExportServiceImpl implements ItemExportService {
|
||||
throw new Exception(
|
||||
"A dspace.cfg entry for 'org.dspace.app.itemexport.work.dir' does not exist.");
|
||||
}
|
||||
return exportDir;
|
||||
// clean work dir path from duplicate separators
|
||||
return StringUtils.replace(exportDir, File.separator + File.separator, File.separator);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -876,14 +910,14 @@ public class ItemExportServiceImpl implements ItemExportService {
|
||||
.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()) {
|
||||
log.error("Unable to delete export file");
|
||||
logError("Unable to delete export file");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -896,7 +930,7 @@ public class ItemExportServiceImpl implements ItemExportService {
|
||||
int hours = configurationService.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(configurationService.getProperty("org.dspace.app.itemexport.download.dir"));
|
||||
if (downloadDir.exists()) {
|
||||
// Get a list of all the sub-directories, potentially one for each ePerson.
|
||||
@@ -907,7 +941,7 @@ public class ItemExportServiceImpl implements ItemExportService {
|
||||
for (File file : files) {
|
||||
if (file.lastModified() < now.getTimeInMillis()) {
|
||||
if (!file.delete()) {
|
||||
log.error("Unable to delete old files");
|
||||
logError("Unable to delete old files");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -915,7 +949,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()) {
|
||||
log.error("Unable to delete directory");
|
||||
logError("Unable to delete directory");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -936,14 +970,14 @@ public class ItemExportServiceImpl implements ItemExportService {
|
||||
|
||||
email.send();
|
||||
} catch (Exception e) {
|
||||
log.warn(LogManager.getHeader(context, "emailSuccessMessage", "cannot notify user of export"), e);
|
||||
logWarn(LogHelper.getHeader(context, "emailSuccessMessage", "cannot notify user of export"), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void emailErrorMessage(EPerson eperson, String error)
|
||||
throws MessagingException {
|
||||
log.warn("An error occurred during item export, the user will be notified. " + error);
|
||||
logWarn("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"));
|
||||
@@ -953,7 +987,7 @@ public class ItemExportServiceImpl implements ItemExportService {
|
||||
|
||||
email.send();
|
||||
} catch (Exception e) {
|
||||
log.warn("error during item export error notification", e);
|
||||
logWarn("error during item export error notification", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -968,7 +1002,7 @@ public class ItemExportServiceImpl implements ItemExportService {
|
||||
}
|
||||
File targetFile = new File(tempFileName);
|
||||
if (!targetFile.createNewFile()) {
|
||||
log.warn("Target file already exists: " + targetFile.getName());
|
||||
logWarn("Target file already exists: " + targetFile.getName());
|
||||
}
|
||||
|
||||
FileOutputStream fos = new FileOutputStream(tempFileName);
|
||||
@@ -984,7 +1018,7 @@ public class ItemExportServiceImpl implements ItemExportService {
|
||||
|
||||
deleteDirectory(cpFile);
|
||||
if (!targetFile.renameTo(new File(target))) {
|
||||
log.error("Unable to rename file");
|
||||
logError("Unable to rename file");
|
||||
}
|
||||
} finally {
|
||||
if (cpZipOutputStream != null) {
|
||||
@@ -1017,8 +1051,11 @@ public class ItemExportServiceImpl implements ItemExportService {
|
||||
return;
|
||||
}
|
||||
String strAbsPath = cpFile.getPath();
|
||||
String strZipEntryName = strAbsPath.substring(strSource
|
||||
.length() + 1, strAbsPath.length());
|
||||
int startIndex = strSource.length();
|
||||
if (!StringUtils.endsWith(strSource, File.separator)) {
|
||||
startIndex++;
|
||||
}
|
||||
String strZipEntryName = strAbsPath.substring(startIndex, strAbsPath.length());
|
||||
|
||||
// byte[] b = new byte[ (int)(cpFile.length()) ];
|
||||
|
||||
@@ -1057,7 +1094,7 @@ public class ItemExportServiceImpl implements ItemExportService {
|
||||
deleteDirectory(file);
|
||||
} else {
|
||||
if (!file.delete()) {
|
||||
log.error("Unable to delete file: " + file.getName());
|
||||
logError("Unable to delete file: " + file.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1066,4 +1103,64 @@ 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,6 +17,7 @@ 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
|
||||
@@ -267,4 +268,10 @@ public interface ItemExportService {
|
||||
*/
|
||||
public void zip(String strSource, String target) throws Exception;
|
||||
|
||||
/**
|
||||
* Set the DSpace Runnable Handler
|
||||
* @param handler
|
||||
*/
|
||||
public void setHandler(DSpaceRunnableHandler handler);
|
||||
|
||||
}
|
||||
|
@@ -0,0 +1,378 @@
|
||||
/**
|
||||
* 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.itemimport;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.apache.commons.cli.ParseException;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.dspace.app.itemimport.factory.ItemImportServiceFactory;
|
||||
import org.dspace.app.itemimport.service.ItemImportService;
|
||||
import org.dspace.authorize.AuthorizeException;
|
||||
import org.dspace.content.Collection;
|
||||
import org.dspace.content.factory.ContentServiceFactory;
|
||||
import org.dspace.content.service.CollectionService;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Import items into DSpace. The conventional use is upload files by copying
|
||||
* them. DSpace writes the item's bitstreams into its assetstore. Metadata is
|
||||
* also loaded to the DSpace database.
|
||||
* <P>
|
||||
* A second use assumes the bitstream files already exist in a storage
|
||||
* resource accessible to DSpace. In this case the bitstreams are 'registered'.
|
||||
* That is, the metadata is loaded to the DSpace database and DSpace is given
|
||||
* the location of the file which is subsumed into DSpace.
|
||||
* <P>
|
||||
* The distinction is controlled by the format of lines in the 'contents' file.
|
||||
* See comments in processContentsFile() below.
|
||||
* <P>
|
||||
* Modified by David Little, UCSD Libraries 12/21/04 to
|
||||
* allow the registration of files (bitstreams) into DSpace.
|
||||
*/
|
||||
public class ItemImport extends DSpaceRunnable<ItemImportScriptConfiguration> {
|
||||
|
||||
public static String TEMP_DIR = "importSAF";
|
||||
public static String MAPFILE_FILENAME = "mapfile";
|
||||
public static String MAPFILE_BITSTREAM_TYPE = "importSAFMapfile";
|
||||
|
||||
protected boolean template = false;
|
||||
protected String command = null;
|
||||
protected String sourcedir = null;
|
||||
protected String mapfile = null;
|
||||
protected String eperson = null;
|
||||
protected String[] collections = null;
|
||||
protected boolean isTest = false;
|
||||
protected boolean isExcludeContent = false;
|
||||
protected boolean isResume = false;
|
||||
protected boolean useWorkflow = false;
|
||||
protected boolean useWorkflowSendEmail = false;
|
||||
protected boolean isQuiet = false;
|
||||
protected boolean commandLineCollections = false;
|
||||
protected boolean zip = false;
|
||||
protected String zipfilename = null;
|
||||
protected boolean help = false;
|
||||
protected File workDir = null;
|
||||
private File workFile = null;
|
||||
|
||||
protected static final CollectionService collectionService =
|
||||
ContentServiceFactory.getInstance().getCollectionService();
|
||||
protected static final EPersonService epersonService =
|
||||
EPersonServiceFactory.getInstance().getEPersonService();
|
||||
protected static final HandleService handleService =
|
||||
HandleServiceFactory.getInstance().getHandleService();
|
||||
|
||||
@Override
|
||||
public ItemImportScriptConfiguration getScriptConfiguration() {
|
||||
return new DSpace().getServiceManager()
|
||||
.getServiceByName("import", ItemImportScriptConfiguration.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setup() throws ParseException {
|
||||
help = commandLine.hasOption('h');
|
||||
|
||||
if (commandLine.hasOption('a')) {
|
||||
command = "add";
|
||||
}
|
||||
|
||||
if (commandLine.hasOption('r')) {
|
||||
command = "replace";
|
||||
}
|
||||
|
||||
if (commandLine.hasOption('d')) {
|
||||
command = "delete";
|
||||
}
|
||||
|
||||
if (commandLine.hasOption('w')) {
|
||||
useWorkflow = true;
|
||||
if (commandLine.hasOption('n')) {
|
||||
useWorkflowSendEmail = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (commandLine.hasOption('v')) {
|
||||
isTest = true;
|
||||
handler.logInfo("**Test Run** - not actually importing items.");
|
||||
}
|
||||
|
||||
isExcludeContent = commandLine.hasOption('x');
|
||||
|
||||
if (commandLine.hasOption('p')) {
|
||||
template = true;
|
||||
}
|
||||
|
||||
if (commandLine.hasOption('c')) { // collections
|
||||
collections = commandLine.getOptionValues('c');
|
||||
commandLineCollections = true;
|
||||
} else {
|
||||
handler.logInfo("No collections given. Assuming 'collections' file inside item directory");
|
||||
}
|
||||
|
||||
if (commandLine.hasOption('R')) {
|
||||
isResume = true;
|
||||
handler.logInfo("**Resume import** - attempting to import items not already imported");
|
||||
}
|
||||
|
||||
if (commandLine.hasOption('q')) {
|
||||
isQuiet = true;
|
||||
}
|
||||
|
||||
setZip();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void internalRun() throws Exception {
|
||||
if (help) {
|
||||
printHelp();
|
||||
return;
|
||||
}
|
||||
|
||||
Date startTime = new Date();
|
||||
Context context = new Context(Context.Mode.BATCH_EDIT);
|
||||
|
||||
setMapFile();
|
||||
|
||||
validate(context);
|
||||
|
||||
setEPerson(context);
|
||||
|
||||
// check collection
|
||||
List<Collection> mycollections = null;
|
||||
// don't need to validate collections set if command is "delete"
|
||||
// also if no collections are given in the command line
|
||||
if (!"delete".equals(command) && commandLineCollections) {
|
||||
handler.logInfo("Destination collections:");
|
||||
|
||||
mycollections = new ArrayList<>();
|
||||
|
||||
// validate each collection arg to see if it's a real collection
|
||||
for (int i = 0; i < collections.length; i++) {
|
||||
Collection collection = null;
|
||||
if (collections[i] != null) {
|
||||
// is the ID a handle?
|
||||
if (collections[i].indexOf('/') != -1) {
|
||||
// string has a / so it must be a handle - try and resolve
|
||||
// it
|
||||
collection = ((Collection) handleService
|
||||
.resolveToObject(context, collections[i]));
|
||||
} else {
|
||||
// not a handle, try and treat it as an integer collection database ID
|
||||
collection = collectionService.find(context, UUID.fromString(collections[i]));
|
||||
}
|
||||
}
|
||||
|
||||
// was the collection valid?
|
||||
if (collection == null
|
||||
|| collection.getType() != Constants.COLLECTION) {
|
||||
throw new IllegalArgumentException("Cannot resolve "
|
||||
+ collections[i] + " to collection");
|
||||
}
|
||||
|
||||
// add resolved collection to list
|
||||
mycollections.add(collection);
|
||||
|
||||
// print progress info
|
||||
handler.logInfo((i == 0 ? "Owning " : "") + "Collection: " + collection.getName());
|
||||
}
|
||||
}
|
||||
// end validation
|
||||
|
||||
// start
|
||||
ItemImportService itemImportService = ItemImportServiceFactory.getInstance()
|
||||
.getItemImportService();
|
||||
try {
|
||||
itemImportService.setTest(isTest);
|
||||
itemImportService.setExcludeContent(isExcludeContent);
|
||||
itemImportService.setResume(isResume);
|
||||
itemImportService.setUseWorkflow(useWorkflow);
|
||||
itemImportService.setUseWorkflowSendEmail(useWorkflowSendEmail);
|
||||
itemImportService.setQuiet(isQuiet);
|
||||
itemImportService.setHandler(handler);
|
||||
|
||||
try {
|
||||
context.turnOffAuthorisationSystem();
|
||||
|
||||
readZip(context, itemImportService);
|
||||
|
||||
process(context, itemImportService, mycollections);
|
||||
|
||||
// complete all transactions
|
||||
context.complete();
|
||||
} catch (Exception e) {
|
||||
context.abort();
|
||||
throw new Exception(
|
||||
"Error committing changes to database: " + e.getMessage() + ", aborting most recent changes", e);
|
||||
}
|
||||
|
||||
if (isTest) {
|
||||
handler.logInfo("***End of Test Run***");
|
||||
}
|
||||
} finally {
|
||||
// clean work dir
|
||||
if (zip) {
|
||||
FileUtils.deleteDirectory(new File(sourcedir));
|
||||
FileUtils.deleteDirectory(workDir);
|
||||
}
|
||||
|
||||
Date endTime = new Date();
|
||||
handler.logInfo("Started: " + startTime.getTime());
|
||||
handler.logInfo("Ended: " + endTime.getTime());
|
||||
handler.logInfo(
|
||||
"Elapsed time: " + ((endTime.getTime() - startTime.getTime()) / 1000) + " secs (" + (endTime
|
||||
.getTime() - startTime.getTime()) + " msecs)");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the options
|
||||
* @param context
|
||||
*/
|
||||
protected void validate(Context context) {
|
||||
if (command == null) {
|
||||
handler.logError("Must run with either add, replace, or remove (run with -h flag for details)");
|
||||
throw new UnsupportedOperationException("Must run with either add, replace, or remove");
|
||||
}
|
||||
|
||||
// can only resume for adds
|
||||
if (isResume && !"add".equals(command)) {
|
||||
handler.logError("Resume option only works with the --add command (run with -h flag for details)");
|
||||
throw new UnsupportedOperationException("Resume option only works with the --add command");
|
||||
}
|
||||
|
||||
if (isResume && StringUtils.isBlank(mapfile)) {
|
||||
handler.logError("The mapfile does not exist. ");
|
||||
throw new UnsupportedOperationException("The mapfile does not exist");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the import
|
||||
* @param context
|
||||
* @param itemImportService
|
||||
* @param collections
|
||||
* @throws Exception
|
||||
*/
|
||||
protected void process(Context context, ItemImportService itemImportService,
|
||||
List<Collection> collections) throws Exception {
|
||||
readMapfile(context);
|
||||
|
||||
if ("add".equals(command)) {
|
||||
itemImportService.addItems(context, collections, sourcedir, mapfile, template);
|
||||
} else if ("replace".equals(command)) {
|
||||
itemImportService.replaceItems(context, collections, sourcedir, mapfile, template);
|
||||
} else if ("delete".equals(command)) {
|
||||
itemImportService.deleteItems(context, mapfile);
|
||||
}
|
||||
|
||||
// write input stream on handler
|
||||
File mapFile = new File(mapfile);
|
||||
try (InputStream mapfileInputStream = new FileInputStream(mapFile)) {
|
||||
handler.writeFilestream(context, MAPFILE_FILENAME, mapfileInputStream, MAPFILE_BITSTREAM_TYPE);
|
||||
} finally {
|
||||
mapFile.delete();
|
||||
workFile.delete();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the ZIP archive in SAF format
|
||||
* @param context
|
||||
* @param itemImportService
|
||||
* @throws Exception
|
||||
*/
|
||||
protected void readZip(Context context, ItemImportService itemImportService) throws Exception {
|
||||
Optional<InputStream> optionalFileStream = handler.getFileStream(context, zipfilename);
|
||||
if (optionalFileStream.isPresent()) {
|
||||
workFile = new File(itemImportService.getTempWorkDir() + File.separator
|
||||
+ zipfilename + "-" + context.getCurrentUser().getID());
|
||||
FileUtils.copyInputStreamToFile(optionalFileStream.get(), workFile);
|
||||
workDir = new File(itemImportService.getTempWorkDir() + File.separator + TEMP_DIR);
|
||||
sourcedir = itemImportService.unzip(workFile, workDir.getAbsolutePath());
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
"Error reading file, the file couldn't be found for filename: " + zipfilename);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the mapfile
|
||||
* @param context
|
||||
*/
|
||||
protected void readMapfile(Context context) {
|
||||
if (isResume) {
|
||||
try {
|
||||
Optional<InputStream> optionalFileStream = handler.getFileStream(context, mapfile);
|
||||
if (optionalFileStream.isPresent()) {
|
||||
File tempFile = File.createTempFile(mapfile, "temp");
|
||||
tempFile.deleteOnExit();
|
||||
FileUtils.copyInputStreamToFile(optionalFileStream.get(), tempFile);
|
||||
mapfile = tempFile.getAbsolutePath();
|
||||
}
|
||||
} catch (IOException | AuthorizeException e) {
|
||||
throw new UnsupportedOperationException("The mapfile does not exist");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the mapfile option
|
||||
* @throws IOException
|
||||
*/
|
||||
protected void setMapFile() throws IOException {
|
||||
if (isResume && commandLine.hasOption('m')) {
|
||||
mapfile = commandLine.getOptionValue('m');
|
||||
} else {
|
||||
mapfile = Files.createTempFile(MAPFILE_FILENAME, "temp").toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the zip option
|
||||
*/
|
||||
protected void setZip() {
|
||||
zip = true;
|
||||
zipfilename = commandLine.getOptionValue('z');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the eperson in the context
|
||||
* @param context
|
||||
* @throws SQLException
|
||||
*/
|
||||
protected 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);
|
||||
}
|
||||
}
|
@@ -0,0 +1,143 @@
|
||||
/**
|
||||
* 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.itemimport;
|
||||
|
||||
import java.io.File;
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.dspace.app.itemimport.service.ItemImportService;
|
||||
import org.dspace.content.Collection;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.eperson.EPerson;
|
||||
|
||||
/**
|
||||
* CLI variant for the {@link ItemImport} class.
|
||||
* This was done to specify the specific behaviors for the CLI.
|
||||
*
|
||||
* @author Francesco Pio Scognamiglio (francescopio.scognamiglio at 4science.com)
|
||||
*/
|
||||
public class ItemImportCLI extends ItemImport {
|
||||
|
||||
@Override
|
||||
protected void validate(Context context) {
|
||||
// can only resume for adds
|
||||
if (isResume && !"add".equals(command)) {
|
||||
handler.logError("Resume option only works with the --add command (run with -h flag for details)");
|
||||
throw new UnsupportedOperationException("Resume option only works with the --add command");
|
||||
}
|
||||
|
||||
if (commandLine.hasOption('e')) {
|
||||
eperson = commandLine.getOptionValue('e');
|
||||
}
|
||||
|
||||
// check eperson identifier (email or id)
|
||||
if (eperson == null) {
|
||||
handler.logError("An eperson to do the importing must be specified (run with -h flag for details)");
|
||||
throw new UnsupportedOperationException("An eperson to do the importing must be specified");
|
||||
}
|
||||
|
||||
File myFile = null;
|
||||
try {
|
||||
myFile = new File(mapfile);
|
||||
} catch (Exception e) {
|
||||
throw new UnsupportedOperationException("The mapfile " + mapfile + " does not exist");
|
||||
}
|
||||
|
||||
if (!isResume && "add".equals(command) && myFile.exists()) {
|
||||
handler.logError("The mapfile " + mapfile + " already exists. "
|
||||
+ "Either delete it or use --resume if attempting to resume an aborted import. "
|
||||
+ "(run with -h flag for details)");
|
||||
throw new UnsupportedOperationException("The mapfile " + mapfile + " already exists");
|
||||
}
|
||||
|
||||
if (command == null) {
|
||||
handler.logError("Must run with either add, replace, or remove (run with -h flag for details)");
|
||||
throw new UnsupportedOperationException("Must run with either add, replace, or remove");
|
||||
} else if ("add".equals(command) || "replace".equals(command)) {
|
||||
if (sourcedir == null) {
|
||||
handler.logError("A source directory containing items must be set (run with -h flag for details)");
|
||||
throw new UnsupportedOperationException("A source directory containing items must be set");
|
||||
}
|
||||
|
||||
if (mapfile == null) {
|
||||
handler.logError(
|
||||
"A map file to hold importing results must be specified (run with -h flag for details)");
|
||||
throw new UnsupportedOperationException("A map file to hold importing results must be specified");
|
||||
}
|
||||
} else if ("delete".equals(command)) {
|
||||
if (mapfile == null) {
|
||||
handler.logError("A map file must be specified (run with -h flag for details)");
|
||||
throw new UnsupportedOperationException("A map file must be specified");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void process(Context context, ItemImportService itemImportService,
|
||||
List<Collection> collections) throws Exception {
|
||||
if ("add".equals(command)) {
|
||||
itemImportService.addItems(context, collections, sourcedir, mapfile, template);
|
||||
} else if ("replace".equals(command)) {
|
||||
itemImportService.replaceItems(context, collections, sourcedir, mapfile, template);
|
||||
} else if ("delete".equals(command)) {
|
||||
itemImportService.deleteItems(context, mapfile);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void readZip(Context context, ItemImportService itemImportService) throws Exception {
|
||||
// If this is a zip archive, unzip it first
|
||||
if (zip) {
|
||||
workDir = new File(itemImportService.getTempWorkDir() + File.separator + TEMP_DIR
|
||||
+ File.separator + context.getCurrentUser().getID());
|
||||
sourcedir = itemImportService.unzip(
|
||||
new File(sourcedir + File.separator + zipfilename), workDir.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setMapFile() {
|
||||
if (commandLine.hasOption('m')) {
|
||||
mapfile = commandLine.getOptionValue('m');
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setZip() {
|
||||
if (commandLine.hasOption('s')) { // source
|
||||
sourcedir = commandLine.getOptionValue('s');
|
||||
}
|
||||
|
||||
if (commandLine.hasOption('z')) {
|
||||
zip = true;
|
||||
zipfilename = commandLine.getOptionValue('z');
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setEPerson(Context context) throws SQLException {
|
||||
EPerson myEPerson = null;
|
||||
if (StringUtils.contains(eperson, '@')) {
|
||||
// @ sign, must be an email
|
||||
myEPerson = epersonService.findByEmail(context, eperson);
|
||||
} else {
|
||||
myEPerson = epersonService.find(context, UUID.fromString(eperson));
|
||||
}
|
||||
|
||||
// check 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);
|
||||
}
|
||||
}
|
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* 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.itemimport;
|
||||
|
||||
import org.apache.commons.cli.Option;
|
||||
import org.apache.commons.cli.Options;
|
||||
import org.dspace.scripts.configuration.ScriptConfiguration;
|
||||
|
||||
/**
|
||||
* The {@link ScriptConfiguration} for the {@link ItemImportCLI} script
|
||||
*
|
||||
* @author Francesco Pio Scognamiglio (francescopio.scognamiglio at 4science.com)
|
||||
*/
|
||||
public class ItemImportCLIScriptConfiguration extends ItemImportScriptConfiguration<ItemImportCLI> {
|
||||
|
||||
@Override
|
||||
public Options getOptions() {
|
||||
Options options = new Options();
|
||||
|
||||
options.addOption(Option.builder("a").longOpt("add")
|
||||
.desc("add items to DSpace")
|
||||
.hasArg(false).required(false).build());
|
||||
options.addOption(Option.builder("r").longOpt("replace")
|
||||
.desc("replace items in mapfile")
|
||||
.hasArg(false).required(false).build());
|
||||
options.addOption(Option.builder("d").longOpt("delete")
|
||||
.desc("delete items listed in mapfile")
|
||||
.hasArg(false).required(false).build());
|
||||
options.addOption(Option.builder("s").longOpt("source")
|
||||
.desc("source of items (directory)")
|
||||
.hasArg().required(false).build());
|
||||
options.addOption(Option.builder("z").longOpt("zip")
|
||||
.desc("name of zip file")
|
||||
.hasArg().required(false).build());
|
||||
options.addOption(Option.builder("c").longOpt("collection")
|
||||
.desc("destination collection(s) Handle or database ID")
|
||||
.hasArg().required(false).build());
|
||||
options.addOption(Option.builder("m").longOpt("mapfile")
|
||||
.desc("mapfile items in mapfile")
|
||||
.hasArg().required().build());
|
||||
options.addOption(Option.builder("e").longOpt("eperson")
|
||||
.desc("email of eperson doing importing")
|
||||
.hasArg().required().build());
|
||||
options.addOption(Option.builder("w").longOpt("workflow")
|
||||
.desc("send submission through collection's workflow")
|
||||
.hasArg(false).required(false).build());
|
||||
options.addOption(Option.builder("n").longOpt("notify")
|
||||
.desc("if sending submissions through the workflow, send notification emails")
|
||||
.hasArg(false).required(false).build());
|
||||
options.addOption(Option.builder("v").longOpt("validate")
|
||||
.desc("test run - do not actually import items")
|
||||
.hasArg(false).required(false).build());
|
||||
options.addOption(Option.builder("x").longOpt("exclude-bitstreams")
|
||||
.desc("do not load or expect content bitstreams")
|
||||
.hasArg(false).required(false).build());
|
||||
options.addOption(Option.builder("p").longOpt("template")
|
||||
.desc("apply template")
|
||||
.hasArg(false).required(false).build());
|
||||
options.addOption(Option.builder("R").longOpt("resume")
|
||||
.desc("resume a failed import (add only)")
|
||||
.hasArg(false).required(false).build());
|
||||
options.addOption(Option.builder("q").longOpt("quiet")
|
||||
.desc("don't display metadata")
|
||||
.hasArg(false).required(false).build());
|
||||
|
||||
options.addOption(Option.builder("h").longOpt("help")
|
||||
.desc("help")
|
||||
.hasArg(false).required(false).build());
|
||||
|
||||
return options;
|
||||
}
|
||||
}
|
@@ -1,429 +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.itemimport;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
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.DefaultParser;
|
||||
import org.apache.commons.cli.HelpFormatter;
|
||||
import org.apache.commons.cli.Options;
|
||||
import org.dspace.app.itemimport.factory.ItemImportServiceFactory;
|
||||
import org.dspace.app.itemimport.service.ItemImportService;
|
||||
import org.dspace.content.Collection;
|
||||
import org.dspace.content.factory.ContentServiceFactory;
|
||||
import org.dspace.content.service.CollectionService;
|
||||
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 items into DSpace. The conventional use is upload files by copying
|
||||
* them. DSpace writes the item's bitstreams into its assetstore. Metadata is
|
||||
* also loaded to the DSpace database.
|
||||
* <P>
|
||||
* A second use assumes the bitstream files already exist in a storage
|
||||
* resource accessible to DSpace. In this case the bitstreams are 'registered'.
|
||||
* That is, the metadata is loaded to the DSpace database and DSpace is given
|
||||
* the location of the file which is subsumed into DSpace.
|
||||
* <P>
|
||||
* The distinction is controlled by the format of lines in the 'contents' file.
|
||||
* See comments in processContentsFile() below.
|
||||
* <P>
|
||||
* Modified by David Little, UCSD Libraries 12/21/04 to
|
||||
* allow the registration of files (bitstreams) into DSpace.
|
||||
*/
|
||||
public class ItemImportCLITool {
|
||||
|
||||
private static boolean template = false;
|
||||
|
||||
private static final CollectionService collectionService = ContentServiceFactory.getInstance()
|
||||
.getCollectionService();
|
||||
private static final EPersonService epersonService = EPersonServiceFactory.getInstance().getEPersonService();
|
||||
private static final HandleService handleService = HandleServiceFactory.getInstance().getHandleService();
|
||||
|
||||
/**
|
||||
* Default constructor
|
||||
*/
|
||||
private ItemImportCLITool() { }
|
||||
|
||||
public static void main(String[] argv) throws Exception {
|
||||
Date startTime = new Date();
|
||||
int status = 0;
|
||||
|
||||
try {
|
||||
// create an options object and populate it
|
||||
CommandLineParser parser = new DefaultParser();
|
||||
|
||||
Options options = new Options();
|
||||
|
||||
options.addOption("a", "add", false, "add items to DSpace");
|
||||
options.addOption("r", "replace", false, "replace items in mapfile");
|
||||
options.addOption("d", "delete", false,
|
||||
"delete items listed in mapfile");
|
||||
options.addOption("i", "inputtype", true, "input type in case of BTE import");
|
||||
options.addOption("s", "source", true, "source of items (directory)");
|
||||
options.addOption("z", "zip", true, "name of zip file");
|
||||
options.addOption("c", "collection", true,
|
||||
"destination collection(s) Handle or database ID");
|
||||
options.addOption("m", "mapfile", true, "mapfile items in mapfile");
|
||||
options.addOption("e", "eperson", true,
|
||||
"email of eperson doing importing");
|
||||
options.addOption("w", "workflow", false,
|
||||
"send submission through collection's workflow");
|
||||
options.addOption("n", "notify", false,
|
||||
"if sending submissions through the workflow, send notification emails");
|
||||
options.addOption("t", "test", false,
|
||||
"test run - do not actually import items");
|
||||
options.addOption("p", "template", false, "apply template");
|
||||
options.addOption("R", "resume", false,
|
||||
"resume a failed import (add only)");
|
||||
options.addOption("q", "quiet", false, "don't display metadata");
|
||||
|
||||
options.addOption("h", "help", false, "help");
|
||||
|
||||
CommandLine line = parser.parse(options, argv);
|
||||
|
||||
String command = null; // add replace remove, etc
|
||||
String bteInputType = null; //ris, endnote, tsv, csv, bibtex
|
||||
String sourcedir = null;
|
||||
String mapfile = null;
|
||||
String eperson = null; // db ID or email
|
||||
String[] collections = null; // db ID or handles
|
||||
boolean isTest = false;
|
||||
boolean isResume = false;
|
||||
boolean useWorkflow = false;
|
||||
boolean useWorkflowSendEmail = false;
|
||||
boolean isQuiet = false;
|
||||
|
||||
if (line.hasOption('h')) {
|
||||
HelpFormatter myhelp = new HelpFormatter();
|
||||
myhelp.printHelp("ItemImport\n", options);
|
||||
System.out
|
||||
.println("\nadding items: ItemImport -a -e eperson -c collection -s sourcedir -m mapfile");
|
||||
System.out
|
||||
.println(
|
||||
"\nadding items from zip file: ItemImport -a -e eperson -c collection -s sourcedir -z " +
|
||||
"filename.zip -m mapfile");
|
||||
System.out
|
||||
.println("replacing items: ItemImport -r -e eperson -c collection -s sourcedir -m mapfile");
|
||||
System.out
|
||||
.println("deleting items: ItemImport -d -e eperson -m mapfile");
|
||||
System.out
|
||||
.println(
|
||||
"If multiple collections are specified, the first collection will be the one that owns the " +
|
||||
"item.");
|
||||
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
if (line.hasOption('a')) {
|
||||
command = "add";
|
||||
}
|
||||
|
||||
if (line.hasOption('r')) {
|
||||
command = "replace";
|
||||
}
|
||||
|
||||
if (line.hasOption('d')) {
|
||||
command = "delete";
|
||||
}
|
||||
|
||||
if (line.hasOption('b')) {
|
||||
command = "add-bte";
|
||||
}
|
||||
|
||||
if (line.hasOption('i')) {
|
||||
bteInputType = line.getOptionValue('i');
|
||||
}
|
||||
|
||||
if (line.hasOption('w')) {
|
||||
useWorkflow = true;
|
||||
if (line.hasOption('n')) {
|
||||
useWorkflowSendEmail = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (line.hasOption('t')) {
|
||||
isTest = true;
|
||||
System.out.println("**Test Run** - not actually importing items.");
|
||||
}
|
||||
|
||||
if (line.hasOption('p')) {
|
||||
template = true;
|
||||
}
|
||||
|
||||
if (line.hasOption('s')) { // source
|
||||
sourcedir = line.getOptionValue('s');
|
||||
}
|
||||
|
||||
if (line.hasOption('m')) { // mapfile
|
||||
mapfile = line.getOptionValue('m');
|
||||
}
|
||||
|
||||
if (line.hasOption('e')) { // eperson
|
||||
eperson = line.getOptionValue('e');
|
||||
}
|
||||
|
||||
if (line.hasOption('c')) { // collections
|
||||
collections = line.getOptionValues('c');
|
||||
}
|
||||
|
||||
if (line.hasOption('R')) {
|
||||
isResume = true;
|
||||
System.out
|
||||
.println("**Resume import** - attempting to import items not already imported");
|
||||
}
|
||||
|
||||
if (line.hasOption('q')) {
|
||||
isQuiet = true;
|
||||
}
|
||||
|
||||
boolean zip = false;
|
||||
String zipfilename = "";
|
||||
if (line.hasOption('z')) {
|
||||
zip = true;
|
||||
zipfilename = line.getOptionValue('z');
|
||||
}
|
||||
|
||||
//By default assume collections will be given on the command line
|
||||
boolean commandLineCollections = true;
|
||||
// now validate
|
||||
// must have a command set
|
||||
if (command == null) {
|
||||
System.out
|
||||
.println("Error - must run with either add, replace, or remove (run with -h flag for details)");
|
||||
System.exit(1);
|
||||
} else if ("add".equals(command) || "replace".equals(command)) {
|
||||
if (sourcedir == null) {
|
||||
System.out
|
||||
.println("Error - a source directory containing items must be set");
|
||||
System.out.println(" (run with -h flag for details)");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
if (mapfile == null) {
|
||||
System.out
|
||||
.println("Error - a map file to hold importing results must be specified");
|
||||
System.out.println(" (run with -h flag for details)");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
if (eperson == null) {
|
||||
System.out
|
||||
.println("Error - an eperson to do the importing must be specified");
|
||||
System.out.println(" (run with -h flag for details)");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
if (collections == null) {
|
||||
System.out.println("No collections given. Assuming 'collections' file inside item directory");
|
||||
commandLineCollections = false;
|
||||
}
|
||||
} else if ("add-bte".equals(command)) {
|
||||
//Source dir can be null, the user can specify the parameters for his loader in the Spring XML
|
||||
// configuration file
|
||||
|
||||
if (mapfile == null) {
|
||||
System.out
|
||||
.println("Error - a map file to hold importing results must be specified");
|
||||
System.out.println(" (run with -h flag for details)");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
if (eperson == null) {
|
||||
System.out
|
||||
.println("Error - an eperson to do the importing must be specified");
|
||||
System.out.println(" (run with -h flag for details)");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
if (collections == null) {
|
||||
System.out.println("No collections given. Assuming 'collections' file inside item directory");
|
||||
commandLineCollections = false;
|
||||
}
|
||||
|
||||
if (bteInputType == null) {
|
||||
System.out
|
||||
.println(
|
||||
"Error - an input type (tsv, csv, ris, endnote, bibtex or any other type you have " +
|
||||
"specified in BTE Spring XML configuration file) must be specified");
|
||||
System.out.println(" (run with -h flag for details)");
|
||||
System.exit(1);
|
||||
}
|
||||
} else if ("delete".equals(command)) {
|
||||
if (eperson == null) {
|
||||
System.out
|
||||
.println("Error - an eperson to do the importing must be specified");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
if (mapfile == null) {
|
||||
System.out.println("Error - a map file must be specified");
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// can only resume for adds
|
||||
if (isResume && !"add".equals(command) && !"add-bte".equals(command)) {
|
||||
System.out
|
||||
.println("Error - resume option only works with the --add or the --add-bte commands");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
// do checks around mapfile - if mapfile exists and 'add' is selected,
|
||||
// resume must be chosen
|
||||
File myFile = new File(mapfile);
|
||||
|
||||
if (!isResume && "add".equals(command) && myFile.exists()) {
|
||||
System.out.println("Error - the mapfile " + mapfile
|
||||
+ " already exists.");
|
||||
System.out
|
||||
.println("Either delete it or use --resume if attempting to resume an aborted import.");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
ItemImportService myloader = ItemImportServiceFactory.getInstance().getItemImportService();
|
||||
myloader.setTest(isTest);
|
||||
myloader.setResume(isResume);
|
||||
myloader.setUseWorkflow(useWorkflow);
|
||||
myloader.setUseWorkflowSendEmail(useWorkflowSendEmail);
|
||||
myloader.setQuiet(isQuiet);
|
||||
|
||||
// create a context
|
||||
Context c = new Context(Context.Mode.BATCH_EDIT);
|
||||
|
||||
// find the EPerson, assign to context
|
||||
EPerson myEPerson = null;
|
||||
|
||||
if (eperson.indexOf('@') != -1) {
|
||||
// @ sign, must be an email
|
||||
myEPerson = epersonService.findByEmail(c, eperson);
|
||||
} else {
|
||||
myEPerson = epersonService.find(c, UUID.fromString(eperson));
|
||||
}
|
||||
|
||||
if (myEPerson == null) {
|
||||
System.out.println("Error, eperson cannot be found: " + eperson);
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
c.setCurrentUser(myEPerson);
|
||||
|
||||
// find collections
|
||||
List<Collection> mycollections = null;
|
||||
|
||||
// don't need to validate collections set if command is "delete"
|
||||
// also if no collections are given in the command line
|
||||
if (!"delete".equals(command) && commandLineCollections) {
|
||||
System.out.println("Destination collections:");
|
||||
|
||||
mycollections = new ArrayList<>();
|
||||
|
||||
// validate each collection arg to see if it's a real collection
|
||||
for (int i = 0; i < collections.length; i++) {
|
||||
// is the ID a handle?
|
||||
if (collections[i].indexOf('/') != -1) {
|
||||
// string has a / so it must be a handle - try and resolve
|
||||
// it
|
||||
mycollections.add((Collection) handleService
|
||||
.resolveToObject(c, collections[i]));
|
||||
|
||||
// resolved, now make sure it's a collection
|
||||
if ((mycollections.get(i) == null)
|
||||
|| (mycollections.get(i).getType() != Constants.COLLECTION)) {
|
||||
mycollections.set(i, null);
|
||||
}
|
||||
} else if (collections[i] != null) {
|
||||
// not a handle, try and treat it as an integer collection database ID
|
||||
mycollections.set(i, collectionService.find(c, UUID.fromString(collections[i])));
|
||||
}
|
||||
|
||||
// was the collection valid?
|
||||
if (mycollections.get(i) == null) {
|
||||
throw new IllegalArgumentException("Cannot resolve "
|
||||
+ collections[i] + " to collection");
|
||||
}
|
||||
|
||||
// print progress info
|
||||
String owningPrefix = "";
|
||||
|
||||
if (i == 0) {
|
||||
owningPrefix = "Owning ";
|
||||
}
|
||||
|
||||
System.out.println(owningPrefix + " Collection: "
|
||||
+ mycollections.get(i).getName());
|
||||
}
|
||||
} // end of validating collections
|
||||
|
||||
try {
|
||||
// If this is a zip archive, unzip it first
|
||||
if (zip) {
|
||||
sourcedir = myloader.unzip(sourcedir, zipfilename);
|
||||
}
|
||||
|
||||
|
||||
c.turnOffAuthorisationSystem();
|
||||
|
||||
if ("add".equals(command)) {
|
||||
myloader.addItems(c, mycollections, sourcedir, mapfile, template);
|
||||
} else if ("replace".equals(command)) {
|
||||
myloader.replaceItems(c, mycollections, sourcedir, mapfile, template);
|
||||
} else if ("delete".equals(command)) {
|
||||
myloader.deleteItems(c, mapfile);
|
||||
}
|
||||
|
||||
// complete all transactions
|
||||
c.complete();
|
||||
} catch (Exception e) {
|
||||
c.abort();
|
||||
e.printStackTrace();
|
||||
System.out.println(e);
|
||||
status = 1;
|
||||
}
|
||||
|
||||
// Delete the unzipped file
|
||||
try {
|
||||
if (zip) {
|
||||
System.gc();
|
||||
System.out.println(
|
||||
"Deleting temporary zip directory: " + myloader.getTempWorkDirFile().getAbsolutePath());
|
||||
myloader.cleanupZipTemp();
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
System.out.println("Unable to delete temporary zip archive location: " + myloader.getTempWorkDirFile()
|
||||
.getAbsolutePath());
|
||||
}
|
||||
|
||||
|
||||
if (isTest) {
|
||||
System.out.println("***End of Test Run***");
|
||||
}
|
||||
} finally {
|
||||
Date endTime = new Date();
|
||||
System.out.println("Started: " + startTime.getTime());
|
||||
System.out.println("Ended: " + endTime.getTime());
|
||||
System.out.println(
|
||||
"Elapsed time: " + ((endTime.getTime() - startTime.getTime()) / 1000) + " secs (" + (endTime
|
||||
.getTime() - startTime.getTime()) + " msecs)");
|
||||
}
|
||||
|
||||
System.exit(status);
|
||||
}
|
||||
}
|
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* 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.itemimport;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.sql.SQLException;
|
||||
|
||||
import org.apache.commons.cli.Option;
|
||||
import org.apache.commons.cli.Options;
|
||||
import org.dspace.authorize.service.AuthorizeService;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.scripts.configuration.ScriptConfiguration;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
/**
|
||||
* The {@link ScriptConfiguration} for the {@link ItemImport} script
|
||||
*
|
||||
* @author Francesco Pio Scognamiglio (francescopio.scognamiglio at 4science.com)
|
||||
*/
|
||||
public class ItemImportScriptConfiguration<T extends ItemImport> extends ScriptConfiguration<T> {
|
||||
|
||||
@Autowired
|
||||
private AuthorizeService authorizeService;
|
||||
|
||||
private Class<T> dspaceRunnableClass;
|
||||
|
||||
@Override
|
||||
public Class<T> getDspaceRunnableClass() {
|
||||
return dspaceRunnableClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDspaceRunnableClass(Class<T> dspaceRunnableClass) {
|
||||
this.dspaceRunnableClass = dspaceRunnableClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAllowedToExecute(final Context context) {
|
||||
try {
|
||||
return authorizeService.isAdmin(context);
|
||||
} catch (SQLException e) {
|
||||
throw new RuntimeException("SQLException occurred when checking if the current user is an admin", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Options getOptions() {
|
||||
Options options = new Options();
|
||||
|
||||
options.addOption(Option.builder("a").longOpt("add")
|
||||
.desc("add items to DSpace")
|
||||
.hasArg(false).required(false).build());
|
||||
options.addOption(Option.builder("r").longOpt("replace")
|
||||
.desc("replace items in mapfile")
|
||||
.hasArg(false).required(false).build());
|
||||
options.addOption(Option.builder("d").longOpt("delete")
|
||||
.desc("delete items listed in mapfile")
|
||||
.hasArg(false).required(false).build());
|
||||
options.addOption(Option.builder("z").longOpt("zip")
|
||||
.desc("name of zip file")
|
||||
.type(InputStream.class)
|
||||
.hasArg().required().build());
|
||||
options.addOption(Option.builder("c").longOpt("collection")
|
||||
.desc("destination collection(s) Handle or database ID")
|
||||
.hasArg().required(false).build());
|
||||
options.addOption(Option.builder("m").longOpt("mapfile")
|
||||
.desc("mapfile items in mapfile")
|
||||
.type(InputStream.class)
|
||||
.hasArg().required(false).build());
|
||||
options.addOption(Option.builder("w").longOpt("workflow")
|
||||
.desc("send submission through collection's workflow")
|
||||
.hasArg(false).required(false).build());
|
||||
options.addOption(Option.builder("n").longOpt("notify")
|
||||
.desc("if sending submissions through the workflow, send notification emails")
|
||||
.hasArg(false).required(false).build());
|
||||
options.addOption(Option.builder("v").longOpt("validate")
|
||||
.desc("test run - do not actually import items")
|
||||
.hasArg(false).required(false).build());
|
||||
options.addOption(Option.builder("x").longOpt("exclude-bitstreams")
|
||||
.desc("do not load or expect content bitstreams")
|
||||
.hasArg(false).required(false).build());
|
||||
options.addOption(Option.builder("p").longOpt("template")
|
||||
.desc("apply template")
|
||||
.hasArg(false).required(false).build());
|
||||
options.addOption(Option.builder("R").longOpt("resume")
|
||||
.desc("resume a failed import (add only)")
|
||||
.hasArg(false).required(false).build());
|
||||
options.addOption(Option.builder("q").longOpt("quiet")
|
||||
.desc("don't display metadata")
|
||||
.hasArg(false).required(false).build());
|
||||
|
||||
options.addOption(Option.builder("h").longOpt("help")
|
||||
.desc("help")
|
||||
.hasArg(false).required(false).build());
|
||||
|
||||
return options;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@@ -16,6 +16,7 @@ import org.dspace.app.itemimport.BatchUpload;
|
||||
import org.dspace.content.Collection;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.eperson.EPerson;
|
||||
import org.dspace.scripts.handler.DSpaceRunnableHandler;
|
||||
|
||||
/**
|
||||
* Import items into DSpace. The conventional use is upload files by copying
|
||||
@@ -105,7 +106,7 @@ public interface ItemImportService {
|
||||
String inputType, Context context, boolean template) throws Exception;
|
||||
|
||||
/**
|
||||
* Since the BTE batch import is done in a new thread we are unable to communicate
|
||||
* If a batch import is done in a new thread we are unable to communicate
|
||||
* with calling method about success or failure. We accomplish this
|
||||
* communication with email instead. Send a success email once the batch
|
||||
* import is complete
|
||||
@@ -119,7 +120,7 @@ public interface ItemImportService {
|
||||
String fileName) throws MessagingException;
|
||||
|
||||
/**
|
||||
* Since the BTE batch import is done in a new thread we are unable to communicate
|
||||
* If a batch import is done in a new thread we are unable to communicate
|
||||
* with calling method about success or failure. We accomplis this
|
||||
* communication with email instead. Send an error email if the batch
|
||||
* import fails
|
||||
@@ -210,6 +211,13 @@ public interface ItemImportService {
|
||||
*/
|
||||
public void setTest(boolean isTest);
|
||||
|
||||
/**
|
||||
* Set exclude-content flag.
|
||||
*
|
||||
* @param isExcludeContent true or false
|
||||
*/
|
||||
public void setExcludeContent(boolean isExcludeContent);
|
||||
|
||||
/**
|
||||
* Set resume flag
|
||||
*
|
||||
@@ -235,4 +243,10 @@ public interface ItemImportService {
|
||||
* @param isQuiet true or false
|
||||
*/
|
||||
public void setQuiet(boolean isQuiet);
|
||||
|
||||
/**
|
||||
* Set the DSpace Runnable Handler
|
||||
* @param handler
|
||||
*/
|
||||
public void setHandler(DSpaceRunnableHandler handler);
|
||||
}
|
||||
|
@@ -11,6 +11,8 @@ import java.io.UnsupportedEncodingException;
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.dspace.app.util.Util;
|
||||
import org.dspace.content.Bitstream;
|
||||
import org.dspace.content.Bundle;
|
||||
@@ -34,8 +36,9 @@ public class ItemMarkingAvailabilityBitstreamStrategy implements ItemMarkingExtr
|
||||
@Autowired(required = true)
|
||||
protected ItemService itemService;
|
||||
|
||||
public ItemMarkingAvailabilityBitstreamStrategy() {
|
||||
private static final Logger LOG = LogManager.getLogger();
|
||||
|
||||
public ItemMarkingAvailabilityBitstreamStrategy() {
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -43,14 +46,14 @@ public class ItemMarkingAvailabilityBitstreamStrategy implements ItemMarkingExtr
|
||||
throws SQLException {
|
||||
|
||||
List<Bundle> bundles = itemService.getBundles(item, "ORIGINAL");
|
||||
if (bundles.size() == 0) {
|
||||
if (bundles.isEmpty()) {
|
||||
ItemMarkingInfo markInfo = new ItemMarkingInfo();
|
||||
markInfo.setImageName(nonAvailableImageName);
|
||||
|
||||
return markInfo;
|
||||
} else {
|
||||
Bundle originalBundle = bundles.iterator().next();
|
||||
if (originalBundle.getBitstreams().size() == 0) {
|
||||
if (originalBundle.getBitstreams().isEmpty()) {
|
||||
ItemMarkingInfo markInfo = new ItemMarkingInfo();
|
||||
markInfo.setImageName(nonAvailableImageName);
|
||||
|
||||
@@ -72,8 +75,7 @@ public class ItemMarkingAvailabilityBitstreamStrategy implements ItemMarkingExtr
|
||||
try {
|
||||
bsLink = bsLink + Util.encodeBitstreamName(bitstream.getName(), Constants.DEFAULT_ENCODING);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
|
||||
e.printStackTrace();
|
||||
LOG.warn("DSpace uses an unsupported encoding", e);
|
||||
}
|
||||
|
||||
signInfo.setLink(bsLink);
|
||||
|
@@ -77,7 +77,7 @@ public class AddBitstreamsAction extends UpdateBitstreamsAction {
|
||||
ItemUpdate.pr("Contents bitstream count: " + contents.size());
|
||||
|
||||
String[] files = dir.list(ItemUpdate.fileFilter);
|
||||
List<String> fileList = new ArrayList<String>();
|
||||
List<String> fileList = new ArrayList<>();
|
||||
for (String filename : files) {
|
||||
fileList.add(filename);
|
||||
ItemUpdate.pr("file: " + filename);
|
||||
@@ -134,9 +134,6 @@ public class AddBitstreamsAction extends UpdateBitstreamsAction {
|
||||
ItemUpdate.pr("contents entry for bitstream: " + ce.toString());
|
||||
File f = new File(dir, ce.filename);
|
||||
|
||||
// get an input stream
|
||||
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(f));
|
||||
|
||||
Bitstream bs = null;
|
||||
String newBundleName = ce.bundlename;
|
||||
|
||||
@@ -173,7 +170,9 @@ public class AddBitstreamsAction extends UpdateBitstreamsAction {
|
||||
targetBundle = bundles.iterator().next();
|
||||
}
|
||||
|
||||
bs = bitstreamService.create(context, targetBundle, bis);
|
||||
try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(f));) {
|
||||
bs = bitstreamService.create(context, targetBundle, bis);
|
||||
}
|
||||
bs.setName(context, ce.filename);
|
||||
|
||||
// Identify the format
|
||||
|
@@ -105,6 +105,7 @@ public class ContentsEntry {
|
||||
return new ContentsEntry(arp[0], arp[1], actionId, groupName, arp[3]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder(filename);
|
||||
if (bundlename != null) {
|
||||
|
@@ -120,6 +120,7 @@ class DtoMetadata {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
String s = "\tSchema: " + schema + " Element: " + element;
|
||||
if (qualifier != null) {
|
||||
|
@@ -17,6 +17,7 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
@@ -55,7 +56,7 @@ public class ItemArchive {
|
||||
protected Transformer transformer = null;
|
||||
|
||||
protected List<DtoMetadata> dtomList = null;
|
||||
protected List<DtoMetadata> undoDtomList = new ArrayList<DtoMetadata>();
|
||||
protected List<DtoMetadata> undoDtomList = new ArrayList<>();
|
||||
|
||||
protected List<UUID> undoAddContents = new ArrayList<>(); // for undo of add
|
||||
|
||||
@@ -325,7 +326,7 @@ public class ItemArchive {
|
||||
PrintWriter pw = null;
|
||||
try {
|
||||
File f = new File(dir, ItemUpdate.DELETE_CONTENTS_FILE);
|
||||
pw = new PrintWriter(new BufferedWriter(new FileWriter(f)));
|
||||
pw = new PrintWriter(new BufferedWriter(new FileWriter(f, StandardCharsets.UTF_8)));
|
||||
for (UUID i : undoAddContents) {
|
||||
pw.println(i);
|
||||
}
|
||||
|
@@ -39,29 +39,34 @@ import org.dspace.handle.factory.HandleServiceFactory;
|
||||
import org.dspace.handle.service.HandleService;
|
||||
|
||||
/**
|
||||
* Provides some batch editing capabilities for items in DSpace:
|
||||
* Metadata fields - Add, Delete
|
||||
* Bitstreams - Add, Delete
|
||||
* Provides some batch editing capabilities for items in DSpace.
|
||||
* <ul>
|
||||
* <li>Metadata fields - Add, Delete</li>
|
||||
* <li>Bitstreams - Add, Delete</li>
|
||||
* </ul>
|
||||
*
|
||||
* The design has been for compatibility with ItemImporter
|
||||
* <p>
|
||||
* The design has been for compatibility with
|
||||
* {@link org.dspace.app.itemimport.service.ItemImportService}
|
||||
* in the use of the DSpace archive format which is used to
|
||||
* specify changes on a per item basis. The directory names
|
||||
* to correspond to each item are arbitrary and will only be
|
||||
* used for logging purposes. The reference to the item is
|
||||
* from a required dc.identifier with the item handle to be
|
||||
* included in the dublin_core.xml (or similar metadata) file.
|
||||
* from a required {@code dc.identifier} with the item handle to be
|
||||
* included in the {@code dublin_core.xml} (or similar metadata) file.
|
||||
*
|
||||
* Any combination of these actions is permitted in a single run of this class
|
||||
* <p>
|
||||
* Any combination of these actions is permitted in a single run of this class.
|
||||
* The order of actions is important when used in combination.
|
||||
* It is the responsibility of the calling class (here, ItemUpdate)
|
||||
* to register UpdateAction classes in the order to which they are
|
||||
* It is the responsibility of the calling class (here, {@code ItemUpdate})
|
||||
* to register {@link UpdateAction} classes in the order which they are
|
||||
* to be performed.
|
||||
*
|
||||
*
|
||||
* It is unfortunate that so much code needs to be borrowed
|
||||
* from ItemImport as it is not reusable in private methods, etc.
|
||||
* Some of this has been placed into the MetadataUtilities class
|
||||
* for possible reuse elsewhere.
|
||||
* <p>
|
||||
* It is unfortunate that so much code needs to be borrowed from
|
||||
* {@link org.dspace.app.itemimport.service.ItemImportService} as it is not
|
||||
* reusable in private methods, etc. Some of this has been placed into the
|
||||
* {@link MetadataUtilities} class for possible reuse elsewhere.
|
||||
*
|
||||
* @author W. Hays based on a conceptual design by R. Rodgers
|
||||
*/
|
||||
@@ -73,7 +78,7 @@ public class ItemUpdate {
|
||||
public static final String DELETE_CONTENTS_FILE = "delete_contents";
|
||||
|
||||
public static String HANDLE_PREFIX = null;
|
||||
public static final Map<String, String> filterAliases = new HashMap<String, String>();
|
||||
public static final Map<String, String> filterAliases = new HashMap<>();
|
||||
|
||||
public static boolean verbose = false;
|
||||
|
||||
@@ -375,7 +380,7 @@ public class ItemUpdate {
|
||||
// open and process the source directory
|
||||
File sourceDir = new File(sourceDirPath);
|
||||
|
||||
if ((sourceDir == null) || !sourceDir.exists() || !sourceDir.isDirectory()) {
|
||||
if (!sourceDir.exists() || !sourceDir.isDirectory()) {
|
||||
pr("Error, cannot open archive source directory " + sourceDirPath);
|
||||
throw new Exception("error with archive source directory " + sourceDirPath);
|
||||
}
|
||||
|
@@ -27,10 +27,12 @@ import javax.xml.transform.TransformerConfigurationException;
|
||||
import javax.xml.transform.TransformerException;
|
||||
import javax.xml.transform.dom.DOMSource;
|
||||
import javax.xml.transform.stream.StreamResult;
|
||||
import javax.xml.xpath.XPath;
|
||||
import javax.xml.xpath.XPathConstants;
|
||||
import javax.xml.xpath.XPathExpressionException;
|
||||
import javax.xml.xpath.XPathFactory;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.xpath.XPathAPI;
|
||||
import org.dspace.authorize.AuthorizeException;
|
||||
import org.dspace.content.Item;
|
||||
import org.dspace.content.MetadataField;
|
||||
import org.dspace.content.MetadataSchema;
|
||||
@@ -170,24 +172,21 @@ public class MetadataUtilities {
|
||||
* @param docBuilder DocumentBuilder
|
||||
* @param is - InputStream of dublin_core.xml
|
||||
* @return list of DtoMetadata representing the metadata fields relating to an Item
|
||||
* @throws SQLException if database error
|
||||
* @throws IOException if IO error
|
||||
* @throws ParserConfigurationException if parser config error
|
||||
* @throws SAXException if XML error
|
||||
* @throws TransformerException if transformer error
|
||||
* @throws AuthorizeException if authorization error
|
||||
*/
|
||||
public static List<DtoMetadata> loadDublinCore(DocumentBuilder docBuilder, InputStream is)
|
||||
throws SQLException, IOException, ParserConfigurationException,
|
||||
SAXException, TransformerException, AuthorizeException {
|
||||
throws IOException, XPathExpressionException, SAXException {
|
||||
Document document = docBuilder.parse(is);
|
||||
|
||||
List<DtoMetadata> dtomList = new ArrayList<DtoMetadata>();
|
||||
|
||||
// Get the schema, for backward compatibility we will default to the
|
||||
// dublin core schema if the schema name is not available in the import file
|
||||
String schema = null;
|
||||
NodeList metadata = XPathAPI.selectNodeList(document, "/dublin_core");
|
||||
String schema;
|
||||
XPath xPath = XPathFactory.newInstance().newXPath();
|
||||
NodeList metadata = (NodeList) xPath.compile("/dublin_core").evaluate(document, XPathConstants.NODESET);
|
||||
Node schemaAttr = metadata.item(0).getAttributes().getNamedItem("schema");
|
||||
if (schemaAttr == null) {
|
||||
schema = MetadataSchemaEnum.DC.getName();
|
||||
@@ -196,7 +195,7 @@ public class MetadataUtilities {
|
||||
}
|
||||
|
||||
// Get the nodes corresponding to formats
|
||||
NodeList dcNodes = XPathAPI.selectNodeList(document, "/dublin_core/dcvalue");
|
||||
NodeList dcNodes = (NodeList) xPath.compile("/dublin_core/dcvalue").evaluate(document, XPathConstants.NODESET);
|
||||
|
||||
for (int i = 0; i < dcNodes.getLength(); i++) {
|
||||
Node n = dcNodes.item(i);
|
||||
|
@@ -16,7 +16,7 @@ import java.io.StreamTokenizer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.jdom.Document;
|
||||
import org.jdom2.Document;
|
||||
|
||||
/**
|
||||
* @author mwood
|
||||
|
@@ -29,9 +29,9 @@ import org.dspace.scripts.service.ScriptService;
|
||||
import org.dspace.servicemanager.DSpaceKernelImpl;
|
||||
import org.dspace.servicemanager.DSpaceKernelInit;
|
||||
import org.dspace.services.RequestService;
|
||||
import org.jdom.Document;
|
||||
import org.jdom.Element;
|
||||
import org.jdom.input.SAXBuilder;
|
||||
import org.jdom2.Document;
|
||||
import org.jdom2.Element;
|
||||
import org.jdom2.input.SAXBuilder;
|
||||
|
||||
/**
|
||||
* A DSpace script launcher.
|
||||
|
@@ -21,10 +21,10 @@ import java.awt.image.BufferedImage;
|
||||
*/
|
||||
|
||||
public class Brand {
|
||||
private int brandWidth;
|
||||
private int brandHeight;
|
||||
private Font font;
|
||||
private int xOffset;
|
||||
private final int brandWidth;
|
||||
private final int brandHeight;
|
||||
private final Font font;
|
||||
private final int xOffset;
|
||||
|
||||
/**
|
||||
* Constructor to set up footer image attributes.
|
||||
@@ -92,7 +92,7 @@ public class Brand {
|
||||
* do the text placements and preparatory work for the brand image generation
|
||||
*
|
||||
* @param brandImage a BufferedImage object where the image is created
|
||||
* @param identifier and Identifier object describing what text is to be placed in what
|
||||
* @param brandText an Identifier object describing what text is to be placed in what
|
||||
* position within the brand
|
||||
*/
|
||||
private void drawImage(BufferedImage brandImage,
|
||||
|
@@ -39,7 +39,7 @@ class BrandText {
|
||||
* its location within a rectangular area.
|
||||
*
|
||||
* @param location one of the class location constants e.g. <code>Identifier.BL</code>
|
||||
* @param the text associated with the location
|
||||
* @param text text associated with the location
|
||||
*/
|
||||
public BrandText(String location, String text) {
|
||||
this.location = location;
|
||||
|
@@ -1,99 +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.mediafilter;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.poi.POITextExtractor;
|
||||
import org.apache.poi.extractor.ExtractorFactory;
|
||||
import org.apache.poi.hssf.extractor.ExcelExtractor;
|
||||
import org.apache.poi.xssf.extractor.XSSFExcelExtractor;
|
||||
import org.dspace.content.Item;
|
||||
|
||||
/*
|
||||
* ExcelFilter
|
||||
*
|
||||
* Entries you must add to dspace.cfg:
|
||||
*
|
||||
* filter.plugins = blah, \
|
||||
* Excel Text Extractor
|
||||
*
|
||||
* plugin.named.org.dspace.app.mediafilter.FormatFilter = \
|
||||
* blah = blah, \
|
||||
* org.dspace.app.mediafilter.ExcelFilter = Excel Text Extractor
|
||||
*
|
||||
* #Configure each filter's input Formats
|
||||
* filter.org.dspace.app.mediafilter.ExcelFilter.inputFormats = Microsoft Excel, Microsoft Excel XML
|
||||
*
|
||||
*/
|
||||
public class ExcelFilter extends MediaFilter {
|
||||
|
||||
private static Logger log = org.apache.logging.log4j.LogManager.getLogger(ExcelFilter.class);
|
||||
|
||||
public String getFilteredName(String oldFilename) {
|
||||
return oldFilename + ".txt";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return String bundle name
|
||||
*/
|
||||
public String getBundleName() {
|
||||
return "TEXT";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return String bitstream format
|
||||
*/
|
||||
public String getFormatString() {
|
||||
return "Text";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return String description
|
||||
*/
|
||||
public String getDescription() {
|
||||
return "Extracted text";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param item item
|
||||
* @param source source input stream
|
||||
* @param verbose verbose mode
|
||||
* @return InputStream the resulting input stream
|
||||
* @throws Exception if error
|
||||
*/
|
||||
@Override
|
||||
public InputStream getDestinationStream(Item item, InputStream source, boolean verbose)
|
||||
throws Exception {
|
||||
String extractedText = null;
|
||||
|
||||
try {
|
||||
POITextExtractor theExtractor = ExtractorFactory.createExtractor(source);
|
||||
if (theExtractor instanceof ExcelExtractor) {
|
||||
// for xls file
|
||||
extractedText = (theExtractor).getText();
|
||||
} else if (theExtractor instanceof XSSFExcelExtractor) {
|
||||
// for xlsx file
|
||||
extractedText = (theExtractor).getText();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Error filtering bitstream: " + e.getMessage(), e);
|
||||
throw e;
|
||||
}
|
||||
|
||||
if (extractedText != null) {
|
||||
// generate an input stream with the extracted text
|
||||
return IOUtils.toInputStream(extractedText, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
@@ -1,81 +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.mediafilter;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
import javax.swing.text.Document;
|
||||
import javax.swing.text.html.HTMLEditorKit;
|
||||
|
||||
import org.dspace.content.Item;
|
||||
|
||||
/*
|
||||
*
|
||||
* to do: helpful error messages - can't find mediafilter.cfg - can't
|
||||
* instantiate filter - bitstream format doesn't exist
|
||||
*
|
||||
*/
|
||||
public class HTMLFilter extends MediaFilter {
|
||||
|
||||
@Override
|
||||
public String getFilteredName(String oldFilename) {
|
||||
return oldFilename + ".txt";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return String bundle name
|
||||
*/
|
||||
@Override
|
||||
public String getBundleName() {
|
||||
return "TEXT";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return String bitstreamformat
|
||||
*/
|
||||
@Override
|
||||
public String getFormatString() {
|
||||
return "Text";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return String description
|
||||
*/
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Extracted text";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param currentItem item
|
||||
* @param source source input stream
|
||||
* @param verbose verbose mode
|
||||
* @return InputStream the resulting input stream
|
||||
* @throws Exception if error
|
||||
*/
|
||||
@Override
|
||||
public InputStream getDestinationStream(Item currentItem, InputStream source, boolean verbose)
|
||||
throws Exception {
|
||||
// try and read the document - set to ignore character set directive,
|
||||
// assuming that the input stream is already set properly (I hope)
|
||||
HTMLEditorKit kit = new HTMLEditorKit();
|
||||
Document doc = kit.createDefaultDocument();
|
||||
|
||||
doc.putProperty("IgnoreCharsetDirective", Boolean.TRUE);
|
||||
|
||||
kit.read(source, doc, 0);
|
||||
|
||||
String extractedText = doc.getText(0, doc.getLength());
|
||||
|
||||
// generate an input stream with the extracted text
|
||||
byte[] textBytes = extractedText.getBytes();
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(textBytes);
|
||||
|
||||
return bais; // will this work? or will the byte array be out of scope?
|
||||
}
|
||||
}
|
@@ -14,6 +14,9 @@ import java.io.InputStream;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.regex.PatternSyntaxException;
|
||||
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDPage;
|
||||
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
||||
import org.dspace.content.Bitstream;
|
||||
import org.dspace.content.Bundle;
|
||||
import org.dspace.content.Item;
|
||||
@@ -119,6 +122,39 @@ public abstract class ImageMagickThumbnailFilter extends MediaFilter {
|
||||
f2.deleteOnExit();
|
||||
ConvertCmd cmd = new ConvertCmd();
|
||||
IMOperation op = new IMOperation();
|
||||
|
||||
// Optionally override ImageMagick's default density of 72 DPI to use a
|
||||
// "supersample" when creating the PDF thumbnail. Note that I prefer to
|
||||
// use the getProperty() method here instead of getIntPropert() because
|
||||
// the latter always returns an integer (0 in the case it's not set). I
|
||||
// would prefer to keep ImageMagick's default to itself rather than for
|
||||
// us to set one. Also note that the density option *must* come before
|
||||
// we open the input file.
|
||||
String density = configurationService.getProperty(PRE + ".density");
|
||||
if (density != null) {
|
||||
op.density(Integer.valueOf(density));
|
||||
}
|
||||
|
||||
// Check the PDF's MediaBox and CropBox to see if they are the same.
|
||||
// If not, then tell ImageMagick to use the CropBox when generating
|
||||
// the thumbnail because the CropBox is generally used to define the
|
||||
// area displayed when a user opens the PDF on a screen, whereas the
|
||||
// MediaBox is used for print. Not all PDFs set these correctly, so
|
||||
// we can use ImageMagick's default behavior unless we see an explit
|
||||
// CropBox. Note: we don't need to do anything special to detect if
|
||||
// the CropBox is missing or empty because pdfbox will set it to the
|
||||
// same size as the MediaBox if it doesn't exist. Also note that we
|
||||
// only need to check the first page, since that's what we use for
|
||||
// generating the thumbnail (PDDocument uses a zero-based index).
|
||||
PDPage pdfPage = PDDocument.load(f).getPage(0);
|
||||
PDRectangle pdfPageMediaBox = pdfPage.getMediaBox();
|
||||
PDRectangle pdfPageCropBox = pdfPage.getCropBox();
|
||||
|
||||
// This option must come *before* we open the input file.
|
||||
if (pdfPageCropBox != pdfPageMediaBox) {
|
||||
op.define("pdf:use-cropbox=true");
|
||||
}
|
||||
|
||||
String s = "[" + page + "]";
|
||||
op.addImage(f.getAbsolutePath() + s);
|
||||
if (configurationService.getBooleanProperty(PRE + ".flatten", true)) {
|
||||
|
@@ -14,13 +14,7 @@ import java.util.Iterator;
|
||||
import java.util.List;
|
||||
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.HelpFormatter;
|
||||
import org.apache.commons.cli.MissingArgumentException;
|
||||
import org.apache.commons.cli.Option;
|
||||
import org.apache.commons.cli.Options;
|
||||
import org.apache.commons.cli.ParseException;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.dspace.app.mediafilter.factory.MediaFilterServiceFactory;
|
||||
import org.dspace.app.mediafilter.service.MediaFilterService;
|
||||
@@ -33,7 +27,9 @@ import org.dspace.core.Context;
|
||||
import org.dspace.core.SelfNamedPlugin;
|
||||
import org.dspace.core.factory.CoreServiceFactory;
|
||||
import org.dspace.handle.factory.HandleServiceFactory;
|
||||
import org.dspace.scripts.DSpaceRunnable;
|
||||
import org.dspace.services.factory.DSpaceServicesFactory;
|
||||
import org.dspace.utils.DSpace;
|
||||
|
||||
/**
|
||||
* MediaFilterManager is the class that invokes the media/format filters over the
|
||||
@@ -44,7 +40,7 @@ import org.dspace.services.factory.DSpaceServicesFactory;
|
||||
* scope to a community, collection or item; and -m [max] limits processing to a
|
||||
* maximum number of items.
|
||||
*/
|
||||
public class MediaFilterCLITool {
|
||||
public class MediaFilterScript extends DSpaceRunnable<MediaFilterScriptConfiguration> {
|
||||
|
||||
//key (in dspace.cfg) which lists all enabled filters by name
|
||||
private static final String MEDIA_FILTER_PLUGINS_KEY = "filter.plugins";
|
||||
@@ -55,127 +51,78 @@ public class MediaFilterCLITool {
|
||||
//suffix (in dspace.cfg) for input formats supported by each filter
|
||||
private static final String INPUT_FORMATS_SUFFIX = "inputFormats";
|
||||
|
||||
/**
|
||||
* Default constructor
|
||||
*/
|
||||
private MediaFilterCLITool() { }
|
||||
private boolean help;
|
||||
private boolean isVerbose = false;
|
||||
private boolean isQuiet = false;
|
||||
private boolean isForce = false; // default to not forced
|
||||
private String identifier = null; // object scope limiter
|
||||
private int max2Process = Integer.MAX_VALUE;
|
||||
private String[] filterNames;
|
||||
private String[] skipIds = null;
|
||||
private Map<String, List<String>> filterFormats = new HashMap<>();
|
||||
|
||||
public MediaFilterScriptConfiguration getScriptConfiguration() {
|
||||
return new DSpace().getServiceManager()
|
||||
.getServiceByName("filter-media", MediaFilterScriptConfiguration.class);
|
||||
}
|
||||
|
||||
public void setup() throws ParseException {
|
||||
|
||||
public static void main(String[] argv) throws Exception {
|
||||
// set headless for non-gui workstations
|
||||
System.setProperty("java.awt.headless", "true");
|
||||
|
||||
// create an options object and populate it
|
||||
CommandLineParser parser = new DefaultParser();
|
||||
|
||||
int status = 0;
|
||||
help = commandLine.hasOption('h');
|
||||
|
||||
Options options = new Options();
|
||||
|
||||
options.addOption("v", "verbose", false,
|
||||
"print all extracted text and other details to STDOUT");
|
||||
options.addOption("q", "quiet", false,
|
||||
"do not print anything except in the event of errors.");
|
||||
options.addOption("f", "force", false,
|
||||
"force all bitstreams to be processed");
|
||||
options.addOption("i", "identifier", true,
|
||||
"ONLY process bitstreams belonging to identifier");
|
||||
options.addOption("m", "maximum", true,
|
||||
"process no more than maximum items");
|
||||
options.addOption("h", "help", false, "help");
|
||||
|
||||
//create a "plugin" option (to specify specific MediaFilter plugins to run)
|
||||
Option pluginOption = Option.builder("p")
|
||||
.longOpt("plugins")
|
||||
.hasArg()
|
||||
.hasArgs()
|
||||
.valueSeparator(',')
|
||||
.desc(
|
||||
"ONLY run the specified Media Filter plugin(s)\n" +
|
||||
"listed from '" + MEDIA_FILTER_PLUGINS_KEY + "' in dspace.cfg.\n" +
|
||||
"Separate multiple with a comma (,)\n" +
|
||||
"(e.g. MediaFilterManager -p \n\"Word Text Extractor\",\"PDF Text Extractor\")")
|
||||
.build();
|
||||
options.addOption(pluginOption);
|
||||
|
||||
//create a "skip" option (to specify communities/collections/items to skip)
|
||||
Option skipOption = Option.builder("s")
|
||||
.longOpt("skip")
|
||||
.hasArg()
|
||||
.hasArgs()
|
||||
.valueSeparator(',')
|
||||
.desc(
|
||||
"SKIP the bitstreams belonging to identifier\n" +
|
||||
"Separate multiple identifiers with a comma (,)\n" +
|
||||
"(e.g. MediaFilterManager -s \n 123456789/34,123456789/323)")
|
||||
.build();
|
||||
options.addOption(skipOption);
|
||||
|
||||
boolean isVerbose = false;
|
||||
boolean isQuiet = false;
|
||||
boolean isForce = false; // default to not forced
|
||||
String identifier = null; // object scope limiter
|
||||
int max2Process = Integer.MAX_VALUE;
|
||||
Map<String, List<String>> filterFormats = new HashMap<>();
|
||||
|
||||
CommandLine line = null;
|
||||
try {
|
||||
line = parser.parse(options, argv);
|
||||
} catch (MissingArgumentException e) {
|
||||
System.out.println("ERROR: " + e.getMessage());
|
||||
HelpFormatter myhelp = new HelpFormatter();
|
||||
myhelp.printHelp("MediaFilterManager\n", options);
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
if (line.hasOption('h')) {
|
||||
HelpFormatter myhelp = new HelpFormatter();
|
||||
myhelp.printHelp("MediaFilterManager\n", options);
|
||||
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
if (line.hasOption('v')) {
|
||||
if (commandLine.hasOption('v')) {
|
||||
isVerbose = true;
|
||||
}
|
||||
|
||||
isQuiet = line.hasOption('q');
|
||||
isQuiet = commandLine.hasOption('q');
|
||||
|
||||
if (line.hasOption('f')) {
|
||||
if (commandLine.hasOption('f')) {
|
||||
isForce = true;
|
||||
}
|
||||
|
||||
if (line.hasOption('i')) {
|
||||
identifier = line.getOptionValue('i');
|
||||
if (commandLine.hasOption('i')) {
|
||||
identifier = commandLine.getOptionValue('i');
|
||||
}
|
||||
|
||||
if (line.hasOption('m')) {
|
||||
max2Process = Integer.parseInt(line.getOptionValue('m'));
|
||||
if (commandLine.hasOption('m')) {
|
||||
max2Process = Integer.parseInt(commandLine.getOptionValue('m'));
|
||||
if (max2Process <= 1) {
|
||||
System.out.println("Invalid maximum value '" +
|
||||
line.getOptionValue('m') + "' - ignoring");
|
||||
handler.logWarning("Invalid maximum value '" +
|
||||
commandLine.getOptionValue('m') + "' - ignoring");
|
||||
max2Process = Integer.MAX_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
String filterNames[] = null;
|
||||
if (line.hasOption('p')) {
|
||||
if (commandLine.hasOption('p')) {
|
||||
//specified which media filter plugins we are using
|
||||
filterNames = line.getOptionValues('p');
|
||||
|
||||
if (filterNames == null || filterNames.length == 0) { //display error, since no plugins specified
|
||||
System.err.println("\nERROR: -p (-plugin) option requires at least one plugin to be specified.\n" +
|
||||
"(e.g. MediaFilterManager -p \"Word Text Extractor\",\"PDF Text Extractor\")\n");
|
||||
HelpFormatter myhelp = new HelpFormatter();
|
||||
myhelp.printHelp("MediaFilterManager\n", options);
|
||||
System.exit(1);
|
||||
}
|
||||
filterNames = commandLine.getOptionValues('p');
|
||||
} else {
|
||||
//retrieve list of all enabled media filter plugins!
|
||||
filterNames = DSpaceServicesFactory.getInstance().getConfigurationService()
|
||||
.getArrayProperty(MEDIA_FILTER_PLUGINS_KEY);
|
||||
}
|
||||
|
||||
//save to a global skip list
|
||||
if (commandLine.hasOption('s')) {
|
||||
//specified which identifiers to skip when processing
|
||||
skipIds = commandLine.getOptionValues('s');
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public void internalRun() throws Exception {
|
||||
if (help) {
|
||||
printHelp();
|
||||
return;
|
||||
}
|
||||
|
||||
MediaFilterService mediaFilterService = MediaFilterServiceFactory.getInstance().getMediaFilterService();
|
||||
mediaFilterService.setLogHandler(handler);
|
||||
mediaFilterService.setForce(isForce);
|
||||
mediaFilterService.setQuiet(isQuiet);
|
||||
mediaFilterService.setVerbose(isVerbose);
|
||||
@@ -184,16 +131,17 @@ public class MediaFilterCLITool {
|
||||
//initialize an array of our enabled filters
|
||||
List<FormatFilter> filterList = new ArrayList<>();
|
||||
|
||||
|
||||
//set up each filter
|
||||
for (int i = 0; i < filterNames.length; i++) {
|
||||
//get filter of this name & add to list of filters
|
||||
FormatFilter filter = (FormatFilter) CoreServiceFactory.getInstance().getPluginService()
|
||||
.getNamedPlugin(FormatFilter.class, filterNames[i]);
|
||||
if (filter == null) {
|
||||
System.err.println(
|
||||
"\nERROR: Unknown MediaFilter specified (either from command-line or in dspace.cfg): '" +
|
||||
filterNames[i] + "'");
|
||||
System.exit(1);
|
||||
handler.handleException("ERROR: Unknown MediaFilter specified (either from command-line or in " +
|
||||
"dspace.cfg): '" + filterNames[i] + "'");
|
||||
handler.logError("ERROR: Unknown MediaFilter specified (either from command-line or in " +
|
||||
"dspace.cfg): '" + filterNames[i] + "'");
|
||||
} else {
|
||||
filterList.add(filter);
|
||||
|
||||
@@ -218,10 +166,10 @@ public class MediaFilterCLITool {
|
||||
//For other MediaFilters, format of key is:
|
||||
// filter.<class-name>.inputFormats
|
||||
String[] formats =
|
||||
DSpaceServicesFactory.getInstance().getConfigurationService().getArrayProperty(
|
||||
FILTER_PREFIX + "." + filterClassName +
|
||||
(pluginName != null ? "." + pluginName : "") +
|
||||
"." + INPUT_FORMATS_SUFFIX);
|
||||
DSpaceServicesFactory.getInstance().getConfigurationService().getArrayProperty(
|
||||
FILTER_PREFIX + "." + filterClassName +
|
||||
(pluginName != null ? "." + pluginName : "") +
|
||||
"." + INPUT_FORMATS_SUFFIX);
|
||||
|
||||
//add to internal map of filters to supported formats
|
||||
if (ArrayUtils.isNotEmpty(formats)) {
|
||||
@@ -230,8 +178,8 @@ public class MediaFilterCLITool {
|
||||
//For other MediaFilters, map key is just:
|
||||
// <class-name>
|
||||
filterFormats.put(filterClassName +
|
||||
(pluginName != null ? MediaFilterService.FILTER_PLUGIN_SEPARATOR +
|
||||
pluginName : ""),
|
||||
(pluginName != null ? MediaFilterService.FILTER_PLUGIN_SEPARATOR +
|
||||
pluginName : ""),
|
||||
Arrays.asList(formats));
|
||||
}
|
||||
} //end if filter!=null
|
||||
@@ -239,11 +187,11 @@ public class MediaFilterCLITool {
|
||||
|
||||
//If verbose, print out loaded mediafilter info
|
||||
if (isVerbose) {
|
||||
System.out.println("The following MediaFilters are enabled: ");
|
||||
handler.logInfo("The following MediaFilters are enabled: ");
|
||||
Iterator<String> i = filterFormats.keySet().iterator();
|
||||
while (i.hasNext()) {
|
||||
String filterName = i.next();
|
||||
System.out.println("Full Filter Name: " + filterName);
|
||||
handler.logInfo("Full Filter Name: " + filterName);
|
||||
String pluginName = null;
|
||||
if (filterName.contains(MediaFilterService.FILTER_PLUGIN_SEPARATOR)) {
|
||||
String[] fields = filterName.split(MediaFilterService.FILTER_PLUGIN_SEPARATOR);
|
||||
@@ -251,8 +199,7 @@ public class MediaFilterCLITool {
|
||||
pluginName = fields[1];
|
||||
}
|
||||
|
||||
System.out.println(filterName +
|
||||
(pluginName != null ? " (Plugin: " + pluginName + ")" : ""));
|
||||
handler.logInfo(filterName + (pluginName != null ? " (Plugin: " + pluginName + ")" : ""));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -262,20 +209,8 @@ public class MediaFilterCLITool {
|
||||
|
||||
|
||||
//Retrieve list of identifiers to skip (if any)
|
||||
String skipIds[] = null;
|
||||
if (line.hasOption('s')) {
|
||||
//specified which identifiers to skip when processing
|
||||
skipIds = line.getOptionValues('s');
|
||||
|
||||
if (skipIds == null || skipIds.length == 0) { //display error, since no identifiers specified to skip
|
||||
System.err.println("\nERROR: -s (-skip) option requires at least one identifier to SKIP.\n" +
|
||||
"Make sure to separate multiple identifiers with a comma!\n" +
|
||||
"(e.g. MediaFilterManager -s 123456789/34,123456789/323)\n");
|
||||
HelpFormatter myhelp = new HelpFormatter();
|
||||
myhelp.printHelp("MediaFilterManager\n", options);
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
if (skipIds != null && skipIds.length > 0) {
|
||||
//save to a global skip list
|
||||
mediaFilterService.setSkipList(Arrays.asList(skipIds));
|
||||
}
|
||||
@@ -296,7 +231,7 @@ public class MediaFilterCLITool {
|
||||
DSpaceObject dso = HandleServiceFactory.getInstance().getHandleService().resolveToObject(c, identifier);
|
||||
if (dso == null) {
|
||||
throw new IllegalArgumentException("Cannot resolve "
|
||||
+ identifier + " to a DSpace object");
|
||||
+ identifier + " to a DSpace object");
|
||||
}
|
||||
|
||||
switch (dso.getType()) {
|
||||
@@ -317,12 +252,11 @@ public class MediaFilterCLITool {
|
||||
c.complete();
|
||||
c = null;
|
||||
} catch (Exception e) {
|
||||
status = 1;
|
||||
handler.handleException(e);
|
||||
} finally {
|
||||
if (c != null) {
|
||||
c.abort();
|
||||
}
|
||||
}
|
||||
System.exit(status);
|
||||
}
|
||||
}
|
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* 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.mediafilter;
|
||||
|
||||
import java.sql.SQLException;
|
||||
|
||||
import org.apache.commons.cli.Option;
|
||||
import org.apache.commons.cli.Options;
|
||||
import org.dspace.authorize.service.AuthorizeService;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.scripts.configuration.ScriptConfiguration;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
public class MediaFilterScriptConfiguration<T extends MediaFilterScript> extends ScriptConfiguration<T> {
|
||||
|
||||
@Autowired
|
||||
private AuthorizeService authorizeService;
|
||||
|
||||
private Class<T> dspaceRunnableClass;
|
||||
|
||||
private static final String MEDIA_FILTER_PLUGINS_KEY = "filter.plugins";
|
||||
|
||||
|
||||
@Override
|
||||
public Class<T> getDspaceRunnableClass() {
|
||||
return dspaceRunnableClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDspaceRunnableClass(Class<T> dspaceRunnableClass) {
|
||||
this.dspaceRunnableClass = dspaceRunnableClass;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isAllowedToExecute(final Context context) {
|
||||
try {
|
||||
return authorizeService.isAdmin(context);
|
||||
} catch (SQLException e) {
|
||||
throw new RuntimeException("SQLException occurred when checking if the current user is an admin", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Options getOptions() {
|
||||
Options options = new Options();
|
||||
options.addOption("v", "verbose", false, "print all extracted text and other details to STDOUT");
|
||||
options.addOption("q", "quiet", false, "do not print anything except in the event of errors.");
|
||||
options.addOption("f", "force", false, "force all bitstreams to be processed");
|
||||
options.addOption("i", "identifier", true, "ONLY process bitstreams belonging to identifier");
|
||||
options.addOption("m", "maximum", true, "process no more than maximum items");
|
||||
options.addOption("h", "help", false, "help");
|
||||
|
||||
Option pluginOption = Option.builder("p")
|
||||
.longOpt("plugins")
|
||||
.hasArg()
|
||||
.hasArgs()
|
||||
.valueSeparator(',')
|
||||
.desc(
|
||||
"ONLY run the specified Media Filter plugin(s)\n" +
|
||||
"listed from '" + MEDIA_FILTER_PLUGINS_KEY + "' in dspace.cfg.\n" +
|
||||
"Separate multiple with a comma (,)\n" +
|
||||
"(e.g. MediaFilterManager -p \n\"Word Text Extractor\",\"PDF Text" +
|
||||
" Extractor\")")
|
||||
.build();
|
||||
options.addOption(pluginOption);
|
||||
|
||||
Option skipOption = Option.builder("s")
|
||||
.longOpt("skip")
|
||||
.hasArg()
|
||||
.hasArgs()
|
||||
.valueSeparator(',')
|
||||
.desc(
|
||||
"SKIP the bitstreams belonging to identifier\n" +
|
||||
"Separate multiple identifiers with a comma (,)\n" +
|
||||
"(e.g. MediaFilterManager -s \n 123456789/34,123456789/323)")
|
||||
.build();
|
||||
options.addOption(skipOption);
|
||||
|
||||
return options;
|
||||
}
|
||||
}
|
@@ -34,6 +34,7 @@ import org.dspace.core.Context;
|
||||
import org.dspace.core.SelfNamedPlugin;
|
||||
import org.dspace.eperson.Group;
|
||||
import org.dspace.eperson.service.GroupService;
|
||||
import org.dspace.scripts.handler.DSpaceRunnableHandler;
|
||||
import org.dspace.services.ConfigurationService;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -67,6 +68,8 @@ public class MediaFilterServiceImpl implements MediaFilterService, InitializingB
|
||||
@Autowired(required = true)
|
||||
protected ConfigurationService configurationService;
|
||||
|
||||
protected DSpaceRunnableHandler handler;
|
||||
|
||||
protected int max2Process = Integer.MAX_VALUE; // maximum number items to process
|
||||
|
||||
protected int processed = 0; // number items processed
|
||||
@@ -225,16 +228,16 @@ public class MediaFilterServiceImpl implements MediaFilterService, InitializingB
|
||||
int assetstore = myBitstream.getStoreNumber();
|
||||
|
||||
// Printout helpful information to find the errored bitstream.
|
||||
System.out.println("ERROR filtering, skipping bitstream:\n");
|
||||
System.out.println("\tItem Handle: " + handle);
|
||||
StringBuilder sb = new StringBuilder("ERROR filtering, skipping bitstream:\n");
|
||||
sb.append("\tItem Handle: ").append(handle);
|
||||
for (Bundle bundle : bundles) {
|
||||
System.out.println("\tBundle Name: " + bundle.getName());
|
||||
sb.append("\tBundle Name: ").append(bundle.getName());
|
||||
}
|
||||
System.out.println("\tFile Size: " + size);
|
||||
System.out.println("\tChecksum: " + checksum);
|
||||
System.out.println("\tAsset Store: " + assetstore);
|
||||
System.out.println(e);
|
||||
e.printStackTrace();
|
||||
sb.append("\tFile Size: ").append(size);
|
||||
sb.append("\tChecksum: ").append(checksum);
|
||||
sb.append("\tAsset Store: ").append(assetstore);
|
||||
logError(sb.toString());
|
||||
logError(e.getMessage(), e);
|
||||
}
|
||||
} else if (filterClass instanceof SelfRegisterInputFormats) {
|
||||
// Filter implements self registration, so check to see if it should be applied
|
||||
@@ -287,7 +290,7 @@ public class MediaFilterServiceImpl implements MediaFilterService, InitializingB
|
||||
filtered = true;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.out.println("ERROR filtering, skipping bitstream #"
|
||||
logError("ERROR filtering, skipping bitstream #"
|
||||
+ myBitstream.getID() + " " + e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
@@ -312,27 +315,27 @@ public class MediaFilterServiceImpl implements MediaFilterService, InitializingB
|
||||
|
||||
// check if destination bitstream exists
|
||||
Bundle existingBundle = null;
|
||||
Bitstream existingBitstream = null;
|
||||
List<Bitstream> existingBitstreams = new ArrayList<Bitstream>();
|
||||
List<Bundle> bundles = itemService.getBundles(item, formatFilter.getBundleName());
|
||||
|
||||
if (bundles.size() > 0) {
|
||||
// only finds the last match (FIXME?)
|
||||
// only finds the last matching bundle and all matching bitstreams in the proper bundle(s)
|
||||
for (Bundle bundle : bundles) {
|
||||
List<Bitstream> bitstreams = bundle.getBitstreams();
|
||||
|
||||
for (Bitstream bitstream : bitstreams) {
|
||||
if (bitstream.getName().trim().equals(newName.trim())) {
|
||||
existingBundle = bundle;
|
||||
existingBitstream = bitstream;
|
||||
existingBitstreams.add(bitstream);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if exists and overwrite = false, exit
|
||||
if (!overWrite && (existingBitstream != null)) {
|
||||
if (!overWrite && (existingBitstreams.size() > 0)) {
|
||||
if (!isQuiet) {
|
||||
System.out.println("SKIPPED: bitstream " + source.getID()
|
||||
logInfo("SKIPPED: bitstream " + source.getID()
|
||||
+ " (item: " + item.getHandle() + ") because '" + newName + "' already exists");
|
||||
}
|
||||
|
||||
@@ -340,11 +343,11 @@ public class MediaFilterServiceImpl implements MediaFilterService, InitializingB
|
||||
}
|
||||
|
||||
if (isVerbose) {
|
||||
System.out.println("PROCESSING: bitstream " + source.getID()
|
||||
logInfo("PROCESSING: bitstream " + source.getID()
|
||||
+ " (item: " + item.getHandle() + ")");
|
||||
}
|
||||
|
||||
System.out.println("File: " + newName);
|
||||
logInfo("File: " + newName);
|
||||
|
||||
// start filtering of the bitstream, using try with resource to close all InputStreams properly
|
||||
try (
|
||||
@@ -356,7 +359,7 @@ public class MediaFilterServiceImpl implements MediaFilterService, InitializingB
|
||||
) {
|
||||
if (destStream == null) {
|
||||
if (!isQuiet) {
|
||||
System.out.println("SKIPPED: bitstream " + source.getID()
|
||||
logInfo("SKIPPED: bitstream " + source.getID()
|
||||
+ " (item: " + item.getHandle() + ") because filtering was unsuccessful");
|
||||
}
|
||||
return false;
|
||||
@@ -394,25 +397,24 @@ public class MediaFilterServiceImpl implements MediaFilterService, InitializingB
|
||||
Group anonymous = groupService.findByName(context, Group.ANONYMOUS);
|
||||
authorizeService.addPolicy(context, b, Constants.READ, anonymous);
|
||||
} else {
|
||||
//- Inherit policies from the source bitstream
|
||||
authorizeService.inheritPolicies(context, source, b);
|
||||
//- replace the policies using the same in the source bitstream
|
||||
authorizeService.replaceAllPolicies(context, source, b);
|
||||
}
|
||||
|
||||
//do post-processing of the generated bitstream
|
||||
formatFilter.postProcessBitstream(context, item, b);
|
||||
|
||||
} catch (OutOfMemoryError oome) {
|
||||
System.out.println("!!! OutOfMemoryError !!!");
|
||||
logError("!!! OutOfMemoryError !!!");
|
||||
}
|
||||
|
||||
// fixme - set date?
|
||||
// we are overwriting, so remove old bitstream
|
||||
if (existingBitstream != null) {
|
||||
for (Bitstream existingBitstream : existingBitstreams) {
|
||||
bundleService.removeBitstream(context, existingBundle, existingBitstream);
|
||||
}
|
||||
|
||||
if (!isQuiet) {
|
||||
System.out.println("FILTERED: bitstream " + source.getID()
|
||||
logInfo("FILTERED: bitstream " + source.getID()
|
||||
+ " (item: " + item.getHandle() + ") and created '" + newName + "'");
|
||||
}
|
||||
|
||||
@@ -428,7 +430,7 @@ public class MediaFilterServiceImpl implements MediaFilterService, InitializingB
|
||||
public boolean inSkipList(String identifier) {
|
||||
if (skipList != null && skipList.contains(identifier)) {
|
||||
if (!isQuiet) {
|
||||
System.out.println("SKIP-LIST: skipped bitstreams within identifier " + identifier);
|
||||
logInfo("SKIP-LIST: skipped bitstreams within identifier " + identifier);
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
@@ -436,6 +438,28 @@ public class MediaFilterServiceImpl implements MediaFilterService, InitializingB
|
||||
}
|
||||
}
|
||||
|
||||
private void logInfo(String message) {
|
||||
if (handler != null) {
|
||||
handler.logInfo(message);
|
||||
} else {
|
||||
System.out.println(message);
|
||||
}
|
||||
}
|
||||
private void logError(String message) {
|
||||
if (handler != null) {
|
||||
handler.logError(message);
|
||||
} else {
|
||||
System.out.println(message);
|
||||
}
|
||||
}
|
||||
private void logError(String message, Exception e) {
|
||||
if (handler != null) {
|
||||
handler.logError(message, e);
|
||||
} else {
|
||||
System.out.println(message);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVerbose(boolean isVerbose) {
|
||||
this.isVerbose = isVerbose;
|
||||
@@ -470,4 +494,9 @@ public class MediaFilterServiceImpl implements MediaFilterService, InitializingB
|
||||
public void setFilterFormats(Map<String, List<String>> filterFormats) {
|
||||
this.filterFormats = filterFormats;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLogHandler(DSpaceRunnableHandler handler) {
|
||||
this.handler = handler;
|
||||
}
|
||||
}
|
||||
|
@@ -1,137 +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.mediafilter;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.Writer;
|
||||
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.encryption.InvalidPasswordException;
|
||||
import org.apache.pdfbox.text.PDFTextStripper;
|
||||
import org.dspace.content.Item;
|
||||
import org.dspace.services.ConfigurationService;
|
||||
import org.dspace.services.factory.DSpaceServicesFactory;
|
||||
|
||||
/*
|
||||
*
|
||||
* to do: helpful error messages - can't find mediafilter.cfg - can't
|
||||
* instantiate filter - bitstream format doesn't exist
|
||||
*
|
||||
*/
|
||||
public class PDFFilter extends MediaFilter {
|
||||
|
||||
private static Logger log = org.apache.logging.log4j.LogManager.getLogger(PDFFilter.class);
|
||||
|
||||
@Override
|
||||
public String getFilteredName(String oldFilename) {
|
||||
return oldFilename + ".txt";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return String bundle name
|
||||
*/
|
||||
@Override
|
||||
public String getBundleName() {
|
||||
return "TEXT";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return String bitstreamformat
|
||||
*/
|
||||
@Override
|
||||
public String getFormatString() {
|
||||
return "Text";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return String description
|
||||
*/
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Extracted text";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param currentItem item
|
||||
* @param source source input stream
|
||||
* @param verbose verbose mode
|
||||
* @return InputStream the resulting input stream
|
||||
* @throws Exception if error
|
||||
*/
|
||||
@Override
|
||||
public InputStream getDestinationStream(Item currentItem, InputStream source, boolean verbose)
|
||||
throws Exception {
|
||||
ConfigurationService configurationService
|
||||
= DSpaceServicesFactory.getInstance().getConfigurationService();
|
||||
try {
|
||||
boolean useTemporaryFile = configurationService.getBooleanProperty("pdffilter.largepdfs", false);
|
||||
|
||||
// get input stream from bitstream
|
||||
// pass to filter, get string back
|
||||
PDFTextStripper pts = new PDFTextStripper();
|
||||
pts.setSortByPosition(true);
|
||||
PDDocument pdfDoc = null;
|
||||
Writer writer = null;
|
||||
File tempTextFile = null;
|
||||
ByteArrayOutputStream byteStream = null;
|
||||
|
||||
if (useTemporaryFile) {
|
||||
tempTextFile = File.createTempFile("dspacepdfextract" + source.hashCode(), ".txt");
|
||||
tempTextFile.deleteOnExit();
|
||||
writer = new OutputStreamWriter(new FileOutputStream(tempTextFile));
|
||||
} else {
|
||||
byteStream = new ByteArrayOutputStream();
|
||||
writer = new OutputStreamWriter(byteStream);
|
||||
}
|
||||
|
||||
try {
|
||||
pdfDoc = PDDocument.load(source);
|
||||
pts.writeText(pdfDoc, writer);
|
||||
} catch (InvalidPasswordException ex) {
|
||||
log.error("PDF is encrypted. Cannot extract text (item: {})",
|
||||
() -> currentItem.getHandle());
|
||||
return null;
|
||||
} finally {
|
||||
try {
|
||||
if (pdfDoc != null) {
|
||||
pdfDoc.close();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Error closing PDF file: " + e.getMessage(), e);
|
||||
}
|
||||
|
||||
try {
|
||||
writer.close();
|
||||
} catch (Exception e) {
|
||||
log.error("Error closing temporary extract file: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
if (useTemporaryFile) {
|
||||
return new FileInputStream(tempTextFile);
|
||||
} else {
|
||||
byte[] bytes = byteStream.toByteArray();
|
||||
return new ByteArrayInputStream(bytes);
|
||||
}
|
||||
} catch (OutOfMemoryError oome) {
|
||||
log.error("Error parsing PDF document " + oome.getMessage(), oome);
|
||||
if (!configurationService.getBooleanProperty("pdffilter.skiponmemoryexception", false)) {
|
||||
throw oome;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
@@ -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.app.mediafilter;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.apache.poi.POITextExtractor;
|
||||
import org.apache.poi.extractor.ExtractorFactory;
|
||||
import org.apache.poi.openxml4j.exceptions.OpenXML4JException;
|
||||
import org.apache.xmlbeans.XmlException;
|
||||
import org.dspace.content.Item;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Extract flat text from Microsoft Word documents (.doc, .docx).
|
||||
*/
|
||||
public class PoiWordFilter
|
||||
extends MediaFilter {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(PoiWordFilter.class);
|
||||
|
||||
@Override
|
||||
public String getFilteredName(String oldFilename) {
|
||||
return oldFilename + ".txt";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBundleName() {
|
||||
return "TEXT";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFormatString() {
|
||||
return "Text";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Extracted text";
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getDestinationStream(Item currentItem, InputStream source, boolean verbose)
|
||||
throws Exception {
|
||||
String text;
|
||||
try {
|
||||
// get input stream from bitstream, pass to filter, get string back
|
||||
POITextExtractor extractor = ExtractorFactory.createExtractor(source);
|
||||
text = extractor.getText();
|
||||
} catch (IOException | OpenXML4JException | XmlException e) {
|
||||
System.err.format("Invalid File Format: %s%n", e.getMessage());
|
||||
LOG.error("Unable to parse the bitstream: ", e);
|
||||
throw e;
|
||||
}
|
||||
|
||||
// if verbose flag is set, print out extracted text to STDOUT
|
||||
if (verbose) {
|
||||
System.out.println(text);
|
||||
}
|
||||
|
||||
// return the extracted text as a stream.
|
||||
return new ByteArrayInputStream(text.getBytes());
|
||||
}
|
||||
}
|
@@ -1,113 +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.mediafilter;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.poi.POITextExtractor;
|
||||
import org.apache.poi.extractor.ExtractorFactory;
|
||||
import org.apache.poi.hslf.extractor.PowerPointExtractor;
|
||||
import org.apache.poi.xslf.extractor.XSLFPowerPointExtractor;
|
||||
import org.dspace.content.Item;
|
||||
|
||||
/*
|
||||
* TODO: Allow user to configure extraction of only text or only notes
|
||||
*
|
||||
*/
|
||||
public class PowerPointFilter extends MediaFilter {
|
||||
|
||||
private static Logger log = org.apache.logging.log4j.LogManager.getLogger(PowerPointFilter.class);
|
||||
|
||||
@Override
|
||||
public String getFilteredName(String oldFilename) {
|
||||
return oldFilename + ".txt";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return String bundle name
|
||||
*/
|
||||
@Override
|
||||
public String getBundleName() {
|
||||
return "TEXT";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return String bitstream format
|
||||
*
|
||||
* TODO: Check that this is correct
|
||||
*/
|
||||
@Override
|
||||
public String getFormatString() {
|
||||
return "Text";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return String description
|
||||
*/
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Extracted text";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param currentItem item
|
||||
* @param source source input stream
|
||||
* @param verbose verbose mode
|
||||
* @return InputStream the resulting input stream
|
||||
* @throws Exception if error
|
||||
*/
|
||||
@Override
|
||||
public InputStream getDestinationStream(Item currentItem, InputStream source, boolean verbose)
|
||||
throws Exception {
|
||||
|
||||
try {
|
||||
|
||||
String extractedText = null;
|
||||
new ExtractorFactory();
|
||||
POITextExtractor pptExtractor = ExtractorFactory
|
||||
.createExtractor(source);
|
||||
|
||||
// PowerPoint XML files and legacy format PowerPoint files
|
||||
// require different classes and APIs for text extraction
|
||||
|
||||
// If this is a PowerPoint XML file, extract accordingly
|
||||
if (pptExtractor instanceof XSLFPowerPointExtractor) {
|
||||
|
||||
// The true method arguments indicate that text from
|
||||
// the slides and the notes is desired
|
||||
extractedText = ((XSLFPowerPointExtractor) pptExtractor)
|
||||
.getText(true, true);
|
||||
} else if (pptExtractor instanceof PowerPointExtractor) { // Legacy PowerPoint files
|
||||
|
||||
extractedText = ((PowerPointExtractor) pptExtractor).getText()
|
||||
+ " " + ((PowerPointExtractor) pptExtractor).getNotes();
|
||||
|
||||
}
|
||||
if (extractedText != null) {
|
||||
// if verbose flag is set, print out extracted text
|
||||
// to STDOUT
|
||||
if (verbose) {
|
||||
System.out.println(extractedText);
|
||||
}
|
||||
|
||||
// generate an input stream with the extracted text
|
||||
byte[] textBytes = extractedText.getBytes();
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(textBytes);
|
||||
|
||||
return bais;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Error filtering bitstream: " + e.getMessage(), e);
|
||||
throw e;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
@@ -0,0 +1,183 @@
|
||||
/**
|
||||
* 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.mediafilter;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.tika.Tika;
|
||||
import org.apache.tika.exception.TikaException;
|
||||
import org.apache.tika.metadata.Metadata;
|
||||
import org.apache.tika.parser.AutoDetectParser;
|
||||
import org.apache.tika.sax.BodyContentHandler;
|
||||
import org.apache.tika.sax.ContentHandlerDecorator;
|
||||
import org.dspace.content.Item;
|
||||
import org.dspace.services.ConfigurationService;
|
||||
import org.dspace.services.factory.DSpaceServicesFactory;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
/**
|
||||
* Text Extraction media filter which uses Apache Tika to extract text from a large number of file formats (including
|
||||
* all Microsoft formats, PDF, HTML, Text, etc). For a more complete list of file formats supported by Tika see the
|
||||
* Tika documentation: https://tika.apache.org/2.3.0/formats.html
|
||||
*/
|
||||
public class TikaTextExtractionFilter
|
||||
extends MediaFilter {
|
||||
private final static Logger log = LogManager.getLogger();
|
||||
|
||||
@Override
|
||||
public String getFilteredName(String oldFilename) {
|
||||
return oldFilename + ".txt";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBundleName() {
|
||||
return "TEXT";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFormatString() {
|
||||
return "Text";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Extracted text";
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getDestinationStream(Item currentItem, InputStream source, boolean verbose)
|
||||
throws Exception {
|
||||
ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService();
|
||||
boolean useTemporaryFile = configurationService.getBooleanProperty("textextractor.use-temp-file", false);
|
||||
|
||||
if (useTemporaryFile) {
|
||||
// Extract text out of source file using a temp file, returning results as InputStream
|
||||
return extractUsingTempFile(source, verbose);
|
||||
}
|
||||
|
||||
// Not using temporary file. We'll use Tika's default in-memory parsing.
|
||||
// Get maximum characters to extract. Default is 100,000 chars, which is also Tika's default setting.
|
||||
String extractedText;
|
||||
int maxChars = configurationService.getIntProperty("textextractor.max-chars", 100000);
|
||||
try {
|
||||
// Use Tika to extract text from input. Tika will automatically detect the file type.
|
||||
Tika tika = new Tika();
|
||||
tika.setMaxStringLength(maxChars); // Tell Tika the maximum number of characters to extract
|
||||
extractedText = tika.parseToString(source);
|
||||
} catch (IOException e) {
|
||||
System.err.format("Unable to extract text from bitstream in Item %s%n", currentItem.getID().toString());
|
||||
e.printStackTrace();
|
||||
log.error("Unable to extract text from bitstream in Item {}", currentItem.getID().toString(), e);
|
||||
throw e;
|
||||
} catch (OutOfMemoryError oe) {
|
||||
System.err.format("OutOfMemoryError occurred when extracting text from bitstream in Item %s. " +
|
||||
"You may wish to enable 'textextractor.use-temp-file'.%n", currentItem.getID().toString());
|
||||
oe.printStackTrace();
|
||||
log.error("OutOfMemoryError occurred when extracting text from bitstream in Item {}. " +
|
||||
"You may wish to enable 'textextractor.use-temp-file'.", currentItem.getID().toString(), oe);
|
||||
throw oe;
|
||||
}
|
||||
|
||||
if (StringUtils.isNotEmpty(extractedText)) {
|
||||
// if verbose flag is set, print out extracted text to STDOUT
|
||||
if (verbose) {
|
||||
System.out.println("(Verbose mode) Extracted text:");
|
||||
System.out.println(extractedText);
|
||||
}
|
||||
|
||||
// return the extracted text as a UTF-8 stream.
|
||||
return new ByteArrayInputStream(extractedText.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the text out of a given source InputStream, using a temporary file. This decreases the amount of memory
|
||||
* necessary for text extraction, but can be slower as it requires writing extracted text to a temporary file.
|
||||
* @param source source InputStream
|
||||
* @param verbose verbose mode enabled/disabled
|
||||
* @return InputStream for temporary file containing extracted text
|
||||
* @throws IOException
|
||||
* @throws SAXException
|
||||
* @throws TikaException
|
||||
*/
|
||||
private InputStream extractUsingTempFile(InputStream source, boolean verbose)
|
||||
throws IOException, TikaException, SAXException {
|
||||
File tempExtractedTextFile = File.createTempFile("dspacetextextract" + source.hashCode(), ".txt");
|
||||
|
||||
if (verbose) {
|
||||
System.out.println("(Verbose mode) Extracted text was written to temporary file at " +
|
||||
tempExtractedTextFile.getAbsolutePath());
|
||||
} else {
|
||||
tempExtractedTextFile.deleteOnExit();
|
||||
}
|
||||
|
||||
// Open temp file for writing
|
||||
try (FileWriter writer = new FileWriter(tempExtractedTextFile, StandardCharsets.UTF_8)) {
|
||||
// Initialize a custom ContentHandlerDecorator which is a BodyContentHandler.
|
||||
// This mimics the behavior of Tika().parseToString(), which only extracts text from the body of the file.
|
||||
// This custom Handler writes any extracted text to the temp file.
|
||||
ContentHandlerDecorator handler = new BodyContentHandler(new ContentHandlerDecorator() {
|
||||
/**
|
||||
* Write all extracted characters directly to the temp file.
|
||||
*/
|
||||
@Override
|
||||
public void characters(char[] ch, int start, int length) throws SAXException {
|
||||
try {
|
||||
writer.append(new String(ch), start, length);
|
||||
} catch (IOException e) {
|
||||
String errorMsg = String.format("Could not append to temporary file at %s " +
|
||||
"when performing text extraction",
|
||||
tempExtractedTextFile.getAbsolutePath());
|
||||
log.error(errorMsg, e);
|
||||
throw new SAXException(errorMsg, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write all ignorable whitespace directly to the temp file.
|
||||
* This mimics the behaviour of Tika().parseToString() which extracts ignorableWhitespace characters
|
||||
* (like blank lines, indentations, etc.), so that we get the same extracted text either way.
|
||||
*/
|
||||
@Override
|
||||
public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {
|
||||
try {
|
||||
writer.append(new String(ch), start, length);
|
||||
} catch (IOException e) {
|
||||
String errorMsg = String.format("Could not append to temporary file at %s " +
|
||||
"when performing text extraction",
|
||||
tempExtractedTextFile.getAbsolutePath());
|
||||
log.error(errorMsg, e);
|
||||
throw new SAXException(errorMsg, e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
AutoDetectParser parser = new AutoDetectParser();
|
||||
Metadata metadata = new Metadata();
|
||||
// parse our source InputStream using the above custom handler
|
||||
parser.parse(source, handler, metadata);
|
||||
}
|
||||
|
||||
// At this point, all extracted text is written to our temp file. So, return a FileInputStream for that file
|
||||
return new FileInputStream(tempExtractedTextFile);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
@@ -16,6 +16,7 @@ import org.dspace.content.Collection;
|
||||
import org.dspace.content.Community;
|
||||
import org.dspace.content.Item;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.scripts.handler.DSpaceRunnableHandler;
|
||||
|
||||
/**
|
||||
* MediaFilterManager is the class that invokes the media/format filters over the
|
||||
@@ -124,4 +125,10 @@ public interface MediaFilterService {
|
||||
public void setSkipList(List<String> skipList);
|
||||
|
||||
public void setFilterFormats(Map<String, List<String>> filterFormats);
|
||||
|
||||
/**
|
||||
* Set the log handler used in the DSpace scripts and processes framework
|
||||
* @param handler
|
||||
*/
|
||||
public void setLogHandler(DSpaceRunnableHandler handler);
|
||||
}
|
||||
|
@@ -631,7 +631,7 @@ public class Packager {
|
||||
//otherwise, just disseminate a single object to a single package file
|
||||
dip.disseminate(context, dso, pkgParams, pkgFile);
|
||||
|
||||
if (pkgFile != null && pkgFile.exists()) {
|
||||
if (pkgFile.exists()) {
|
||||
System.out.println("\nCREATED package file: " + pkgFile.getCanonicalPath());
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* 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.requestitem;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.dspace.content.Collection;
|
||||
import org.dspace.content.Item;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.eperson.EPerson;
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
/**
|
||||
* Derive request recipients from groups of the Collection which owns an Item.
|
||||
* The list will include all members of the administrators group. If the
|
||||
* resulting list is empty, delegates to {@link RequestItemHelpdeskStrategy}.
|
||||
*
|
||||
* @author Mark H. Wood <mwood@iupui.edu>
|
||||
*/
|
||||
public class CollectionAdministratorsRequestItemStrategy
|
||||
extends RequestItemHelpdeskStrategy {
|
||||
@Override
|
||||
@NonNull
|
||||
public List<RequestItemAuthor> getRequestItemAuthor(Context context,
|
||||
Item item)
|
||||
throws SQLException {
|
||||
List<RequestItemAuthor> recipients = new ArrayList<>();
|
||||
Collection collection = item.getOwningCollection();
|
||||
for (EPerson admin : collection.getAdministrators().getMembers()) {
|
||||
recipients.add(new RequestItemAuthor(admin));
|
||||
}
|
||||
if (recipients.isEmpty()) {
|
||||
return super.getRequestItemAuthor(context, item);
|
||||
} else {
|
||||
return recipients;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
package org.dspace.app.requestitem;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.dspace.content.Item;
|
||||
import org.dspace.core.Context;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Assemble a list of recipients from the results of other strategies.
|
||||
* The list of strategy classes is injected as the constructor argument
|
||||
* {@code strategies}.
|
||||
* If the strategy list is not configured, returns an empty List.
|
||||
*
|
||||
* @author Mark H. Wood <mwood@iupui.edu>
|
||||
*/
|
||||
public class CombiningRequestItemStrategy
|
||||
implements RequestItemAuthorExtractor {
|
||||
/** The strategies to combine. */
|
||||
private final List<RequestItemAuthorExtractor> strategies;
|
||||
|
||||
/**
|
||||
* Initialize a combination of strategies.
|
||||
* @param strategies the author extraction strategies to combine.
|
||||
*/
|
||||
public CombiningRequestItemStrategy(@NonNull List<RequestItemAuthorExtractor> strategies) {
|
||||
Assert.notNull(strategies, "Strategy list may not be null");
|
||||
this.strategies = strategies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Do not call.
|
||||
* @throws IllegalArgumentException always
|
||||
*/
|
||||
private CombiningRequestItemStrategy() {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public List<RequestItemAuthor> getRequestItemAuthor(Context context, Item item)
|
||||
throws SQLException {
|
||||
List<RequestItemAuthor> recipients = new ArrayList<>();
|
||||
|
||||
for (RequestItemAuthorExtractor strategy : strategies) {
|
||||
recipients.addAll(strategy.getRequestItemAuthor(context, item));
|
||||
}
|
||||
|
||||
return recipients;
|
||||
}
|
||||
}
|
@@ -27,13 +27,12 @@ import org.dspace.core.Context;
|
||||
import org.dspace.core.ReloadableEntity;
|
||||
|
||||
/**
|
||||
* Object representing an Item Request
|
||||
* Object representing an Item Request.
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "requestitem")
|
||||
public class RequestItem implements ReloadableEntity<Integer> {
|
||||
|
||||
|
||||
@Id
|
||||
@Column(name = "requestitem_id")
|
||||
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "requestitem_seq")
|
||||
@@ -54,8 +53,6 @@ public class RequestItem implements ReloadableEntity<Integer> {
|
||||
@Column(name = "request_name", length = 64)
|
||||
private String reqName;
|
||||
|
||||
// @Column(name = "request_message")
|
||||
// @Lob
|
||||
@Column(name = "request_message", columnDefinition = "text")
|
||||
private String reqMessage;
|
||||
|
||||
@@ -82,8 +79,8 @@ public class RequestItem implements ReloadableEntity<Integer> {
|
||||
|
||||
/**
|
||||
* Protected constructor, create object using:
|
||||
* {@link org.dspace.app.requestitem.service.RequestItemService#createRequest(Context, Bitstream, Item,
|
||||
* boolean, String, String, String)}
|
||||
* {@link org.dspace.app.requestitem.service.RequestItemService#createRequest(
|
||||
* Context, Bitstream, Item, boolean, String, String, String)}
|
||||
*/
|
||||
protected RequestItem() {
|
||||
}
|
||||
@@ -97,6 +94,9 @@ public class RequestItem implements ReloadableEntity<Integer> {
|
||||
this.allfiles = allfiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@code true} if all of the Item's files are requested.
|
||||
*/
|
||||
public boolean isAllfiles() {
|
||||
return allfiles;
|
||||
}
|
||||
@@ -105,6 +105,9 @@ public class RequestItem implements ReloadableEntity<Integer> {
|
||||
this.reqMessage = reqMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a message from the requester.
|
||||
*/
|
||||
public String getReqMessage() {
|
||||
return reqMessage;
|
||||
}
|
||||
@@ -113,6 +116,9 @@ public class RequestItem implements ReloadableEntity<Integer> {
|
||||
this.reqName = reqName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Human-readable name of the user requesting access.
|
||||
*/
|
||||
public String getReqName() {
|
||||
return reqName;
|
||||
}
|
||||
@@ -121,6 +127,9 @@ public class RequestItem implements ReloadableEntity<Integer> {
|
||||
this.reqEmail = reqEmail;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return address of the user requesting access.
|
||||
*/
|
||||
public String getReqEmail() {
|
||||
return reqEmail;
|
||||
}
|
||||
@@ -129,6 +138,9 @@ public class RequestItem implements ReloadableEntity<Integer> {
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a unique request identifier which can be emailed.
|
||||
*/
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
@@ -11,20 +11,31 @@ import org.dspace.eperson.EPerson;
|
||||
|
||||
/**
|
||||
* Simple DTO to transfer data about the corresponding author for the Request
|
||||
* Copy feature
|
||||
* Copy feature.
|
||||
*
|
||||
* @author Andrea Bollini
|
||||
*/
|
||||
public class RequestItemAuthor {
|
||||
private String fullName;
|
||||
private String email;
|
||||
private final String fullName;
|
||||
private final String email;
|
||||
|
||||
/**
|
||||
* Construct an author record from given data.
|
||||
*
|
||||
* @param fullName the author's full name.
|
||||
* @param email the author's email address.
|
||||
*/
|
||||
public RequestItemAuthor(String fullName, String email) {
|
||||
super();
|
||||
this.fullName = fullName;
|
||||
this.email = email;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an author from an EPerson's metadata.
|
||||
*
|
||||
* @param ePerson the EPerson.
|
||||
*/
|
||||
public RequestItemAuthor(EPerson ePerson) {
|
||||
super();
|
||||
this.fullName = ePerson.getFullName();
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user