mirror of
https://github.com/DSpace/DSpace.git
synced 2025-10-07 01:54:22 +00:00
Merged in CST-12881-conflics-with-main (pull request #1402)
CST-12881 merge conflicts with main
This commit is contained in:
22
.github/workflows/build.yml
vendored
22
.github/workflows/build.yml
vendored
@@ -45,7 +45,7 @@ jobs:
|
||||
steps:
|
||||
# https://github.com/actions/checkout
|
||||
- name: Checkout codebase
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# https://github.com/actions/setup-java
|
||||
- name: Install JDK ${{ matrix.java }}
|
||||
@@ -53,16 +53,7 @@ jobs:
|
||||
with:
|
||||
java-version: ${{ matrix.java }}
|
||||
distribution: 'temurin'
|
||||
|
||||
# https://github.com/actions/cache
|
||||
- name: Cache Maven dependencies
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
# Cache entire ~/.m2/repository
|
||||
path: ~/.m2/repository
|
||||
# Cache key is hash of all pom.xml files. Therefore any changes to POMs will invalidate cache
|
||||
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
|
||||
restore-keys: ${{ runner.os }}-maven-
|
||||
cache: 'maven'
|
||||
|
||||
# Run parallel Maven builds based on the above 'strategy.matrix'
|
||||
- name: Run Maven ${{ matrix.type }}
|
||||
@@ -96,7 +87,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Download artifacts from previous 'tests' job
|
||||
- name: Download coverage artifacts
|
||||
@@ -108,10 +99,13 @@ jobs:
|
||||
# Retry action: https://github.com/marketplace/actions/retry-action
|
||||
# Codecov action: https://github.com/codecov/codecov-action
|
||||
- name: Upload coverage to Codecov.io
|
||||
uses: Wandalen/wretry.action@v1.0.36
|
||||
uses: Wandalen/wretry.action@v1.3.0
|
||||
with:
|
||||
action: codecov/codecov-action@v3
|
||||
# Try upload 5 times max
|
||||
# Ensure codecov-action throws an error when it fails to upload
|
||||
with: |
|
||||
fail_ci_if_error: true
|
||||
# Try re-running action 5 times max
|
||||
attempt_limit: 5
|
||||
# Run again in 30 seconds
|
||||
attempt_delay: 30000
|
||||
|
2
.github/workflows/codescan.yml
vendored
2
.github/workflows/codescan.yml
vendored
@@ -35,7 +35,7 @@ jobs:
|
||||
steps:
|
||||
# https://github.com/actions/checkout
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# https://github.com/actions/setup-java
|
||||
- name: Install JDK
|
||||
|
430
.github/workflows/docker.yml
vendored
430
.github/workflows/docker.yml
vendored
@@ -3,6 +3,7 @@ name: Docker images
|
||||
|
||||
# Run this Build for all pushes to 'main' or maintenance branches, or tagged releases.
|
||||
# Also run for PRs to ensure PR doesn't break Docker build process
|
||||
# NOTE: uses "reusable-docker-build.yml" to actually build each of the Docker images.
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
@@ -15,83 +16,22 @@ on:
|
||||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
|
||||
# Define shared environment variables for all jobs below
|
||||
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 'latest' on Docker image.
|
||||
# For a new commit on other branches, use the branch name as the tag for Docker image.
|
||||
# For a new tag, copy that tag name as the tag for Docker image.
|
||||
IMAGE_TAGS: |
|
||||
type=raw,value=latest,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 manage the 'latest' tag ourselves to the 'main' branch (see settings above)
|
||||
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' || '' }}
|
||||
|
||||
jobs:
|
||||
####################################################
|
||||
# Build/Push the 'dspace/dspace-dependencies' image.
|
||||
# This image is used by all other jobs.
|
||||
# This image is used by all other DSpace build jobs.
|
||||
####################################################
|
||||
dspace-dependencies:
|
||||
# Ensure this job never runs on forked repos. It's only executed for 'dspace/dspace'
|
||||
if: github.repository == 'dspace/dspace'
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
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 }}
|
||||
|
||||
# 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@v4
|
||||
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 }}
|
||||
uses: ./.github/workflows/reusable-docker-build.yml
|
||||
with:
|
||||
build_id: dspace-dependencies
|
||||
image_name: dspace/dspace-dependencies
|
||||
dockerfile_path: ./Dockerfile.dependencies
|
||||
secrets:
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
DOCKER_ACCESS_TOKEN: ${{ secrets.DOCKER_ACCESS_TOKEN }}
|
||||
|
||||
#######################################
|
||||
# Build/Push the 'dspace/dspace' image
|
||||
@@ -101,52 +41,18 @@ jobs:
|
||||
if: github.repository == 'dspace/dspace'
|
||||
# Must run after 'dspace-dependencies' job above
|
||||
needs: dspace-dependencies
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
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 }}
|
||||
|
||||
# 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@v4
|
||||
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 }}
|
||||
uses: ./.github/workflows/reusable-docker-build.yml
|
||||
with:
|
||||
build_id: dspace
|
||||
image_name: dspace/dspace
|
||||
dockerfile_path: ./Dockerfile
|
||||
secrets:
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
DOCKER_ACCESS_TOKEN: ${{ secrets.DOCKER_ACCESS_TOKEN }}
|
||||
# Enable redeploy of sandbox & demo if the branch for this image matches the deployment branch of
|
||||
# these sites as specified in reusable-docker-build.xml
|
||||
REDEPLOY_SANDBOX_URL: ${{ secrets.REDEPLOY_SANDBOX_URL }}
|
||||
REDEPLOY_DEMO_URL: ${{ secrets.REDEPLOY_DEMO_URL }}
|
||||
|
||||
#############################################################
|
||||
# Build/Push the 'dspace/dspace' image ('-test' tag)
|
||||
@@ -156,55 +62,17 @@ jobs:
|
||||
if: github.repository == 'dspace/dspace'
|
||||
# Must run after 'dspace-dependencies' job above
|
||||
needs: dspace-dependencies
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
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 }}
|
||||
|
||||
# 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@v4
|
||||
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 }}
|
||||
uses: ./.github/workflows/reusable-docker-build.yml
|
||||
with:
|
||||
build_id: dspace-test
|
||||
image_name: dspace/dspace
|
||||
dockerfile_path: ./Dockerfile.test
|
||||
# As this is a test/development image, its tags are all suffixed with "-test". Otherwise, it uses the same
|
||||
# tagging logic as the primary 'dspace/dspace' image above.
|
||||
tags_flavor: suffix=-test
|
||||
secrets:
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
DOCKER_ACCESS_TOKEN: ${{ secrets.DOCKER_ACCESS_TOKEN }}
|
||||
|
||||
###########################################
|
||||
# Build/Push the 'dspace/dspace-cli' image
|
||||
@@ -214,52 +82,14 @@ jobs:
|
||||
if: github.repository == 'dspace/dspace'
|
||||
# Must run after 'dspace-dependencies' job above
|
||||
needs: dspace-dependencies
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
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 }}
|
||||
|
||||
# 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@v4
|
||||
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 }}
|
||||
uses: ./.github/workflows/reusable-docker-build.yml
|
||||
with:
|
||||
build_id: dspace-cli
|
||||
image_name: dspace/dspace-cli
|
||||
dockerfile_path: ./Dockerfile.cli
|
||||
secrets:
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
DOCKER_ACCESS_TOKEN: ${{ secrets.DOCKER_ACCESS_TOKEN }}
|
||||
|
||||
###########################################
|
||||
# Build/Push the 'dspace/dspace-solr' image
|
||||
@@ -267,52 +97,18 @@ jobs:
|
||||
dspace-solr:
|
||||
# Ensure this job never runs on forked repos. It's only executed for 'dspace/dspace'
|
||||
if: github.repository == 'dspace/dspace'
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
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 }}
|
||||
|
||||
# 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@v4
|
||||
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 }}
|
||||
uses: ./.github/workflows/reusable-docker-build.yml
|
||||
with:
|
||||
build_id: dspace-solr
|
||||
image_name: dspace/dspace-solr
|
||||
dockerfile_path: ./dspace/src/main/docker/dspace-solr/Dockerfile
|
||||
secrets:
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
DOCKER_ACCESS_TOKEN: ${{ secrets.DOCKER_ACCESS_TOKEN }}
|
||||
# Enable redeploy of sandbox & demo SOLR instance whenever dspace-solr image changes for deployed branch.
|
||||
# These URLs MUST use different secrets than 'dspace/dspace' image build above as they are deployed separately.
|
||||
REDEPLOY_SANDBOX_URL: ${{ secrets.REDEPLOY_SANDBOX_SOLR_URL }}
|
||||
REDEPLOY_DEMO_URL: ${{ secrets.REDEPLOY_DEMO_SOLR_URL }}
|
||||
|
||||
###########################################################
|
||||
# Build/Push the 'dspace/dspace-postgres-pgcrypto' image
|
||||
@@ -320,53 +116,16 @@ jobs:
|
||||
dspace-postgres-pgcrypto:
|
||||
# Ensure this job never runs on forked repos. It's only executed for 'dspace/dspace'
|
||||
if: github.repository == 'dspace/dspace'
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
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 }}
|
||||
|
||||
# Get Metadata for docker_build_postgres step below
|
||||
- name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace-postgres-pgcrypto' image
|
||||
id: meta_build_postgres
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: dspace/dspace-postgres-pgcrypto
|
||||
tags: ${{ env.IMAGE_TAGS }}
|
||||
flavor: ${{ env.TAGS_FLAVOR }}
|
||||
|
||||
- name: Build and push 'dspace-postgres-pgcrypto' image
|
||||
id: docker_build_postgres
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
# Must build out of subdirectory to have access to install script for pgcrypto
|
||||
context: ./dspace/src/main/docker/dspace-postgres-pgcrypto/
|
||||
dockerfile: 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_postgres.outputs.tags }}
|
||||
labels: ${{ steps.meta_build_postgres.outputs.labels }}
|
||||
uses: ./.github/workflows/reusable-docker-build.yml
|
||||
with:
|
||||
build_id: dspace-postgres-pgcrypto
|
||||
image_name: dspace/dspace-postgres-pgcrypto
|
||||
# Must build out of subdirectory to have access to install script for pgcrypto.
|
||||
# NOTE: this context will build the image based on the Dockerfile in the specified directory
|
||||
dockerfile_context: ./dspace/src/main/docker/dspace-postgres-pgcrypto/
|
||||
secrets:
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
DOCKER_ACCESS_TOKEN: ${{ secrets.DOCKER_ACCESS_TOKEN }}
|
||||
|
||||
########################################################################
|
||||
# Build/Push the 'dspace/dspace-postgres-pgcrypto' image (-loadsql tag)
|
||||
@@ -374,53 +133,16 @@ jobs:
|
||||
dspace-postgres-pgcrypto-loadsql:
|
||||
# Ensure this job never runs on forked repos. It's only executed for 'dspace/dspace'
|
||||
if: github.repository == 'dspace/dspace'
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
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 }}
|
||||
|
||||
# Get Metadata for docker_build_postgres_loadsql step below
|
||||
- name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace-postgres-pgcrypto-loadsql' image
|
||||
id: meta_build_postgres_loadsql
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: dspace/dspace-postgres-pgcrypto
|
||||
tags: ${{ env.IMAGE_TAGS }}
|
||||
# Suffix all tags with "-loadsql". Otherwise, it uses the same
|
||||
# tagging logic as the primary 'dspace/dspace-postgres-pgcrypto' image above.
|
||||
flavor: ${{ env.TAGS_FLAVOR }}
|
||||
suffix=-loadsql
|
||||
|
||||
- name: Build and push 'dspace-postgres-pgcrypto-loadsql' image
|
||||
id: docker_build_postgres_loadsql
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
# Must build out of subdirectory to have access to install script for pgcrypto
|
||||
context: ./dspace/src/main/docker/dspace-postgres-pgcrypto-curl/
|
||||
dockerfile: 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_postgres_loadsql.outputs.tags }}
|
||||
labels: ${{ steps.meta_build_postgres_loadsql.outputs.labels }}
|
||||
uses: ./.github/workflows/reusable-docker-build.yml
|
||||
with:
|
||||
build_id: dspace-postgres-pgcrypto-loadsql
|
||||
image_name: dspace/dspace-postgres-pgcrypto
|
||||
# Must build out of subdirectory to have access to install script for pgcrypto.
|
||||
# NOTE: this context will build the image based on the Dockerfile in the specified directory
|
||||
dockerfile_context: ./dspace/src/main/docker/dspace-postgres-pgcrypto-curl/
|
||||
# Suffix all tags with "-loadsql". Otherwise, it uses the same
|
||||
# tagging logic as the primary 'dspace/dspace-postgres-pgcrypto' image above.
|
||||
tags_flavor: suffix=-loadsql
|
||||
secrets:
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
DOCKER_ACCESS_TOKEN: ${{ secrets.DOCKER_ACCESS_TOKEN }}
|
@@ -23,11 +23,11 @@ jobs:
|
||||
if: github.event.pull_request.merged
|
||||
steps:
|
||||
# Checkout code
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
# Port PR to other branch (ONLY if labeled with "port to")
|
||||
# See https://github.com/korthout/backport-action
|
||||
- name: Create backport pull requests
|
||||
uses: korthout/backport-action@v1
|
||||
uses: korthout/backport-action@v2
|
||||
with:
|
||||
# Trigger based on a "port to [branch]" label on PR
|
||||
# (This label must specify the branch name to port to)
|
||||
@@ -39,6 +39,8 @@ jobs:
|
||||
# Copy all labels from original PR to (newly created) port PR
|
||||
# NOTE: The labels matching 'label_pattern' are automatically excluded
|
||||
copy_labels_pattern: '.*'
|
||||
# Skip any merge commits in the ported PR. This means only non-merge commits are cherry-picked to the new PR
|
||||
merge_commits: 'skip'
|
||||
# Use a personal access token (PAT) to create PR as 'dspace-bot' user.
|
||||
# A PAT is required in order for the new PR to trigger its own actions (for CI checks)
|
||||
github_token: ${{ secrets.PR_PORT_TOKEN }}
|
2
.github/workflows/pull_request_opened.yml
vendored
2
.github/workflows/pull_request_opened.yml
vendored
@@ -21,4 +21,4 @@ jobs:
|
||||
# Assign the PR to whomever created it. This is useful for visualizing assignments on project boards
|
||||
# See https://github.com/toshimaru/auto-author-assign
|
||||
- name: Assign PR to creator
|
||||
uses: toshimaru/auto-author-assign@v1.6.2
|
||||
uses: toshimaru/auto-author-assign@v2.0.1
|
||||
|
217
.github/workflows/reusable-docker-build.yml
vendored
Normal file
217
.github/workflows/reusable-docker-build.yml
vendored
Normal file
@@ -0,0 +1,217 @@
|
||||
#
|
||||
# DSpace's reusable Docker build/push workflow.
|
||||
#
|
||||
# This is used by docker.yml for all Docker image builds
|
||||
name: Reusable DSpace Docker Build
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
# Possible Inputs to this reusable job
|
||||
inputs:
|
||||
# Build name/id for this Docker build. Used for digest storage to avoid digest overlap between builds.
|
||||
build_id:
|
||||
required: true
|
||||
type: string
|
||||
# Requires the image name to build (e.g dspace/dspace-test)
|
||||
image_name:
|
||||
required: true
|
||||
type: string
|
||||
# Optionally the path to the Dockerfile to use for the build. (Default is [dockerfile_context]/Dockerfile)
|
||||
dockerfile_path:
|
||||
required: false
|
||||
type: string
|
||||
# Optionally the context directory to build the Dockerfile within. Defaults to "." (current directory)
|
||||
dockerfile_context:
|
||||
required: false
|
||||
type: string
|
||||
# If Docker image should have additional tag flavor details (e.g. a suffix), it may be passed in.
|
||||
tags_flavor:
|
||||
required: false
|
||||
type: string
|
||||
secrets:
|
||||
# Requires that Docker login info be passed in as secrets.
|
||||
DOCKER_USERNAME:
|
||||
required: true
|
||||
DOCKER_ACCESS_TOKEN:
|
||||
required: true
|
||||
# These URL secrets are optional. When specified & branch checks match, the redeployment code below will trigger.
|
||||
# Therefore builds which need to trigger redeployment MUST specify these URLs. All others should leave them empty.
|
||||
REDEPLOY_SANDBOX_URL:
|
||||
required: false
|
||||
REDEPLOY_DEMO_URL:
|
||||
required: false
|
||||
|
||||
# Define shared default settings as environment variables
|
||||
env:
|
||||
IMAGE_NAME: ${{ inputs.image_name }}
|
||||
# Define tags to use for Docker images based on Git tags/branches (for docker/metadata-action)
|
||||
# For a new commit on default branch (main), use the literal tag 'latest' on Docker image.
|
||||
# For a new commit on other branches, use the branch name as the tag for Docker image.
|
||||
# For a new tag, copy that tag name as the tag for Docker image.
|
||||
IMAGE_TAGS: |
|
||||
type=raw,value=latest,enable=${{ github.ref_name == github.event.repository.default_branch }}
|
||||
type=ref,event=branch,enable=${{ github.ref_name != github.event.repository.default_branch }}
|
||||
type=ref,event=tag
|
||||
# Define default tag "flavor" for docker/metadata-action per
|
||||
# https://github.com/docker/metadata-action#flavor-input
|
||||
# We manage the 'latest' tag ourselves to the 'main' branch (see settings above)
|
||||
TAGS_FLAVOR: |
|
||||
latest=false
|
||||
${{ inputs.tags_flavor }}
|
||||
# When these URL variables are specified & required branch matches, then the sandbox or demo site will be redeployed.
|
||||
# See "Redeploy" steps below for more details.
|
||||
REDEPLOY_SANDBOX_URL: ${{ secrets.REDEPLOY_SANDBOX_URL }}
|
||||
REDEPLOY_DEMO_URL: ${{ secrets.REDEPLOY_DEMO_URL }}
|
||||
# Current DSpace maintenance branch (and architecture) which is deployed to demo.dspace.org / sandbox.dspace.org
|
||||
# (NOTE: No deployment branch specified for sandbox.dspace.org as it uses the default_branch)
|
||||
DEPLOY_DEMO_BRANCH: 'dspace-7_x'
|
||||
DEPLOY_ARCH: 'linux/amd64'
|
||||
|
||||
jobs:
|
||||
docker-build:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
# Architectures / Platforms for which we will build Docker images
|
||||
arch: [ 'linux/amd64', 'linux/arm64' ]
|
||||
os: [ ubuntu-latest ]
|
||||
isPr:
|
||||
- ${{ github.event_name == 'pull_request' }}
|
||||
# If this is a PR, we ONLY build for AMD64. For PRs we only do a sanity check test to ensure Docker builds work.
|
||||
# The below exclude therefore ensures we do NOT build ARM64 for PRs.
|
||||
exclude:
|
||||
- isPr: true
|
||||
os: ubuntu-latest
|
||||
arch: linux/arm64
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
# https://github.com/actions/checkout
|
||||
- name: Checkout codebase
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# https://github.com/docker/setup-buildx-action
|
||||
- name: Setup Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
# https://github.com/docker/setup-qemu-action
|
||||
- name: Set up QEMU emulation to build for multiple architectures
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
# https://github.com/docker/login-action
|
||||
- name: Login to DockerHub
|
||||
# Only login if not a PR, as PRs only trigger a Docker build and not a push
|
||||
if: ${{ ! matrix.isPr }}
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
|
||||
|
||||
# https://github.com/docker/metadata-action
|
||||
# Get Metadata for docker_build_deps step below
|
||||
- name: Sync metadata (tags, labels) from GitHub to Docker for image
|
||||
id: meta_build
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.IMAGE_NAME }}
|
||||
tags: ${{ env.IMAGE_TAGS }}
|
||||
flavor: ${{ env.TAGS_FLAVOR }}
|
||||
|
||||
# https://github.com/docker/build-push-action
|
||||
- name: Build and push image
|
||||
id: docker_build
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: ${{ inputs.dockerfile_context || '.' }}
|
||||
file: ${{ inputs.dockerfile_path }}
|
||||
platforms: ${{ matrix.arch }}
|
||||
# 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: ${{ ! matrix.isPr }}
|
||||
# Use tags / labels provided by 'docker/metadata-action' above
|
||||
tags: ${{ steps.meta_build.outputs.tags }}
|
||||
labels: ${{ steps.meta_build.outputs.labels }}
|
||||
|
||||
# Export the digest of Docker build locally (for non PRs only)
|
||||
- name: Export Docker build digest
|
||||
if: ${{ ! matrix.isPr }}
|
||||
run: |
|
||||
mkdir -p /tmp/digests
|
||||
digest="${{ steps.docker_build.outputs.digest }}"
|
||||
touch "/tmp/digests/${digest#sha256:}"
|
||||
|
||||
# Upload digest to an artifact, so that it can be used in manifest below
|
||||
- name: Upload Docker build digest to artifact
|
||||
if: ${{ ! matrix.isPr }}
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: digests-${{ inputs.build_id }}
|
||||
path: /tmp/digests/*
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
|
||||
# If this build is NOT a PR and passed in a REDEPLOY_SANDBOX_URL secret,
|
||||
# Then redeploy https://sandbox.dspace.org if this build is for our deployment architecture and 'main' branch.
|
||||
- name: Redeploy sandbox.dspace.org (based on main branch)
|
||||
if: |
|
||||
!matrix.isPR &&
|
||||
env.REDEPLOY_SANDBOX_URL != '' &&
|
||||
matrix.arch == env.DEPLOY_ARCH &&
|
||||
github.ref_name == github.event.repository.default_branch
|
||||
run: |
|
||||
curl -X POST $REDEPLOY_SANDBOX_URL
|
||||
|
||||
# If this build is NOT a PR and passed in a REDEPLOY_DEMO_URL secret,
|
||||
# Then redeploy https://demo.dspace.org if this build is for our deployment architecture and demo branch.
|
||||
- name: Redeploy demo.dspace.org (based on maintenace branch)
|
||||
if: |
|
||||
!matrix.isPR &&
|
||||
env.REDEPLOY_DEMO_URL != '' &&
|
||||
matrix.arch == env.DEPLOY_ARCH &&
|
||||
github.ref_name == env.DEPLOY_DEMO_BRANCH
|
||||
run: |
|
||||
curl -X POST $REDEPLOY_DEMO_URL
|
||||
|
||||
# Merge Docker digests (from various architectures) into a manifest.
|
||||
# This runs after all Docker builds complete above, and it tells hub.docker.com
|
||||
# that these builds should be all included in the manifest for this tag.
|
||||
# (e.g. AMD64 and ARM64 should be listed as options under the same tagged Docker image)
|
||||
docker-build_manifest:
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- docker-build
|
||||
steps:
|
||||
- name: Download Docker build digests
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: digests-${{ inputs.build_id }}
|
||||
path: /tmp/digests
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Add Docker metadata for image
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.IMAGE_NAME }}
|
||||
tags: ${{ env.IMAGE_TAGS }}
|
||||
flavor: ${{ env.TAGS_FLAVOR }}
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
|
||||
|
||||
- name: Create manifest list from digests and push
|
||||
working-directory: /tmp/digests
|
||||
run: |
|
||||
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
|
||||
$(printf '${{ env.IMAGE_NAME }}@sha256:%s ' *)
|
||||
|
||||
- name: Inspect image
|
||||
run: |
|
||||
docker buildx imagetools inspect ${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }}
|
@@ -21,7 +21,10 @@ USER dspace
|
||||
ADD --chown=dspace . /app/
|
||||
# Build DSpace (note: this build doesn't include the optional, deprecated "dspace-rest" webapp)
|
||||
# Copy the dspace-installer directory to /install. Clean up the build to keep the docker image small
|
||||
RUN mvn --no-transfer-progress package && \
|
||||
# Maven flags here ensure that we skip building test environment and skip all code verification checks.
|
||||
# These flags speed up this compilation as much as reasonably possible.
|
||||
ENV MAVEN_FLAGS="-P-test-environment -Denforcer.skip=true -Dcheckstyle.skip=true -Dlicense.skip=true -Dxml.skip=true"
|
||||
RUN mvn --no-transfer-progress package ${MAVEN_FLAGS} && \
|
||||
mv /app/dspace/target/${TARGET_DIR}/* /install && \
|
||||
mvn clean
|
||||
|
||||
@@ -51,7 +54,7 @@ RUN ant init_installation update_configs update_code update_webapps
|
||||
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 the /dspace directory from 'ant_build' container to /dspace in this container
|
||||
COPY --from=ant_build /dspace $DSPACE_INSTALL
|
||||
# Expose Tomcat port and AJP port
|
||||
EXPOSE 8080 8009
|
||||
|
@@ -15,11 +15,6 @@ RUN useradd 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
|
||||
@@ -28,7 +23,10 @@ USER dspace
|
||||
ADD --chown=dspace . /app/
|
||||
|
||||
# Trigger the installation of all maven dependencies (hide download progress messages)
|
||||
RUN mvn --no-transfer-progress package
|
||||
# Maven flags here ensure that we skip final assembly, skip building test environment and skip all code verification checks.
|
||||
# These flags speed up this installation as much as reasonably possible.
|
||||
ENV MAVEN_FLAGS="-P-assembly -P-test-environment -Denforcer.skip=true -Dcheckstyle.skip=true -Dlicense.skip=true -Dxml.skip=true"
|
||||
RUN mvn --no-transfer-progress install ${MAVEN_FLAGS}
|
||||
|
||||
# Clear the contents of the /app directory (including all maven builds), so no artifacts remain.
|
||||
# This ensures when dspace:dspace is built, it will use the Maven local cache (~/.m2) for dependencies
|
||||
|
@@ -28,6 +28,7 @@ services:
|
||||
# proxies.trusted.ipranges: This setting is required for a REST API running in Docker to trust requests
|
||||
# from the host machine. This IP range MUST correspond to the 'dspacenet' subnet defined above.
|
||||
proxies__P__trusted__P__ipranges: '172.23.0'
|
||||
LOGGING_CONFIG: /dspace/config/log4j2-container.xml
|
||||
image: "${DOCKER_OWNER:-dspace}/dspace:${DSPACE_VER:-latest-test}"
|
||||
build:
|
||||
context: .
|
||||
|
@@ -102,7 +102,7 @@
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>build-helper-maven-plugin</artifactId>
|
||||
<version>3.0.0</version>
|
||||
<version>3.4.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>validate</phase>
|
||||
@@ -116,7 +116,10 @@
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>buildnumber-maven-plugin</artifactId>
|
||||
<version>1.4</version>
|
||||
<version>3.2.0</version>
|
||||
<configuration>
|
||||
<revisionOnScmFailure>UNKNOWN_REVISION</revisionOnScmFailure>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>validate</phase>
|
||||
@@ -718,10 +721,6 @@
|
||||
<artifactId>annotations</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>joda-time</groupId>
|
||||
<artifactId>joda-time</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.inject</groupId>
|
||||
<artifactId>javax.inject</artifactId>
|
||||
@@ -791,7 +790,7 @@
|
||||
<dependency>
|
||||
<groupId>org.json</groupId>
|
||||
<artifactId>json</artifactId>
|
||||
<version>20230227</version>
|
||||
<version>20231013</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Useful for testing command-line tools -->
|
||||
|
@@ -22,9 +22,21 @@ public interface AccessStatusHelper {
|
||||
*
|
||||
* @param context the DSpace context
|
||||
* @param item the item
|
||||
* @param threshold the embargo threshold date
|
||||
* @return an access status value
|
||||
* @throws SQLException An exception that provides information on a database access error or other errors.
|
||||
*/
|
||||
public String getAccessStatusFromItem(Context context, Item item, Date threshold)
|
||||
throws SQLException;
|
||||
|
||||
/**
|
||||
* Retrieve embargo information for the item
|
||||
*
|
||||
* @param context the DSpace context
|
||||
* @param item the item to check for embargo information
|
||||
* @param threshold the embargo threshold date
|
||||
* @return an embargo date
|
||||
* @throws SQLException An exception that provides information on a database access error or other errors.
|
||||
*/
|
||||
public String getEmbargoFromItem(Context context, Item item, Date threshold) throws SQLException;
|
||||
}
|
||||
|
@@ -8,6 +8,8 @@
|
||||
package org.dspace.access.status;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.time.LocalDate;
|
||||
import java.time.ZoneId;
|
||||
import java.util.Date;
|
||||
|
||||
import org.dspace.access.status.service.AccessStatusService;
|
||||
@@ -15,7 +17,6 @@ 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;
|
||||
|
||||
/**
|
||||
@@ -55,7 +56,10 @@ public class AccessStatusServiceImpl implements AccessStatusService {
|
||||
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();
|
||||
forever_date = Date.from(LocalDate.of(year, month, day)
|
||||
.atStartOfDay()
|
||||
.atZone(ZoneId.systemDefault())
|
||||
.toInstant());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,4 +67,9 @@ public class AccessStatusServiceImpl implements AccessStatusService {
|
||||
public String getAccessStatus(Context context, Item item) throws SQLException {
|
||||
return helper.getAccessStatusFromItem(context, item, forever_date);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEmbargoFromItem(Context context, Item item) throws SQLException {
|
||||
return helper.getEmbargoFromItem(context, item, forever_date);
|
||||
}
|
||||
}
|
||||
|
@@ -26,6 +26,7 @@ import org.dspace.content.service.ItemService;
|
||||
import org.dspace.core.Constants;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.eperson.Group;
|
||||
import org.joda.time.LocalDate;
|
||||
|
||||
/**
|
||||
* Default plugin implementation of the access status helper.
|
||||
@@ -33,6 +34,11 @@ import org.dspace.eperson.Group;
|
||||
* calculate the access status of an item based on the policies of
|
||||
* the primary or the first bitstream in the original bundle.
|
||||
* Users can override this method for enhanced functionality.
|
||||
*
|
||||
* The getEmbargoInformationFromItem method provides a simple logic to
|
||||
* * retrieve embargo information of bitstreams from an item based on the policies of
|
||||
* * the primary or the first bitstream in the original bundle.
|
||||
* * Users can override this method for enhanced functionality.
|
||||
*/
|
||||
public class DefaultAccessStatusHelper implements AccessStatusHelper {
|
||||
public static final String EMBARGO = "embargo";
|
||||
@@ -54,12 +60,12 @@ public class DefaultAccessStatusHelper implements AccessStatusHelper {
|
||||
|
||||
/**
|
||||
* Look at the item's policies to determine an access status value.
|
||||
* It is also considering a date threshold for embargos and restrictions.
|
||||
* It is also considering a date threshold for embargoes and restrictions.
|
||||
*
|
||||
* If the item is null, simply returns the "unknown" value.
|
||||
*
|
||||
* @param context the DSpace context
|
||||
* @param item the item to embargo
|
||||
* @param item the item to check for embargoes
|
||||
* @param threshold the embargo threshold date
|
||||
* @return an access status value
|
||||
*/
|
||||
@@ -86,7 +92,7 @@ public class DefaultAccessStatusHelper implements AccessStatusHelper {
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
return caculateAccessStatusForDso(context, bitstream, threshold);
|
||||
return calculateAccessStatusForDso(context, bitstream, threshold);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -104,7 +110,7 @@ public class DefaultAccessStatusHelper implements AccessStatusHelper {
|
||||
* @param threshold the embargo threshold date
|
||||
* @return an access status value
|
||||
*/
|
||||
private String caculateAccessStatusForDso(Context context, DSpaceObject dso, Date threshold)
|
||||
private String calculateAccessStatusForDso(Context context, DSpaceObject dso, Date threshold)
|
||||
throws SQLException {
|
||||
if (dso == null) {
|
||||
return METADATA_ONLY;
|
||||
@@ -156,4 +162,87 @@ public class DefaultAccessStatusHelper implements AccessStatusHelper {
|
||||
}
|
||||
return RESTRICTED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Look at the policies of the primary (or first) bitstream of the item to retrieve its embargo.
|
||||
*
|
||||
* If the item is null, simply returns an empty map with no embargo information.
|
||||
*
|
||||
* @param context the DSpace context
|
||||
* @param item the item to embargo
|
||||
* @return an access status value
|
||||
*/
|
||||
@Override
|
||||
public String getEmbargoFromItem(Context context, Item item, Date threshold)
|
||||
throws SQLException {
|
||||
Date embargoDate;
|
||||
|
||||
// If Item status is not "embargo" then return a null embargo date.
|
||||
String accessStatus = getAccessStatusFromItem(context, item, threshold);
|
||||
|
||||
if (item == null || !accessStatus.equals(EMBARGO)) {
|
||||
return null;
|
||||
}
|
||||
// Consider only the original bundles.
|
||||
List<Bundle> bundles = item.getBundles(Constants.DEFAULT_BUNDLE_NAME);
|
||||
// Check for primary bitstreams first.
|
||||
Bitstream bitstream = bundles.stream()
|
||||
.map(bundle -> bundle.getPrimaryBitstream())
|
||||
.filter(Objects::nonNull)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
if (bitstream == null) {
|
||||
// If there is no primary bitstream,
|
||||
// take the first bitstream in the bundles.
|
||||
bitstream = bundles.stream()
|
||||
.map(bundle -> bundle.getBitstreams())
|
||||
.flatMap(List::stream)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
if (bitstream == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
embargoDate = this.retrieveShortestEmbargo(context, bitstream);
|
||||
|
||||
return embargoDate != null ? embargoDate.toString() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private Date retrieveShortestEmbargo(Context context, Bitstream bitstream) throws SQLException {
|
||||
Date embargoDate = null;
|
||||
// Only consider read policies.
|
||||
List<ResourcePolicy> policies = authorizeService
|
||||
.getPoliciesActionFilter(context, bitstream, Constants.READ);
|
||||
|
||||
// Looks at all read policies.
|
||||
for (ResourcePolicy policy : policies) {
|
||||
boolean isValid = resourcePolicyService.isDateValid(policy);
|
||||
Group group = policy.getGroup();
|
||||
|
||||
if (group != null && StringUtils.equals(group.getName(), Group.ANONYMOUS)) {
|
||||
// Only calculate the status for the anonymous group.
|
||||
if (!isValid) {
|
||||
// If the policy is not valid there is an active embargo
|
||||
Date startDate = policy.getStartDate();
|
||||
|
||||
if (startDate != null && !startDate.before(LocalDate.now().toDate())) {
|
||||
// There is an active embargo: aim to take the shortest embargo (account for rare cases where
|
||||
// more than one resource policy exists)
|
||||
if (embargoDate == null) {
|
||||
embargoDate = startDate;
|
||||
} else {
|
||||
embargoDate = startDate.before(embargoDate) ? startDate : embargoDate;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return embargoDate;
|
||||
}
|
||||
}
|
||||
|
@@ -40,7 +40,18 @@ public interface AccessStatusService {
|
||||
*
|
||||
* @param context the DSpace context
|
||||
* @param item the item
|
||||
* @return an access status value
|
||||
* @throws SQLException An exception that provides information on a database access error or other errors.
|
||||
*/
|
||||
public String getAccessStatus(Context context, Item item) throws SQLException;
|
||||
|
||||
/**
|
||||
* Retrieve embargo information for the item
|
||||
*
|
||||
* @param context the DSpace context
|
||||
* @param item the item to check for embargo information
|
||||
* @return an embargo date
|
||||
* @throws SQLException An exception that provides information on a database access error or other errors.
|
||||
*/
|
||||
public String getEmbargoFromItem(Context context, Item item) throws SQLException;
|
||||
}
|
||||
|
@@ -464,7 +464,7 @@ public class BulkAccessControl extends DSpaceRunnable<BulkAccessControlScriptCon
|
||||
.forEach(accessCondition -> createResourcePolicy(item, accessCondition,
|
||||
itemAccessConditions.get(accessCondition.getName())));
|
||||
|
||||
itemService.adjustItemPolicies(context, item, item.getOwningCollection());
|
||||
itemService.adjustItemPolicies(context, item, item.getOwningCollection(), false);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -578,6 +578,10 @@ public class MetadataImport extends DSpaceRunnable<MetadataImportScriptConfigura
|
||||
wfItem = workflowService.startWithoutNotify(c, wsItem);
|
||||
}
|
||||
} else {
|
||||
// Add provenance info
|
||||
String provenance = installItemService.getSubmittedByProvenanceMessage(c, wsItem.getItem());
|
||||
itemService.addMetadata(c, item, MetadataSchemaEnum.DC.getName(),
|
||||
"description", "provenance", "en", provenance);
|
||||
// Install the item
|
||||
installItemService.installItem(c, wsItem);
|
||||
}
|
||||
@@ -1363,7 +1367,7 @@ public class MetadataImport extends DSpaceRunnable<MetadataImportScriptConfigura
|
||||
* is the field is defined as authority controlled
|
||||
*/
|
||||
private static boolean isAuthorityControlledField(String md) {
|
||||
String mdf = StringUtils.substringAfter(md, ":");
|
||||
String mdf = md.contains(":") ? StringUtils.substringAfter(md, ":") : md;
|
||||
mdf = StringUtils.substringBefore(mdf, "[");
|
||||
return authorityControlled.contains(mdf);
|
||||
}
|
||||
|
@@ -774,6 +774,10 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea
|
||||
// put item in system
|
||||
if (!isTest) {
|
||||
try {
|
||||
// Add provenance info
|
||||
String provenance = installItemService.getSubmittedByProvenanceMessage(c, wi.getItem());
|
||||
itemService.addMetadata(c, wi.getItem(), MetadataSchemaEnum.DC.getName(),
|
||||
"description", "provenance", "en", provenance);
|
||||
installItemService.installItem(c, wi, myhandle);
|
||||
} catch (Exception e) {
|
||||
workspaceItemService.deleteAll(c, wi);
|
||||
|
@@ -21,6 +21,7 @@ import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.scripts.DSpaceRunnable;
|
||||
import org.dspace.scripts.DSpaceRunnable.StepResult;
|
||||
import org.dspace.scripts.configuration.ScriptConfiguration;
|
||||
import org.dspace.scripts.factory.ScriptServiceFactory;
|
||||
import org.dspace.scripts.handler.DSpaceRunnableHandler;
|
||||
@@ -145,8 +146,13 @@ public class ScriptLauncher {
|
||||
private static int executeScript(String[] args, DSpaceRunnableHandler dSpaceRunnableHandler,
|
||||
DSpaceRunnable script) {
|
||||
try {
|
||||
script.initialize(args, dSpaceRunnableHandler, null);
|
||||
script.run();
|
||||
StepResult result = script.initialize(args, dSpaceRunnableHandler, null);
|
||||
// check the StepResult, only run the script if the result is Continue;
|
||||
// otherwise - for example the script is started with the help as argument, nothing is to do
|
||||
if (StepResult.Continue.equals(result)) {
|
||||
// runs the script, the normal initialization is successful
|
||||
script.run();
|
||||
}
|
||||
return 0;
|
||||
} catch (ParseException e) {
|
||||
script.printHelp();
|
||||
|
@@ -10,6 +10,7 @@ package org.dspace.app.mediafilter;
|
||||
import java.io.InputStream;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
@@ -40,6 +41,7 @@ import org.dspace.eperson.Group;
|
||||
import org.dspace.eperson.service.GroupService;
|
||||
import org.dspace.scripts.handler.DSpaceRunnableHandler;
|
||||
import org.dspace.services.ConfigurationService;
|
||||
import org.dspace.util.ThrowableUtils;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
@@ -225,23 +227,9 @@ public class MediaFilterServiceImpl implements MediaFilterService, InitializingB
|
||||
filtered = true;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
String handle = myItem.getHandle();
|
||||
List<Bundle> bundles = myBitstream.getBundles();
|
||||
long size = myBitstream.getSizeBytes();
|
||||
String checksum = myBitstream.getChecksum() + " (" + myBitstream.getChecksumAlgorithm() + ")";
|
||||
int assetstore = myBitstream.getStoreNumber();
|
||||
|
||||
// Printout helpful information to find the errored bitstream.
|
||||
StringBuilder sb = new StringBuilder("ERROR filtering, skipping bitstream:\n");
|
||||
sb.append("\tItem Handle: ").append(handle);
|
||||
for (Bundle bundle : bundles) {
|
||||
sb.append("\tBundle Name: ").append(bundle.getName());
|
||||
}
|
||||
sb.append("\tFile Size: ").append(size);
|
||||
sb.append("\tChecksum: ").append(checksum);
|
||||
sb.append("\tAsset Store: ").append(assetstore);
|
||||
logError(sb.toString());
|
||||
logError(e.getMessage(), e);
|
||||
logError(formatBitstreamDetails(myItem.getHandle(), myBitstream));
|
||||
logError(ThrowableUtils.formatCauseChain(e));
|
||||
}
|
||||
} else if (filterClass instanceof SelfRegisterInputFormats) {
|
||||
// Filter implements self registration, so check to see if it should be applied
|
||||
@@ -319,10 +307,10 @@ public class MediaFilterServiceImpl implements MediaFilterService, InitializingB
|
||||
|
||||
// check if destination bitstream exists
|
||||
Bundle existingBundle = null;
|
||||
List<Bitstream> existingBitstreams = new ArrayList<Bitstream>();
|
||||
List<Bitstream> existingBitstreams = new ArrayList<>();
|
||||
List<Bundle> bundles = itemService.getBundles(item, formatFilter.getBundleName());
|
||||
|
||||
if (bundles.size() > 0) {
|
||||
if (!bundles.isEmpty()) {
|
||||
// only finds the last matching bundle and all matching bitstreams in the proper bundle(s)
|
||||
for (Bundle bundle : bundles) {
|
||||
List<Bitstream> bitstreams = bundle.getBitstreams();
|
||||
@@ -337,7 +325,7 @@ public class MediaFilterServiceImpl implements MediaFilterService, InitializingB
|
||||
}
|
||||
|
||||
// if exists and overwrite = false, exit
|
||||
if (!overWrite && (existingBitstreams.size() > 0)) {
|
||||
if (!overWrite && (!existingBitstreams.isEmpty())) {
|
||||
if (!isQuiet) {
|
||||
logInfo("SKIPPED: bitstream " + source.getID()
|
||||
+ " (item: " + item.getHandle() + ") because '" + newName + "' already exists");
|
||||
@@ -370,7 +358,7 @@ public class MediaFilterServiceImpl implements MediaFilterService, InitializingB
|
||||
}
|
||||
|
||||
Bundle targetBundle; // bundle we're modifying
|
||||
if (bundles.size() < 1) {
|
||||
if (bundles.isEmpty()) {
|
||||
// create new bundle if needed
|
||||
targetBundle = bundleService.create(context, item, formatFilter.getBundleName());
|
||||
} else {
|
||||
@@ -399,6 +387,7 @@ public class MediaFilterServiceImpl implements MediaFilterService, InitializingB
|
||||
|
||||
} catch (OutOfMemoryError oome) {
|
||||
logError("!!! OutOfMemoryError !!!");
|
||||
logError(formatBitstreamDetails(item.getHandle(), source));
|
||||
}
|
||||
|
||||
// we are overwriting, so remove old bitstream
|
||||
@@ -496,6 +485,37 @@ public class MediaFilterServiceImpl implements MediaFilterService, InitializingB
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Describe a Bitstream in detail. Format a single line of text with
|
||||
* information such as Bitstore index, backing file ID, size, checksum,
|
||||
* enclosing Item and Bundles.
|
||||
*
|
||||
* @param itemHandle Handle of the Item by which we found the Bitstream.
|
||||
* @param bitstream the Bitstream to be described.
|
||||
* @return Bitstream details.
|
||||
*/
|
||||
private String formatBitstreamDetails(String itemHandle,
|
||||
Bitstream bitstream) {
|
||||
List<Bundle> bundles;
|
||||
try {
|
||||
bundles = bitstream.getBundles();
|
||||
} catch (SQLException ex) {
|
||||
logError("Unexpected error fetching Bundles", ex);
|
||||
bundles = Collections.EMPTY_LIST;
|
||||
}
|
||||
StringBuilder sb = new StringBuilder("ERROR filtering, skipping bitstream:\n");
|
||||
sb.append("\tItem Handle: ").append(itemHandle);
|
||||
for (Bundle bundle : bundles) {
|
||||
sb.append("\tBundle Name: ").append(bundle.getName());
|
||||
}
|
||||
sb.append("\tFile Size: ").append(bitstream.getSizeBytes());
|
||||
sb.append("\tChecksum: ").append(bitstream.getChecksum())
|
||||
.append(" (").append(bitstream.getChecksumAlgorithm()).append(')');
|
||||
sb.append("\tAsset Store: ").append(bitstream.getStoreNumber());
|
||||
sb.append("\tInternal ID: ").append(bitstream.getInternalId());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private void logInfo(String message) {
|
||||
if (handler != null) {
|
||||
handler.logInfo(message);
|
||||
|
@@ -7,18 +7,10 @@
|
||||
*/
|
||||
package org.dspace.app.sitemap;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLEncoder;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.cli.CommandLine;
|
||||
@@ -29,12 +21,8 @@ import org.apache.commons.cli.Options;
|
||||
import org.apache.commons.cli.ParseException;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.dspace.content.Collection;
|
||||
import org.dspace.content.Community;
|
||||
import org.dspace.content.Item;
|
||||
import org.dspace.content.factory.ContentServiceFactory;
|
||||
import org.dspace.content.service.CollectionService;
|
||||
import org.dspace.content.service.CommunityService;
|
||||
@@ -43,6 +31,7 @@ import org.dspace.core.Context;
|
||||
import org.dspace.core.LogHelper;
|
||||
import org.dspace.discovery.DiscoverQuery;
|
||||
import org.dspace.discovery.DiscoverResult;
|
||||
import org.dspace.discovery.IndexableObject;
|
||||
import org.dspace.discovery.SearchService;
|
||||
import org.dspace.discovery.SearchServiceException;
|
||||
import org.dspace.discovery.SearchUtils;
|
||||
@@ -68,6 +57,7 @@ public class GenerateSitemaps {
|
||||
private static final ConfigurationService configurationService =
|
||||
DSpaceServicesFactory.getInstance().getConfigurationService();
|
||||
private static final SearchService searchService = SearchUtils.getSearchService();
|
||||
private static final int PAGE_SIZE = 100;
|
||||
|
||||
/**
|
||||
* Default constructor
|
||||
@@ -87,11 +77,6 @@ public class GenerateSitemaps {
|
||||
"do not generate sitemaps.org protocol sitemap");
|
||||
options.addOption("b", "no_htmlmap", false,
|
||||
"do not generate a basic HTML sitemap");
|
||||
options.addOption("a", "ping_all", false,
|
||||
"ping configured search engines");
|
||||
options
|
||||
.addOption("p", "ping", true,
|
||||
"ping specified search engine URL");
|
||||
options
|
||||
.addOption("d", "delete", false,
|
||||
"delete sitemaps dir and its contents");
|
||||
@@ -116,14 +101,13 @@ public class GenerateSitemaps {
|
||||
}
|
||||
|
||||
/*
|
||||
* Sanity check -- if no sitemap generation or pinging to do, or deletion, print usage
|
||||
* Sanity check -- if no sitemap generation or deletion, print usage
|
||||
*/
|
||||
if (line.getArgs().length != 0 || line.hasOption('d') || line.hasOption('b')
|
||||
&& line.hasOption('s') && !line.hasOption('g')
|
||||
&& !line.hasOption('m') && !line.hasOption('y')
|
||||
&& !line.hasOption('p')) {
|
||||
&& !line.hasOption('m') && !line.hasOption('y')) {
|
||||
System.err
|
||||
.println("Nothing to do (no sitemap to generate, no search engines to ping)");
|
||||
.println("Nothing to do (no sitemap to generate)");
|
||||
hf.printHelp(usage, options);
|
||||
System.exit(1);
|
||||
}
|
||||
@@ -137,20 +121,6 @@ public class GenerateSitemaps {
|
||||
deleteSitemaps();
|
||||
}
|
||||
|
||||
if (line.hasOption('a')) {
|
||||
pingConfiguredSearchEngines();
|
||||
}
|
||||
|
||||
if (line.hasOption('p')) {
|
||||
try {
|
||||
pingSearchEngine(line.getOptionValue('p'));
|
||||
} catch (MalformedURLException me) {
|
||||
System.err
|
||||
.println("Bad search engine URL (include all except sitemap URL)");
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
@@ -189,7 +159,10 @@ public class GenerateSitemaps {
|
||||
*/
|
||||
public static void generateSitemaps(boolean makeHTMLMap, boolean makeSitemapOrg) throws SQLException, IOException {
|
||||
String uiURLStem = configurationService.getProperty("dspace.ui.url");
|
||||
String sitemapStem = uiURLStem + "/sitemap";
|
||||
if (!uiURLStem.endsWith("/")) {
|
||||
uiURLStem = uiURLStem + '/';
|
||||
}
|
||||
String sitemapStem = uiURLStem + "sitemap";
|
||||
|
||||
File outputDir = new File(configurationService.getProperty("sitemap.dir"));
|
||||
if (!outputDir.exists() && !outputDir.mkdir()) {
|
||||
@@ -208,171 +181,113 @@ public class GenerateSitemaps {
|
||||
}
|
||||
|
||||
Context c = new Context(Context.Mode.READ_ONLY);
|
||||
|
||||
List<Community> comms = communityService.findAll(c);
|
||||
|
||||
for (Community comm : comms) {
|
||||
String url = uiURLStem + "/communities/" + comm.getID();
|
||||
|
||||
if (makeHTMLMap) {
|
||||
html.addURL(url, null);
|
||||
}
|
||||
if (makeSitemapOrg) {
|
||||
sitemapsOrg.addURL(url, null);
|
||||
}
|
||||
|
||||
c.uncacheEntity(comm);
|
||||
}
|
||||
|
||||
List<Collection> colls = collectionService.findAll(c);
|
||||
|
||||
for (Collection coll : colls) {
|
||||
String url = uiURLStem + "/collections/" + coll.getID();
|
||||
|
||||
if (makeHTMLMap) {
|
||||
html.addURL(url, null);
|
||||
}
|
||||
if (makeSitemapOrg) {
|
||||
sitemapsOrg.addURL(url, null);
|
||||
}
|
||||
|
||||
c.uncacheEntity(coll);
|
||||
}
|
||||
|
||||
Iterator<Item> allItems = itemService.findAll(c);
|
||||
int itemCount = 0;
|
||||
|
||||
while (allItems.hasNext()) {
|
||||
Item i = allItems.next();
|
||||
|
||||
DiscoverQuery entityQuery = new DiscoverQuery();
|
||||
entityQuery.setQuery("search.uniqueid:\"Item-" + i.getID() + "\" and entityType:*");
|
||||
entityQuery.addSearchField("entityType");
|
||||
|
||||
try {
|
||||
DiscoverResult discoverResult = searchService.search(c, entityQuery);
|
||||
|
||||
String url;
|
||||
if (CollectionUtils.isNotEmpty(discoverResult.getIndexableObjects())
|
||||
&& CollectionUtils.isNotEmpty(discoverResult.getSearchDocument(
|
||||
discoverResult.getIndexableObjects().get(0)).get(0).getSearchFieldValues("entityType"))
|
||||
&& StringUtils.isNotBlank(discoverResult.getSearchDocument(
|
||||
discoverResult.getIndexableObjects().get(0)).get(0).getSearchFieldValues("entityType").get(0))
|
||||
) {
|
||||
url = uiURLStem + "/entities/" + StringUtils.lowerCase(discoverResult.getSearchDocument(
|
||||
discoverResult.getIndexableObjects().get(0))
|
||||
.get(0).getSearchFieldValues("entityType").get(0)) + "/" + i.getID();
|
||||
} else {
|
||||
url = uiURLStem + "/items/" + i.getID();
|
||||
}
|
||||
Date lastMod = i.getLastModified();
|
||||
|
||||
if (makeHTMLMap) {
|
||||
html.addURL(url, lastMod);
|
||||
}
|
||||
if (makeSitemapOrg) {
|
||||
sitemapsOrg.addURL(url, lastMod);
|
||||
}
|
||||
} catch (SearchServiceException e) {
|
||||
log.error("Failed getting entitytype through solr for item " + i.getID() + ": " + e.getMessage());
|
||||
}
|
||||
|
||||
c.uncacheEntity(i);
|
||||
|
||||
itemCount++;
|
||||
}
|
||||
|
||||
if (makeHTMLMap) {
|
||||
int files = html.finish();
|
||||
log.info(LogHelper.getHeader(c, "write_sitemap",
|
||||
"type=html,num_files=" + files + ",communities="
|
||||
+ comms.size() + ",collections=" + colls.size()
|
||||
+ ",items=" + itemCount));
|
||||
}
|
||||
|
||||
if (makeSitemapOrg) {
|
||||
int files = sitemapsOrg.finish();
|
||||
log.info(LogHelper.getHeader(c, "write_sitemap",
|
||||
"type=html,num_files=" + files + ",communities="
|
||||
+ comms.size() + ",collections=" + colls.size()
|
||||
+ ",items=" + itemCount));
|
||||
}
|
||||
|
||||
c.abort();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ping all search engines configured in {@code dspace.cfg}.
|
||||
*
|
||||
* @throws UnsupportedEncodingException theoretically should never happen
|
||||
*/
|
||||
public static void pingConfiguredSearchEngines()
|
||||
throws UnsupportedEncodingException {
|
||||
String[] engineURLs = configurationService
|
||||
.getArrayProperty("sitemap.engineurls");
|
||||
|
||||
if (ArrayUtils.isEmpty(engineURLs)) {
|
||||
log.warn("No search engine URLs configured to ping");
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < engineURLs.length; i++) {
|
||||
try {
|
||||
pingSearchEngine(engineURLs[i]);
|
||||
} catch (MalformedURLException me) {
|
||||
log.warn("Bad search engine URL in configuration: "
|
||||
+ engineURLs[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ping the given search engine.
|
||||
*
|
||||
* @param engineURL Search engine URL minus protocol etc, e.g.
|
||||
* {@code www.google.com}
|
||||
* @throws MalformedURLException if the passed in URL is malformed
|
||||
* @throws UnsupportedEncodingException theoretically should never happen
|
||||
*/
|
||||
public static void pingSearchEngine(String engineURL)
|
||||
throws MalformedURLException, UnsupportedEncodingException {
|
||||
// Set up HTTP proxy
|
||||
if ((StringUtils.isNotBlank(configurationService.getProperty("http.proxy.host")))
|
||||
&& (StringUtils.isNotBlank(configurationService.getProperty("http.proxy.port")))) {
|
||||
System.setProperty("proxySet", "true");
|
||||
System.setProperty("proxyHost", configurationService
|
||||
.getProperty("http.proxy.host"));
|
||||
System.getProperty("proxyPort", configurationService
|
||||
.getProperty("http.proxy.port"));
|
||||
}
|
||||
|
||||
String sitemapURL = configurationService.getProperty("dspace.ui.url")
|
||||
+ "/sitemap";
|
||||
|
||||
URL url = new URL(engineURL + URLEncoder.encode(sitemapURL, "UTF-8"));
|
||||
int offset = 0;
|
||||
long commsCount = 0;
|
||||
long collsCount = 0;
|
||||
long itemsCount = 0;
|
||||
|
||||
try {
|
||||
HttpURLConnection connection = (HttpURLConnection) url
|
||||
.openConnection();
|
||||
DiscoverQuery discoveryQuery = new DiscoverQuery();
|
||||
discoveryQuery.setMaxResults(PAGE_SIZE);
|
||||
discoveryQuery.setQuery("search.resourcetype:Community");
|
||||
do {
|
||||
discoveryQuery.setStart(offset);
|
||||
DiscoverResult discoverResult = searchService.search(c, discoveryQuery);
|
||||
List<IndexableObject> docs = discoverResult.getIndexableObjects();
|
||||
commsCount = discoverResult.getTotalSearchResults();
|
||||
|
||||
BufferedReader in = new BufferedReader(new InputStreamReader(
|
||||
connection.getInputStream()));
|
||||
for (IndexableObject doc : docs) {
|
||||
String url = uiURLStem + "communities/" + doc.getID();
|
||||
c.uncacheEntity(doc.getIndexedObject());
|
||||
|
||||
String inputLine;
|
||||
StringBuffer resp = new StringBuffer();
|
||||
while ((inputLine = in.readLine()) != null) {
|
||||
resp.append(inputLine).append("\n");
|
||||
if (makeHTMLMap) {
|
||||
html.addURL(url, null);
|
||||
}
|
||||
if (makeSitemapOrg) {
|
||||
sitemapsOrg.addURL(url, null);
|
||||
}
|
||||
}
|
||||
offset += PAGE_SIZE;
|
||||
} while (offset < commsCount);
|
||||
|
||||
offset = 0;
|
||||
discoveryQuery = new DiscoverQuery();
|
||||
discoveryQuery.setMaxResults(PAGE_SIZE);
|
||||
discoveryQuery.setQuery("search.resourcetype:Collection");
|
||||
do {
|
||||
discoveryQuery.setStart(offset);
|
||||
DiscoverResult discoverResult = searchService.search(c, discoveryQuery);
|
||||
List<IndexableObject> docs = discoverResult.getIndexableObjects();
|
||||
collsCount = discoverResult.getTotalSearchResults();
|
||||
|
||||
for (IndexableObject doc : docs) {
|
||||
String url = uiURLStem + "collections/" + doc.getID();
|
||||
c.uncacheEntity(doc.getIndexedObject());
|
||||
|
||||
if (makeHTMLMap) {
|
||||
html.addURL(url, null);
|
||||
}
|
||||
if (makeSitemapOrg) {
|
||||
sitemapsOrg.addURL(url, null);
|
||||
}
|
||||
}
|
||||
offset += PAGE_SIZE;
|
||||
} while (offset < collsCount);
|
||||
|
||||
offset = 0;
|
||||
discoveryQuery = new DiscoverQuery();
|
||||
discoveryQuery.setMaxResults(PAGE_SIZE);
|
||||
discoveryQuery.setQuery("search.resourcetype:Item");
|
||||
discoveryQuery.addSearchField("search.entitytype");
|
||||
do {
|
||||
|
||||
discoveryQuery.setStart(offset);
|
||||
DiscoverResult discoverResult = searchService.search(c, discoveryQuery);
|
||||
List<IndexableObject> docs = discoverResult.getIndexableObjects();
|
||||
itemsCount = discoverResult.getTotalSearchResults();
|
||||
|
||||
for (IndexableObject doc : docs) {
|
||||
String url;
|
||||
List<String> entityTypeFieldValues = discoverResult.getSearchDocument(doc).get(0)
|
||||
.getSearchFieldValues("search.entitytype");
|
||||
if (CollectionUtils.isNotEmpty(entityTypeFieldValues)) {
|
||||
url = uiURLStem + "entities/" + StringUtils.lowerCase(entityTypeFieldValues.get(0)) + "/"
|
||||
+ doc.getID();
|
||||
} else {
|
||||
url = uiURLStem + "items/" + doc.getID();
|
||||
}
|
||||
Date lastMod = doc.getLastModified();
|
||||
c.uncacheEntity(doc.getIndexedObject());
|
||||
|
||||
if (makeHTMLMap) {
|
||||
html.addURL(url, null);
|
||||
}
|
||||
if (makeSitemapOrg) {
|
||||
sitemapsOrg.addURL(url, null);
|
||||
}
|
||||
}
|
||||
offset += PAGE_SIZE;
|
||||
} while (offset < itemsCount);
|
||||
|
||||
if (makeHTMLMap) {
|
||||
int files = html.finish();
|
||||
log.info(LogHelper.getHeader(c, "write_sitemap",
|
||||
"type=html,num_files=" + files + ",communities="
|
||||
+ commsCount + ",collections=" + collsCount
|
||||
+ ",items=" + itemsCount));
|
||||
}
|
||||
in.close();
|
||||
|
||||
if (connection.getResponseCode() == 200) {
|
||||
log.info("Pinged " + url.toString() + " successfully");
|
||||
} else {
|
||||
log.warn("Error response pinging " + url.toString() + ":\n"
|
||||
+ resp);
|
||||
if (makeSitemapOrg) {
|
||||
int files = sitemapsOrg.finish();
|
||||
log.info(LogHelper.getHeader(c, "write_sitemap",
|
||||
"type=html,num_files=" + files + ",communities="
|
||||
+ commsCount + ",collections=" + collsCount
|
||||
+ ",items=" + itemsCount));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.warn("Error pinging " + url.toString(), e);
|
||||
} catch (SearchServiceException e) {
|
||||
throw new RuntimeException(e);
|
||||
} finally {
|
||||
c.abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -24,6 +24,7 @@ import org.dspace.content.Collection;
|
||||
import org.dspace.content.MetadataSchemaEnum;
|
||||
import org.dspace.core.Utils;
|
||||
import org.dspace.services.factory.DSpaceServicesFactory;
|
||||
import org.dspace.submit.factory.SubmissionServiceFactory;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.NamedNodeMap;
|
||||
import org.w3c.dom.Node;
|
||||
@@ -158,7 +159,8 @@ public class DCInputsReader {
|
||||
throws DCInputsReaderException {
|
||||
SubmissionConfig config;
|
||||
try {
|
||||
config = new SubmissionConfigReader().getSubmissionConfigByCollection(collectionHandle);
|
||||
config = SubmissionServiceFactory.getInstance().getSubmissionConfigService()
|
||||
.getSubmissionConfigByCollection(collectionHandle);
|
||||
String formName = config.getSubmissionName();
|
||||
if (formName == null) {
|
||||
throw new DCInputsReaderException("No form designated as default");
|
||||
@@ -180,7 +182,8 @@ public class DCInputsReader {
|
||||
throws DCInputsReaderException {
|
||||
SubmissionConfig config;
|
||||
try {
|
||||
config = new SubmissionConfigReader().getSubmissionConfigByName(name);
|
||||
config = SubmissionServiceFactory.getInstance().getSubmissionConfigService()
|
||||
.getSubmissionConfigByName(name);
|
||||
String formName = config.getSubmissionName();
|
||||
if (formName == null) {
|
||||
throw new DCInputsReaderException("No form designated as default");
|
||||
|
@@ -153,6 +153,22 @@ public interface AuthenticationMethod {
|
||||
public List<Group> getSpecialGroups(Context context, HttpServletRequest request)
|
||||
throws SQLException;
|
||||
|
||||
/**
|
||||
* Returns true if the special groups returned by
|
||||
* {@link org.dspace.authenticate.AuthenticationMethod#getSpecialGroups(Context, HttpServletRequest)}
|
||||
* should be implicitly be added to the groups related to the current user. By
|
||||
* default this is true if the authentication method is the actual
|
||||
* authentication mechanism used by the user.
|
||||
* @param context A valid DSpace context.
|
||||
* @param request The request that started this operation, or null if not
|
||||
* applicable.
|
||||
* @return true is the special groups must be considered, false
|
||||
* otherwise
|
||||
*/
|
||||
public default boolean areSpecialGroupsApplicable(Context context, HttpServletRequest request) {
|
||||
return getName().equals(context.getAuthenticationMethod());
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate the given or implicit credentials.
|
||||
* This is the heart of the authentication method: test the
|
||||
|
@@ -179,10 +179,15 @@ public class AuthenticationServiceImpl implements AuthenticationService {
|
||||
int totalLen = 0;
|
||||
|
||||
for (AuthenticationMethod method : getAuthenticationMethodStack()) {
|
||||
List<Group> gl = method.getSpecialGroups(context, request);
|
||||
if (gl.size() > 0) {
|
||||
result.addAll(gl);
|
||||
totalLen += gl.size();
|
||||
|
||||
if (method.areSpecialGroupsApplicable(context, request)) {
|
||||
|
||||
List<Group> gl = method.getSpecialGroups(context, request);
|
||||
if (gl.size() > 0) {
|
||||
result.addAll(gl);
|
||||
totalLen += gl.size();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -252,6 +252,11 @@ public class IPAuthentication implements AuthenticationMethod {
|
||||
return groups;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areSpecialGroupsApplicable(Context context, HttpServletRequest request) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int authenticate(Context context, String username, String password,
|
||||
String realm, HttpServletRequest request) throws SQLException {
|
||||
|
@@ -494,6 +494,8 @@ public class LDAPAuthentication
|
||||
try {
|
||||
SearchControls ctrls = new SearchControls();
|
||||
ctrls.setSearchScope(ldap_search_scope_value);
|
||||
// Fetch both user attributes '*' (eg. uid, cn) and operational attributes '+' (eg. memberOf)
|
||||
ctrls.setReturningAttributes(new String[] {"*", "+"});
|
||||
|
||||
String searchName;
|
||||
if (useTLS) {
|
||||
@@ -700,21 +702,21 @@ public class LDAPAuthentication
|
||||
/*
|
||||
* Add authenticated users to the group defined in dspace.cfg by
|
||||
* the authentication-ldap.login.groupmap.* key.
|
||||
*
|
||||
*
|
||||
* @param dn
|
||||
* The string containing distinguished name of the user
|
||||
*
|
||||
*
|
||||
* @param group
|
||||
* List of strings with LDAP dn of groups
|
||||
*
|
||||
*
|
||||
* @param context
|
||||
* DSpace context
|
||||
*/
|
||||
private void assignGroups(String dn, ArrayList<String> group, Context context) {
|
||||
if (StringUtils.isNotBlank(dn)) {
|
||||
System.out.println("dn:" + dn);
|
||||
int i = 1;
|
||||
String groupMap = configurationService.getProperty("authentication-ldap.login.groupmap." + i);
|
||||
int groupmapIndex = 1;
|
||||
String groupMap = configurationService.getProperty("authentication-ldap.login.groupmap." + groupmapIndex);
|
||||
boolean cmp;
|
||||
|
||||
|
||||
@@ -725,52 +727,75 @@ public class LDAPAuthentication
|
||||
String ldapSearchString = t[0];
|
||||
String dspaceGroupName = t[1];
|
||||
|
||||
// list of strings with dn from LDAP groups
|
||||
// inner loop
|
||||
Iterator<String> groupIterator = group.iterator();
|
||||
while (groupIterator.hasNext()) {
|
||||
|
||||
// save the current entry from iterator for further use
|
||||
String currentGroup = groupIterator.next();
|
||||
|
||||
// very much the old code from DSpace <= 7.5
|
||||
if (currentGroup == null) {
|
||||
cmp = StringUtils.containsIgnoreCase(dn, ldapSearchString + ",");
|
||||
} else {
|
||||
cmp = StringUtils.equalsIgnoreCase(currentGroup, ldapSearchString);
|
||||
}
|
||||
if (group == null) {
|
||||
cmp = StringUtils.containsIgnoreCase(dn, ldapSearchString + ",");
|
||||
|
||||
if (cmp) {
|
||||
// assign user to this group
|
||||
try {
|
||||
Group ldapGroup = groupService.findByName(context, dspaceGroupName);
|
||||
if (ldapGroup != null) {
|
||||
groupService.addMember(context, ldapGroup, context.getCurrentUser());
|
||||
groupService.update(context, ldapGroup);
|
||||
} else {
|
||||
// The group does not exist
|
||||
log.warn(LogHelper.getHeader(context,
|
||||
"ldap_assignGroupsBasedOnLdapDn",
|
||||
"Group defined in authentication-ldap.login.groupmap." + i
|
||||
+ " does not exist :: " + dspaceGroupName));
|
||||
}
|
||||
} catch (AuthorizeException ae) {
|
||||
log.debug(LogHelper.getHeader(context,
|
||||
"assignGroupsBasedOnLdapDn could not authorize addition to " +
|
||||
"group",
|
||||
dspaceGroupName));
|
||||
} catch (SQLException e) {
|
||||
log.debug(LogHelper.getHeader(context, "assignGroupsBasedOnLdapDn could not find group",
|
||||
dspaceGroupName));
|
||||
assignGroup(context, groupmapIndex, dspaceGroupName);
|
||||
}
|
||||
} else {
|
||||
// list of strings with dn from LDAP groups
|
||||
// inner loop
|
||||
Iterator<String> groupIterator = group.iterator();
|
||||
while (groupIterator.hasNext()) {
|
||||
|
||||
// save the current entry from iterator for further use
|
||||
String currentGroup = groupIterator.next();
|
||||
|
||||
// very much the old code from DSpace <= 7.5
|
||||
if (currentGroup == null) {
|
||||
cmp = StringUtils.containsIgnoreCase(dn, ldapSearchString + ",");
|
||||
} else {
|
||||
cmp = StringUtils.equalsIgnoreCase(currentGroup, ldapSearchString);
|
||||
}
|
||||
|
||||
if (cmp) {
|
||||
assignGroup(context, groupmapIndex, dspaceGroupName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
groupMap = configurationService.getProperty("authentication-ldap.login.groupmap." + ++i);
|
||||
groupMap = configurationService.getProperty("authentication-ldap.login.groupmap." + ++groupmapIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the current authenticated user to the specified group
|
||||
*
|
||||
* @param context
|
||||
* DSpace context
|
||||
*
|
||||
* @param groupmapIndex
|
||||
* authentication-ldap.login.groupmap.* key index defined in dspace.cfg
|
||||
*
|
||||
* @param dspaceGroupName
|
||||
* The DSpace group to add the user to
|
||||
*/
|
||||
private void assignGroup(Context context, int groupmapIndex, String dspaceGroupName) {
|
||||
try {
|
||||
Group ldapGroup = groupService.findByName(context, dspaceGroupName);
|
||||
if (ldapGroup != null) {
|
||||
groupService.addMember(context, ldapGroup, context.getCurrentUser());
|
||||
groupService.update(context, ldapGroup);
|
||||
} else {
|
||||
// The group does not exist
|
||||
log.warn(LogHelper.getHeader(context,
|
||||
"ldap_assignGroupsBasedOnLdapDn",
|
||||
"Group defined in authentication-ldap.login.groupmap." + groupmapIndex
|
||||
+ " does not exist :: " + dspaceGroupName));
|
||||
}
|
||||
} catch (AuthorizeException ae) {
|
||||
log.debug(LogHelper.getHeader(context,
|
||||
"assignGroupsBasedOnLdapDn could not authorize addition to " +
|
||||
"group",
|
||||
dspaceGroupName));
|
||||
} catch (SQLException e) {
|
||||
log.debug(LogHelper.getHeader(context, "assignGroupsBasedOnLdapDn could not find group",
|
||||
dspaceGroupName));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUsed(final Context context, final HttpServletRequest request) {
|
||||
if (request != null &&
|
||||
|
@@ -9,6 +9,10 @@ package org.dspace.authority;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.text.DateFormat;
|
||||
import java.time.DateTimeException;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
@@ -16,6 +20,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.solr.common.SolrDocument;
|
||||
import org.apache.solr.common.SolrInputDocument;
|
||||
@@ -25,9 +30,6 @@ import org.dspace.content.MetadataValue;
|
||||
import org.dspace.content.factory.ContentServiceFactory;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.util.SolrUtils;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.format.DateTimeFormatter;
|
||||
import org.joda.time.format.ISODateTimeFormat;
|
||||
|
||||
/**
|
||||
* @author Antoine Snyers (antoine at atmire.com)
|
||||
@@ -192,7 +194,7 @@ public class AuthorityValue {
|
||||
}
|
||||
|
||||
/**
|
||||
* Information that can be used the choice ui
|
||||
* Information that can be used the choice ui.
|
||||
*
|
||||
* @return map
|
||||
*/
|
||||
@@ -200,42 +202,51 @@ public class AuthorityValue {
|
||||
return new HashMap<>();
|
||||
}
|
||||
|
||||
|
||||
public List<DateTimeFormatter> getDateFormatters() {
|
||||
List<DateTimeFormatter> list = new ArrayList<>();
|
||||
list.add(ISODateTimeFormat.dateTime());
|
||||
list.add(ISODateTimeFormat.dateTimeNoMillis());
|
||||
/**
|
||||
* Build a list of ISO date formatters to parse various forms.
|
||||
*
|
||||
* <p><strong>Note:</strong> any formatter which does not parse a zone or
|
||||
* offset must have a default zone set. See {@link stringToDate}.
|
||||
*
|
||||
* @return the formatters.
|
||||
*/
|
||||
static private List<DateTimeFormatter> getDateFormatters() {
|
||||
List<java.time.format.DateTimeFormatter> list = new ArrayList<>();
|
||||
list.add(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss[.SSS]X"));
|
||||
list.add(java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME
|
||||
.withZone(ZoneId.systemDefault().normalized()));
|
||||
return list;
|
||||
}
|
||||
|
||||
public Date stringToDate(String date) {
|
||||
/**
|
||||
* Convert a date string to internal form, trying several parsers.
|
||||
*
|
||||
* @param date serialized date to be converted.
|
||||
* @return converted date, or null if no parser accepted the input.
|
||||
*/
|
||||
static public Date stringToDate(String date) {
|
||||
Date result = null;
|
||||
if (StringUtils.isNotBlank(date)) {
|
||||
List<DateTimeFormatter> dateFormatters = getDateFormatters();
|
||||
boolean converted = false;
|
||||
int formatter = 0;
|
||||
while (!converted) {
|
||||
for (DateTimeFormatter formatter : getDateFormatters()) {
|
||||
try {
|
||||
DateTimeFormatter dateTimeFormatter = dateFormatters.get(formatter);
|
||||
DateTime dateTime = dateTimeFormatter.parseDateTime(date);
|
||||
result = dateTime.toDate();
|
||||
converted = true;
|
||||
} catch (IllegalArgumentException e) {
|
||||
formatter++;
|
||||
if (formatter > dateFormatters.size()) {
|
||||
converted = true;
|
||||
}
|
||||
log.error("Could not find a valid date format for: \"" + date + "\"", e);
|
||||
ZonedDateTime dateTime = ZonedDateTime.parse(date, formatter);
|
||||
result = Date.from(dateTime.toInstant());
|
||||
break;
|
||||
} catch (DateTimeException e) {
|
||||
log.debug("Input '{}' did not match {}", date, formatter);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (null == result) {
|
||||
log.error("Could not find a valid date format for: \"{}\"", date);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* log4j logger
|
||||
*/
|
||||
private static Logger log = org.apache.logging.log4j.LogManager.getLogger(AuthorityValue.class);
|
||||
private static Logger log = LogManager.getLogger();
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
@@ -272,6 +283,10 @@ public class AuthorityValue {
|
||||
return new AuthorityValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the type of authority which created this value.
|
||||
* @return type name.
|
||||
*/
|
||||
public String getAuthorityType() {
|
||||
return "internal";
|
||||
}
|
||||
|
@@ -451,7 +451,7 @@ public class AuthorizeServiceImpl implements AuthorizeService {
|
||||
if (e == null) {
|
||||
return false; // anonymous users can't be admins....
|
||||
} else {
|
||||
return groupService.isMember(c, e, Group.ADMIN);
|
||||
return groupService.isMember(c, e, c.getAdminGroup());
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -108,7 +108,7 @@ public class CrossLinks {
|
||||
} else {
|
||||
// Exact match, if the key field has no .* wildcard
|
||||
if (links.containsKey(metadata)) {
|
||||
return links.get(key);
|
||||
return links.get(metadata);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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.cli;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.apache.commons.cli.CommandLine;
|
||||
import org.apache.commons.cli.DefaultParser;
|
||||
import org.apache.commons.cli.Options;
|
||||
import org.apache.commons.cli.ParseException;
|
||||
|
||||
/**
|
||||
* Extended version of the DefaultParser. This parser skip/ignore unknown arguments.
|
||||
*/
|
||||
public class DSpaceSkipUnknownArgumentsParser extends DefaultParser {
|
||||
|
||||
|
||||
@Override
|
||||
public CommandLine parse(Options options, String[] arguments) throws ParseException {
|
||||
return super.parse(options, getOnlyKnownArguments(options, arguments));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandLine parse(Options options, String[] arguments, Properties properties) throws ParseException {
|
||||
return super.parse(options, getOnlyKnownArguments(options, arguments), properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the arguments according to the specified options and properties.
|
||||
* @param options the specified Options
|
||||
* @param arguments the command line arguments
|
||||
* @param stopAtNonOption can be ignored - an unrecognized argument is ignored, an unrecognized argument doesn't
|
||||
* stop the parsing and doesn't trigger a ParseException
|
||||
*
|
||||
* @return the list of atomic option and value tokens
|
||||
* @throws ParseException if there are any problems encountered while parsing the command line tokens.
|
||||
*/
|
||||
@Override
|
||||
public CommandLine parse(Options options, String[] arguments, boolean stopAtNonOption) throws ParseException {
|
||||
return super.parse(options, getOnlyKnownArguments(options, arguments), stopAtNonOption);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the arguments according to the specified options and properties.
|
||||
* @param options the specified Options
|
||||
* @param arguments the command line arguments
|
||||
* @param properties command line option name-value pairs
|
||||
* @param stopAtNonOption can be ignored - an unrecognized argument is ignored, an unrecognized argument doesn't
|
||||
* stop the parsing and doesn't trigger a ParseException
|
||||
*
|
||||
* @return the list of atomic option and value tokens
|
||||
* @throws ParseException if there are any problems encountered while parsing the command line tokens.
|
||||
*/
|
||||
@Override
|
||||
public CommandLine parse(Options options, String[] arguments, Properties properties, boolean stopAtNonOption)
|
||||
throws ParseException {
|
||||
return super.parse(options, getOnlyKnownArguments(options, arguments), properties, stopAtNonOption);
|
||||
}
|
||||
|
||||
|
||||
private String[] getOnlyKnownArguments(Options options, String[] arguments) {
|
||||
List<String> knownArguments = new ArrayList<>();
|
||||
for (String arg : arguments) {
|
||||
if (options.hasOption(arg)) {
|
||||
knownArguments.add(arg);
|
||||
}
|
||||
}
|
||||
return knownArguments.toArray(new String[0]);
|
||||
}
|
||||
}
|
@@ -276,6 +276,11 @@ public class BitstreamServiceImpl extends DSpaceObjectServiceImpl<Bitstream> imp
|
||||
//Remove our bitstream from all our bundles
|
||||
final List<Bundle> bundles = bitstream.getBundles();
|
||||
for (Bundle bundle : bundles) {
|
||||
authorizeService.authorizeAction(context, bundle, Constants.REMOVE);
|
||||
//We also need to remove the bitstream id when it's set as bundle's primary bitstream
|
||||
if (bitstream.equals(bundle.getPrimaryBitstream())) {
|
||||
bundle.unsetPrimaryBitstreamID();
|
||||
}
|
||||
bundle.removeBitstream(bitstream);
|
||||
}
|
||||
|
||||
@@ -403,7 +408,7 @@ public class BitstreamServiceImpl extends DSpaceObjectServiceImpl<Bitstream> imp
|
||||
|
||||
@Override
|
||||
public Bitstream getThumbnail(Context context, Bitstream bitstream) throws SQLException {
|
||||
Pattern pattern = Pattern.compile("^" + bitstream.getName() + ".([^.]+)$");
|
||||
Pattern pattern = getBitstreamNamePattern(bitstream);
|
||||
|
||||
for (Bundle bundle : bitstream.getBundles()) {
|
||||
for (Item item : bundle.getItems()) {
|
||||
@@ -420,6 +425,13 @@ public class BitstreamServiceImpl extends DSpaceObjectServiceImpl<Bitstream> imp
|
||||
return null;
|
||||
}
|
||||
|
||||
protected Pattern getBitstreamNamePattern(Bitstream bitstream) {
|
||||
if (bitstream.getName() != null) {
|
||||
return Pattern.compile("^" + Pattern.quote(bitstream.getName()) + ".([^.]+)$");
|
||||
}
|
||||
return Pattern.compile("^" + bitstream.getName() + ".([^.]+)$");
|
||||
}
|
||||
|
||||
@Override
|
||||
public BitstreamFormat getFormat(Context context, Bitstream bitstream) throws SQLException {
|
||||
if (bitstream.getBitstreamFormat() == null) {
|
||||
|
@@ -126,7 +126,7 @@ public class Bundle extends DSpaceObject implements DSpaceObjectLegacySupport {
|
||||
* Unset the primary bitstream ID of the bundle
|
||||
*/
|
||||
public void unsetPrimaryBitstreamID() {
|
||||
primaryBitstream = null;
|
||||
setPrimaryBitstreamID(null);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -194,7 +194,6 @@ public class BundleServiceImpl extends DSpaceObjectServiceImpl<Bundle> implement
|
||||
List<Group> defaultBitstreamReadGroups =
|
||||
authorizeService.getAuthorizedGroups(context, owningCollection,
|
||||
Constants.DEFAULT_BITSTREAM_READ);
|
||||
log.info(defaultBitstreamReadGroups.size());
|
||||
// If this collection is configured with a DEFAULT_BITSTREAM_READ group, overwrite the READ policy
|
||||
// inherited from the bundle with this policy.
|
||||
if (!defaultBitstreamReadGroups.isEmpty()) {
|
||||
|
@@ -93,7 +93,7 @@ public class InstallItemServiceImpl implements InstallItemService {
|
||||
// As this is a BRAND NEW item, as a final step we need to remove the
|
||||
// submitter item policies created during deposit and replace them with
|
||||
// the default policies from the collection.
|
||||
itemService.inheritCollectionDefaultPolicies(c, item, collection);
|
||||
itemService.inheritCollectionDefaultPolicies(c, item, collection, false);
|
||||
|
||||
return item;
|
||||
}
|
||||
@@ -271,4 +271,28 @@ public class InstallItemServiceImpl implements InstallItemService {
|
||||
|
||||
return myMessage.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSubmittedByProvenanceMessage(Context context, Item item) throws SQLException {
|
||||
// get date
|
||||
DCDate now = DCDate.getCurrent();
|
||||
|
||||
// Create provenance description
|
||||
StringBuffer provmessage = new StringBuffer();
|
||||
|
||||
if (item.getSubmitter() != null) {
|
||||
provmessage.append("Submitted by ").append(item.getSubmitter().getFullName())
|
||||
.append(" (").append(item.getSubmitter().getEmail()).append(") on ")
|
||||
.append(now.toString());
|
||||
} else {
|
||||
// else, null submitter
|
||||
provmessage.append("Submitted by unknown (probably automated) on")
|
||||
.append(now.toString());
|
||||
}
|
||||
provmessage.append("\n");
|
||||
|
||||
// add sizes and checksums of bitstreams
|
||||
provmessage.append(getBitstreamProvenanceMessage(context, item));
|
||||
return provmessage.toString();
|
||||
}
|
||||
}
|
||||
|
@@ -929,8 +929,16 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl<Item> implements It
|
||||
@Override
|
||||
public void inheritCollectionDefaultPolicies(Context context, Item item, Collection collection)
|
||||
throws SQLException, AuthorizeException {
|
||||
adjustItemPolicies(context, item, collection);
|
||||
adjustBundleBitstreamPolicies(context, item, collection);
|
||||
inheritCollectionDefaultPolicies(context, item, collection, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void inheritCollectionDefaultPolicies(Context context, Item item, Collection collection,
|
||||
boolean replaceReadRPWithCollectionRP)
|
||||
throws SQLException, AuthorizeException {
|
||||
|
||||
adjustItemPolicies(context, item, collection, replaceReadRPWithCollectionRP);
|
||||
adjustBundleBitstreamPolicies(context, item, collection, replaceReadRPWithCollectionRP);
|
||||
|
||||
log.debug(LogHelper.getHeader(context, "item_inheritCollectionDefaultPolicies",
|
||||
"item_id=" + item.getID()));
|
||||
@@ -939,6 +947,13 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl<Item> implements It
|
||||
@Override
|
||||
public void adjustBundleBitstreamPolicies(Context context, Item item, Collection collection)
|
||||
throws SQLException, AuthorizeException {
|
||||
adjustBundleBitstreamPolicies(context, item, collection, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void adjustBundleBitstreamPolicies(Context context, Item item, Collection collection,
|
||||
boolean replaceReadRPWithCollectionRP)
|
||||
throws SQLException, AuthorizeException {
|
||||
// Bundles should inherit from DEFAULT_ITEM_READ so that if the item is readable, the files
|
||||
// can be listed (even if they are themselves not readable as per DEFAULT_BITSTREAM_READ or other
|
||||
// policies or embargos applied
|
||||
@@ -957,10 +972,19 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl<Item> implements It
|
||||
}
|
||||
// TODO: should we also throw an exception if no DEFAULT_ITEM_READ?
|
||||
|
||||
boolean removeCurrentReadRPBitstream =
|
||||
replaceReadRPWithCollectionRP && defaultCollectionBitstreamPolicies.size() > 0;
|
||||
boolean removeCurrentReadRPBundle =
|
||||
replaceReadRPWithCollectionRP && defaultCollectionBundlePolicies.size() > 0;
|
||||
|
||||
// remove all policies from bundles, add new ones
|
||||
// Remove bundles
|
||||
List<Bundle> bunds = item.getBundles();
|
||||
for (Bundle mybundle : bunds) {
|
||||
// If collection has default READ policies, remove the bundle's READ policies.
|
||||
if (removeCurrentReadRPBundle) {
|
||||
authorizeService.removePoliciesActionFilter(context, mybundle, Constants.READ);
|
||||
}
|
||||
|
||||
// if come from InstallItem: remove all submission/workflow policies
|
||||
authorizeService.removeAllPoliciesByDSOAndType(context, mybundle, ResourcePolicy.TYPE_SUBMISSION);
|
||||
@@ -969,6 +993,11 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl<Item> implements It
|
||||
addDefaultPoliciesNotInPlace(context, mybundle, defaultCollectionBundlePolicies);
|
||||
|
||||
for (Bitstream bitstream : mybundle.getBitstreams()) {
|
||||
// If collection has default READ policies, remove the bundle's READ policies.
|
||||
if (removeCurrentReadRPBitstream) {
|
||||
authorizeService.removePoliciesActionFilter(context, bitstream, Constants.READ);
|
||||
}
|
||||
|
||||
// if come from InstallItem: remove all submission/workflow policies
|
||||
removeAllPoliciesAndAddDefault(context, bitstream, defaultItemPolicies,
|
||||
defaultCollectionBitstreamPolicies);
|
||||
@@ -977,7 +1006,14 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl<Item> implements It
|
||||
}
|
||||
|
||||
@Override
|
||||
public void adjustBitstreamPolicies(Context context, Item item, Collection collection , Bitstream bitstream)
|
||||
public void adjustBitstreamPolicies(Context context, Item item, Collection collection, Bitstream bitstream)
|
||||
throws SQLException, AuthorizeException {
|
||||
adjustBitstreamPolicies(context, item, collection, bitstream, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void adjustBitstreamPolicies(Context context, Item item, Collection collection , Bitstream bitstream,
|
||||
boolean replaceReadRPWithCollectionRP)
|
||||
throws SQLException, AuthorizeException {
|
||||
List<ResourcePolicy> defaultCollectionPolicies = authorizeService
|
||||
.getPoliciesActionFilter(context, collection, Constants.DEFAULT_BITSTREAM_READ);
|
||||
@@ -1007,10 +1043,22 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl<Item> implements It
|
||||
@Override
|
||||
public void adjustItemPolicies(Context context, Item item, Collection collection)
|
||||
throws SQLException, AuthorizeException {
|
||||
adjustItemPolicies(context, item, collection, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void adjustItemPolicies(Context context, Item item, Collection collection,
|
||||
boolean replaceReadRPWithCollectionRP)
|
||||
throws SQLException, AuthorizeException {
|
||||
// read collection's default READ policies
|
||||
List<ResourcePolicy> defaultCollectionPolicies = authorizeService
|
||||
.getPoliciesActionFilter(context, collection, Constants.DEFAULT_ITEM_READ);
|
||||
|
||||
// If collection has defaultREAD policies, remove the item's READ policies.
|
||||
if (replaceReadRPWithCollectionRP && defaultCollectionPolicies.size() > 0) {
|
||||
authorizeService.removePoliciesActionFilter(context, item, Constants.READ);
|
||||
}
|
||||
|
||||
// MUST have default policies
|
||||
if (defaultCollectionPolicies.size() < 1) {
|
||||
throw new SQLException("Collection " + collection.getID()
|
||||
|
@@ -17,6 +17,7 @@ import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.dspace.app.util.DCInput;
|
||||
@@ -24,7 +25,6 @@ import org.dspace.app.util.DCInputSet;
|
||||
import org.dspace.app.util.DCInputsReader;
|
||||
import org.dspace.app.util.DCInputsReaderException;
|
||||
import org.dspace.app.util.SubmissionConfig;
|
||||
import org.dspace.app.util.SubmissionConfigReader;
|
||||
import org.dspace.app.util.SubmissionConfigReaderException;
|
||||
import org.dspace.content.Collection;
|
||||
import org.dspace.content.MetadataValue;
|
||||
@@ -34,6 +34,8 @@ import org.dspace.core.service.PluginService;
|
||||
import org.dspace.discovery.configuration.DiscoveryConfigurationService;
|
||||
import org.dspace.discovery.configuration.DiscoverySearchFilterFacet;
|
||||
import org.dspace.services.ConfigurationService;
|
||||
import org.dspace.submit.factory.SubmissionServiceFactory;
|
||||
import org.dspace.submit.service.SubmissionConfigService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
/**
|
||||
@@ -87,7 +89,7 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService
|
||||
protected Map<String, DSpaceControlledVocabularyIndex> vocabularyIndexMap = new HashMap<>();
|
||||
|
||||
// the item submission reader
|
||||
private SubmissionConfigReader itemSubmissionConfigReader;
|
||||
private SubmissionConfigService submissionConfigService;
|
||||
|
||||
@Autowired(required = true)
|
||||
protected ConfigurationService configurationService;
|
||||
@@ -134,7 +136,7 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService
|
||||
private synchronized void init() {
|
||||
if (!initialized) {
|
||||
try {
|
||||
itemSubmissionConfigReader = new SubmissionConfigReader();
|
||||
submissionConfigService = SubmissionServiceFactory.getInstance().getSubmissionConfigService();
|
||||
} catch (SubmissionConfigReaderException e) {
|
||||
// the system is in an illegal state as the submission definition is not valid
|
||||
throw new IllegalStateException("Error reading the item submission configuration: " + e.getMessage(),
|
||||
@@ -239,7 +241,7 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService
|
||||
// there is an authority configured for the metadata valid for some collections,
|
||||
// check if it is the requested collection
|
||||
Map<String, ChoiceAuthority> controllerFormDef = controllerFormDefinitions.get(fieldKey);
|
||||
SubmissionConfig submissionConfig = itemSubmissionConfigReader
|
||||
SubmissionConfig submissionConfig = submissionConfigService
|
||||
.getSubmissionConfigByCollection(collection.getHandle());
|
||||
String submissionName = submissionConfig.getSubmissionName();
|
||||
// check if the requested collection has a submission definition that use an authority for the metadata
|
||||
@@ -261,14 +263,14 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearCache() {
|
||||
public void clearCache() throws SubmissionConfigReaderException {
|
||||
controller.clear();
|
||||
authorities.clear();
|
||||
presentation.clear();
|
||||
closed.clear();
|
||||
controllerFormDefinitions.clear();
|
||||
authoritiesFormDefinitions.clear();
|
||||
itemSubmissionConfigReader = null;
|
||||
submissionConfigService.reload();
|
||||
initialized = false;
|
||||
}
|
||||
|
||||
@@ -318,7 +320,7 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService
|
||||
*/
|
||||
private void autoRegisterChoiceAuthorityFromInputReader() {
|
||||
try {
|
||||
List<SubmissionConfig> submissionConfigs = itemSubmissionConfigReader
|
||||
List<SubmissionConfig> submissionConfigs = submissionConfigService
|
||||
.getAllSubmissionConfigs(Integer.MAX_VALUE, 0);
|
||||
DCInputsReader dcInputsReader = new DCInputsReader();
|
||||
|
||||
@@ -489,10 +491,11 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService
|
||||
init();
|
||||
ChoiceAuthority ma = controller.get(fieldKey);
|
||||
if (ma == null && collection != null) {
|
||||
SubmissionConfigReader configReader;
|
||||
SubmissionConfigService configReaderService;
|
||||
try {
|
||||
configReader = new SubmissionConfigReader();
|
||||
SubmissionConfig submissionName = configReader.getSubmissionConfigByCollection(collection.getHandle());
|
||||
configReaderService = SubmissionServiceFactory.getInstance().getSubmissionConfigService();
|
||||
SubmissionConfig submissionName = configReaderService
|
||||
.getSubmissionConfigByCollection(collection.getHandle());
|
||||
ma = controllerFormDefinitions.get(fieldKey).get(submissionName.getSubmissionName());
|
||||
} catch (SubmissionConfigReaderException e) {
|
||||
// the system is in an illegal state as the submission definition is not valid
|
||||
@@ -557,6 +560,15 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService
|
||||
init();
|
||||
ChoiceAuthority source = this.getChoiceAuthorityByAuthorityName(nameVocab);
|
||||
if (source != null && source instanceof DSpaceControlledVocabulary) {
|
||||
|
||||
// First, check if this vocabulary index is disabled
|
||||
String[] vocabulariesDisabled = configurationService
|
||||
.getArrayProperty("webui.browse.vocabularies.disabled");
|
||||
if (vocabulariesDisabled != null && ArrayUtils.contains(vocabulariesDisabled, nameVocab)) {
|
||||
// Discard this vocabulary browse index
|
||||
return null;
|
||||
}
|
||||
|
||||
Set<String> metadataFields = new HashSet<>();
|
||||
Map<String, List<String>> formsToFields = this.authoritiesFormDefinitions.get(nameVocab);
|
||||
for (Map.Entry<String, List<String>> formToField : formsToFields.entrySet()) {
|
||||
|
@@ -10,6 +10,7 @@ package org.dspace.content.authority.service;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.dspace.app.util.SubmissionConfigReaderException;
|
||||
import org.dspace.content.Collection;
|
||||
import org.dspace.content.MetadataValue;
|
||||
import org.dspace.content.authority.Choice;
|
||||
@@ -174,7 +175,7 @@ public interface ChoiceAuthorityService {
|
||||
/**
|
||||
* This method has been created to have a way of clearing the cache kept inside the service
|
||||
*/
|
||||
public void clearCache();
|
||||
public void clearCache() throws SubmissionConfigReaderException;
|
||||
|
||||
/**
|
||||
* Should we store the authority key (if any) for such field key and collection?
|
||||
|
@@ -68,9 +68,9 @@ public class BitstreamDAOImpl extends AbstractHibernateDSODAO<Bitstream> impleme
|
||||
|
||||
@Override
|
||||
public List<Bitstream> findBitstreamsWithNoRecentChecksum(Context context) throws SQLException {
|
||||
Query query = createQuery(context,
|
||||
"select b from Bitstream b where b not in (select c.bitstream from " +
|
||||
"MostRecentChecksum c)");
|
||||
Query query = createQuery(context, "SELECT b FROM MostRecentChecksum c RIGHT JOIN Bitstream b " +
|
||||
"ON c.bitstream = b WHERE c IS NULL" );
|
||||
|
||||
return query.getResultList();
|
||||
}
|
||||
|
||||
|
@@ -83,4 +83,15 @@ public interface InstallItemService {
|
||||
public String getBitstreamProvenanceMessage(Context context, Item myitem)
|
||||
throws SQLException;
|
||||
|
||||
/**
|
||||
* Generate provenance description of direct item submission (not through workflow).
|
||||
*
|
||||
* @param context context
|
||||
* @param item the item to generate description for
|
||||
* @return provenance description
|
||||
* @throws SQLException if database error
|
||||
*/
|
||||
public String getSubmittedByProvenanceMessage(Context context, Item item)
|
||||
throws SQLException;;
|
||||
|
||||
}
|
||||
|
@@ -473,7 +473,7 @@ public interface ItemService
|
||||
public void removeGroupPolicies(Context context, Item item, Group group) throws SQLException, AuthorizeException;
|
||||
|
||||
/**
|
||||
* remove all policies on an item and its contents, and replace them with
|
||||
* Remove all policies on an item and its contents, and replace them with
|
||||
* the DEFAULT_ITEM_READ and DEFAULT_BITSTREAM_READ policies belonging to
|
||||
* the collection.
|
||||
*
|
||||
@@ -488,6 +488,26 @@ public interface ItemService
|
||||
public void inheritCollectionDefaultPolicies(Context context, Item item, Collection collection)
|
||||
throws java.sql.SQLException, AuthorizeException;
|
||||
|
||||
/**
|
||||
* Remove all submission and workflow policies on an item and its contents, and add
|
||||
* default collection policies which are not yet already in place.
|
||||
* If overrideItemReadPolicies is true, then all read policies on the item are replaced (but only if the
|
||||
* collection has a default read policy).
|
||||
*
|
||||
* @param context DSpace context object
|
||||
* @param item item to reset policies on
|
||||
* @param collection Collection
|
||||
* @param overrideItemReadPolicies if true, all read policies on the item are replaced (but only if the
|
||||
* collection has a default read policy)
|
||||
* @throws SQLException if database error
|
||||
* if an SQL error or if no default policies found. It's a bit
|
||||
* draconian, but default policies must be enforced.
|
||||
* @throws AuthorizeException if authorization error
|
||||
*/
|
||||
public void inheritCollectionDefaultPolicies(Context context, Item item, Collection collection,
|
||||
boolean overrideItemReadPolicies)
|
||||
throws java.sql.SQLException, AuthorizeException;
|
||||
|
||||
/**
|
||||
* Adjust the Bundle and Bitstream policies to reflect what have been defined
|
||||
* during the submission/workflow. The temporary SUBMISSION and WORKFLOW
|
||||
@@ -507,6 +527,28 @@ public interface ItemService
|
||||
public void adjustBundleBitstreamPolicies(Context context, Item item, Collection collection)
|
||||
throws SQLException, AuthorizeException;
|
||||
|
||||
/**
|
||||
* Adjust the Bundle and Bitstream policies to reflect what have been defined
|
||||
* during the submission/workflow. The temporary SUBMISSION and WORKFLOW
|
||||
* policies are removed and the policies defined at the item and collection
|
||||
* level are copied and inherited as appropriate. Custom selected Item policies
|
||||
* are copied to the bundle/bitstream only if no explicit custom policies were
|
||||
* already applied to the bundle/bitstream. Collection's policies are inherited
|
||||
* if there are no other policies defined or if the append mode is defined by
|
||||
* the configuration via the core.authorization.installitem.inheritance-read.append-mode property
|
||||
*
|
||||
* @param context DSpace context object
|
||||
* @param item Item to adjust policies on
|
||||
* @param collection Collection
|
||||
* @param replaceReadRPWithCollectionRP if true, all read policies on the item are replaced (but only if the
|
||||
* collection has a default read policy)
|
||||
* @throws SQLException If database error
|
||||
* @throws AuthorizeException If authorization error
|
||||
*/
|
||||
public void adjustBundleBitstreamPolicies(Context context, Item item, Collection collection,
|
||||
boolean replaceReadRPWithCollectionRP)
|
||||
throws SQLException, AuthorizeException;
|
||||
|
||||
/**
|
||||
* Adjust the Bitstream policies to reflect what have been defined
|
||||
* during the submission/workflow. The temporary SUBMISSION and WORKFLOW
|
||||
@@ -527,6 +569,29 @@ public interface ItemService
|
||||
public void adjustBitstreamPolicies(Context context, Item item, Collection collection, Bitstream bitstream)
|
||||
throws SQLException, AuthorizeException;
|
||||
|
||||
/**
|
||||
* Adjust the Bitstream policies to reflect what have been defined
|
||||
* during the submission/workflow. The temporary SUBMISSION and WORKFLOW
|
||||
* policies are removed and the policies defined at the item and collection
|
||||
* level are copied and inherited as appropriate. Custom selected Item policies
|
||||
* are copied to the bitstream only if no explicit custom policies were
|
||||
* already applied to the bitstream. Collection's policies are inherited
|
||||
* if there are no other policies defined or if the append mode is defined by
|
||||
* the configuration via the core.authorization.installitem.inheritance-read.append-mode property
|
||||
*
|
||||
* @param context DSpace context object
|
||||
* @param item Item to adjust policies on
|
||||
* @param collection Collection
|
||||
* @param bitstream Bitstream to adjust policies on
|
||||
* @param replaceReadRPWithCollectionRP If true, all read policies on the bitstream are replaced (but only if the
|
||||
* collection has a default read policy)
|
||||
* @throws SQLException If database error
|
||||
* @throws AuthorizeException If authorization error
|
||||
*/
|
||||
public void adjustBitstreamPolicies(Context context, Item item, Collection collection, Bitstream bitstream,
|
||||
boolean replaceReadRPWithCollectionRP)
|
||||
throws SQLException, AuthorizeException;
|
||||
|
||||
|
||||
/**
|
||||
* Adjust the Item's policies to reflect what have been defined during the
|
||||
@@ -545,6 +610,26 @@ public interface ItemService
|
||||
public void adjustItemPolicies(Context context, Item item, Collection collection)
|
||||
throws SQLException, AuthorizeException;
|
||||
|
||||
/**
|
||||
* Adjust the Item's policies to reflect what have been defined during the
|
||||
* submission/workflow. The temporary SUBMISSION and WORKFLOW policies are
|
||||
* removed and the default policies defined at the collection level are
|
||||
* inherited as appropriate. Collection's policies are inherited if there are no
|
||||
* other policies defined or if the append mode is defined by the configuration
|
||||
* via the core.authorization.installitem.inheritance-read.append-mode property
|
||||
*
|
||||
* @param context DSpace context object
|
||||
* @param item Item to adjust policies on
|
||||
* @param collection Collection
|
||||
* @param replaceReadRPWithCollectionRP If true, all read policies on the item are replaced (but only if the
|
||||
* collection has a default read policy)
|
||||
* @throws SQLException If database error
|
||||
* @throws AuthorizeException If authorization error
|
||||
*/
|
||||
public void adjustItemPolicies(Context context, Item item, Collection collection,
|
||||
boolean replaceReadRPWithCollectionRP)
|
||||
throws SQLException, AuthorizeException;
|
||||
|
||||
/**
|
||||
* Moves the item from one collection to another one
|
||||
*
|
||||
@@ -790,24 +875,24 @@ public interface ItemService
|
||||
int countWithdrawnItems(Context context) throws SQLException;
|
||||
|
||||
/**
|
||||
* finds all items for which the current user has editing rights
|
||||
* @param context DSpace context object
|
||||
* @param offset page offset
|
||||
* @param limit page size limit
|
||||
* @return list of items for which the current user has editing rights
|
||||
* @throws SQLException
|
||||
* @throws SearchServiceException
|
||||
*/
|
||||
* finds all items for which the current user has editing rights
|
||||
* @param context DSpace context object
|
||||
* @param offset page offset
|
||||
* @param limit page size limit
|
||||
* @return list of items for which the current user has editing rights
|
||||
* @throws SQLException
|
||||
* @throws SearchServiceException
|
||||
*/
|
||||
public List<Item> findItemsWithEdit(Context context, int offset, int limit)
|
||||
throws SQLException, SearchServiceException;
|
||||
|
||||
/**
|
||||
* counts all items for which the current user has editing rights
|
||||
* @param context DSpace context object
|
||||
* @return list of items for which the current user has editing rights
|
||||
* @throws SQLException
|
||||
* @throws SearchServiceException
|
||||
*/
|
||||
* counts all items for which the current user has editing rights
|
||||
* @param context DSpace context object
|
||||
* @return list of items for which the current user has editing rights
|
||||
* @throws SQLException
|
||||
* @throws SearchServiceException
|
||||
*/
|
||||
public int countItemsWithEdit(Context context) throws SQLException, SearchServiceException;
|
||||
|
||||
/**
|
||||
|
@@ -83,13 +83,14 @@ public abstract class AbstractHibernateDSODAO<T extends DSpaceObject> extends Ab
|
||||
if (CollectionUtils.isNotEmpty(metadataFields) || StringUtils.isNotBlank(additionalWhere)) {
|
||||
//Add the where query on metadata
|
||||
query.append(" WHERE ");
|
||||
// Group the 'OR' clauses below in outer parentheses, e.g. "WHERE (clause1 OR clause2 OR clause3)".
|
||||
// Grouping these 'OR' clauses allows for later code to append 'AND' clauses without unexpected behaviors
|
||||
query.append("(");
|
||||
for (int i = 0; i < metadataFields.size(); i++) {
|
||||
MetadataField metadataField = metadataFields.get(i);
|
||||
if (StringUtils.isNotBlank(operator)) {
|
||||
query.append(" (");
|
||||
query.append("lower(STR(" + metadataField.toString()).append(".value)) ").append(operator)
|
||||
.append(" lower(:queryParam)");
|
||||
query.append(")");
|
||||
if (i < metadataFields.size() - 1) {
|
||||
query.append(" OR ");
|
||||
}
|
||||
@@ -102,6 +103,7 @@ public abstract class AbstractHibernateDSODAO<T extends DSpaceObject> extends Ab
|
||||
}
|
||||
query.append(additionalWhere);
|
||||
}
|
||||
query.append(")");
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -128,6 +128,11 @@ public class Context implements AutoCloseable {
|
||||
|
||||
private DBConnection dbConnection;
|
||||
|
||||
/**
|
||||
* The default administrator group
|
||||
*/
|
||||
private Group adminGroup;
|
||||
|
||||
public enum Mode {
|
||||
READ_ONLY,
|
||||
READ_WRITE,
|
||||
@@ -810,6 +815,15 @@ public class Context implements AutoCloseable {
|
||||
readOnlyCache.clear();
|
||||
}
|
||||
|
||||
// When going to READ_ONLY, flush database changes to ensure that the current data is retrieved
|
||||
if (newMode == Mode.READ_ONLY && mode != Mode.READ_ONLY) {
|
||||
try {
|
||||
dbConnection.flushSession();
|
||||
} catch (SQLException ex) {
|
||||
log.warn("Unable to flush database changes after switching to READ_ONLY mode", ex);
|
||||
}
|
||||
}
|
||||
|
||||
//save the new mode
|
||||
mode = newMode;
|
||||
}
|
||||
@@ -951,4 +965,15 @@ public class Context implements AutoCloseable {
|
||||
public boolean isContextUserSwitched() {
|
||||
return currentUserPreviousState != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default "Administrator" group for DSpace administrators.
|
||||
* The result is cached in the 'adminGroup' field, so it is only looked up once.
|
||||
* This is done to improve performance, as this method is called quite often.
|
||||
*/
|
||||
public Group getAdminGroup() throws SQLException {
|
||||
return (adminGroup == null) ? EPersonServiceFactory.getInstance()
|
||||
.getGroupService()
|
||||
.findByName(this, Group.ADMIN) : adminGroup;
|
||||
}
|
||||
}
|
||||
|
@@ -148,4 +148,12 @@ public interface DBConnection<T> {
|
||||
* @throws java.sql.SQLException passed through.
|
||||
*/
|
||||
public <E extends ReloadableEntity> void uncacheEntity(E entity) throws SQLException;
|
||||
|
||||
/**
|
||||
* Do a manual flush. This synchronizes the in-memory state of the Session
|
||||
* with the database (write changes to the database)
|
||||
*
|
||||
* @throws SQLException passed through.
|
||||
*/
|
||||
public void flushSession() throws SQLException;
|
||||
}
|
||||
|
@@ -21,7 +21,6 @@ import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import javax.activation.DataHandler;
|
||||
@@ -41,7 +40,6 @@ import javax.mail.internet.MimeMessage;
|
||||
import javax.mail.internet.MimeMultipart;
|
||||
import javax.mail.internet.ParseException;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.velocity.Template;
|
||||
@@ -57,26 +55,40 @@ import org.dspace.services.ConfigurationService;
|
||||
import org.dspace.services.factory.DSpaceServicesFactory;
|
||||
|
||||
/**
|
||||
* Class representing an e-mail message, also used to send e-mails.
|
||||
* Class representing an e-mail message. The {@link send} method causes the
|
||||
* assembled message to be formatted and sent.
|
||||
* <p>
|
||||
* Typical use:
|
||||
* </p>
|
||||
* <pre>
|
||||
* <code>Email email = Email.getEmail(path);</code>
|
||||
* <code>email.addRecipient("foo@bar.com");</code>
|
||||
* <code>email.addArgument("John");</code>
|
||||
* <code>email.addArgument("On the Testing of DSpace");</code>
|
||||
* <code>email.send();</code>
|
||||
* </pre>
|
||||
* {@code path} is the filesystem path of an email template, typically in
|
||||
* {@code ${dspace.dir}/config/emails/} and can include the subject -- see
|
||||
* below. Templates are processed by <a href='https://velocity.apache.org/'>
|
||||
* Apache Velocity</a>. They may contain VTL directives and property
|
||||
* placeholders.
|
||||
* <p>
|
||||
* <code>Email email = new Email();</code><br>
|
||||
* <code>email.addRecipient("foo@bar.com");</code><br>
|
||||
* <code>email.addArgument("John");</code><br>
|
||||
* <code>email.addArgument("On the Testing of DSpace");</code><br>
|
||||
* <code>email.send();</code><br>
|
||||
* </p>
|
||||
* {@link addArgument(string)} adds a property to the {@code params} array
|
||||
* in the Velocity context, which can be used to replace placeholder tokens
|
||||
* in the message. These arguments are indexed by number in the order they were
|
||||
* added to the message.
|
||||
* <p>
|
||||
* <code>name</code> is the name of an email template in
|
||||
* <code>dspace-dir/config/emails/</code> (which also includes the subject.)
|
||||
* <code>arg0</code> and <code>arg1</code> are arguments to fill out the
|
||||
* message with.
|
||||
* <P>
|
||||
* Emails are formatted using Apache Velocity. Headers such as Subject may be
|
||||
* supplied by the template, by defining them using #set(). Example:
|
||||
* </p>
|
||||
* The DSpace configuration properties are also available to templates as the
|
||||
* array {@code config}, indexed by name. Example: {@code ${config.get('dspace.name')}}
|
||||
* <p>
|
||||
* Recipients and attachments may be added as needed. See {@link addRecipient},
|
||||
* {@link addAttachment(File, String)}, and
|
||||
* {@link addAttachment(InputStream, String, String)}.
|
||||
* <p>
|
||||
* Headers such as Subject may be supplied by the template, by defining them
|
||||
* using the VTL directive {@code #set()}. Only headers named in the DSpace
|
||||
* configuration array property {@code mail.message.headers} will be added.
|
||||
* <p>
|
||||
* Example:
|
||||
*
|
||||
* <pre>
|
||||
*
|
||||
@@ -91,12 +103,14 @@ import org.dspace.services.factory.DSpaceServicesFactory;
|
||||
*
|
||||
* Thank you for sending us your submission "${params[1]}".
|
||||
*
|
||||
* --
|
||||
* The ${config.get('dspace.name')} Team
|
||||
*
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* If the example code above was used to send this mail, the resulting mail
|
||||
* would have the subject <code>Example e-mail</code> and the body would be:
|
||||
* </p>
|
||||
*
|
||||
* <pre>
|
||||
*
|
||||
@@ -105,7 +119,16 @@ import org.dspace.services.factory.DSpaceServicesFactory;
|
||||
*
|
||||
* Thank you for sending us your submission "On the Testing of DSpace".
|
||||
*
|
||||
* --
|
||||
* The DSpace Team
|
||||
*
|
||||
* </pre>
|
||||
* <p>
|
||||
* There are two ways to load a message body. One can create an instance of
|
||||
* {@link Email} and call {@link setContent} on it, passing the body as a String. Or
|
||||
* one can use the static factory method {@link getEmail} to load a file by its
|
||||
* complete filesystem path. In either case the text will be loaded into a
|
||||
* Velocity template.
|
||||
*
|
||||
* @author Robert Tansley
|
||||
* @author Jim Downing - added attachment handling code
|
||||
@@ -115,7 +138,6 @@ public class Email {
|
||||
/**
|
||||
* The content of the message
|
||||
*/
|
||||
private String content;
|
||||
private String contentName;
|
||||
|
||||
/**
|
||||
@@ -176,13 +198,12 @@ public class Email {
|
||||
moreAttachments = new ArrayList<>(10);
|
||||
subject = "";
|
||||
template = null;
|
||||
content = "";
|
||||
replyTo = null;
|
||||
charset = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a recipient
|
||||
* Add a recipient.
|
||||
*
|
||||
* @param email the recipient's email address
|
||||
*/
|
||||
@@ -196,16 +217,24 @@ public class Email {
|
||||
* "Subject:" line must be stripped.
|
||||
*
|
||||
* @param name a name for this message body
|
||||
* @param cnt the content of the message
|
||||
* @param content the content of the message
|
||||
*/
|
||||
public void setContent(String name, String cnt) {
|
||||
content = cnt;
|
||||
public void setContent(String name, String content) {
|
||||
contentName = name;
|
||||
arguments.clear();
|
||||
|
||||
VelocityEngine templateEngine = new VelocityEngine();
|
||||
templateEngine.init(VELOCITY_PROPERTIES);
|
||||
|
||||
StringResourceRepository repo = (StringResourceRepository)
|
||||
templateEngine.getApplicationAttribute(RESOURCE_REPOSITORY_NAME);
|
||||
repo.putStringResource(contentName, content);
|
||||
// Turn content into a template.
|
||||
template = templateEngine.getTemplate(contentName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the subject of the message
|
||||
* Set the subject of the message.
|
||||
*
|
||||
* @param s the subject of the message
|
||||
*/
|
||||
@@ -214,7 +243,7 @@ public class Email {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the reply-to email address
|
||||
* Set the reply-to email address.
|
||||
*
|
||||
* @param email the reply-to email address
|
||||
*/
|
||||
@@ -223,7 +252,7 @@ public class Email {
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill out the next argument in the template
|
||||
* Fill out the next argument in the template.
|
||||
*
|
||||
* @param arg the value for the next argument
|
||||
*/
|
||||
@@ -231,6 +260,13 @@ public class Email {
|
||||
arguments.add(arg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an attachment bodypart to the message from an external file.
|
||||
*
|
||||
* @param f reference to a file to be attached.
|
||||
* @param name a name for the resulting bodypart in the message's MIME
|
||||
* structure.
|
||||
*/
|
||||
public void addAttachment(File f, String name) {
|
||||
attachments.add(new FileAttachment(f, name));
|
||||
}
|
||||
@@ -238,6 +274,17 @@ public class Email {
|
||||
/** When given a bad MIME type for an attachment, use this instead. */
|
||||
private static final String DEFAULT_ATTACHMENT_TYPE = "application/octet-stream";
|
||||
|
||||
/**
|
||||
* Add an attachment bodypart to the message from a byte stream.
|
||||
*
|
||||
* @param is the content of this stream will become the content of the
|
||||
* bodypart.
|
||||
* @param name a name for the resulting bodypart in the message's MIME
|
||||
* structure.
|
||||
* @param mimetype the MIME type of the resulting bodypart, such as
|
||||
* "text/pdf". If {@code null} it will default to
|
||||
* "application/octet-stream", which is MIME for "unknown format".
|
||||
*/
|
||||
public void addAttachment(InputStream is, String name, String mimetype) {
|
||||
if (null == mimetype) {
|
||||
LOG.error("Null MIME type replaced with '" + DEFAULT_ATTACHMENT_TYPE
|
||||
@@ -257,6 +304,11 @@ public class Email {
|
||||
moreAttachments.add(new InputStreamAttachment(is, name, mimetype));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the character set of the message.
|
||||
*
|
||||
* @param cs the name of a character set, such as "UTF-8" or "EUC-JP".
|
||||
*/
|
||||
public void setCharset(String cs) {
|
||||
charset = cs;
|
||||
}
|
||||
@@ -280,15 +332,20 @@ public class Email {
|
||||
* {@code mail.message.headers} then that name and its value will be added
|
||||
* to the message's headers.
|
||||
*
|
||||
* <p>"subject" is treated specially: if {@link setSubject()} has not been called,
|
||||
* the value of any "subject" property will be used as if setSubject had
|
||||
* been called with that value. Thus a template may define its subject, but
|
||||
* the caller may override it.
|
||||
* <p>"subject" is treated specially: if {@link setSubject()} has not been
|
||||
* called, the value of any "subject" property will be used as if setSubject
|
||||
* had been called with that value. Thus a template may define its subject,
|
||||
* but the caller may override it.
|
||||
*
|
||||
* @throws MessagingException if there was a problem sending the mail.
|
||||
* @throws IOException if IO error
|
||||
*/
|
||||
public void send() throws MessagingException, IOException {
|
||||
if (null == template) {
|
||||
// No template -- no content -- PANIC!!!
|
||||
throw new MessagingException("Email has no body");
|
||||
}
|
||||
|
||||
ConfigurationService config
|
||||
= DSpaceServicesFactory.getInstance().getConfigurationService();
|
||||
|
||||
@@ -308,37 +365,18 @@ public class Email {
|
||||
MimeMessage message = new MimeMessage(session);
|
||||
|
||||
// Set the recipients of the message
|
||||
Iterator<String> i = recipients.iterator();
|
||||
|
||||
while (i.hasNext()) {
|
||||
message.addRecipient(Message.RecipientType.TO, new InternetAddress(
|
||||
i.next()));
|
||||
for (String recipient : recipients) {
|
||||
message.addRecipient(Message.RecipientType.TO,
|
||||
new InternetAddress(recipient));
|
||||
}
|
||||
// Get headers defined by the template.
|
||||
String[] templateHeaders = config.getArrayProperty("mail.message.headers");
|
||||
|
||||
// Format the mail message body
|
||||
VelocityEngine templateEngine = new VelocityEngine();
|
||||
templateEngine.init(VELOCITY_PROPERTIES);
|
||||
|
||||
VelocityContext vctx = new VelocityContext();
|
||||
vctx.put("config", new UnmodifiableConfigurationService(config));
|
||||
vctx.put("params", Collections.unmodifiableList(arguments));
|
||||
|
||||
if (null == template) {
|
||||
if (StringUtils.isBlank(content)) {
|
||||
// No template and no content -- PANIC!!!
|
||||
throw new MessagingException("Email has no body");
|
||||
}
|
||||
// No template, so use a String of content.
|
||||
StringResourceRepository repo = (StringResourceRepository)
|
||||
templateEngine.getApplicationAttribute(RESOURCE_REPOSITORY_NAME);
|
||||
repo.putStringResource(contentName, content);
|
||||
// Turn content into a template.
|
||||
template = templateEngine.getTemplate(contentName);
|
||||
templateHeaders = new String[] {};
|
||||
}
|
||||
|
||||
StringWriter writer = new StringWriter();
|
||||
try {
|
||||
template.merge(vctx, writer);
|
||||
@@ -405,7 +443,8 @@ public class Email {
|
||||
// add the stream
|
||||
messageBodyPart = new MimeBodyPart();
|
||||
messageBodyPart.setDataHandler(new DataHandler(
|
||||
new InputStreamDataSource(attachment.name,attachment.mimetype,attachment.is)));
|
||||
new InputStreamDataSource(attachment.name,
|
||||
attachment.mimetype, attachment.is)));
|
||||
messageBodyPart.setFileName(attachment.name);
|
||||
multipart.addBodyPart(messageBodyPart);
|
||||
}
|
||||
@@ -447,6 +486,9 @@ public class Email {
|
||||
/**
|
||||
* Get the VTL template for an email message. The message is suitable
|
||||
* for inserting values using Apache Velocity.
|
||||
* <p>
|
||||
* Note that everything is stored here, so that only send() throws a
|
||||
* MessagingException.
|
||||
*
|
||||
* @param emailFile
|
||||
* full name for the email template, for example "/dspace/config/emails/register".
|
||||
@@ -484,15 +526,6 @@ public class Email {
|
||||
}
|
||||
return email;
|
||||
}
|
||||
/*
|
||||
* Implementation note: It might be necessary to add a quick utility method
|
||||
* like "send(to, subject, message)". We'll see how far we get without it -
|
||||
* having all emails as templates in the config allows customisation and
|
||||
* internationalisation.
|
||||
*
|
||||
* Note that everything is stored and the run in send() so that only send()
|
||||
* throws a MessagingException.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Test method to send an email to check email server settings
|
||||
@@ -547,7 +580,7 @@ public class Email {
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility struct class for handling file attachments.
|
||||
* Utility record class for handling file attachments.
|
||||
*
|
||||
* @author ojd20
|
||||
*/
|
||||
@@ -563,7 +596,7 @@ public class Email {
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility struct class for handling file attachments.
|
||||
* Utility record class for handling file attachments.
|
||||
*
|
||||
* @author Adán Román Ruiz at arvo.es
|
||||
*/
|
||||
@@ -580,6 +613,8 @@ public class Email {
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap an {@link InputStream} in a {@link DataSource}.
|
||||
*
|
||||
* @author arnaldo
|
||||
*/
|
||||
public static class InputStreamDataSource implements DataSource {
|
||||
@@ -587,6 +622,14 @@ public class Email {
|
||||
private final String contentType;
|
||||
private final ByteArrayOutputStream baos;
|
||||
|
||||
/**
|
||||
* Consume the content of an InputStream and store it in a local buffer.
|
||||
*
|
||||
* @param name give the DataSource a name.
|
||||
* @param contentType the DataSource contains this type of data.
|
||||
* @param inputStream content to be buffered in the DataSource.
|
||||
* @throws IOException if the stream cannot be read.
|
||||
*/
|
||||
InputStreamDataSource(String name, String contentType, InputStream inputStream) throws IOException {
|
||||
this.name = name;
|
||||
this.contentType = contentType;
|
||||
|
@@ -337,4 +337,17 @@ public class HibernateDBConnection implements DBConnection<Session> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Do a manual flush. This synchronizes the in-memory state of the Session
|
||||
* with the database (write changes to the database)
|
||||
*
|
||||
* @throws SQLException passed through.
|
||||
*/
|
||||
@Override
|
||||
public void flushSession() throws SQLException {
|
||||
if (getSession().isDirty()) {
|
||||
getSession().flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -17,9 +17,12 @@ import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.PrintWriter;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.dspace.core.service.LicenseService;
|
||||
import org.dspace.services.factory.DSpaceServicesFactory;
|
||||
import org.dspace.services.model.Request;
|
||||
import org.dspace.web.ContextUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -101,13 +104,14 @@ public class LicenseServiceImpl implements LicenseService {
|
||||
/**
|
||||
* Get the site-wide default license that submitters need to grant
|
||||
*
|
||||
* Localized license requires: default_{{locale}}.license file.
|
||||
* Locale also must be listed in webui.supported.locales setting.
|
||||
*
|
||||
* @return the default license
|
||||
*/
|
||||
@Override
|
||||
public String getDefaultSubmissionLicense() {
|
||||
if (null == license) {
|
||||
init();
|
||||
}
|
||||
init();
|
||||
return license;
|
||||
}
|
||||
|
||||
@@ -115,9 +119,8 @@ public class LicenseServiceImpl implements LicenseService {
|
||||
* Load in the default license.
|
||||
*/
|
||||
protected void init() {
|
||||
File licenseFile = new File(
|
||||
DSpaceServicesFactory.getInstance().getConfigurationService().getProperty("dspace.dir")
|
||||
+ File.separator + "config" + File.separator + "default.license");
|
||||
Context context = obtainContext();
|
||||
File licenseFile = new File(I18nUtil.getDefaultLicense(context));
|
||||
|
||||
FileInputStream fir = null;
|
||||
InputStreamReader ir = null;
|
||||
@@ -169,4 +172,24 @@ public class LicenseServiceImpl implements LicenseService {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtaining current request context.
|
||||
* Return new context if getting one from current request failed.
|
||||
*
|
||||
* @return DSpace context object
|
||||
*/
|
||||
private Context obtainContext() {
|
||||
try {
|
||||
Request currentRequest = DSpaceServicesFactory.getInstance().getRequestService().getCurrentRequest();
|
||||
if (currentRequest != null) {
|
||||
HttpServletRequest request = currentRequest.getHttpServletRequest();
|
||||
return ContextUtil.obtainContext(request);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Can't load current request context.");
|
||||
}
|
||||
|
||||
return new Context();
|
||||
}
|
||||
}
|
||||
|
@@ -152,17 +152,10 @@ public class Curation extends DSpaceRunnable<CurationScriptConfiguration> {
|
||||
super.handler.logInfo("Curating id: " + entry.getObjectId());
|
||||
}
|
||||
curator.clear();
|
||||
// does entry relate to a DSO or workflow object?
|
||||
if (entry.getObjectId().indexOf('/') > 0) {
|
||||
for (String taskName : entry.getTaskNames()) {
|
||||
curator.addTask(taskName);
|
||||
}
|
||||
curator.curate(context, entry.getObjectId());
|
||||
} else {
|
||||
// TODO: Remove this exception once curation tasks are supported by configurable workflow
|
||||
// e.g. see https://github.com/DSpace/DSpace/pull/3157
|
||||
throw new IllegalArgumentException("curation for workflow items is no longer supported");
|
||||
for (String taskName : entry.getTaskNames()) {
|
||||
curator.addTask(taskName);
|
||||
}
|
||||
curator.curate(context, entry.getObjectId());
|
||||
}
|
||||
queue.release(this.queue, ticket, true);
|
||||
return ticket;
|
||||
|
@@ -13,6 +13,8 @@ import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.dspace.authorize.AuthorizeException;
|
||||
import org.dspace.content.Collection;
|
||||
@@ -30,6 +32,7 @@ import org.dspace.workflow.CurationTaskConfig;
|
||||
import org.dspace.workflow.FlowStep;
|
||||
import org.dspace.workflow.Task;
|
||||
import org.dspace.workflow.TaskSet;
|
||||
import org.dspace.xmlworkflow.Role;
|
||||
import org.dspace.xmlworkflow.RoleMembers;
|
||||
import org.dspace.xmlworkflow.WorkflowConfigurationException;
|
||||
import org.dspace.xmlworkflow.factory.XmlWorkflowFactory;
|
||||
@@ -47,14 +50,17 @@ import org.springframework.stereotype.Service;
|
||||
* Manage interactions between curation and workflow. A curation task can be
|
||||
* attached to a workflow step, to be executed during the step.
|
||||
*
|
||||
* <p>
|
||||
* <strong>NOTE:</strong> when run in workflow, curation tasks <em>run with
|
||||
* authorization disabled</em>.
|
||||
*
|
||||
* @see CurationTaskConfig
|
||||
* @author mwood
|
||||
*/
|
||||
@Service
|
||||
public class XmlWorkflowCuratorServiceImpl
|
||||
implements XmlWorkflowCuratorService {
|
||||
private static final Logger LOG
|
||||
= org.apache.logging.log4j.LogManager.getLogger();
|
||||
private static final Logger LOG = LogManager.getLogger();
|
||||
|
||||
@Autowired(required = true)
|
||||
protected XmlWorkflowFactory workflowFactory;
|
||||
@@ -97,7 +103,18 @@ public class XmlWorkflowCuratorServiceImpl
|
||||
throws AuthorizeException, IOException, SQLException {
|
||||
Curator curator = new Curator();
|
||||
curator.setReporter(reporter);
|
||||
return curate(curator, c, wfi);
|
||||
c.turnOffAuthorisationSystem();
|
||||
boolean wasAnonymous = false;
|
||||
if (null == c.getCurrentUser()) { // We need someone to email
|
||||
wasAnonymous = true;
|
||||
c.setCurrentUser(ePersonService.getSystemEPerson(c));
|
||||
}
|
||||
boolean failedP = curate(curator, c, wfi);
|
||||
if (wasAnonymous) {
|
||||
c.setCurrentUser(null);
|
||||
}
|
||||
c.restoreAuthSystemState();
|
||||
return failedP;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -123,40 +140,47 @@ public class XmlWorkflowCuratorServiceImpl
|
||||
item.setOwningCollection(wfi.getCollection());
|
||||
for (Task task : step.tasks) {
|
||||
curator.addTask(task.name);
|
||||
curator.curate(item);
|
||||
int status = curator.getStatus(task.name);
|
||||
String result = curator.getResult(task.name);
|
||||
String action = "none";
|
||||
switch (status) {
|
||||
case Curator.CURATE_FAIL:
|
||||
// task failed - notify any contacts the task has assigned
|
||||
if (task.powers.contains("reject")) {
|
||||
action = "reject";
|
||||
}
|
||||
notifyContacts(c, wfi, task, "fail", action, result);
|
||||
// if task so empowered, reject submission and terminate
|
||||
if ("reject".equals(action)) {
|
||||
workflowService.sendWorkflowItemBackSubmission(c, wfi,
|
||||
c.getCurrentUser(), null,
|
||||
task.name + ": " + result);
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case Curator.CURATE_SUCCESS:
|
||||
if (task.powers.contains("approve")) {
|
||||
action = "approve";
|
||||
}
|
||||
notifyContacts(c, wfi, task, "success", action, result);
|
||||
if ("approve".equals(action)) {
|
||||
// cease further task processing and advance submission
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case Curator.CURATE_ERROR:
|
||||
notifyContacts(c, wfi, task, "error", action, result);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
// Check whether the task is configured to be queued rather than automatically run
|
||||
if (StringUtils.isNotEmpty(step.queue)) {
|
||||
// queue attribute has been set in the FlowStep configuration: add task to configured queue
|
||||
curator.queue(c, item.getID().toString(), step.queue);
|
||||
} else {
|
||||
// Task is configured to be run automatically
|
||||
curator.curate(c, item);
|
||||
int status = curator.getStatus(task.name);
|
||||
String result = curator.getResult(task.name);
|
||||
String action = "none";
|
||||
switch (status) {
|
||||
case Curator.CURATE_FAIL:
|
||||
// task failed - notify any contacts the task has assigned
|
||||
if (task.powers.contains("reject")) {
|
||||
action = "reject";
|
||||
}
|
||||
notifyContacts(c, wfi, task, "fail", action, result);
|
||||
// if task so empowered, reject submission and terminate
|
||||
if ("reject".equals(action)) {
|
||||
workflowService.sendWorkflowItemBackSubmission(c, wfi,
|
||||
c.getCurrentUser(), null,
|
||||
task.name + ": " + result);
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case Curator.CURATE_SUCCESS:
|
||||
if (task.powers.contains("approve")) {
|
||||
action = "approve";
|
||||
}
|
||||
notifyContacts(c, wfi, task, "success", action, result);
|
||||
if ("approve".equals(action)) {
|
||||
// cease further task processing and advance submission
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case Curator.CURATE_ERROR:
|
||||
notifyContacts(c, wfi, task, "error", action, result);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
curator.clear();
|
||||
}
|
||||
@@ -223,8 +247,12 @@ public class XmlWorkflowCuratorServiceImpl
|
||||
String status, String action, String message)
|
||||
throws AuthorizeException, IOException, SQLException {
|
||||
List<EPerson> epa = resolveContacts(c, task.getContacts(status), wfi);
|
||||
if (epa.size() > 0) {
|
||||
if (!epa.isEmpty()) {
|
||||
workflowService.notifyOfCuration(c, wfi, epa, task.name, action, message);
|
||||
} else {
|
||||
LOG.warn("No contacts were found for workflow item {}: "
|
||||
+ "task {} returned action {} with message {}",
|
||||
wfi.getID(), task.name, action, message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -247,8 +275,7 @@ public class XmlWorkflowCuratorServiceImpl
|
||||
// decode contacts
|
||||
if ("$flowgroup".equals(contact)) {
|
||||
// special literal for current flowgoup
|
||||
ClaimedTask claimedTask = claimedTaskService.findByWorkflowIdAndEPerson(c, wfi, c.getCurrentUser());
|
||||
String stepID = claimedTask.getStepID();
|
||||
String stepID = getFlowStep(c, wfi).step;
|
||||
Step step;
|
||||
try {
|
||||
Workflow workflow = workflowFactory.getWorkflow(wfi.getCollection());
|
||||
@@ -258,19 +285,26 @@ public class XmlWorkflowCuratorServiceImpl
|
||||
String.valueOf(wfi.getID()), e);
|
||||
return epList;
|
||||
}
|
||||
RoleMembers roleMembers = step.getRole().getMembers(c, wfi);
|
||||
for (EPerson ep : roleMembers.getEPersons()) {
|
||||
epList.add(ep);
|
||||
}
|
||||
for (Group group : roleMembers.getGroups()) {
|
||||
epList.addAll(group.getMembers());
|
||||
Role role = step.getRole();
|
||||
if (null != role) {
|
||||
RoleMembers roleMembers = role.getMembers(c, wfi);
|
||||
for (EPerson ep : roleMembers.getEPersons()) {
|
||||
epList.add(ep);
|
||||
}
|
||||
for (Group group : roleMembers.getGroups()) {
|
||||
epList.addAll(group.getMembers());
|
||||
}
|
||||
} else {
|
||||
epList.add(ePersonService.getSystemEPerson(c));
|
||||
}
|
||||
} else if ("$colladmin".equals(contact)) {
|
||||
// special literal for collection administrators
|
||||
Group adGroup = wfi.getCollection().getAdministrators();
|
||||
if (adGroup != null) {
|
||||
epList.addAll(groupService.allMembers(c, adGroup));
|
||||
}
|
||||
} else if ("$siteadmin".equals(contact)) {
|
||||
// special literal for site administrator
|
||||
EPerson siteEp = ePersonService.findByEmail(c,
|
||||
configurationService.getProperty("mail.admin"));
|
||||
if (siteEp != null) {
|
||||
|
@@ -42,9 +42,9 @@ public interface XmlWorkflowCuratorService {
|
||||
*
|
||||
* @param c the context
|
||||
* @param wfi the workflow item
|
||||
* @return true if curation was completed or not required,
|
||||
* @return true if curation was completed or not required;
|
||||
* false if tasks were queued for later completion,
|
||||
* or item was rejected
|
||||
* or item was rejected.
|
||||
* @throws AuthorizeException if authorization error
|
||||
* @throws IOException if IO error
|
||||
* @throws SQLException if database error
|
||||
@@ -58,7 +58,9 @@ public interface XmlWorkflowCuratorService {
|
||||
* @param curator the curation context
|
||||
* @param c the user context
|
||||
* @param wfId the workflow item's ID
|
||||
* @return true if curation failed.
|
||||
* @return true if curation curation was completed or not required;
|
||||
* false if tasks were queued for later completion,
|
||||
* or item was rejected.
|
||||
* @throws AuthorizeException if authorization error
|
||||
* @throws IOException if IO error
|
||||
* @throws SQLException if database error
|
||||
@@ -72,7 +74,9 @@ public interface XmlWorkflowCuratorService {
|
||||
* @param curator the curation context
|
||||
* @param c the user context
|
||||
* @param wfi the workflow item
|
||||
* @return true if curation failed.
|
||||
* @return true if workflow curation was completed or not required;
|
||||
* false if tasks were queued for later completion,
|
||||
* or item was rejected.
|
||||
* @throws AuthorizeException if authorization error
|
||||
* @throws IOException if IO error
|
||||
* @throws SQLException if database error
|
||||
|
@@ -154,7 +154,11 @@ public class IndexEventConsumer implements Consumer {
|
||||
|
||||
case Event.REMOVE:
|
||||
case Event.ADD:
|
||||
if (object == null) {
|
||||
// At this time, ADD and REMOVE actions are ignored on SITE object. They are only triggered for
|
||||
// top-level communities. No action is necessary as Community itself is indexed (or deleted) separately.
|
||||
if (event.getSubjectType() == Constants.SITE) {
|
||||
log.debug(event.getEventTypeAsString() + " event triggered for Site object. Skipping it.");
|
||||
} else if (object == null) {
|
||||
log.warn(event.getEventTypeAsString() + " event, could not get object for "
|
||||
+ event.getObjectTypeAsString() + " id="
|
||||
+ event.getObjectID()
|
||||
@@ -201,6 +205,10 @@ public class IndexEventConsumer implements Consumer {
|
||||
@Override
|
||||
public void end(Context ctx) throws Exception {
|
||||
|
||||
// Change the mode to readonly to improve performance
|
||||
Context.Mode originalMode = ctx.getCurrentMode();
|
||||
ctx.setMode(Context.Mode.READ_ONLY);
|
||||
|
||||
try {
|
||||
for (String uid : uniqueIdsToDelete) {
|
||||
try {
|
||||
@@ -230,6 +238,8 @@ public class IndexEventConsumer implements Consumer {
|
||||
uniqueIdsToDelete.clear();
|
||||
createdItemsToUpdate.clear();
|
||||
}
|
||||
|
||||
ctx.setMode(originalMode);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1031,9 +1031,8 @@ public class SolrServiceImpl implements SearchService, IndexingService {
|
||||
// Add information about our search fields
|
||||
for (String field : searchFields) {
|
||||
List<String> valuesAsString = new ArrayList<>();
|
||||
for (Object o : doc.getFieldValues(field)) {
|
||||
valuesAsString.add(String.valueOf(o));
|
||||
}
|
||||
Optional.ofNullable(doc.getFieldValues(field))
|
||||
.ifPresent(l -> l.forEach(o -> valuesAsString.add(String.valueOf(o))));
|
||||
resultDoc.addSearchField(field, valuesAsString.toArray(new String[valuesAsString.size()]));
|
||||
}
|
||||
result.addSearchDocument(indexableObject, resultDoc);
|
||||
|
@@ -49,6 +49,7 @@ import org.dspace.eperson.service.SubscribeService;
|
||||
import org.dspace.event.Event;
|
||||
import org.dspace.orcid.service.OrcidTokenService;
|
||||
import org.dspace.qaevent.dao.QAEventsDao;
|
||||
import org.dspace.services.ConfigurationService;
|
||||
import org.dspace.util.UUIDUtils;
|
||||
import org.dspace.versioning.Version;
|
||||
import org.dspace.versioning.VersionHistory;
|
||||
@@ -103,6 +104,8 @@ public class EPersonServiceImpl extends DSpaceObjectServiceImpl<EPerson> impleme
|
||||
protected VersionDAO versionDAO;
|
||||
@Autowired(required = true)
|
||||
protected ClaimedTaskService claimedTaskService;
|
||||
@Autowired(required = true)
|
||||
protected ConfigurationService configurationService;
|
||||
@Autowired
|
||||
protected OrcidTokenService orcidTokenService;
|
||||
@Autowired
|
||||
@@ -117,6 +120,30 @@ public class EPersonServiceImpl extends DSpaceObjectServiceImpl<EPerson> impleme
|
||||
return ePersonDAO.findByID(context, EPerson.class, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a fake EPerson which can receive email. Its address will be the
|
||||
* value of "mail.admin", or "postmaster" if all else fails.
|
||||
* @param c
|
||||
* @return
|
||||
* @throws SQLException
|
||||
*/
|
||||
@Override
|
||||
public EPerson getSystemEPerson(Context c)
|
||||
throws SQLException {
|
||||
String adminEmail = configurationService.getProperty("mail.admin");
|
||||
if (null == adminEmail) {
|
||||
adminEmail = "postmaster"; // Last-ditch attempt to send *somewhere*
|
||||
}
|
||||
EPerson systemEPerson = findByEmail(c, adminEmail);
|
||||
|
||||
if (null == systemEPerson) {
|
||||
systemEPerson = new EPerson();
|
||||
systemEPerson.setEmail(adminEmail);
|
||||
}
|
||||
|
||||
return systemEPerson;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EPerson findByIdOrLegacyId(Context context, String id) throws SQLException {
|
||||
if (StringUtils.isNumeric(id)) {
|
||||
@@ -161,32 +188,98 @@ public class EPersonServiceImpl extends DSpaceObjectServiceImpl<EPerson> impleme
|
||||
|
||||
@Override
|
||||
public List<EPerson> search(Context context, String query, int offset, int limit) throws SQLException {
|
||||
try {
|
||||
List<EPerson> ePerson = new ArrayList<>();
|
||||
EPerson person = find(context, UUID.fromString(query));
|
||||
if (person != null) {
|
||||
ePerson.add(person);
|
||||
}
|
||||
return ePerson;
|
||||
} catch (IllegalArgumentException e) {
|
||||
List<EPerson> ePersons = new ArrayList<>();
|
||||
UUID uuid = UUIDUtils.fromString(query);
|
||||
if (uuid == null) {
|
||||
// Search by firstname & lastname (NOTE: email will also be included automatically)
|
||||
MetadataField firstNameField = metadataFieldService.findByElement(context, "eperson", "firstname", null);
|
||||
MetadataField lastNameField = metadataFieldService.findByElement(context, "eperson", "lastname", null);
|
||||
if (StringUtils.isBlank(query)) {
|
||||
query = null;
|
||||
}
|
||||
return ePersonDAO.search(context, query, Arrays.asList(firstNameField, lastNameField),
|
||||
Arrays.asList(firstNameField, lastNameField), offset, limit);
|
||||
ePersons = ePersonDAO.search(context, query, Arrays.asList(firstNameField, lastNameField),
|
||||
Arrays.asList(firstNameField, lastNameField), offset, limit);
|
||||
} else {
|
||||
// Search by UUID
|
||||
EPerson person = find(context, uuid);
|
||||
if (person != null) {
|
||||
ePersons.add(person);
|
||||
}
|
||||
}
|
||||
return ePersons;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int searchResultCount(Context context, String query) throws SQLException {
|
||||
MetadataField firstNameField = metadataFieldService.findByElement(context, "eperson", "firstname", null);
|
||||
MetadataField lastNameField = metadataFieldService.findByElement(context, "eperson", "lastname", null);
|
||||
if (StringUtils.isBlank(query)) {
|
||||
query = null;
|
||||
int result = 0;
|
||||
UUID uuid = UUIDUtils.fromString(query);
|
||||
if (uuid == null) {
|
||||
// Count results found by firstname & lastname (email is also included automatically)
|
||||
MetadataField firstNameField = metadataFieldService.findByElement(context, "eperson", "firstname", null);
|
||||
MetadataField lastNameField = metadataFieldService.findByElement(context, "eperson", "lastname", null);
|
||||
if (StringUtils.isBlank(query)) {
|
||||
query = null;
|
||||
}
|
||||
result = ePersonDAO.searchResultCount(context, query, Arrays.asList(firstNameField, lastNameField));
|
||||
} else {
|
||||
// Search by UUID
|
||||
EPerson person = find(context, uuid);
|
||||
if (person != null) {
|
||||
result = 1;
|
||||
}
|
||||
}
|
||||
return ePersonDAO.searchResultCount(context, query, Arrays.asList(firstNameField, lastNameField));
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<EPerson> searchNonMembers(Context context, String query, Group excludeGroup, int offset, int limit)
|
||||
throws SQLException {
|
||||
List<EPerson> ePersons = new ArrayList<>();
|
||||
UUID uuid = UUIDUtils.fromString(query);
|
||||
if (uuid == null) {
|
||||
// Search by firstname & lastname (NOTE: email will also be included automatically)
|
||||
MetadataField firstNameField = metadataFieldService.findByElement(context, "eperson", "firstname", null);
|
||||
MetadataField lastNameField = metadataFieldService.findByElement(context, "eperson", "lastname", null);
|
||||
if (StringUtils.isBlank(query)) {
|
||||
query = null;
|
||||
}
|
||||
ePersons = ePersonDAO.searchNotMember(context, query, Arrays.asList(firstNameField, lastNameField),
|
||||
excludeGroup, Arrays.asList(firstNameField, lastNameField),
|
||||
offset, limit);
|
||||
} else {
|
||||
// Search by UUID
|
||||
EPerson person = find(context, uuid);
|
||||
// Verify EPerson is NOT a member of the given excludeGroup before adding
|
||||
if (person != null && !groupService.isDirectMember(excludeGroup, person)) {
|
||||
ePersons.add(person);
|
||||
}
|
||||
}
|
||||
|
||||
return ePersons;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int searchNonMembersCount(Context context, String query, Group excludeGroup) throws SQLException {
|
||||
int result = 0;
|
||||
UUID uuid = UUIDUtils.fromString(query);
|
||||
if (uuid == null) {
|
||||
// Count results found by firstname & lastname (email is also included automatically)
|
||||
MetadataField firstNameField = metadataFieldService.findByElement(context, "eperson", "firstname", null);
|
||||
MetadataField lastNameField = metadataFieldService.findByElement(context, "eperson", "lastname", null);
|
||||
if (StringUtils.isBlank(query)) {
|
||||
query = null;
|
||||
}
|
||||
result = ePersonDAO.searchNotMemberCount(context, query, Arrays.asList(firstNameField, lastNameField),
|
||||
excludeGroup);
|
||||
} else {
|
||||
// Search by UUID
|
||||
EPerson person = find(context, uuid);
|
||||
// Verify EPerson is NOT a member of the given excludeGroup before counting
|
||||
if (person != null && !groupService.isDirectMember(excludeGroup, person)) {
|
||||
result = 1;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -282,10 +375,13 @@ public class EPersonServiceImpl extends DSpaceObjectServiceImpl<EPerson> impleme
|
||||
throw new AuthorizeException(
|
||||
"You must be an admin to delete an EPerson");
|
||||
}
|
||||
// Get all workflow-related groups that the current EPerson belongs to
|
||||
Set<Group> workFlowGroups = getAllWorkFlowGroups(context, ePerson);
|
||||
for (Group group: workFlowGroups) {
|
||||
List<EPerson> ePeople = groupService.allMembers(context, group);
|
||||
if (ePeople.size() == 1 && ePeople.contains(ePerson)) {
|
||||
// Get total number of unique EPerson objs who are a member of this group (or subgroup)
|
||||
int totalMembers = groupService.countAllMembers(context, group);
|
||||
// If only one EPerson is a member, then we cannot delete the last member of this group.
|
||||
if (totalMembers == 1) {
|
||||
throw new EmptyWorkflowGroupException(ePerson.getID(), group.getID());
|
||||
}
|
||||
}
|
||||
@@ -549,14 +645,29 @@ public class EPersonServiceImpl extends DSpaceObjectServiceImpl<EPerson> impleme
|
||||
|
||||
@Override
|
||||
public List<EPerson> findByGroups(Context c, Set<Group> groups) throws SQLException {
|
||||
return findByGroups(c, groups, -1, -1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<EPerson> findByGroups(Context c, Set<Group> groups, int pageSize, int offset) throws SQLException {
|
||||
//Make sure we at least have one group, if not don't even bother searching.
|
||||
if (CollectionUtils.isNotEmpty(groups)) {
|
||||
return ePersonDAO.findByGroups(c, groups);
|
||||
return ePersonDAO.findByGroups(c, groups, pageSize, offset);
|
||||
} else {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int countByGroups(Context c, Set<Group> groups) throws SQLException {
|
||||
//Make sure we at least have one group, if not don't even bother counting.
|
||||
if (CollectionUtils.isNotEmpty(groups)) {
|
||||
return ePersonDAO.countByGroups(c, groups);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<EPerson> findEPeopleWithSubscription(Context context) throws SQLException {
|
||||
return ePersonDAO.findAllSubscribers(context);
|
||||
|
@@ -141,20 +141,10 @@ public class Groomer {
|
||||
System.out.println();
|
||||
|
||||
if (delete) {
|
||||
List<String> whyNot = ePersonService.getDeleteConstraints(myContext, account);
|
||||
if (!whyNot.isEmpty()) {
|
||||
System.out.print("\tCannot be deleted; referenced in");
|
||||
for (String table : whyNot) {
|
||||
System.out.print(' ');
|
||||
System.out.print(table);
|
||||
}
|
||||
System.out.println();
|
||||
} else {
|
||||
try {
|
||||
ePersonService.delete(myContext, account);
|
||||
} catch (AuthorizeException | IOException ex) {
|
||||
System.err.println(ex.getMessage());
|
||||
}
|
||||
try {
|
||||
ePersonService.delete(myContext, account);
|
||||
} catch (AuthorizeException | IOException ex) {
|
||||
System.err.println(ex.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -98,7 +98,11 @@ public class Group extends DSpaceObject implements DSpaceObjectLegacySupport {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return EPerson members of a Group
|
||||
* Return EPerson members of a Group.
|
||||
* <P>
|
||||
* WARNING: This method may have bad performance for Groups with large numbers of EPerson members.
|
||||
* Therefore, only use this when you need to access every EPerson member. Instead, consider using
|
||||
* EPersonService.findByGroups() for a paginated list of EPersons.
|
||||
*
|
||||
* @return list of EPersons
|
||||
*/
|
||||
@@ -143,9 +147,13 @@ public class Group extends DSpaceObject implements DSpaceObjectLegacySupport {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return Group members of a Group.
|
||||
* Return Group members (i.e. direct subgroups) of a Group.
|
||||
* <P>
|
||||
* WARNING: This method may have bad performance for Groups with large numbers of Subgroups.
|
||||
* Therefore, only use this when you need to access every Subgroup. Instead, consider using
|
||||
* GroupService.findByParent() for a paginated list of Subgroups.
|
||||
*
|
||||
* @return list of groups
|
||||
* @return list of subgroups
|
||||
*/
|
||||
public List<Group> getMemberGroups() {
|
||||
return groups;
|
||||
|
@@ -179,8 +179,13 @@ public class GroupServiceImpl extends DSpaceObjectServiceImpl<Group> implements
|
||||
for (CollectionRole collectionRole : collectionRoles) {
|
||||
if (StringUtils.equals(collectionRole.getRoleId(), role.getId())
|
||||
&& claimedTask.getWorkflowItem().getCollection() == collectionRole.getCollection()) {
|
||||
List<EPerson> ePeople = allMembers(context, group);
|
||||
if (ePeople.size() == 1 && ePeople.contains(ePerson)) {
|
||||
// Count number of EPersons who are *direct* members of this group
|
||||
int totalDirectEPersons = ePersonService.countByGroups(context, Set.of(group));
|
||||
// Count number of Groups which have this groupParent as a direct parent
|
||||
int totalChildGroups = countByParent(context, group);
|
||||
// If this group has only one direct EPerson and *zero* child groups, then we cannot delete the
|
||||
// EPerson or we will leave this group empty.
|
||||
if (totalDirectEPersons == 1 && totalChildGroups == 0) {
|
||||
throw new IllegalStateException(
|
||||
"Refused to remove user " + ePerson
|
||||
.getID() + " from workflow group because the group " + group
|
||||
@@ -191,8 +196,13 @@ public class GroupServiceImpl extends DSpaceObjectServiceImpl<Group> implements
|
||||
}
|
||||
}
|
||||
if (!poolTasks.isEmpty()) {
|
||||
List<EPerson> ePeople = allMembers(context, group);
|
||||
if (ePeople.size() == 1 && ePeople.contains(ePerson)) {
|
||||
// Count number of EPersons who are *direct* members of this group
|
||||
int totalDirectEPersons = ePersonService.countByGroups(context, Set.of(group));
|
||||
// Count number of Groups which have this groupParent as a direct parent
|
||||
int totalChildGroups = countByParent(context, group);
|
||||
// If this group has only one direct EPerson and *zero* child groups, then we cannot delete the
|
||||
// EPerson or we will leave this group empty.
|
||||
if (totalDirectEPersons == 1 && totalChildGroups == 0) {
|
||||
throw new IllegalStateException(
|
||||
"Refused to remove user " + ePerson
|
||||
.getID() + " from workflow group because the group " + group
|
||||
@@ -212,9 +222,13 @@ public class GroupServiceImpl extends DSpaceObjectServiceImpl<Group> implements
|
||||
if (!collectionRoles.isEmpty()) {
|
||||
List<PoolTask> poolTasks = poolTaskService.findByGroup(context, groupParent);
|
||||
if (!poolTasks.isEmpty()) {
|
||||
List<EPerson> parentPeople = allMembers(context, groupParent);
|
||||
List<EPerson> childPeople = allMembers(context, childGroup);
|
||||
if (childPeople.containsAll(parentPeople)) {
|
||||
// Count number of Groups which have this groupParent as a direct parent
|
||||
int totalChildGroups = countByParent(context, groupParent);
|
||||
// Count number of EPersons who are *direct* members of this group
|
||||
int totalDirectEPersons = ePersonService.countByGroups(context, Set.of(groupParent));
|
||||
// If this group has only one childGroup and *zero* direct EPersons, then we cannot delete the
|
||||
// childGroup or we will leave this group empty.
|
||||
if (totalChildGroups == 1 && totalDirectEPersons == 0) {
|
||||
throw new IllegalStateException(
|
||||
"Refused to remove sub group " + childGroup
|
||||
.getID() + " from workflow group because the group " + groupParent
|
||||
@@ -368,7 +382,8 @@ public class GroupServiceImpl extends DSpaceObjectServiceImpl<Group> implements
|
||||
|
||||
// Get all groups which are a member of this group
|
||||
List<Group2GroupCache> group2GroupCaches = group2GroupCacheDAO.findByParent(c, g);
|
||||
Set<Group> groups = new HashSet<>();
|
||||
// Initialize HashSet based on List size to avoid Set resizing. See https://stackoverflow.com/a/21822273
|
||||
Set<Group> groups = new HashSet<>((int) (group2GroupCaches.size() / 0.75 + 1));
|
||||
for (Group2GroupCache group2GroupCache : group2GroupCaches) {
|
||||
groups.add(group2GroupCache.getChild());
|
||||
}
|
||||
@@ -381,6 +396,23 @@ public class GroupServiceImpl extends DSpaceObjectServiceImpl<Group> implements
|
||||
return new ArrayList<>(childGroupChildren);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int countAllMembers(Context context, Group group) throws SQLException {
|
||||
// Get all groups which are a member of this group
|
||||
List<Group2GroupCache> group2GroupCaches = group2GroupCacheDAO.findByParent(context, group);
|
||||
// Initialize HashSet based on List size + current 'group' to avoid Set resizing.
|
||||
// See https://stackoverflow.com/a/21822273
|
||||
Set<Group> groups = new HashSet<>((int) ((group2GroupCaches.size() + 1) / 0.75 + 1));
|
||||
for (Group2GroupCache group2GroupCache : group2GroupCaches) {
|
||||
groups.add(group2GroupCache.getChild());
|
||||
}
|
||||
// Append current group as well
|
||||
groups.add(group);
|
||||
|
||||
// Return total number of unique EPerson objects in any of these groups
|
||||
return ePersonService.countByGroups(context, groups);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Group find(Context context, UUID id) throws SQLException {
|
||||
if (id == null) {
|
||||
@@ -428,17 +460,17 @@ public class GroupServiceImpl extends DSpaceObjectServiceImpl<Group> implements
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Group> search(Context context, String groupIdentifier) throws SQLException {
|
||||
return search(context, groupIdentifier, -1, -1);
|
||||
public List<Group> search(Context context, String query) throws SQLException {
|
||||
return search(context, query, -1, -1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Group> search(Context context, String groupIdentifier, int offset, int limit) throws SQLException {
|
||||
public List<Group> search(Context context, String query, int offset, int limit) throws SQLException {
|
||||
List<Group> groups = new ArrayList<>();
|
||||
UUID uuid = UUIDUtils.fromString(groupIdentifier);
|
||||
UUID uuid = UUIDUtils.fromString(query);
|
||||
if (uuid == null) {
|
||||
//Search by group name
|
||||
groups = groupDAO.findByNameLike(context, groupIdentifier, offset, limit);
|
||||
groups = groupDAO.findByNameLike(context, query, offset, limit);
|
||||
} else {
|
||||
//Search by group id
|
||||
Group group = find(context, uuid);
|
||||
@@ -451,12 +483,12 @@ public class GroupServiceImpl extends DSpaceObjectServiceImpl<Group> implements
|
||||
}
|
||||
|
||||
@Override
|
||||
public int searchResultCount(Context context, String groupIdentifier) throws SQLException {
|
||||
public int searchResultCount(Context context, String query) throws SQLException {
|
||||
int result = 0;
|
||||
UUID uuid = UUIDUtils.fromString(groupIdentifier);
|
||||
UUID uuid = UUIDUtils.fromString(query);
|
||||
if (uuid == null) {
|
||||
//Search by group name
|
||||
result = groupDAO.countByNameLike(context, groupIdentifier);
|
||||
result = groupDAO.countByNameLike(context, query);
|
||||
} else {
|
||||
//Search by group id
|
||||
Group group = find(context, uuid);
|
||||
@@ -468,6 +500,44 @@ public class GroupServiceImpl extends DSpaceObjectServiceImpl<Group> implements
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Group> searchNonMembers(Context context, String query, Group excludeParentGroup,
|
||||
int offset, int limit) throws SQLException {
|
||||
List<Group> groups = new ArrayList<>();
|
||||
UUID uuid = UUIDUtils.fromString(query);
|
||||
if (uuid == null) {
|
||||
// Search by group name
|
||||
groups = groupDAO.findByNameLikeAndNotMember(context, query, excludeParentGroup, offset, limit);
|
||||
} else if (!uuid.equals(excludeParentGroup.getID())) {
|
||||
// Search by group id
|
||||
Group group = find(context, uuid);
|
||||
// Verify it is NOT a member of the given excludeParentGroup before adding
|
||||
if (group != null && !isMember(excludeParentGroup, group)) {
|
||||
groups.add(group);
|
||||
}
|
||||
}
|
||||
|
||||
return groups;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int searchNonMembersCount(Context context, String query, Group excludeParentGroup) throws SQLException {
|
||||
int result = 0;
|
||||
UUID uuid = UUIDUtils.fromString(query);
|
||||
if (uuid == null) {
|
||||
// Search by group name
|
||||
result = groupDAO.countByNameLikeAndNotMember(context, query, excludeParentGroup);
|
||||
} else if (!uuid.equals(excludeParentGroup.getID())) {
|
||||
// Search by group id
|
||||
Group group = find(context, uuid);
|
||||
// Verify it is NOT a member of the given excludeParentGroup before adding
|
||||
if (group != null && !isMember(excludeParentGroup, group)) {
|
||||
result = 1;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(Context context, Group group) throws SQLException {
|
||||
if (group.isPermanent()) {
|
||||
@@ -829,4 +899,20 @@ public class GroupServiceImpl extends DSpaceObjectServiceImpl<Group> implements
|
||||
public String getName(Group dso) {
|
||||
return dso.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Group> findByParent(Context context, Group parent, int pageSize, int offset) throws SQLException {
|
||||
if (parent == null) {
|
||||
return null;
|
||||
}
|
||||
return groupDAO.findByParent(context, parent, pageSize, offset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int countByParent(Context context, Group parent) throws SQLException {
|
||||
if (parent == null) {
|
||||
return 0;
|
||||
}
|
||||
return groupDAO.countByParent(context, parent);
|
||||
}
|
||||
}
|
||||
|
@@ -33,12 +33,91 @@ public interface EPersonDAO extends DSpaceObjectDAO<EPerson>, DSpaceObjectLegacy
|
||||
|
||||
public EPerson findByNetid(Context context, String netid) throws SQLException;
|
||||
|
||||
/**
|
||||
* Search all EPersons by the given MetadataField objects, sorting by the given sort fields.
|
||||
* <P>
|
||||
* NOTE: As long as a query is specified, the EPerson's email address is included in the search alongside any given
|
||||
* metadata fields.
|
||||
*
|
||||
* @param context DSpace context
|
||||
* @param query the text to search EPersons for
|
||||
* @param queryFields the metadata fields to search within (email is also included automatically)
|
||||
* @param sortFields the metadata field(s) to sort the results by
|
||||
* @param offset the position of the first result to return
|
||||
* @param limit how many results return
|
||||
* @return List of matching EPerson objects
|
||||
* @throws SQLException if an error occurs
|
||||
*/
|
||||
public List<EPerson> search(Context context, String query, List<MetadataField> queryFields,
|
||||
List<MetadataField> sortFields, int offset, int limit) throws SQLException;
|
||||
|
||||
/**
|
||||
* Count number of EPersons who match a search on the given metadata fields. This returns the count of total
|
||||
* results for the same query using the 'search()', and therefore can be used to provide pagination.
|
||||
*
|
||||
* @param context DSpace context
|
||||
* @param query the text to search EPersons for
|
||||
* @param queryFields the metadata fields to search within (email is also included automatically)
|
||||
* @return total number of EPersons who match the query
|
||||
* @throws SQLException if an error occurs
|
||||
*/
|
||||
public int searchResultCount(Context context, String query, List<MetadataField> queryFields) throws SQLException;
|
||||
|
||||
public List<EPerson> findByGroups(Context context, Set<Group> groups) throws SQLException;
|
||||
/**
|
||||
* Search all EPersons via their firstname, lastname, email (fuzzy match), limited to those EPersons which are NOT
|
||||
* a member of the given group. This may be used to search across EPersons which are valid to add as members to the
|
||||
* given group.
|
||||
*
|
||||
* @param context The DSpace context
|
||||
* @param query the text to search EPersons for
|
||||
* @param queryFields the metadata fields to search within (email is also included automatically)
|
||||
* @param excludeGroup Group to exclude results from. Members of this group will never be returned.
|
||||
* @param offset the position of the first result to return
|
||||
* @param limit how many results return
|
||||
* @return EPersons matching the query (which are not members of the given group)
|
||||
* @throws SQLException if database error
|
||||
*/
|
||||
List<EPerson> searchNotMember(Context context, String query, List<MetadataField> queryFields, Group excludeGroup,
|
||||
List<MetadataField> sortFields, int offset, int limit) throws SQLException;
|
||||
|
||||
/**
|
||||
* Count number of EPersons that match a given search (fuzzy match) across firstname, lastname and email. This
|
||||
* search is limited to those EPersons which are NOT a member of the given group. This may be used
|
||||
* (with searchNotMember()) to perform a paginated search across EPersons which are valid to add to the given group.
|
||||
*
|
||||
* @param context The DSpace context
|
||||
* @param query querystring to fuzzy match against.
|
||||
* @param queryFields the metadata fields to search within (email is also included automatically)
|
||||
* @param excludeGroup Group to exclude results from. Members of this group will never be returned.
|
||||
* @return Groups matching the query (which are not members of the given parent)
|
||||
* @throws SQLException if database error
|
||||
*/
|
||||
int searchNotMemberCount(Context context, String query, List<MetadataField> queryFields, Group excludeGroup)
|
||||
throws SQLException;
|
||||
|
||||
/**
|
||||
* Find all EPersons who are a member of one or more of the listed groups in a paginated fashion. This returns
|
||||
* EPersons ordered by UUID.
|
||||
*
|
||||
* @param context current Context
|
||||
* @param groups Set of group(s) to check membership in
|
||||
* @param pageSize number of EPerson objects to load at one time. Set to <=0 to disable pagination
|
||||
* @param offset number of page to load (starting with 1). Set to <=0 to disable pagination
|
||||
* @return List of all EPersons who are a member of one or more groups.
|
||||
* @throws SQLException
|
||||
*/
|
||||
List<EPerson> findByGroups(Context context, Set<Group> groups, int pageSize, int offset) throws SQLException;
|
||||
|
||||
/**
|
||||
* Count total number of EPersons who are a member of one or more of the listed groups. This provides the total
|
||||
* number of results to expect from corresponding findByGroups() for pagination purposes.
|
||||
*
|
||||
* @param context current Context
|
||||
* @param groups Set of group(s) to check membership in
|
||||
* @return total number of (unique) EPersons who are a member of one or more groups.
|
||||
* @throws SQLException
|
||||
*/
|
||||
int countByGroups(Context context, Set<Group> groups) throws SQLException;
|
||||
|
||||
public List<EPerson> findWithPasswordWithoutDigestAlgorithm(Context context) throws SQLException;
|
||||
|
||||
|
@@ -135,6 +135,38 @@ public interface GroupDAO extends DSpaceObjectDAO<Group>, DSpaceObjectLegacySupp
|
||||
*/
|
||||
int countByNameLike(Context context, String groupName) throws SQLException;
|
||||
|
||||
/**
|
||||
* Search all groups via their name (fuzzy match), limited to those groups which are NOT a member of the given
|
||||
* parent group. This may be used to search across groups which are valid to add to the given parent group.
|
||||
* <P>
|
||||
* NOTE: The parent group itself is also excluded from the search.
|
||||
*
|
||||
* @param context The DSpace context
|
||||
* @param groupName Group name to fuzzy match against.
|
||||
* @param excludeParent Parent Group to exclude results from. Groups under this parent will never be returned.
|
||||
* @param offset Offset to use for pagination (-1 to disable)
|
||||
* @param limit The maximum number of results to return (-1 to disable)
|
||||
* @return Groups matching the query (which are not members of the given parent)
|
||||
* @throws SQLException if database error
|
||||
*/
|
||||
List<Group> findByNameLikeAndNotMember(Context context, String groupName, Group excludeParent,
|
||||
int offset, int limit) throws SQLException;
|
||||
|
||||
/**
|
||||
* Count number of groups that match a given name (fuzzy match), limited to those groups which are NOT a member of
|
||||
* the given parent group. This may be used (with findByNameLikeAndNotMember()) to search across groups which are
|
||||
* valid to add to the given parent group.
|
||||
* <P>
|
||||
* NOTE: The parent group itself is also excluded from the count.
|
||||
*
|
||||
* @param context The DSpace context
|
||||
* @param groupName Group name to fuzzy match against.
|
||||
* @param excludeParent Parent Group to exclude results from. Groups under this parent will never be returned.
|
||||
* @return Groups matching the query (which are not members of the given parent)
|
||||
* @throws SQLException if database error
|
||||
*/
|
||||
int countByNameLikeAndNotMember(Context context, String groupName, Group excludeParent) throws SQLException;
|
||||
|
||||
/**
|
||||
* Find a group by its name and the membership of the given EPerson
|
||||
*
|
||||
@@ -146,4 +178,28 @@ public interface GroupDAO extends DSpaceObjectDAO<Group>, DSpaceObjectLegacySupp
|
||||
*/
|
||||
Group findByIdAndMembership(Context context, UUID id, EPerson ePerson) throws SQLException;
|
||||
|
||||
/**
|
||||
* Find all groups which are members of a given parent group.
|
||||
* This provides the same behavior as group.getMemberGroups(), but in a paginated fashion.
|
||||
*
|
||||
* @param context The DSpace context
|
||||
* @param parent Parent Group to search within
|
||||
* @param pageSize how many results return
|
||||
* @param offset the position of the first result to return
|
||||
* @return Groups matching the query
|
||||
* @throws SQLException if database error
|
||||
*/
|
||||
List<Group> findByParent(Context context, Group parent, int pageSize, int offset) throws SQLException;
|
||||
|
||||
/**
|
||||
* Returns the number of groups which are members of a given parent group.
|
||||
* This provides the same behavior as group.getMemberGroups().size(), but with better performance for large groups.
|
||||
* This method may be used with findByParent() to perform pagination.
|
||||
*
|
||||
* @param context The DSpace context
|
||||
* @param parent Parent Group to search within
|
||||
* @return Number of Groups matching the query
|
||||
* @throws SQLException if database error
|
||||
*/
|
||||
int countByParent(Context context, Group parent) throws SQLException;
|
||||
}
|
||||
|
@@ -70,17 +70,9 @@ public class EPersonDAOImpl extends AbstractHibernateDSODAO<EPerson> implements
|
||||
String queryString = "SELECT " + EPerson.class.getSimpleName()
|
||||
.toLowerCase() + " FROM EPerson as " + EPerson.class
|
||||
.getSimpleName().toLowerCase() + " ";
|
||||
if (query != null) {
|
||||
query = "%" + query.toLowerCase() + "%";
|
||||
}
|
||||
Query hibernateQuery = getSearchQuery(context, queryString, query, queryFields, sortFields, null);
|
||||
|
||||
if (0 <= offset) {
|
||||
hibernateQuery.setFirstResult(offset);
|
||||
}
|
||||
if (0 <= limit) {
|
||||
hibernateQuery.setMaxResults(limit);
|
||||
}
|
||||
Query hibernateQuery = getSearchQuery(context, queryString, query, queryFields, null,
|
||||
sortFields, null, limit, offset);
|
||||
return list(hibernateQuery);
|
||||
}
|
||||
|
||||
@@ -92,6 +84,28 @@ public class EPersonDAOImpl extends AbstractHibernateDSODAO<EPerson> implements
|
||||
return count(hibernateQuery);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<EPerson> searchNotMember(Context context, String query, List<MetadataField> queryFields,
|
||||
Group excludeGroup, List<MetadataField> sortFields,
|
||||
int offset, int limit) throws SQLException {
|
||||
String queryString = "SELECT " + EPerson.class.getSimpleName()
|
||||
.toLowerCase() + " FROM EPerson as " + EPerson.class
|
||||
.getSimpleName().toLowerCase() + " ";
|
||||
|
||||
Query hibernateQuery = getSearchQuery(context, queryString, query, queryFields, excludeGroup,
|
||||
sortFields, null, limit, offset);
|
||||
return list(hibernateQuery);
|
||||
}
|
||||
|
||||
public int searchNotMemberCount(Context context, String query, List<MetadataField> queryFields,
|
||||
Group excludeGroup) throws SQLException {
|
||||
String queryString = "SELECT count(*) FROM EPerson as " + EPerson.class.getSimpleName().toLowerCase();
|
||||
|
||||
Query hibernateQuery = getSearchQuery(context, queryString, query, queryFields, excludeGroup,
|
||||
Collections.EMPTY_LIST, null, -1, -1);
|
||||
return count(hibernateQuery);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<EPerson> findAll(Context context, MetadataField metadataSortField, String sortField, int pageSize,
|
||||
int offset) throws SQLException {
|
||||
@@ -105,19 +119,43 @@ public class EPersonDAOImpl extends AbstractHibernateDSODAO<EPerson> implements
|
||||
sortFields = Collections.singletonList(metadataSortField);
|
||||
}
|
||||
|
||||
Query query = getSearchQuery(context, queryString, null, ListUtils.EMPTY_LIST, sortFields, sortField, pageSize,
|
||||
offset);
|
||||
Query query = getSearchQuery(context, queryString, null, ListUtils.EMPTY_LIST, null,
|
||||
sortFields, sortField, pageSize, offset);
|
||||
return list(query);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<EPerson> findByGroups(Context context, Set<Group> groups) throws SQLException {
|
||||
public List<EPerson> findByGroups(Context context, Set<Group> groups, int pageSize, int offset)
|
||||
throws SQLException {
|
||||
Query query = createQuery(context,
|
||||
"SELECT DISTINCT e FROM EPerson e " +
|
||||
"JOIN e.groups g " +
|
||||
"WHERE g.id IN (:idList) ");
|
||||
|
||||
List<UUID> idList = new ArrayList<>(groups.size());
|
||||
for (Group group : groups) {
|
||||
idList.add(group.getID());
|
||||
}
|
||||
query.setParameter("idList", idList);
|
||||
|
||||
if (pageSize > 0) {
|
||||
query.setMaxResults(pageSize);
|
||||
}
|
||||
if (offset > 0) {
|
||||
query.setFirstResult(offset);
|
||||
}
|
||||
|
||||
return list(query);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int countByGroups(Context context, Set<Group> groups) throws SQLException {
|
||||
Query query = createQuery(context,
|
||||
"SELECT count(DISTINCT e) FROM EPerson e " +
|
||||
"JOIN e.groups g " +
|
||||
"WHERE g.id IN (:idList) ");
|
||||
|
||||
List<UUID> idList = new ArrayList<>(groups.size());
|
||||
for (Group group : groups) {
|
||||
idList.add(group.getID());
|
||||
@@ -125,7 +163,7 @@ public class EPersonDAOImpl extends AbstractHibernateDSODAO<EPerson> implements
|
||||
|
||||
query.setParameter("idList", idList);
|
||||
|
||||
return list(query);
|
||||
return count(query);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -154,43 +192,88 @@ public class EPersonDAOImpl extends AbstractHibernateDSODAO<EPerson> implements
|
||||
protected Query getSearchQuery(Context context, String queryString, String queryParam,
|
||||
List<MetadataField> queryFields, List<MetadataField> sortFields, String sortField)
|
||||
throws SQLException {
|
||||
return getSearchQuery(context, queryString, queryParam, queryFields, sortFields, sortField, -1, -1);
|
||||
return getSearchQuery(context, queryString, queryParam, queryFields, null, sortFields, sortField, -1, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a search query across EPersons based on the given metadata fields and sorted based on the given metadata
|
||||
* field(s) or database column.
|
||||
* <P>
|
||||
* NOTE: the EPerson's email address is included in the search alongside any given metadata fields.
|
||||
*
|
||||
* @param context DSpace Context
|
||||
* @param queryString String which defines the beginning "SELECT" for the SQL query
|
||||
* @param queryParam Actual text being searched for
|
||||
* @param queryFields List of metadata fields to search within
|
||||
* @param excludeGroup Optional Group which should be excluded from search. Any EPersons who are members
|
||||
* of this group will not be included in the results.
|
||||
* @param sortFields Optional List of metadata fields to sort by (should not be specified if sortField is used)
|
||||
* @param sortField Optional database column to sort on (should not be specified if sortFields is used)
|
||||
* @param pageSize how many results return
|
||||
* @param offset the position of the first result to return
|
||||
* @return built Query object
|
||||
* @throws SQLException if error occurs
|
||||
*/
|
||||
protected Query getSearchQuery(Context context, String queryString, String queryParam,
|
||||
List<MetadataField> queryFields, List<MetadataField> sortFields, String sortField,
|
||||
int pageSize, int offset) throws SQLException {
|
||||
|
||||
List<MetadataField> queryFields, Group excludeGroup,
|
||||
List<MetadataField> sortFields, String sortField,
|
||||
int pageSize, int offset) throws SQLException {
|
||||
// Initialize SQL statement using the passed in "queryString"
|
||||
StringBuilder queryBuilder = new StringBuilder();
|
||||
queryBuilder.append(queryString);
|
||||
|
||||
Set<MetadataField> metadataFieldsToJoin = new LinkedHashSet<>();
|
||||
metadataFieldsToJoin.addAll(queryFields);
|
||||
metadataFieldsToJoin.addAll(sortFields);
|
||||
|
||||
// Append necessary join information for MetadataFields we will search within
|
||||
if (!CollectionUtils.isEmpty(metadataFieldsToJoin)) {
|
||||
addMetadataLeftJoin(queryBuilder, EPerson.class.getSimpleName().toLowerCase(), metadataFieldsToJoin);
|
||||
}
|
||||
if (queryParam != null) {
|
||||
// Always append a search on EPerson "email" based on query
|
||||
if (StringUtils.isNotBlank(queryParam)) {
|
||||
addMetadataValueWhereQuery(queryBuilder, queryFields, "like",
|
||||
EPerson.class.getSimpleName().toLowerCase() + ".email like :queryParam");
|
||||
}
|
||||
// If excludeGroup is specified, exclude members of that group from results
|
||||
// This uses a subquery to find the excluded group & verify that it is not in the EPerson list of "groups"
|
||||
if (excludeGroup != null) {
|
||||
// If query params exist, then we already have a WHERE clause (see above) and just need to append an AND
|
||||
if (StringUtils.isNotBlank(queryParam)) {
|
||||
queryBuilder.append(" AND ");
|
||||
} else {
|
||||
// no WHERE clause yet, so this is the start of the WHERE
|
||||
queryBuilder.append(" WHERE ");
|
||||
}
|
||||
queryBuilder.append("(FROM Group g where g.id = :group_id) NOT IN elements (")
|
||||
.append(EPerson.class.getSimpleName().toLowerCase()).append(".groups)");
|
||||
}
|
||||
// Add sort/order by info to query, if specified
|
||||
if (!CollectionUtils.isEmpty(sortFields) || StringUtils.isNotBlank(sortField)) {
|
||||
addMetadataSortQuery(queryBuilder, sortFields, Collections.singletonList(sortField));
|
||||
}
|
||||
|
||||
// Create the final SQL SELECT statement (based on included params above)
|
||||
Query query = createQuery(context, queryBuilder.toString());
|
||||
// Set pagesize & offset for pagination
|
||||
if (pageSize > 0) {
|
||||
query.setMaxResults(pageSize);
|
||||
}
|
||||
if (offset > 0) {
|
||||
query.setFirstResult(offset);
|
||||
}
|
||||
// Set all parameters to the SQL SELECT statement (based on included params above)
|
||||
if (StringUtils.isNotBlank(queryParam)) {
|
||||
query.setParameter("queryParam", "%" + queryParam.toLowerCase() + "%");
|
||||
}
|
||||
for (MetadataField metadataField : metadataFieldsToJoin) {
|
||||
query.setParameter(metadataField.toString(), metadataField.getID());
|
||||
}
|
||||
if (excludeGroup != null) {
|
||||
query.setParameter("group_id", excludeGroup.getID());
|
||||
}
|
||||
|
||||
query.setHint("org.hibernate.cacheable", Boolean.TRUE);
|
||||
|
||||
return query;
|
||||
}
|
||||
|
@@ -164,6 +164,41 @@ public class GroupDAOImpl extends AbstractHibernateDSODAO<Group> implements Grou
|
||||
return count(query);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Group> findByNameLikeAndNotMember(Context context, String groupName, Group excludeParent,
|
||||
int offset, int limit) throws SQLException {
|
||||
Query query = createQuery(context,
|
||||
"FROM Group " +
|
||||
"WHERE lower(name) LIKE lower(:group_name) " +
|
||||
"AND id != :parent_id " +
|
||||
"AND (from Group g where g.id = :parent_id) not in elements (parentGroups)");
|
||||
query.setParameter("parent_id", excludeParent.getID());
|
||||
query.setParameter("group_name", "%" + StringUtils.trimToEmpty(groupName) + "%");
|
||||
|
||||
if (0 <= offset) {
|
||||
query.setFirstResult(offset);
|
||||
}
|
||||
if (0 <= limit) {
|
||||
query.setMaxResults(limit);
|
||||
}
|
||||
query.setHint("org.hibernate.cacheable", Boolean.TRUE);
|
||||
|
||||
return list(query);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int countByNameLikeAndNotMember(Context context, String groupName, Group excludeParent) throws SQLException {
|
||||
Query query = createQuery(context,
|
||||
"SELECT count(*) FROM Group " +
|
||||
"WHERE lower(name) LIKE lower(:group_name) " +
|
||||
"AND id != :parent_id " +
|
||||
"AND (from Group g where g.id = :parent_id) not in elements (parentGroups)");
|
||||
query.setParameter("parent_id", excludeParent.getID());
|
||||
query.setParameter("group_name", "%" + StringUtils.trimToEmpty(groupName) + "%");
|
||||
|
||||
return count(query);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(Context context, Group group) throws SQLException {
|
||||
Query query = getHibernateSession(context)
|
||||
@@ -196,4 +231,29 @@ public class GroupDAOImpl extends AbstractHibernateDSODAO<Group> implements Grou
|
||||
return count(createQuery(context, "SELECT count(*) FROM Group"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Group> findByParent(Context context, Group parent, int pageSize, int offset) throws SQLException {
|
||||
Query query = createQuery(context,
|
||||
"SELECT g FROM Group g JOIN g.parentGroups pg " +
|
||||
"WHERE pg.id = :parent_id");
|
||||
query.setParameter("parent_id", parent.getID());
|
||||
if (pageSize > 0) {
|
||||
query.setMaxResults(pageSize);
|
||||
}
|
||||
if (offset > 0) {
|
||||
query.setFirstResult(offset);
|
||||
}
|
||||
query.setHint("org.hibernate.cacheable", Boolean.TRUE);
|
||||
|
||||
return list(query);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int countByParent(Context context, Group parent) throws SQLException {
|
||||
Query query = createQuery(context, "SELECT count(g) FROM Group g JOIN g.parentGroups pg " +
|
||||
"WHERE pg.id = :parent_id");
|
||||
query.setParameter("parent_id", parent.getID());
|
||||
|
||||
return count(query);
|
||||
}
|
||||
}
|
||||
|
@@ -13,6 +13,7 @@ import java.sql.SQLException;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
import org.dspace.authorize.AuthorizeException;
|
||||
import org.dspace.content.Item;
|
||||
@@ -97,9 +98,9 @@ public interface EPersonService extends DSpaceObjectService<EPerson>, DSpaceObje
|
||||
*
|
||||
* @param context The relevant DSpace Context.
|
||||
* @param query The search string
|
||||
* @param offset Inclusive offset
|
||||
* @param offset Inclusive offset (the position of the first result to return)
|
||||
* @param limit Maximum number of matches returned
|
||||
* @return array of EPerson objects
|
||||
* @return List of matching EPerson objects
|
||||
* @throws SQLException An exception that provides information on a database access error or other errors.
|
||||
*/
|
||||
public List<EPerson> search(Context context, String query, int offset, int limit)
|
||||
@@ -117,6 +118,34 @@ public interface EPersonService extends DSpaceObjectService<EPerson>, DSpaceObje
|
||||
public int searchResultCount(Context context, String query)
|
||||
throws SQLException;
|
||||
|
||||
/**
|
||||
* Find the EPersons that match the search query which are NOT currently members of the given Group. The search
|
||||
* query is run against firstname, lastname or email.
|
||||
*
|
||||
* @param context DSpace context
|
||||
* @param query The search string
|
||||
* @param excludeGroup Group to exclude results from. Members of this group will never be returned.
|
||||
* @param offset Inclusive offset (the position of the first result to return)
|
||||
* @param limit Maximum number of matches returned
|
||||
* @return List of matching EPerson objects
|
||||
* @throws SQLException if error
|
||||
*/
|
||||
List<EPerson> searchNonMembers(Context context, String query, Group excludeGroup,
|
||||
int offset, int limit) throws SQLException;
|
||||
|
||||
/**
|
||||
* Returns the total number of EPersons that match the search query which are NOT currently members of the given
|
||||
* Group. The search query is run against firstname, lastname or email. Can be used with searchNonMembers() to
|
||||
* support pagination
|
||||
*
|
||||
* @param context DSpace context
|
||||
* @param query The search string
|
||||
* @param excludeGroup Group to exclude results from. Members of this group will never be returned.
|
||||
* @return List of matching EPerson objects
|
||||
* @throws SQLException if error
|
||||
*/
|
||||
int searchNonMembersCount(Context context, String query, Group excludeGroup) throws SQLException;
|
||||
|
||||
/**
|
||||
* Find all the {@code EPerson}s in a specific order by field.
|
||||
* The sortable fields are:
|
||||
@@ -157,6 +186,19 @@ public interface EPersonService extends DSpaceObjectService<EPerson>, DSpaceObje
|
||||
public List<EPerson> findAll(Context context, int sortField, int pageSize, int offset)
|
||||
throws SQLException;
|
||||
|
||||
/**
|
||||
* The "System EPerson" is a fake account that exists only to receive email.
|
||||
* It has an email address that should be presumed usable. It does not
|
||||
* exist in the database and is not complete.
|
||||
*
|
||||
* @param context current DSpace session.
|
||||
* @return an EPerson that can presumably receive email.
|
||||
* @throws SQLException
|
||||
*/
|
||||
@NotNull
|
||||
public EPerson getSystemEPerson(Context context)
|
||||
throws SQLException;
|
||||
|
||||
/**
|
||||
* Create a new eperson
|
||||
*
|
||||
@@ -238,14 +280,42 @@ public interface EPersonService extends DSpaceObjectService<EPerson>, DSpaceObje
|
||||
public List<String> getDeleteConstraints(Context context, EPerson ePerson) throws SQLException;
|
||||
|
||||
/**
|
||||
* Retrieve all accounts which belong to at least one of the specified groups.
|
||||
* Retrieve all EPerson accounts which belong to at least one of the specified groups.
|
||||
* <P>
|
||||
* WARNING: This method may have bad performance issues for Groups with a very large number of members,
|
||||
* as it will load all member EPerson objects into memory.
|
||||
* <P>
|
||||
* For better performance, use the paginated version of this method.
|
||||
*
|
||||
* @param c The relevant DSpace Context.
|
||||
* @param groups set of eperson groups
|
||||
* @return a list of epeople
|
||||
* @throws SQLException An exception that provides information on a database access error or other errors.
|
||||
*/
|
||||
public List<EPerson> findByGroups(Context c, Set<Group> groups) throws SQLException;
|
||||
List<EPerson> findByGroups(Context c, Set<Group> groups) throws SQLException;
|
||||
|
||||
/**
|
||||
* Retrieve all EPerson accounts which belong to at least one of the specified groups, in a paginated fashion.
|
||||
*
|
||||
* @param c The relevant DSpace Context.
|
||||
* @param groups Set of group(s) to check membership in
|
||||
* @param pageSize number of EPerson objects to load at one time. Set to <=0 to disable pagination
|
||||
* @param offset number of page to load (starting with 1). Set to <=0 to disable pagination
|
||||
* @return a list of epeople
|
||||
* @throws SQLException An exception that provides information on a database access error or other errors.
|
||||
*/
|
||||
List<EPerson> findByGroups(Context c, Set<Group> groups, int pageSize, int offset) throws SQLException;
|
||||
|
||||
/**
|
||||
* Count all EPerson accounts which belong to at least one of the specified groups. This provides the total
|
||||
* number of results to expect from corresponding findByGroups() for pagination purposes.
|
||||
*
|
||||
* @param c The relevant DSpace Context.
|
||||
* @param groups Set of group(s) to check membership in
|
||||
* @return total number of (unique) EPersons who are a member of one or more groups.
|
||||
* @throws SQLException An exception that provides information on a database access error or other errors.
|
||||
*/
|
||||
int countByGroups(Context c, Set<Group> groups) throws SQLException;
|
||||
|
||||
/**
|
||||
* Retrieve all accounts which are subscribed to receive information about new items.
|
||||
|
@@ -189,9 +189,11 @@ public interface GroupService extends DSpaceObjectService<Group>, DSpaceObjectLe
|
||||
Set<Group> allMemberGroupsSet(Context context, EPerson ePerson) throws SQLException;
|
||||
|
||||
/**
|
||||
* Get all of the epeople who are a member of the
|
||||
* specified group, or a member of a sub-group of the
|
||||
* Get all of the EPerson objects who are a member of the specified group, or a member of a subgroup of the
|
||||
* specified group, etc.
|
||||
* <P>
|
||||
* WARNING: This method may have bad performance for Groups with a very large number of members, as it will load
|
||||
* all member EPerson objects into memory. Only use if you need access to *every* EPerson object at once.
|
||||
*
|
||||
* @param context The relevant DSpace Context.
|
||||
* @param group Group object
|
||||
@@ -200,6 +202,18 @@ public interface GroupService extends DSpaceObjectService<Group>, DSpaceObjectLe
|
||||
*/
|
||||
public List<EPerson> allMembers(Context context, Group group) throws SQLException;
|
||||
|
||||
/**
|
||||
* Count all of the EPerson objects who are a member of the specified group, or a member of a subgroup of the
|
||||
* specified group, etc.
|
||||
* In other words, this will return the size of "allMembers()" without having to load all EPerson objects into
|
||||
* memory.
|
||||
* @param context current DSpace context
|
||||
* @param group Group object
|
||||
* @return count of EPerson object members
|
||||
* @throws SQLException if error
|
||||
*/
|
||||
int countAllMembers(Context context, Group group) throws SQLException;
|
||||
|
||||
/**
|
||||
* Find the group by its name - assumes name is unique
|
||||
*
|
||||
@@ -247,37 +261,67 @@ public interface GroupService extends DSpaceObjectService<Group>, DSpaceObjectLe
|
||||
public List<Group> findAll(Context context, int sortField) throws SQLException;
|
||||
|
||||
/**
|
||||
* Find the groups that match the search query across eperson_group_id or name
|
||||
* Find the Groups that match the query across both Group name and Group ID. This is an unpaginated search,
|
||||
* which means it will load all matching groups into memory at once. This may provide POOR PERFORMANCE when a large
|
||||
* number of groups are matched.
|
||||
*
|
||||
* @param context DSpace context
|
||||
* @param groupIdentifier The group name or group ID
|
||||
* @return array of Group objects
|
||||
* @param context DSpace context
|
||||
* @param query The search string used to search across group name or group ID
|
||||
* @return List of matching Group objects
|
||||
* @throws SQLException if error
|
||||
*/
|
||||
public List<Group> search(Context context, String groupIdentifier) throws SQLException;
|
||||
List<Group> search(Context context, String query) throws SQLException;
|
||||
|
||||
/**
|
||||
* Find the groups that match the search query across eperson_group_id or name
|
||||
* Find the Groups that match the query across both Group name and Group ID. This method supports pagination,
|
||||
* which provides better performance than the above non-paginated search() method.
|
||||
*
|
||||
* @param context DSpace context
|
||||
* @param groupIdentifier The group name or group ID
|
||||
* @param offset Inclusive offset
|
||||
* @param limit Maximum number of matches returned
|
||||
* @return array of Group objects
|
||||
* @param context DSpace context
|
||||
* @param query The search string used to search across group name or group ID
|
||||
* @param offset Inclusive offset (the position of the first result to return)
|
||||
* @param limit Maximum number of matches returned
|
||||
* @return List of matching Group objects
|
||||
* @throws SQLException if error
|
||||
*/
|
||||
public List<Group> search(Context context, String groupIdentifier, int offset, int limit) throws SQLException;
|
||||
List<Group> search(Context context, String query, int offset, int limit) throws SQLException;
|
||||
|
||||
/**
|
||||
* Returns the total number of groups returned by a specific query, without the overhead
|
||||
* of creating the Group objects to store the results.
|
||||
* Returns the total number of Groups returned by a specific query. Search is performed based on Group name
|
||||
* and Group ID. May be used with search() above to support pagination of matching Groups.
|
||||
*
|
||||
* @param context DSpace context
|
||||
* @param query The search string
|
||||
* @param query The search string used to search across group name or group ID
|
||||
* @return the number of groups matching the query
|
||||
* @throws SQLException if error
|
||||
*/
|
||||
public int searchResultCount(Context context, String query) throws SQLException;
|
||||
int searchResultCount(Context context, String query) throws SQLException;
|
||||
|
||||
/**
|
||||
* Find the groups that match the search query which are NOT currently members (subgroups)
|
||||
* of the given parentGroup
|
||||
*
|
||||
* @param context DSpace context
|
||||
* @param query The search string used to search across group name or group ID
|
||||
* @param excludeParentGroup Parent group to exclude results from
|
||||
* @param offset Inclusive offset (the position of the first result to return)
|
||||
* @param limit Maximum number of matches returned
|
||||
* @return List of matching Group objects
|
||||
* @throws SQLException if error
|
||||
*/
|
||||
List<Group> searchNonMembers(Context context, String query, Group excludeParentGroup,
|
||||
int offset, int limit) throws SQLException;
|
||||
|
||||
/**
|
||||
* Returns the total number of groups that match the search query which are NOT currently members (subgroups)
|
||||
* of the given parentGroup. Can be used with searchNonMembers() to support pagination.
|
||||
*
|
||||
* @param context DSpace context
|
||||
* @param query The search string used to search across group name or group ID
|
||||
* @param excludeParentGroup Parent group to exclude results from
|
||||
* @return the number of Groups matching the query
|
||||
* @throws SQLException if error
|
||||
*/
|
||||
int searchNonMembersCount(Context context, String query, Group excludeParentGroup) throws SQLException;
|
||||
|
||||
/**
|
||||
* Return true if group has no direct or indirect members
|
||||
@@ -327,4 +371,29 @@ public interface GroupService extends DSpaceObjectService<Group>, DSpaceObjectLe
|
||||
*/
|
||||
List<Group> findByMetadataField(Context context, String searchValue, MetadataField metadataField)
|
||||
throws SQLException;
|
||||
|
||||
/**
|
||||
* Find all groups which are a member of the given Parent group
|
||||
*
|
||||
* @param context The relevant DSpace Context.
|
||||
* @param parent The parent Group to search on
|
||||
* @param pageSize how many results return
|
||||
* @param offset the position of the first result to return
|
||||
* @return List of all groups which are members of the parent group
|
||||
* @throws SQLException database exception if error
|
||||
*/
|
||||
List<Group> findByParent(Context context, Group parent, int pageSize, int offset)
|
||||
throws SQLException;
|
||||
|
||||
/**
|
||||
* Return number of groups which are a member of the given Parent group.
|
||||
* Can be used with findByParent() for pagination of all groups within a given Parent group.
|
||||
*
|
||||
* @param context The relevant DSpace Context.
|
||||
* @param parent The parent Group to search on
|
||||
* @return number of groups which are members of the parent group
|
||||
* @throws SQLException database exception if error
|
||||
*/
|
||||
int countByParent(Context context, Group parent)
|
||||
throws SQLException;
|
||||
}
|
||||
|
@@ -22,6 +22,8 @@ import org.apache.commons.collections.buffer.CircularFifoBuffer;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.dspace.content.Bitstream;
|
||||
import org.dspace.content.Bundle;
|
||||
import org.dspace.content.factory.ContentServiceFactory;
|
||||
import org.dspace.core.Constants;
|
||||
import org.dspace.core.Context;
|
||||
@@ -77,7 +79,7 @@ public class GoogleAsyncEventListener extends AbstractUsageEventListener {
|
||||
UsageEvent usageEvent = (UsageEvent) event;
|
||||
LOGGER.debug("Usage event received " + event.getName());
|
||||
|
||||
if (isNotBitstreamViewEvent(usageEvent)) {
|
||||
if (!isContentBitstream(usageEvent)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -171,9 +173,33 @@ public class GoogleAsyncEventListener extends AbstractUsageEventListener {
|
||||
return documentPath;
|
||||
}
|
||||
|
||||
private boolean isNotBitstreamViewEvent(UsageEvent usageEvent) {
|
||||
return usageEvent.getAction() != UsageEvent.Action.VIEW
|
||||
|| usageEvent.getObject().getType() != Constants.BITSTREAM;
|
||||
/**
|
||||
* Verifies if the usage event is a content bitstream view event, by checking if:<ul>
|
||||
* <li>the usage event is a view event</li>
|
||||
* <li>the object of the usage event is a bitstream</li>
|
||||
* <li>the bitstream belongs to one of the configured bundles (fallback: ORIGINAL bundle)</li></ul>
|
||||
*/
|
||||
private boolean isContentBitstream(UsageEvent usageEvent) {
|
||||
// check if event is a VIEW event and object is a Bitstream
|
||||
if (usageEvent.getAction() == UsageEvent.Action.VIEW
|
||||
&& usageEvent.getObject().getType() == Constants.BITSTREAM) {
|
||||
// check if bitstream belongs to a configured bundle
|
||||
List<String> allowedBundles = List.of(configurationService
|
||||
.getArrayProperty("google-analytics.bundles", new String[]{Constants.CONTENT_BUNDLE_NAME}));
|
||||
if (allowedBundles.contains("none")) {
|
||||
// GA events for bitstream views were turned off in config
|
||||
return false;
|
||||
}
|
||||
List<String> bitstreamBundles;
|
||||
try {
|
||||
bitstreamBundles = ((Bitstream) usageEvent.getObject())
|
||||
.getBundles().stream().map(Bundle::getName).collect(Collectors.toList());
|
||||
} catch (SQLException e) {
|
||||
throw new RuntimeException(e.getMessage(), e);
|
||||
}
|
||||
return allowedBundles.stream().anyMatch(bitstreamBundles::contains);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isGoogleAnalyticsKeyNotConfigured() {
|
||||
|
@@ -90,13 +90,11 @@ public class HandleDAOImpl extends AbstractHibernateDAO<Handle> implements Handl
|
||||
|
||||
@Override
|
||||
public long countHandlesByPrefix(Context context, String prefix) throws SQLException {
|
||||
|
||||
|
||||
CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context);
|
||||
CriteriaQuery<Long> criteriaQuery = criteriaBuilder.createQuery(Long.class);
|
||||
CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, Handle.class);
|
||||
|
||||
Root<Handle> handleRoot = criteriaQuery.from(Handle.class);
|
||||
criteriaQuery.select(criteriaBuilder.count(criteriaQuery.from(Handle.class)));
|
||||
criteriaQuery.select(handleRoot);
|
||||
criteriaQuery.where(criteriaBuilder.like(handleRoot.get(Handle_.handle), prefix + "%"));
|
||||
return countLong(context, criteriaQuery, criteriaBuilder, handleRoot);
|
||||
}
|
||||
|
@@ -68,10 +68,9 @@ public class HandleIdentifierProvider extends IdentifierProvider {
|
||||
try {
|
||||
String id = mint(context, dso);
|
||||
|
||||
// move canonical to point the latest version
|
||||
// Populate metadata
|
||||
if (dso instanceof Item || dso instanceof Collection || dso instanceof Community) {
|
||||
Item item = (Item) dso;
|
||||
populateHandleMetadata(context, item, id);
|
||||
populateHandleMetadata(context, dso, id);
|
||||
}
|
||||
|
||||
return id;
|
||||
@@ -88,8 +87,7 @@ public class HandleIdentifierProvider extends IdentifierProvider {
|
||||
try {
|
||||
handleService.createHandle(context, dso, identifier);
|
||||
if (dso instanceof Item || dso instanceof Collection || dso instanceof Community) {
|
||||
Item item = (Item) dso;
|
||||
populateHandleMetadata(context, item, identifier);
|
||||
populateHandleMetadata(context, dso, identifier);
|
||||
}
|
||||
} catch (IOException | IllegalStateException | SQLException | AuthorizeException e) {
|
||||
log.error(LogHelper.getHeader(context,
|
||||
|
@@ -95,11 +95,11 @@ public class VersionedHandleIdentifierProviderWithCanonicalHandles extends Ident
|
||||
String id = mint(context, dso);
|
||||
|
||||
// move canonical to point the latest version
|
||||
if (dso != null && dso.getType() == Constants.ITEM) {
|
||||
if (dso.getType() == Constants.ITEM && dso instanceof Item) {
|
||||
Item item = (Item) dso;
|
||||
VersionHistory history = null;
|
||||
VersionHistory history;
|
||||
try {
|
||||
history = versionHistoryService.findByItem(context, (Item) dso);
|
||||
history = versionHistoryService.findByItem(context, item);
|
||||
} catch (SQLException ex) {
|
||||
throw new RuntimeException("A problem with the database connection occured.", ex);
|
||||
}
|
||||
@@ -180,45 +180,46 @@ public class VersionedHandleIdentifierProviderWithCanonicalHandles extends Ident
|
||||
@Override
|
||||
public void register(Context context, DSpaceObject dso, String identifier) {
|
||||
try {
|
||||
if (dso instanceof Item) {
|
||||
Item item = (Item) dso;
|
||||
// if this identifier is already present in the Handle table and the corresponding item
|
||||
// has a history, then someone is trying to restore the latest version for the item. When
|
||||
// trying to restore the latest version, the identifier in input doesn't have the
|
||||
// 1234/123.latestVersion. Instead, it is the canonical 1234/123
|
||||
VersionHistory itemHistory = getHistory(context, identifier);
|
||||
if (!identifier.matches(".*/.*\\.\\d+") && itemHistory != null) {
|
||||
|
||||
Item item = (Item) dso;
|
||||
int newVersionNumber = versionHistoryService.getLatestVersion(context, itemHistory)
|
||||
.getVersionNumber() + 1;
|
||||
String canonical = identifier;
|
||||
identifier = identifier.concat(".").concat("" + newVersionNumber);
|
||||
restoreItAsVersion(context, dso, identifier, item, canonical, itemHistory);
|
||||
} else if (identifier.matches(".*/.*\\.\\d+")) {
|
||||
// if identifier == 1234.5/100.4 reinstate the version 4 in the version table if absent
|
||||
|
||||
// if for this identifier is already present a record in the Handle table and the corresponding item
|
||||
// has an history someone is trying to restore the latest version for the item. When
|
||||
// trying to restore the latest version the identifier in input doesn't have the for 1234/123.latestVersion
|
||||
// it is the canonical 1234/123
|
||||
VersionHistory itemHistory = getHistory(context, identifier);
|
||||
if (!identifier.matches(".*/.*\\.\\d+") && itemHistory != null) {
|
||||
|
||||
int newVersionNumber = versionHistoryService.getLatestVersion(context, itemHistory)
|
||||
.getVersionNumber() + 1;
|
||||
String canonical = identifier;
|
||||
identifier = identifier.concat(".").concat("" + newVersionNumber);
|
||||
restoreItAsVersion(context, dso, identifier, item, canonical, itemHistory);
|
||||
} else if (identifier.matches(".*/.*\\.\\d+")) {
|
||||
// if identifier == 1234.5/100.4 reinstate the version 4 in the version table if absent
|
||||
|
||||
// if it is a version of an item is needed to put back the record
|
||||
// in the versionitem table
|
||||
String canonical = getCanonical(identifier);
|
||||
DSpaceObject canonicalItem = this.resolve(context, canonical);
|
||||
if (canonicalItem == null) {
|
||||
restoreItAsCanonical(context, dso, identifier, item, canonical);
|
||||
} else {
|
||||
VersionHistory history = versionHistoryService.findByItem(context, (Item) canonicalItem);
|
||||
if (history == null) {
|
||||
// if it is a version of an item is needed to put back the record
|
||||
// in the versionitem table
|
||||
String canonical = getCanonical(identifier);
|
||||
DSpaceObject canonicalItem = this.resolve(context, canonical);
|
||||
if (canonicalItem == null) {
|
||||
restoreItAsCanonical(context, dso, identifier, item, canonical);
|
||||
} else {
|
||||
restoreItAsVersion(context, dso, identifier, item, canonical, history);
|
||||
VersionHistory history = versionHistoryService.findByItem(context, (Item) canonicalItem);
|
||||
if (history == null) {
|
||||
restoreItAsCanonical(context, dso, identifier, item, canonical);
|
||||
} else {
|
||||
restoreItAsVersion(context, dso, identifier, item, canonical, history);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
//A regular handle
|
||||
createNewIdentifier(context, dso, identifier);
|
||||
if (dso instanceof Item) {
|
||||
} else {
|
||||
// A regular handle to create for an Item
|
||||
createNewIdentifier(context, dso, identifier);
|
||||
modifyHandleMetadata(context, item, getCanonical(identifier));
|
||||
}
|
||||
} else {
|
||||
// Handle being registered for a different type of object (e.g. Collection or Community)
|
||||
createNewIdentifier(context, dso, identifier);
|
||||
}
|
||||
} catch (IOException | SQLException | AuthorizeException e) {
|
||||
log.error(LogHelper.getHeader(context,
|
||||
|
@@ -7,7 +7,8 @@
|
||||
*/
|
||||
package org.dspace.importer.external.crossref;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
@@ -18,12 +19,11 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.dspace.importer.external.metadatamapping.contributor.JsonPathMetadataProcessor;
|
||||
import org.joda.time.LocalDate;
|
||||
|
||||
/**
|
||||
* This class is used for CrossRef's Live-Import to extract
|
||||
* issued attribute.
|
||||
* Beans are configured in the crossref-integration.xml file.
|
||||
* Beans are configured in the {@code crossref-integration.xml} file.
|
||||
*
|
||||
* @author Francesco Pio Scognamiglio (francescopio.scognamiglio at 4science.com)
|
||||
*/
|
||||
@@ -41,22 +41,25 @@ public class CrossRefDateMetadataProcessor implements JsonPathMetadataProcessor
|
||||
while (dates.hasNext()) {
|
||||
JsonNode date = dates.next();
|
||||
LocalDate issuedDate = null;
|
||||
SimpleDateFormat issuedDateFormat = null;
|
||||
DateTimeFormatter issuedDateFormat = null;
|
||||
if (date.has(0) && date.has(1) && date.has(2)) {
|
||||
issuedDate = new LocalDate(
|
||||
issuedDate = LocalDate.of(
|
||||
date.get(0).numberValue().intValue(),
|
||||
date.get(1).numberValue().intValue(),
|
||||
date.get(2).numberValue().intValue());
|
||||
issuedDateFormat = new SimpleDateFormat("yyyy-MM-dd");
|
||||
issuedDateFormat = DateTimeFormatter.ISO_LOCAL_DATE;
|
||||
} else if (date.has(0) && date.has(1)) {
|
||||
issuedDate = new LocalDate().withYear(date.get(0).numberValue().intValue())
|
||||
.withMonthOfYear(date.get(1).numberValue().intValue());
|
||||
issuedDateFormat = new SimpleDateFormat("yyyy-MM");
|
||||
issuedDate = LocalDate.of(date.get(0).numberValue().intValue(),
|
||||
date.get(1).numberValue().intValue(),
|
||||
1);
|
||||
issuedDateFormat = DateTimeFormatter.ofPattern("yyyy-MM");
|
||||
} else if (date.has(0)) {
|
||||
issuedDate = new LocalDate().withYear(date.get(0).numberValue().intValue());
|
||||
issuedDateFormat = new SimpleDateFormat("yyyy");
|
||||
issuedDate = LocalDate.of(date.get(0).numberValue().intValue(),
|
||||
1,
|
||||
1);
|
||||
issuedDateFormat = DateTimeFormatter.ofPattern("yyyy");
|
||||
}
|
||||
values.add(issuedDateFormat.format(issuedDate.toDate()));
|
||||
values.add(issuedDate.format(issuedDateFormat));
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
@@ -162,7 +162,9 @@ public class CrossRefImportMetadataSourceServiceImpl extends AbstractImportMetad
|
||||
Iterator<JsonNode> nodes = jsonNode.at("/message/items").iterator();
|
||||
while (nodes.hasNext()) {
|
||||
JsonNode node = nodes.next();
|
||||
results.add(transformSourceRecords(node.toString()));
|
||||
if (!node.isMissingNode()) {
|
||||
results.add(transformSourceRecords(node.toString()));
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
@@ -196,7 +198,9 @@ public class CrossRefImportMetadataSourceServiceImpl extends AbstractImportMetad
|
||||
String responseString = liveImportClient.executeHttpGetRequest(1000, uriBuilder.toString(), params);
|
||||
JsonNode jsonNode = convertStringJsonToJsonNode(responseString);
|
||||
JsonNode messageNode = jsonNode.at("/message");
|
||||
results.add(transformSourceRecords(messageNode.toString()));
|
||||
if (!messageNode.isMissingNode()) {
|
||||
results.add(transformSourceRecords(messageNode.toString()));
|
||||
}
|
||||
return results;
|
||||
}
|
||||
}
|
||||
@@ -250,7 +254,9 @@ public class CrossRefImportMetadataSourceServiceImpl extends AbstractImportMetad
|
||||
Iterator<JsonNode> nodes = jsonNode.at("/message/items").iterator();
|
||||
while (nodes.hasNext()) {
|
||||
JsonNode node = nodes.next();
|
||||
results.add(transformSourceRecords(node.toString()));
|
||||
if (!node.isMissingNode()) {
|
||||
results.add(transformSourceRecords(node.toString()));
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
@@ -333,4 +339,4 @@ public class CrossRefImportMetadataSourceServiceImpl extends AbstractImportMetad
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -18,6 +18,7 @@ import org.apache.commons.cli.Option;
|
||||
import org.apache.commons.cli.Options;
|
||||
import org.apache.commons.cli.ParseException;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.dspace.cli.DSpaceSkipUnknownArgumentsParser;
|
||||
import org.dspace.eperson.EPerson;
|
||||
import org.dspace.scripts.configuration.ScriptConfiguration;
|
||||
import org.dspace.scripts.handler.DSpaceRunnableHandler;
|
||||
@@ -36,6 +37,11 @@ public abstract class DSpaceRunnable<T extends ScriptConfiguration> implements R
|
||||
*/
|
||||
protected CommandLine commandLine;
|
||||
|
||||
/**
|
||||
* The minimal CommandLine object for the script that'll hold help information
|
||||
*/
|
||||
protected CommandLine helpCommandLine;
|
||||
|
||||
/**
|
||||
* This EPerson identifier variable is the UUID of the EPerson that's running the script
|
||||
*/
|
||||
@@ -64,26 +70,66 @@ public abstract class DSpaceRunnable<T extends ScriptConfiguration> implements R
|
||||
* @param args The arguments given to the script
|
||||
* @param dSpaceRunnableHandler The DSpaceRunnableHandler object that defines from where the script was ran
|
||||
* @param currentUser
|
||||
* @return the result of this step; StepResult.Continue: continue the normal process,
|
||||
* initialize is successful; otherwise exit the process (the help or version is shown)
|
||||
* @throws ParseException If something goes wrong
|
||||
*/
|
||||
public void initialize(String[] args, DSpaceRunnableHandler dSpaceRunnableHandler,
|
||||
public StepResult initialize(String[] args, DSpaceRunnableHandler dSpaceRunnableHandler,
|
||||
EPerson currentUser) throws ParseException {
|
||||
if (currentUser != null) {
|
||||
this.setEpersonIdentifier(currentUser.getID());
|
||||
}
|
||||
this.setHandler(dSpaceRunnableHandler);
|
||||
this.parse(args);
|
||||
|
||||
// parse the command line in a first step for the help options
|
||||
// --> no other option is required
|
||||
StepResult result = this.parseForHelp(args);
|
||||
switch (result) {
|
||||
case Exit:
|
||||
// arguments of the command line matches the help options, handle this
|
||||
handleHelpCommandLine();
|
||||
break;
|
||||
|
||||
case Continue:
|
||||
// arguments of the command line matches NOT the help options, parse the args for the normal options
|
||||
result = this.parse(args);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This method handle the help command line. In this easy implementation only the help is printed. For more
|
||||
* complexity override this method.
|
||||
*/
|
||||
private void handleHelpCommandLine() {
|
||||
printHelp();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This method will take the primitive array of String objects that represent the parameters given to the String
|
||||
* and it'll parse these into a CommandLine object that can be used by the script to retrieve the data
|
||||
* @param args The primitive array of Strings representing the parameters
|
||||
* @throws ParseException If something goes wrong
|
||||
*/
|
||||
private void parse(String[] args) throws ParseException {
|
||||
private StepResult parse(String[] args) throws ParseException {
|
||||
commandLine = new DefaultParser().parse(getScriptConfiguration().getOptions(), args);
|
||||
setup();
|
||||
return StepResult.Continue;
|
||||
}
|
||||
|
||||
private StepResult parseForHelp(String[] args) throws ParseException {
|
||||
helpCommandLine = new DSpaceSkipUnknownArgumentsParser().parse(getScriptConfiguration().getHelpOptions(), args);
|
||||
if (helpCommandLine.getOptions() != null && helpCommandLine.getOptions().length > 0) {
|
||||
return StepResult.Exit;
|
||||
}
|
||||
|
||||
return StepResult.Continue;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -158,4 +204,8 @@ public abstract class DSpaceRunnable<T extends ScriptConfiguration> implements R
|
||||
public void setEpersonIdentifier(UUID epersonIdentifier) {
|
||||
this.epersonIdentifier = epersonIdentifier;
|
||||
}
|
||||
|
||||
public enum StepResult {
|
||||
Continue, Exit;
|
||||
}
|
||||
}
|
||||
|
@@ -10,6 +10,7 @@ package org.dspace.scripts.configuration;
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.cli.Option;
|
||||
import org.apache.commons.cli.Options;
|
||||
import org.dspace.authorize.service.AuthorizeService;
|
||||
import org.dspace.core.Context;
|
||||
@@ -105,6 +106,19 @@ public abstract class ScriptConfiguration<T extends DSpaceRunnable> implements B
|
||||
*/
|
||||
public abstract Options getOptions();
|
||||
|
||||
/**
|
||||
* The getter for the options of the Script (help informations)
|
||||
*
|
||||
* @return the options value of this ScriptConfiguration for help
|
||||
*/
|
||||
public Options getHelpOptions() {
|
||||
Options options = new Options();
|
||||
|
||||
options.addOption(Option.builder("h").longOpt("help").desc("help").hasArg(false).required(false).build());
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBeanName(String beanName) {
|
||||
this.name = beanName;
|
||||
|
@@ -37,7 +37,7 @@ public class GeoIpService {
|
||||
public DatabaseReader getDatabaseReader() throws IllegalStateException {
|
||||
String dbPath = configurationService.getProperty("usage-statistics.dbfile");
|
||||
if (StringUtils.isBlank(dbPath)) {
|
||||
throw new IllegalStateException("The required 'dbfile' configuration is missing in solr-statistics.cfg!");
|
||||
throw new IllegalStateException("The required 'dbfile' configuration is missing in usage-statistics.cfg!");
|
||||
}
|
||||
|
||||
try {
|
||||
|
@@ -1203,22 +1203,6 @@ public class SolrLoggerServiceImpl implements SolrLoggerService, InitializingBea
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void optimizeSOLR() {
|
||||
try {
|
||||
long start = System.currentTimeMillis();
|
||||
System.out.println("SOLR Optimize -- Process Started:" + start);
|
||||
solr.optimize();
|
||||
long finish = System.currentTimeMillis();
|
||||
System.out.println("SOLR Optimize -- Process Finished:" + finish);
|
||||
System.out.println("SOLR Optimize -- Total time taken:" + (finish - start) + " (ms).");
|
||||
} catch (SolrServerException sse) {
|
||||
System.err.println(sse.getMessage());
|
||||
} catch (IOException ioe) {
|
||||
System.err.println(ioe.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shardSolrIndex() throws IOException, SolrServerException {
|
||||
if (!(solr instanceof HttpSolrClient)) {
|
||||
@@ -1691,11 +1675,14 @@ public class SolrLoggerServiceImpl implements SolrLoggerService, InitializingBea
|
||||
statisticYearCores
|
||||
.add(baseSolrUrl.replace("http://", "").replace("https://", "") + statCoreName);
|
||||
}
|
||||
//Also add the core containing the current year !
|
||||
statisticYearCores.add(((HttpSolrClient) solr)
|
||||
var baseCore = ((HttpSolrClient) solr)
|
||||
.getBaseURL()
|
||||
.replace("http://", "")
|
||||
.replace("https://", ""));
|
||||
.replace("https://", "");
|
||||
if (!statisticYearCores.contains(baseCore)) {
|
||||
//Also add the core containing the current year, if it hasn't been added already
|
||||
statisticYearCores.add(baseCore);
|
||||
}
|
||||
} catch (IOException | SolrServerException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
}
|
||||
|
@@ -266,12 +266,6 @@ public interface SolrLoggerService {
|
||||
*/
|
||||
public String getIgnoreSpiderIPs();
|
||||
|
||||
/**
|
||||
* Maintenance to keep a SOLR index efficient.
|
||||
* Note: This might take a long time.
|
||||
*/
|
||||
public void optimizeSOLR();
|
||||
|
||||
public void shardSolrIndex() throws IOException, SolrServerException;
|
||||
|
||||
public void reindexBitstreamHits(boolean removeDeletedBitstreams) throws Exception;
|
||||
|
@@ -67,7 +67,6 @@ public class StatisticsClient {
|
||||
options.addOption("m", "mark-spiders", false, "Update isBot Flag in Solr");
|
||||
options.addOption("f", "delete-spiders-by-flag", false, "Delete Spiders in Solr By isBot Flag");
|
||||
options.addOption("i", "delete-spiders-by-ip", false, "Delete Spiders in Solr By IP Address");
|
||||
options.addOption("o", "optimize", false, "Run maintenance on the SOLR index");
|
||||
options.addOption("b", "reindex-bitstreams", false, "Reindex the bitstreams to ensure we have the bundle name");
|
||||
options.addOption("e", "export", false,
|
||||
"Export SOLR view statistics data to usage-statistics-intermediate-format");
|
||||
@@ -93,8 +92,6 @@ public class StatisticsClient {
|
||||
solrLoggerService.deleteRobotsByIsBotFlag();
|
||||
} else if (line.hasOption('i')) {
|
||||
solrLoggerService.deleteRobotsByIP();
|
||||
} else if (line.hasOption('o')) {
|
||||
solrLoggerService.optimizeSOLR();
|
||||
} else if (line.hasOption('b')) {
|
||||
solrLoggerService.reindexBitstreamHits(line.hasOption('r'));
|
||||
} else if (line.hasOption('e')) {
|
||||
|
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* 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.submit.consumer;
|
||||
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.dspace.content.Collection;
|
||||
import org.dspace.content.DSpaceObject;
|
||||
import org.dspace.core.Constants;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.discovery.IndexingService;
|
||||
import org.dspace.discovery.indexobject.IndexableCollection;
|
||||
import org.dspace.event.Consumer;
|
||||
import org.dspace.event.Event;
|
||||
import org.dspace.services.factory.DSpaceServicesFactory;
|
||||
import org.dspace.submit.factory.SubmissionServiceFactory;
|
||||
|
||||
/**
|
||||
* Consumer implementation to be used for Item Submission Configuration
|
||||
*
|
||||
* @author paulo.graca at fccn.pt
|
||||
*/
|
||||
public class SubmissionConfigConsumer implements Consumer {
|
||||
/**
|
||||
* log4j logger
|
||||
*/
|
||||
private static Logger log = org.apache.logging.log4j.LogManager.getLogger(SubmissionConfigConsumer.class);
|
||||
|
||||
IndexingService indexer = DSpaceServicesFactory.getInstance().getServiceManager()
|
||||
.getServiceByName(IndexingService.class.getName(),
|
||||
IndexingService.class);
|
||||
|
||||
@Override
|
||||
public void initialize() throws Exception {
|
||||
// No-op
|
||||
}
|
||||
|
||||
@Override
|
||||
public void consume(Context ctx, Event event) throws Exception {
|
||||
int st = event.getSubjectType();
|
||||
int et = event.getEventType();
|
||||
|
||||
|
||||
if ( st == Constants.COLLECTION ) {
|
||||
switch (et) {
|
||||
case Event.MODIFY_METADATA:
|
||||
// Submission configuration it's based on solr
|
||||
// for collection's entity type but, at this point
|
||||
// that info isn't indexed yet, we need to force it
|
||||
DSpaceObject subject = event.getSubject(ctx);
|
||||
Collection collectionFromDSOSubject = (Collection) subject;
|
||||
indexer.indexContent(ctx, new IndexableCollection (collectionFromDSOSubject), true, false, false);
|
||||
indexer.commit();
|
||||
|
||||
log.debug("SubmissionConfigConsumer occured: " + event.toString());
|
||||
// reload submission configurations
|
||||
SubmissionServiceFactory.getInstance().getSubmissionConfigService().reload();
|
||||
break;
|
||||
|
||||
default:
|
||||
log.debug("SubmissionConfigConsumer occured: " + event.toString());
|
||||
// reload submission configurations
|
||||
SubmissionServiceFactory.getInstance().getSubmissionConfigService().reload();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void end(Context ctx) throws Exception {
|
||||
// No-op
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finish(Context ctx) throws Exception {
|
||||
// No-op
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* 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.submit.factory;
|
||||
|
||||
import org.dspace.app.util.SubmissionConfigReaderException;
|
||||
import org.dspace.services.factory.DSpaceServicesFactory;
|
||||
import org.dspace.submit.service.SubmissionConfigService;
|
||||
|
||||
/**
|
||||
* Abstract factory to get services for submission, use SubmissionServiceFactory.getInstance() to retrieve an
|
||||
* implementation
|
||||
*
|
||||
* @author paulo.graca at fccn.pt
|
||||
*/
|
||||
public abstract class SubmissionServiceFactory {
|
||||
|
||||
public abstract SubmissionConfigService getSubmissionConfigService() throws SubmissionConfigReaderException;
|
||||
|
||||
public static SubmissionServiceFactory getInstance() {
|
||||
return DSpaceServicesFactory.getInstance().getServiceManager()
|
||||
.getServiceByName("submissionServiceFactory", SubmissionServiceFactory.class);
|
||||
}
|
||||
}
|
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* 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.submit.factory;
|
||||
|
||||
import org.dspace.app.util.SubmissionConfigReaderException;
|
||||
import org.dspace.submit.service.SubmissionConfigService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
/**
|
||||
* Factory implementation to get services for submission, use SubmissionServiceFactory.getInstance() to
|
||||
* retrieve an implementation
|
||||
*
|
||||
* @author paulo.graca at fccn.pt
|
||||
*/
|
||||
public class SubmissionServiceFactoryImpl extends SubmissionServiceFactory {
|
||||
@Autowired(required = true)
|
||||
private SubmissionConfigService submissionConfigService;
|
||||
|
||||
@Override
|
||||
public SubmissionConfigService getSubmissionConfigService() throws SubmissionConfigReaderException {
|
||||
return submissionConfigService;
|
||||
}
|
||||
}
|
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* 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.submit.service;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
|
||||
import org.dspace.app.util.SubmissionConfig;
|
||||
import org.dspace.app.util.SubmissionConfigReaderException;
|
||||
import org.dspace.app.util.SubmissionStepConfig;
|
||||
import org.dspace.content.Collection;
|
||||
import org.dspace.core.Context;
|
||||
|
||||
/**
|
||||
* Item Submission Configuration Service
|
||||
* enables interaction with a submission config like
|
||||
* getting a config by a collection name or handle
|
||||
* as also retrieving submission configuration steps
|
||||
*
|
||||
* @author paulo.graca at fccn.pt
|
||||
*/
|
||||
public interface SubmissionConfigService {
|
||||
|
||||
public void reload() throws SubmissionConfigReaderException;
|
||||
|
||||
public String getDefaultSubmissionConfigName();
|
||||
|
||||
public List<SubmissionConfig> getAllSubmissionConfigs(Integer limit, Integer offset);
|
||||
|
||||
public int countSubmissionConfigs();
|
||||
|
||||
public SubmissionConfig getSubmissionConfigByCollection(String collectionHandle);
|
||||
|
||||
public SubmissionConfig getSubmissionConfigByName(String submitName);
|
||||
|
||||
public SubmissionStepConfig getStepConfig(String stepID)
|
||||
throws SubmissionConfigReaderException;
|
||||
|
||||
public List<Collection> getCollectionsBySubmissionConfig(Context context, String submitName)
|
||||
throws IllegalStateException, SQLException, SubmissionConfigReaderException;
|
||||
|
||||
}
|
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* 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.submit.service;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
|
||||
import org.dspace.app.util.SubmissionConfig;
|
||||
import org.dspace.app.util.SubmissionConfigReader;
|
||||
import org.dspace.app.util.SubmissionConfigReaderException;
|
||||
import org.dspace.app.util.SubmissionStepConfig;
|
||||
import org.dspace.content.Collection;
|
||||
import org.dspace.core.Context;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
|
||||
/**
|
||||
* An implementation for Submission Config service
|
||||
*
|
||||
* @author paulo.graca at fccn.pt
|
||||
*/
|
||||
public class SubmissionConfigServiceImpl implements SubmissionConfigService, InitializingBean {
|
||||
|
||||
protected SubmissionConfigReader submissionConfigReader;
|
||||
|
||||
public SubmissionConfigServiceImpl () throws SubmissionConfigReaderException {
|
||||
submissionConfigReader = new SubmissionConfigReader();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
submissionConfigReader.reload();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reload() throws SubmissionConfigReaderException {
|
||||
submissionConfigReader.reload();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDefaultSubmissionConfigName() {
|
||||
return submissionConfigReader.getDefaultSubmissionConfigName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SubmissionConfig> getAllSubmissionConfigs(Integer limit, Integer offset) {
|
||||
return submissionConfigReader.getAllSubmissionConfigs(limit, offset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int countSubmissionConfigs() {
|
||||
return submissionConfigReader.countSubmissionConfigs();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SubmissionConfig getSubmissionConfigByCollection(String collectionHandle) {
|
||||
return submissionConfigReader.getSubmissionConfigByCollection(collectionHandle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SubmissionConfig getSubmissionConfigByName(String submitName) {
|
||||
return submissionConfigReader.getSubmissionConfigByName(submitName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SubmissionStepConfig getStepConfig(String stepID) throws SubmissionConfigReaderException {
|
||||
return submissionConfigReader.getStepConfig(stepID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Collection> getCollectionsBySubmissionConfig(Context context, String submitName)
|
||||
throws IllegalStateException, SQLException {
|
||||
return submissionConfigReader.getCollectionsBySubmissionConfig(context, submitName);
|
||||
}
|
||||
|
||||
}
|
@@ -56,8 +56,16 @@ public class ContentGenerator implements SubscriptionGenerator<IndexableObject>
|
||||
Locale supportedLocale = I18nUtil.getEPersonLocale(ePerson);
|
||||
Email email = Email.getEmail(I18nUtil.getEmailFilename(supportedLocale, "subscriptions_content"));
|
||||
email.addRecipient(ePerson.getEmail());
|
||||
email.addArgument(generateBodyMail(context, indexableComm));
|
||||
email.addArgument(generateBodyMail(context, indexableColl));
|
||||
|
||||
String bodyCommunities = generateBodyMail(context, indexableComm);
|
||||
String bodyCollections = generateBodyMail(context, indexableColl);
|
||||
if (bodyCommunities.equals(EMPTY) && bodyCollections.equals(EMPTY)) {
|
||||
log.debug("subscription(s) of eperson {} do(es) not match any new items: nothing to send" +
|
||||
" - exit silently", ePerson::getID);
|
||||
return;
|
||||
}
|
||||
email.addArgument(bodyCommunities);
|
||||
email.addArgument(bodyCollections);
|
||||
email.send();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
@@ -67,21 +75,19 @@ public class ContentGenerator implements SubscriptionGenerator<IndexableObject>
|
||||
}
|
||||
|
||||
private String generateBodyMail(Context context, List<IndexableObject> indexableObjects) {
|
||||
if (indexableObjects == null || indexableObjects.isEmpty()) {
|
||||
return EMPTY;
|
||||
}
|
||||
try {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
out.write("\n".getBytes(UTF_8));
|
||||
if (indexableObjects.size() > 0) {
|
||||
for (IndexableObject indexableObject : indexableObjects) {
|
||||
out.write("\n".getBytes(UTF_8));
|
||||
Item item = (Item) indexableObject.getIndexedObject();
|
||||
String entityType = itemService.getEntityTypeLabel(item);
|
||||
Optional.ofNullable(entityType2Disseminator.get(entityType))
|
||||
.orElseGet(() -> entityType2Disseminator.get("Item"))
|
||||
.disseminate(context, item, out);
|
||||
}
|
||||
return out.toString();
|
||||
} else {
|
||||
out.write("No items".getBytes(UTF_8));
|
||||
for (IndexableObject indexableObject : indexableObjects) {
|
||||
out.write("\n".getBytes(UTF_8));
|
||||
Item item = (Item) indexableObject.getIndexedObject();
|
||||
String entityType = itemService.getEntityTypeLabel(item);
|
||||
Optional.ofNullable(entityType2Disseminator.get(entityType))
|
||||
.orElseGet(() -> entityType2Disseminator.get("Item"))
|
||||
.disseminate(context, item, out);
|
||||
}
|
||||
return out.toString();
|
||||
} catch (Exception e) {
|
||||
|
@@ -35,6 +35,8 @@ public class SolrUtils {
|
||||
* @return date formatter compatible with Solr.
|
||||
*/
|
||||
public static DateFormat getDateFormatter() {
|
||||
return new SimpleDateFormat(SolrUtils.SOLR_DATE_FORMAT);
|
||||
DateFormat formatter = new SimpleDateFormat(SolrUtils.SOLR_DATE_FORMAT);
|
||||
formatter.setTimeZone(SOLR_TIME_ZONE);
|
||||
return formatter;
|
||||
}
|
||||
}
|
||||
|
43
dspace-api/src/main/java/org/dspace/util/ThrowableUtils.java
Normal file
43
dspace-api/src/main/java/org/dspace/util/ThrowableUtils.java
Normal file
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
package org.dspace.util;
|
||||
|
||||
/**
|
||||
* Things you wish {@link Throwable} or some logging package would do for you.
|
||||
*
|
||||
* @author mwood
|
||||
*/
|
||||
public class ThrowableUtils {
|
||||
/**
|
||||
* Utility class: do not instantiate.
|
||||
*/
|
||||
private ThrowableUtils() { }
|
||||
|
||||
/**
|
||||
* Trace a chain of {@code Throwable}s showing only causes.
|
||||
* Less voluminous than a stack trace. Useful if you just want to know
|
||||
* what caused third-party code to return an uninformative exception
|
||||
* message.
|
||||
*
|
||||
* @param throwable the exception or whatever.
|
||||
* @return list of messages from each {@code Throwable} in the chain,
|
||||
* separated by '\n'.
|
||||
*/
|
||||
static public String formatCauseChain(Throwable throwable) {
|
||||
StringBuilder trace = new StringBuilder();
|
||||
trace.append(throwable.getMessage());
|
||||
Throwable cause = throwable.getCause();
|
||||
while (null != cause) {
|
||||
trace.append("\nCaused by: ")
|
||||
.append(cause.getClass().getCanonicalName()).append(' ')
|
||||
.append(cause.getMessage());
|
||||
cause = cause.getCause();
|
||||
}
|
||||
return trace.toString();
|
||||
}
|
||||
}
|
@@ -221,6 +221,8 @@ public class XmlWorkflowServiceImpl implements XmlWorkflowService {
|
||||
//Get our next step, if none is found, archive our item
|
||||
firstStep = wf.getNextStep(context, wfi, firstStep, ActionResult.OUTCOME_COMPLETE);
|
||||
if (firstStep == null) {
|
||||
// record the submitted provenance message
|
||||
recordStart(context, wfi.getItem(),null);
|
||||
archive(context, wfi);
|
||||
} else {
|
||||
activateFirstStep(context, wf, firstStep, wfi);
|
||||
@@ -1187,25 +1189,30 @@ public class XmlWorkflowServiceImpl implements XmlWorkflowService {
|
||||
DCDate now = DCDate.getCurrent();
|
||||
|
||||
// Create provenance description
|
||||
String provmessage = "";
|
||||
StringBuffer provmessage = new StringBuffer();
|
||||
|
||||
if (myitem.getSubmitter() != null) {
|
||||
provmessage = "Submitted by " + myitem.getSubmitter().getFullName()
|
||||
+ " (" + myitem.getSubmitter().getEmail() + ") on "
|
||||
+ now.toString() + " workflow start=" + action.getProvenanceStartId() + "\n";
|
||||
provmessage.append("Submitted by ").append(myitem.getSubmitter().getFullName())
|
||||
.append(" (").append(myitem.getSubmitter().getEmail()).append(") on ")
|
||||
.append(now.toString());
|
||||
} else {
|
||||
// else, null submitter
|
||||
provmessage = "Submitted by unknown (probably automated) on"
|
||||
+ now.toString() + " workflow start=" + action.getProvenanceStartId() + "\n";
|
||||
provmessage.append("Submitted by unknown (probably automated) on")
|
||||
.append(now.toString());
|
||||
}
|
||||
if (action != null) {
|
||||
provmessage.append(" workflow start=").append(action.getProvenanceStartId()).append("\n");
|
||||
} else {
|
||||
provmessage.append("\n");
|
||||
}
|
||||
|
||||
// add sizes and checksums of bitstreams
|
||||
provmessage += installItemService.getBitstreamProvenanceMessage(context, myitem);
|
||||
provmessage.append(installItemService.getBitstreamProvenanceMessage(context, myitem));
|
||||
|
||||
// Add message to the DC
|
||||
itemService
|
||||
.addMetadata(context, myitem, MetadataSchemaEnum.DC.getName(),
|
||||
"description", "provenance", "en", provmessage);
|
||||
"description", "provenance", "en", provmessage.toString());
|
||||
itemService.update(context, myitem);
|
||||
}
|
||||
|
||||
|
@@ -1,9 +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/
|
||||
--
|
||||
|
||||
ALTER TABLE process MODIFY (parameters CLOB);
|
@@ -0,0 +1,34 @@
|
||||
--
|
||||
-- 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/
|
||||
--
|
||||
|
||||
BEGIN;
|
||||
|
||||
-- Unset any primary bitstream that is marked as deleted
|
||||
UPDATE bundle
|
||||
SET primary_bitstream_id = NULL
|
||||
WHERE primary_bitstream_id IN
|
||||
( SELECT bs.uuid
|
||||
FROM bitstream AS bs
|
||||
INNER JOIN bundle as bl ON bs.uuid = bl.primary_bitstream_id
|
||||
WHERE bs.deleted IS TRUE );
|
||||
|
||||
-- Unset any primary bitstream that don't belong to bundle's bitstream list
|
||||
UPDATE bundle
|
||||
SET primary_bitstream_id = NULL
|
||||
WHERE primary_bitstream_id IN
|
||||
( SELECT bl.primary_bitstream_id
|
||||
FROM bundle as bl
|
||||
WHERE bl.primary_bitstream_id IS NOT NULL
|
||||
AND bl.primary_bitstream_id NOT IN
|
||||
( SELECT bitstream_id
|
||||
FROM bundle2bitstream AS b2b
|
||||
WHERE b2b.bundle_id = bl.uuid
|
||||
)
|
||||
);
|
||||
|
||||
COMMIT;
|
@@ -14,6 +14,8 @@ import static org.junit.Assert.fail;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.sql.SQLException;
|
||||
import java.time.LocalDate;
|
||||
import java.time.ZoneId;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
@@ -42,7 +44,6 @@ import org.dspace.core.Constants;
|
||||
import org.dspace.eperson.Group;
|
||||
import org.dspace.eperson.factory.EPersonServiceFactory;
|
||||
import org.dspace.eperson.service.GroupService;
|
||||
import org.joda.time.LocalDate;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
@@ -129,7 +130,7 @@ public class DefaultAccessStatusHelperTest extends AbstractUnitTest {
|
||||
fail("SQL Error in init: " + ex.getMessage());
|
||||
}
|
||||
helper = new DefaultAccessStatusHelper();
|
||||
threshold = new LocalDate(10000, 1, 1).toDate();
|
||||
threshold = dateFrom(10000, 1, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -266,13 +267,15 @@ public class DefaultAccessStatusHelperTest extends AbstractUnitTest {
|
||||
Group group = groupService.findByName(context, Group.ANONYMOUS);
|
||||
policy.setGroup(group);
|
||||
policy.setAction(Constants.READ);
|
||||
policy.setStartDate(new LocalDate(9999, 12, 31).toDate());
|
||||
policy.setStartDate(dateFrom(9999, 12, 31));
|
||||
policies.add(policy);
|
||||
authorizeService.removeAllPolicies(context, bitstream);
|
||||
authorizeService.addPolicies(context, policies, bitstream);
|
||||
context.restoreAuthSystemState();
|
||||
String status = helper.getAccessStatusFromItem(context, itemWithEmbargo, threshold);
|
||||
assertThat("testWithEmbargo 0", status, equalTo(DefaultAccessStatusHelper.EMBARGO));
|
||||
String embargoDate = helper.getEmbargoFromItem(context, itemWithEmbargo, threshold);
|
||||
assertThat("testWithEmbargo 1", embargoDate, equalTo(policy.getStartDate().toString()));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -293,7 +296,7 @@ public class DefaultAccessStatusHelperTest extends AbstractUnitTest {
|
||||
Group group = groupService.findByName(context, Group.ANONYMOUS);
|
||||
policy.setGroup(group);
|
||||
policy.setAction(Constants.READ);
|
||||
policy.setStartDate(new LocalDate(10000, 1, 1).toDate());
|
||||
policy.setStartDate(dateFrom(10000, 1, 1));
|
||||
policies.add(policy);
|
||||
authorizeService.removeAllPolicies(context, bitstream);
|
||||
authorizeService.addPolicies(context, policies, bitstream);
|
||||
@@ -383,13 +386,15 @@ public class DefaultAccessStatusHelperTest extends AbstractUnitTest {
|
||||
Group group = groupService.findByName(context, Group.ANONYMOUS);
|
||||
policy.setGroup(group);
|
||||
policy.setAction(Constants.READ);
|
||||
policy.setStartDate(new LocalDate(9999, 12, 31).toDate());
|
||||
policy.setStartDate(dateFrom(9999, 12, 31));
|
||||
policies.add(policy);
|
||||
authorizeService.removeAllPolicies(context, primaryBitstream);
|
||||
authorizeService.addPolicies(context, policies, primaryBitstream);
|
||||
context.restoreAuthSystemState();
|
||||
String status = helper.getAccessStatusFromItem(context, itemWithPrimaryAndMultipleBitstreams, threshold);
|
||||
assertThat("testWithPrimaryAndMultipleBitstreams 0", status, equalTo(DefaultAccessStatusHelper.EMBARGO));
|
||||
String embargoDate = helper.getEmbargoFromItem(context, itemWithPrimaryAndMultipleBitstreams, threshold);
|
||||
assertThat("testWithPrimaryAndMultipleBitstreams 1", embargoDate, equalTo(policy.getStartDate().toString()));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -412,12 +417,29 @@ public class DefaultAccessStatusHelperTest extends AbstractUnitTest {
|
||||
Group group = groupService.findByName(context, Group.ANONYMOUS);
|
||||
policy.setGroup(group);
|
||||
policy.setAction(Constants.READ);
|
||||
policy.setStartDate(new LocalDate(9999, 12, 31).toDate());
|
||||
policy.setStartDate(dateFrom(9999, 12, 31));
|
||||
policies.add(policy);
|
||||
authorizeService.removeAllPolicies(context, anotherBitstream);
|
||||
authorizeService.addPolicies(context, policies, anotherBitstream);
|
||||
context.restoreAuthSystemState();
|
||||
String status = helper.getAccessStatusFromItem(context, itemWithoutPrimaryAndMultipleBitstreams, threshold);
|
||||
assertThat("testWithNoPrimaryAndMultipleBitstreams 0", status, equalTo(DefaultAccessStatusHelper.OPEN_ACCESS));
|
||||
String embargoDate = helper.getEmbargoFromItem(context, itemWithEmbargo, threshold);
|
||||
assertThat("testWithNoPrimaryAndMultipleBitstreams 1", embargoDate, equalTo(null));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Date from local year, month, day.
|
||||
*
|
||||
* @param year the year.
|
||||
* @param month the month.
|
||||
* @param day the day.
|
||||
* @return the assembled date.
|
||||
*/
|
||||
private Date dateFrom(int year, int month, int day) {
|
||||
return Date.from(LocalDate.of(year, month, day)
|
||||
.atStartOfDay()
|
||||
.atZone(ZoneId.systemDefault())
|
||||
.toInstant());
|
||||
}
|
||||
}
|
||||
|
@@ -99,8 +99,9 @@ public class MetadataExportIT
|
||||
script = scriptService.createDSpaceRunnableForScriptConfiguration(scriptConfiguration);
|
||||
}
|
||||
if (script != null) {
|
||||
script.initialize(args, testDSpaceRunnableHandler, null);
|
||||
script.run();
|
||||
if (DSpaceRunnable.StepResult.Continue.equals(script.initialize(args, testDSpaceRunnableHandler, null))) {
|
||||
script.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,8 +207,9 @@ public class MetadataExportIT
|
||||
script = scriptService.createDSpaceRunnableForScriptConfiguration(scriptConfiguration);
|
||||
}
|
||||
if (script != null) {
|
||||
script.initialize(args, testDSpaceRunnableHandler, null);
|
||||
script.run();
|
||||
if (DSpaceRunnable.StepResult.Continue.equals(script.initialize(args, testDSpaceRunnableHandler, null))) {
|
||||
script.run();
|
||||
}
|
||||
}
|
||||
|
||||
Exception exceptionDuringTestRun = testDSpaceRunnableHandler.getException();
|
||||
@@ -235,8 +237,9 @@ public class MetadataExportIT
|
||||
script = scriptService.createDSpaceRunnableForScriptConfiguration(scriptConfiguration);
|
||||
}
|
||||
if (script != null) {
|
||||
script.initialize(args, testDSpaceRunnableHandler, null);
|
||||
script.run();
|
||||
if (DSpaceRunnable.StepResult.Continue.equals(script.initialize(args, testDSpaceRunnableHandler, null))) {
|
||||
script.run();
|
||||
}
|
||||
}
|
||||
|
||||
Exception exceptionDuringTestRun = testDSpaceRunnableHandler.getException();
|
||||
|
@@ -144,8 +144,9 @@ public class MetadataImportIT extends AbstractIntegrationTestWithDatabase {
|
||||
script = scriptService.createDSpaceRunnableForScriptConfiguration(scriptConfiguration);
|
||||
}
|
||||
if (script != null) {
|
||||
script.initialize(args, testDSpaceRunnableHandler, null);
|
||||
script.run();
|
||||
if (DSpaceRunnable.StepResult.Continue.equals(script.initialize(args, testDSpaceRunnableHandler, null))) {
|
||||
script.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -702,8 +702,10 @@ public class CSVMetadataImportReferenceIT extends AbstractIntegrationTestWithDat
|
||||
script = scriptService.createDSpaceRunnableForScriptConfiguration(scriptConfiguration);
|
||||
}
|
||||
if (script != null) {
|
||||
script.initialize(args, testDSpaceRunnableHandler, null);
|
||||
script.run();
|
||||
if (DSpaceRunnable.StepResult.Continue
|
||||
.equals(script.initialize(args, testDSpaceRunnableHandler, null))) {
|
||||
script.run();
|
||||
}
|
||||
}
|
||||
if (testDSpaceRunnableHandler.getException() != null) {
|
||||
throw testDSpaceRunnableHandler.getException();
|
||||
|
@@ -16,11 +16,15 @@ import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.sql.SQLException;
|
||||
import java.time.Period;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.base.Splitter;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.dspace.AbstractUnitTest;
|
||||
import org.dspace.authorize.AuthorizeException;
|
||||
@@ -41,10 +45,6 @@ import org.dspace.core.Constants;
|
||||
import org.dspace.eperson.Group;
|
||||
import org.dspace.eperson.factory.EPersonServiceFactory;
|
||||
import org.dspace.eperson.service.GroupService;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.DateTimeZone;
|
||||
import org.joda.time.MutablePeriod;
|
||||
import org.joda.time.format.PeriodFormat;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
@@ -54,7 +54,7 @@ public class GoogleMetadataTest extends AbstractUnitTest {
|
||||
/**
|
||||
* log4j category
|
||||
*/
|
||||
private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(GoogleMetadataTest.class);
|
||||
private static final Logger log = LogManager.getLogger();
|
||||
|
||||
/**
|
||||
* Item instance for the tests
|
||||
@@ -319,6 +319,7 @@ public class GoogleMetadataTest extends AbstractUnitTest {
|
||||
|
||||
/**
|
||||
* Test empty bitstreams
|
||||
* @throws java.lang.Exception passed through.
|
||||
*/
|
||||
@Test
|
||||
public void testGetPDFURLWithEmptyBitstreams() throws Exception {
|
||||
@@ -348,8 +349,9 @@ public class GoogleMetadataTest extends AbstractUnitTest {
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify there is no mapping for {@link GoogleMetadata#PDF} if there are only embargoed (non-publically accessible
|
||||
* bitstream) files
|
||||
* Verify there is no mapping for {@link GoogleMetadata#PDF} if there are
|
||||
* only embargoed (non-publicly accessible bitstream) files.
|
||||
* @throws java.lang.Exception passed through.
|
||||
*/
|
||||
@Test
|
||||
public void testGetPdfUrlOfEmbargoed() throws Exception {
|
||||
@@ -363,8 +365,10 @@ public class GoogleMetadataTest extends AbstractUnitTest {
|
||||
b.getFormat(context).setMIMEType("unknown");
|
||||
bundleService.addBitstream(context, bundle, b);
|
||||
// Set 3 month embargo on pdf
|
||||
MutablePeriod period = PeriodFormat.getDefault().parseMutablePeriod("3 months");
|
||||
Date embargoDate = DateTime.now(DateTimeZone.UTC).plus(period).toDate();
|
||||
Period period = Period.ofMonths(3);
|
||||
Date embargoDate = Date.from(ZonedDateTime.now(ZoneOffset.UTC)
|
||||
.plus(period)
|
||||
.toInstant());
|
||||
Group anonGroup = groupService.findByName(context, Group.ANONYMOUS);
|
||||
authorizeService.removeAllPolicies(context, b);
|
||||
resourcePolicyService.removeAllPolicies(context, b);
|
||||
|
@@ -14,6 +14,7 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.dspace.AbstractUnitTest;
|
||||
import org.dspace.submit.factory.SubmissionServiceFactory;
|
||||
import org.junit.After;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
@@ -65,7 +66,8 @@ public class SubmissionConfigTest extends AbstractUnitTest {
|
||||
|
||||
// Get submission configuration
|
||||
SubmissionConfig submissionConfig =
|
||||
new SubmissionConfigReader().getSubmissionConfigByCollection(typeBindHandle);
|
||||
SubmissionServiceFactory.getInstance().getSubmissionConfigService()
|
||||
.getSubmissionConfigByCollection(typeBindHandle);
|
||||
// Submission name should match name defined in item-submission.xml
|
||||
assertEquals(typeBindSubmissionName, submissionConfig.getSubmissionName());
|
||||
// Step 0 - our process only has one step. It should not be null and have the ID typebindtest
|
||||
|
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* 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.authority;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.Date;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author mwood
|
||||
*/
|
||||
public class AuthorityValueTest {
|
||||
/**
|
||||
* Test of stringToDate method, of class AuthorityValue.
|
||||
*/
|
||||
@Test
|
||||
public void testStringToDate() {
|
||||
Date expected;
|
||||
Date actual;
|
||||
|
||||
// Test an invalid date.
|
||||
actual = AuthorityValue.stringToDate("not a date");
|
||||
assertNull("Unparseable date should return null", actual);
|
||||
|
||||
// Test a date-time without zone or offset.
|
||||
expected = Date.from(LocalDateTime.of(1957, 01, 27, 01, 23, 45)
|
||||
.atZone(ZoneId.systemDefault())
|
||||
.toInstant());
|
||||
actual = AuthorityValue.stringToDate("1957-01-27T01:23:45");
|
||||
assertEquals("Local date-time should convert", expected, actual);
|
||||
|
||||
// Test a date-time with milliseconds and offset from UTC.
|
||||
expected = Date.from(LocalDateTime.of(1957, 01, 27, 01, 23, 45, 678_000_000)
|
||||
.atZone(ZoneOffset.of("-05"))
|
||||
.toInstant());
|
||||
actual = AuthorityValue.stringToDate("1957-01-27T01:23:45.678-05");
|
||||
assertEquals("Zoned date-time with milliseconds should convert",
|
||||
expected, actual);
|
||||
}
|
||||
}
|
@@ -26,7 +26,7 @@ import org.mockito.junit.MockitoJUnitRunner;
|
||||
* @author Luca Giamminonni (luca.giamminonni at 4science.it)
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class RegexPasswordValidatorTest extends AbstractIntegrationTest {
|
||||
public class RegexPasswordValidatorIT extends AbstractIntegrationTest {
|
||||
|
||||
@Mock
|
||||
private ConfigurationService configurationService;
|
103
dspace-api/src/test/java/org/dspace/browse/CrossLinksTest.java
Normal file
103
dspace-api/src/test/java/org/dspace/browse/CrossLinksTest.java
Normal file
@@ -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.browse;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
import org.dspace.AbstractDSpaceTest;
|
||||
import org.dspace.services.ConfigurationService;
|
||||
import org.dspace.utils.DSpace;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Test class for {@link CrossLinks}
|
||||
*/
|
||||
public class CrossLinksTest extends AbstractDSpaceTest {
|
||||
protected ConfigurationService configurationService;
|
||||
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
configurationService = new DSpace().getConfigurationService();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindLinkType_Null() throws Exception {
|
||||
CrossLinks crossLinks = new CrossLinks();
|
||||
assertNull(crossLinks.findLinkType(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindLinkType_NoMatch() throws Exception {
|
||||
CrossLinks crossLinks = new CrossLinks();
|
||||
String metadataField = "foo.bar.baz.does.not.exist";
|
||||
assertNull(crossLinks.findLinkType(metadataField));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindLinkType_WildcardMatch() throws Exception {
|
||||
configurationService.setProperty("webui.browse.link.1", "author:dc.contributor.*");
|
||||
CrossLinks crossLinks = new CrossLinks();
|
||||
|
||||
String metadataField = "dc.contributor.author";
|
||||
assertEquals("author",crossLinks.findLinkType(metadataField));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindLinkType_SingleExactMatch_Author() throws Exception {
|
||||
configurationService.setProperty("webui.browse.link.1", "author:dc.contributor.author");
|
||||
CrossLinks crossLinks = new CrossLinks();
|
||||
|
||||
assertEquals("type",crossLinks.findLinkType("dc.genre"));
|
||||
assertEquals("author",crossLinks.findLinkType("dc.contributor.author"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindLinkType_SingleExactMatch_Type() throws Exception {
|
||||
configurationService.setProperty("webui.browse.link.1", "type:dc.genre");
|
||||
CrossLinks crossLinks = new CrossLinks();
|
||||
|
||||
assertEquals("type",crossLinks.findLinkType("dc.genre"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindLinkType_MultipleExactMatches_DifferentIndexes() throws Exception {
|
||||
configurationService.setProperty("webui.browse.link.1", "author:dc.contributor.author");
|
||||
configurationService.setProperty("webui.browse.link.2", "type:dc.genre");
|
||||
CrossLinks crossLinks = new CrossLinks();
|
||||
|
||||
assertEquals("author",crossLinks.findLinkType("dc.contributor.author"));
|
||||
assertEquals("type",crossLinks.findLinkType("dc.genre"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindLinkType_MultipleWildcardMatches_DifferentIndexes() throws Exception {
|
||||
configurationService.setProperty("webui.browse.link.1", "author:dc.contributor.*");
|
||||
configurationService.setProperty("webui.browse.link.2", "subject:dc.subject.*");
|
||||
CrossLinks crossLinks = new CrossLinks();
|
||||
|
||||
assertEquals("author",crossLinks.findLinkType("dc.contributor.author"));
|
||||
assertEquals("subject",crossLinks.findLinkType("dc.subject.lcsh"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindLinkType_MultiplExactAndWildcardMatches_DifferentIndexes() throws Exception {
|
||||
configurationService.setProperty("webui.browse.link.1", "author:dc.contributor.*");
|
||||
configurationService.setProperty("webui.browse.link.2", "subject:dc.subject.*");
|
||||
configurationService.setProperty("webui.browse.link.3", "type:dc.genre");
|
||||
configurationService.setProperty("webui.browse.link.4", "dateissued:dc.date.issued");
|
||||
CrossLinks crossLinks = new CrossLinks();
|
||||
|
||||
assertEquals("author",crossLinks.findLinkType("dc.contributor.author"));
|
||||
assertEquals("subject",crossLinks.findLinkType("dc.subject.lcsh"));
|
||||
assertEquals("type",crossLinks.findLinkType("dc.genre"));
|
||||
assertEquals("dateissued",crossLinks.findLinkType("dc.date.issued"));
|
||||
}
|
||||
}
|
@@ -21,6 +21,7 @@ import org.dspace.app.ldn.service.NotifyServiceInboundPatternService;
|
||||
import org.dspace.app.requestitem.factory.RequestItemServiceFactory;
|
||||
import org.dspace.app.requestitem.service.RequestItemService;
|
||||
import org.dspace.app.suggestion.SolrSuggestionStorageService;
|
||||
import org.dspace.app.util.SubmissionConfigReaderException;
|
||||
import org.dspace.authorize.AuthorizeException;
|
||||
import org.dspace.authorize.factory.AuthorizeServiceFactory;
|
||||
import org.dspace.authorize.service.AuthorizeService;
|
||||
@@ -57,6 +58,8 @@ import org.dspace.qaevent.service.QAEventService;
|
||||
import org.dspace.scripts.factory.ScriptServiceFactory;
|
||||
import org.dspace.scripts.service.ProcessService;
|
||||
import org.dspace.services.factory.DSpaceServicesFactory;
|
||||
import org.dspace.submit.factory.SubmissionServiceFactory;
|
||||
import org.dspace.submit.service.SubmissionConfigService;
|
||||
import org.dspace.supervision.factory.SupervisionOrderServiceFactory;
|
||||
import org.dspace.supervision.service.SupervisionOrderService;
|
||||
import org.dspace.utils.DSpace;
|
||||
@@ -114,6 +117,7 @@ public abstract class AbstractBuilder<T, S> {
|
||||
static OrcidQueueService orcidQueueService;
|
||||
static OrcidTokenService orcidTokenService;
|
||||
static SystemWideAlertService systemWideAlertService;
|
||||
static SubmissionConfigService submissionConfigService;
|
||||
static SubscribeService subscribeService;
|
||||
static SupervisionOrderService supervisionOrderService;
|
||||
static NotifyService notifyService;
|
||||
@@ -183,6 +187,11 @@ public abstract class AbstractBuilder<T, S> {
|
||||
orcidTokenService = OrcidServiceFactory.getInstance().getOrcidTokenService();
|
||||
systemWideAlertService = DSpaceServicesFactory.getInstance().getServiceManager()
|
||||
.getServicesByType(SystemWideAlertService.class).get(0);
|
||||
try {
|
||||
submissionConfigService = SubmissionServiceFactory.getInstance().getSubmissionConfigService();
|
||||
} catch (SubmissionConfigReaderException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
}
|
||||
subscribeService = ContentServiceFactory.getInstance().getSubscribeService();
|
||||
supervisionOrderService = SupervisionOrderServiceFactory.getInstance().getSupervisionOrderService();
|
||||
notifyService = NotifyServiceFactory.getInstance().getNotifyService();
|
||||
@@ -224,6 +233,7 @@ public abstract class AbstractBuilder<T, S> {
|
||||
versioningService = null;
|
||||
orcidTokenService = null;
|
||||
systemWideAlertService = null;
|
||||
submissionConfigService = null;
|
||||
subscribeService = null;
|
||||
supervisionOrderService = null;
|
||||
notifyService = null;
|
||||
|
@@ -8,6 +8,10 @@
|
||||
package org.dspace.builder;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.Period;
|
||||
import java.time.ZoneId;
|
||||
import java.util.Date;
|
||||
|
||||
import org.apache.logging.log4j.Logger;
|
||||
@@ -20,17 +24,13 @@ import org.dspace.core.Constants;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.eperson.EPerson;
|
||||
import org.dspace.eperson.Group;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.DateTimeZone;
|
||||
import org.joda.time.MutablePeriod;
|
||||
import org.joda.time.format.PeriodFormat;
|
||||
import org.joda.time.format.PeriodFormatter;
|
||||
|
||||
/**
|
||||
* Abstract builder to construct DSpace Objects
|
||||
*
|
||||
* @author Tom Desair (tom dot desair at atmire dot com)
|
||||
* @author Raf Ponsaerts (raf dot ponsaerts at atmire dot com)
|
||||
* @param <T> concrete type of DSpaceObject
|
||||
*/
|
||||
public abstract class AbstractDSpaceObjectBuilder<T extends DSpaceObject>
|
||||
extends AbstractBuilder<T, DSpaceObjectService> {
|
||||
@@ -112,21 +112,27 @@ public abstract class AbstractDSpaceObjectBuilder<T extends DSpaceObject>
|
||||
}
|
||||
|
||||
/**
|
||||
* Support method to grant the {@link Constants#READ} permission over an object only to the {@link Group#ANONYMOUS}
|
||||
* after the specified embargoPeriod. Any other READ permissions will be removed
|
||||
* Support method to grant the {@link Constants#READ} permission over an
|
||||
* object only to the {@link Group#ANONYMOUS} after the specified
|
||||
* embargoPeriod. Any other READ permissions will be removed.
|
||||
*
|
||||
* @param <B> type of this Builder.
|
||||
* @param embargoPeriod
|
||||
* the embargo period after which the READ permission will be active. It is parsed using the
|
||||
* {@link PeriodFormatter#parseMutablePeriod(String)} method of the joda library
|
||||
* @param dso
|
||||
* the DSpaceObject on which grant the permission
|
||||
* @return the builder properly configured to retain read permission on the object only for the specified group
|
||||
* the embargo period after which the READ permission will be
|
||||
* active.
|
||||
* @param dso the DSpaceObject on which to grant the permission.
|
||||
* @return the builder properly configured to retain read permission on the
|
||||
* object only for the specified group.
|
||||
*/
|
||||
protected <B extends AbstractDSpaceObjectBuilder<T>> B setEmbargo(String embargoPeriod, DSpaceObject dso) {
|
||||
protected <B extends AbstractDSpaceObjectBuilder<T>> B setEmbargo(Period embargoPeriod, DSpaceObject dso) {
|
||||
// add policy just for anonymous
|
||||
try {
|
||||
MutablePeriod period = PeriodFormat.getDefault().parseMutablePeriod(embargoPeriod);
|
||||
Date embargoDate = DateTime.now(DateTimeZone.UTC).plus(period).toDate();
|
||||
Instant embargoInstant = LocalDate.now()
|
||||
.plus(embargoPeriod)
|
||||
.atStartOfDay()
|
||||
.atZone(ZoneId.systemDefault())
|
||||
.toInstant();
|
||||
Date embargoDate = Date.from(embargoInstant);
|
||||
|
||||
return setOnlyReadPermission(dso, groupService.findByName(context, Group.ANONYMOUS), embargoDate);
|
||||
} catch (Exception e) {
|
||||
@@ -135,14 +141,19 @@ public abstract class AbstractDSpaceObjectBuilder<T extends DSpaceObject>
|
||||
}
|
||||
|
||||
/**
|
||||
* Support method to grant the {@link Constants#READ} permission over an object only to a specific group. Any other
|
||||
* READ permissions will be removed
|
||||
* Support method to grant the {@link Constants#READ} permission over an
|
||||
* object only to a specific group.Any other READ permissions will be
|
||||
* removed.
|
||||
*
|
||||
* @param <B> type of this Builder.
|
||||
* @param dso
|
||||
* the DSpaceObject on which grant the permission
|
||||
* @param group
|
||||
* the EPersonGroup that will be granted of the permission
|
||||
* @return the builder properly configured to retain read permission on the object only for the specified group
|
||||
* @param startDate
|
||||
* the date on which access begins.
|
||||
* @return the builder properly configured to retain read permission on the
|
||||
* object only for the specified group.
|
||||
*/
|
||||
protected <B extends AbstractDSpaceObjectBuilder<T>> B setOnlyReadPermission(DSpaceObject dso, Group group,
|
||||
Date startDate) {
|
||||
@@ -161,15 +172,20 @@ public abstract class AbstractDSpaceObjectBuilder<T extends DSpaceObject>
|
||||
}
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Support method to grant the {@link Constants#ADMIN} permission over an object only to a specific eperson.
|
||||
* If another ADMIN policy is in place for an eperson it will be replaced
|
||||
* Support method to grant the {@link Constants#READ} permission over an
|
||||
* object only to a specific EPerson. Any other READ permissions will be
|
||||
* removed.
|
||||
*
|
||||
* @param <B> type of this Builder.
|
||||
* @param dso
|
||||
* the DSpaceObject on which grant the permission
|
||||
* @param eperson
|
||||
* the eperson that will be granted of the permission
|
||||
* @return the builder properly configured to build the object with the additional admin permission
|
||||
* the EPerson that will be granted of the permission
|
||||
* @param startDate the date on which access begins.
|
||||
* @return the builder properly configured to build the object with the
|
||||
* additional admin permission.
|
||||
*/
|
||||
protected <B extends AbstractDSpaceObjectBuilder<T>> B setAdminPermission(DSpaceObject dso, EPerson eperson,
|
||||
Date startDate) {
|
||||
@@ -191,6 +207,7 @@ public abstract class AbstractDSpaceObjectBuilder<T extends DSpaceObject>
|
||||
/**
|
||||
* Support method to grant {@link Constants#REMOVE} permission to a specific eperson
|
||||
*
|
||||
* @param <B> type of this Builder.
|
||||
* @param dso
|
||||
* the DSpaceObject on which grant the permission
|
||||
* @param eperson
|
||||
@@ -220,6 +237,7 @@ public abstract class AbstractDSpaceObjectBuilder<T extends DSpaceObject>
|
||||
/**
|
||||
* Support method to grant {@link Constants#ADD} permission to a specific eperson
|
||||
*
|
||||
* @param <B> type of this Builder.
|
||||
* @param dso
|
||||
* the DSpaceObject on which grant the permission
|
||||
* @param eperson
|
||||
@@ -249,6 +267,7 @@ public abstract class AbstractDSpaceObjectBuilder<T extends DSpaceObject>
|
||||
/**
|
||||
* Support method to grant {@link Constants#WRITE} permission to a specific eperson
|
||||
*
|
||||
* @param <B> type of this Builder.
|
||||
* @param dso
|
||||
* the DSpaceObject on which grant the permission
|
||||
* @param eperson
|
||||
|
@@ -10,6 +10,7 @@ package org.dspace.builder;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.sql.SQLException;
|
||||
import java.time.Period;
|
||||
import java.util.List;
|
||||
|
||||
import org.dspace.authorize.AuthorizeException;
|
||||
@@ -17,7 +18,11 @@ import org.dspace.content.Bitstream;
|
||||
import org.dspace.content.BitstreamFormat;
|
||||
import org.dspace.content.Bundle;
|
||||
import org.dspace.content.Item;
|
||||
import org.dspace.content.MetadataField;
|
||||
import org.dspace.content.MetadataValue;
|
||||
import org.dspace.content.factory.ContentServiceFactory;
|
||||
import org.dspace.content.service.DSpaceObjectService;
|
||||
import org.dspace.content.service.MetadataValueService;
|
||||
import org.dspace.core.Constants;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.eperson.Group;
|
||||
@@ -54,6 +59,13 @@ public class BitstreamBuilder extends AbstractDSpaceObjectBuilder<Bitstream> {
|
||||
return builder.createInRequestedBundle(context, item, is, bundleName);
|
||||
}
|
||||
|
||||
public static BitstreamBuilder createBitstream(Context context, Item item, InputStream is,
|
||||
String bundleName, boolean iiifEnabled)
|
||||
throws SQLException, AuthorizeException, IOException {
|
||||
BitstreamBuilder builder = new BitstreamBuilder(context);
|
||||
return builder.createInRequestedBundleWithIiifDisabled(context, item, is, bundleName, iiifEnabled);
|
||||
}
|
||||
|
||||
private BitstreamBuilder create(Context context, Item item, InputStream is)
|
||||
throws SQLException, AuthorizeException, IOException {
|
||||
this.context = context;
|
||||
@@ -87,6 +99,41 @@ public class BitstreamBuilder extends AbstractDSpaceObjectBuilder<Bitstream> {
|
||||
return this;
|
||||
}
|
||||
|
||||
private BitstreamBuilder createInRequestedBundleWithIiifDisabled(Context context, Item item, InputStream is,
|
||||
String bundleName, boolean iiifEnabled)
|
||||
throws SQLException, AuthorizeException, IOException {
|
||||
this.context = context;
|
||||
this.item = item;
|
||||
|
||||
Bundle bundle = getBundleByNameAndIiiEnabled(item, bundleName, iiifEnabled);
|
||||
|
||||
bitstream = bitstreamService.create(context, bundle, is);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private Bundle getBundleByNameAndIiiEnabled(Item item, String bundleName, boolean iiifEnabled)
|
||||
throws SQLException, AuthorizeException {
|
||||
List<Bundle> bundles = itemService.getBundles(item, bundleName);
|
||||
Bundle targetBundle = null;
|
||||
|
||||
if (bundles.size() < 1) {
|
||||
// not found, create a new one
|
||||
targetBundle = bundleService.create(context, item, bundleName);
|
||||
MetadataValueService metadataValueService = ContentServiceFactory.getInstance().getMetadataValueService();
|
||||
MetadataField iiifEnabledField = metadataFieldService.
|
||||
findByString(context, "dspace.iiif.enabled", '.');
|
||||
MetadataValue metadataValue = metadataValueService.create(context, targetBundle, iiifEnabledField);
|
||||
metadataValue.setValue(String.valueOf(iiifEnabled));
|
||||
|
||||
} else {
|
||||
// put bitstreams into first bundle
|
||||
targetBundle = bundles.iterator().next();
|
||||
}
|
||||
return targetBundle;
|
||||
}
|
||||
|
||||
|
||||
private Bundle getBundleByName(Item item, String bundleName) throws SQLException, AuthorizeException {
|
||||
List<Bundle> bundles = itemService.getBundles(item, bundleName);
|
||||
Bundle targetBundle = null;
|
||||
@@ -136,6 +183,11 @@ public class BitstreamBuilder extends AbstractDSpaceObjectBuilder<Bitstream> {
|
||||
}
|
||||
|
||||
|
||||
public BitstreamBuilder withIIIFDisabled() throws SQLException {
|
||||
bitstreamService.addMetadata(context, bitstream, "dspace", "iiif", "enabled", null, "false");
|
||||
return this;
|
||||
}
|
||||
|
||||
public BitstreamBuilder withIIIFLabel(String label) throws SQLException {
|
||||
bitstreamService.addMetadata(context, bitstream, "iiif", "label", null, null, label);
|
||||
return this;
|
||||
@@ -171,7 +223,7 @@ public class BitstreamBuilder extends AbstractDSpaceObjectBuilder<Bitstream> {
|
||||
return targetBundle;
|
||||
}
|
||||
|
||||
public BitstreamBuilder withEmbargoPeriod(String embargoPeriod) {
|
||||
public BitstreamBuilder withEmbargoPeriod(Period embargoPeriod) {
|
||||
return setEmbargo(embargoPeriod, bitstream);
|
||||
}
|
||||
|
||||
|
@@ -13,6 +13,7 @@ import static org.dspace.content.authority.Choices.CF_ACCEPTED;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.sql.SQLException;
|
||||
import java.time.Period;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.dspace.authorize.AuthorizeException;
|
||||
@@ -285,8 +286,8 @@ public class ItemBuilder extends AbstractDSpaceObjectBuilder<Item> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Withdrawn the item under build. Please note that an user need to be loggedin the context to avoid NPE during the
|
||||
* creation of the provenance metadata
|
||||
* Withdraw the item under build. Please note that the Context must be
|
||||
* logged in to avoid NPE during the creation of the provenance metadata.
|
||||
*
|
||||
* @return the ItemBuilder
|
||||
*/
|
||||
@@ -295,7 +296,13 @@ public class ItemBuilder extends AbstractDSpaceObjectBuilder<Item> {
|
||||
return this;
|
||||
}
|
||||
|
||||
public ItemBuilder withEmbargoPeriod(String embargoPeriod) {
|
||||
/**
|
||||
* Set an embargo to end after some time from "now".
|
||||
*
|
||||
* @param embargoPeriod embargo starting "now", for this long.
|
||||
* @return the ItemBuilder.
|
||||
*/
|
||||
public ItemBuilder withEmbargoPeriod(Period embargoPeriod) {
|
||||
return setEmbargo(embargoPeriod, item);
|
||||
}
|
||||
|
||||
|
@@ -432,6 +432,51 @@ public class BitstreamTest extends AbstractDSpaceObjectTest {
|
||||
assertThat("testExpunge 0", bitstreamService.find(context, bitstreamId), nullValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test of delete method, of class Bitstream.
|
||||
*/
|
||||
@Test
|
||||
public void testDeleteBitstreamAndUnsetPrimaryBitstreamID()
|
||||
throws IOException, SQLException, AuthorizeException {
|
||||
|
||||
context.turnOffAuthorisationSystem();
|
||||
|
||||
Community owningCommunity = communityService.create(null, context);
|
||||
Collection collection = collectionService.create(context, owningCommunity);
|
||||
WorkspaceItem workspaceItem = workspaceItemService.create(context, collection, false);
|
||||
Item item = installItemService.installItem(context, workspaceItem);
|
||||
Bundle b = bundleService.create(context, item, "TESTBUNDLE");
|
||||
|
||||
// Allow Bundle REMOVE permissions
|
||||
doNothing().when(authorizeServiceSpy).authorizeAction(context, b, Constants.REMOVE);
|
||||
// Allow Bitstream WRITE permissions
|
||||
doNothing().when(authorizeServiceSpy)
|
||||
.authorizeAction(any(Context.class), any(Bitstream.class), eq(Constants.WRITE));
|
||||
// Allow Bitstream DELETE permissions
|
||||
doNothing().when(authorizeServiceSpy)
|
||||
.authorizeAction(any(Context.class), any(Bitstream.class), eq(Constants.DELETE));
|
||||
|
||||
//set a value different than default
|
||||
File f = new File(testProps.get("test.bitstream").toString());
|
||||
|
||||
// Create a new bitstream, which we can delete.
|
||||
Bitstream delBS = bitstreamService.create(context, new FileInputStream(f));
|
||||
bundleService.addBitstream(context, b, delBS);
|
||||
// set primary bitstream
|
||||
b.setPrimaryBitstreamID(delBS);
|
||||
context.restoreAuthSystemState();
|
||||
|
||||
// Test that delete will flag the bitstream as deleted
|
||||
assertFalse("testDeleteBitstreamAndUnsetPrimaryBitstreamID 0", delBS.isDeleted());
|
||||
assertThat("testDeleteBitstreamAndUnsetPrimaryBitstreamID 1", b.getPrimaryBitstream(), equalTo(delBS));
|
||||
// Delete bitstream
|
||||
bitstreamService.delete(context, delBS);
|
||||
assertTrue("testDeleteBitstreamAndUnsetPrimaryBitstreamID 2", delBS.isDeleted());
|
||||
|
||||
// Now test if the primary bitstream was unset from bundle
|
||||
assertThat("testDeleteBitstreamAndUnsetPrimaryBitstreamID 3", b.getPrimaryBitstream(), equalTo(null));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test of retrieve method, of class Bitstream.
|
||||
*/
|
||||
|
@@ -513,6 +513,41 @@ public class BundleTest extends AbstractDSpaceObjectTest {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test removeBitstream method and also the unsetPrimaryBitstreamID method, of class Bundle.
|
||||
*/
|
||||
@Test
|
||||
public void testRemoveBitstreamAuthAndUnsetPrimaryBitstreamID()
|
||||
throws IOException, SQLException, AuthorizeException {
|
||||
// Allow Item WRITE permissions
|
||||
doNothing().when(authorizeServiceSpy).authorizeAction(context, item, Constants.WRITE);
|
||||
// Allow Bundle ADD permissions
|
||||
doNothing().when(authorizeServiceSpy).authorizeAction(context, b, Constants.ADD);
|
||||
// Allow Bundle REMOVE permissions
|
||||
doNothing().when(authorizeServiceSpy).authorizeAction(context, b, Constants.REMOVE);
|
||||
// Allow Bitstream WRITE permissions
|
||||
doNothing().when(authorizeServiceSpy)
|
||||
.authorizeAction(any(Context.class), any(Bitstream.class), eq(Constants.WRITE));
|
||||
// Allow Bitstream DELETE permissions
|
||||
doNothing().when(authorizeServiceSpy)
|
||||
.authorizeAction(any(Context.class), any(Bitstream.class), eq(Constants.DELETE));
|
||||
|
||||
|
||||
context.turnOffAuthorisationSystem();
|
||||
//set a value different than default
|
||||
File f = new File(testProps.get("test.bitstream").toString());
|
||||
Bitstream bs = bitstreamService.create(context, new FileInputStream(f));
|
||||
bundleService.addBitstream(context, b, bs);
|
||||
b.setPrimaryBitstreamID(bs);
|
||||
context.restoreAuthSystemState();
|
||||
|
||||
assertThat("testRemoveBitstreamAuthAndUnsetPrimaryBitstreamID 0", b.getPrimaryBitstream(), equalTo(bs));
|
||||
//remove bitstream
|
||||
bundleService.removeBitstream(context, b, bs);
|
||||
//is -1 when not set
|
||||
assertThat("testRemoveBitstreamAuthAndUnsetPrimaryBitstreamID 1", b.getPrimaryBitstream(), equalTo(null));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test of update method, of class Bundle.
|
||||
*/
|
||||
|
@@ -26,7 +26,7 @@ import org.dspace.services.factory.DSpaceServicesFactory;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
public class RelationshipServiceImplVersioningTest extends AbstractIntegrationTestWithDatabase {
|
||||
public class RelationshipServiceImplVersioningIT extends AbstractIntegrationTestWithDatabase {
|
||||
|
||||
private RelationshipService relationshipService;
|
||||
private RelationshipDAO relationshipDAO;
|
@@ -70,7 +70,7 @@ import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
|
||||
|
||||
public class VersioningWithRelationshipsTest extends AbstractIntegrationTestWithDatabase {
|
||||
public class VersioningWithRelationshipsIT extends AbstractIntegrationTestWithDatabase {
|
||||
|
||||
private final RelationshipService relationshipService =
|
||||
ContentServiceFactory.getInstance().getRelationshipService();
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user