diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 679543cb01aa..615765967266 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -45,11 +45,11 @@ jobs:
steps:
# https://github.com/actions/checkout
- name: Checkout codebase
- uses: actions/checkout@v4
+ uses: actions/checkout@v6
# https://github.com/actions/setup-java
- name: Install JDK ${{ matrix.java }}
- uses: actions/setup-java@v4
+ uses: actions/setup-java@v5
with:
java-version: ${{ matrix.java }}
distribution: 'temurin'
@@ -65,14 +65,14 @@ jobs:
# (This artifact is downloadable at the bottom of any job's summary page)
- name: Upload Results of ${{ matrix.type }} to Artifact
if: ${{ failure() }}
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v7
with:
name: ${{ matrix.type }} results
path: ${{ matrix.resultsdir }}
# Upload code coverage report to artifact, so that it can be shared with the 'codecov' job (see below)
- name: Upload code coverage report to Artifact
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v7
with:
name: ${{ matrix.type }} coverage report
path: 'dspace/target/site/jacoco-aggregate/jacoco.xml'
@@ -88,19 +88,19 @@ jobs:
# runs-on: ubuntu-latest
# steps:
# - name: Checkout
- # uses: actions/checkout@v4
-
+ # uses: actions/checkout@v6
+ #
# # Download artifacts from previous 'tests' job
# - name: Download coverage artifacts
- # uses: actions/download-artifact@v4
-
+ # uses: actions/download-artifact@v8
+ #
# # Now attempt upload to Codecov using its action.
# # NOTE: We use a retry action to retry the Codecov upload if it fails the first time.
# #
# # Retry action: https://github.com/marketplace/actions/retry-action
# # Codecov action: https://github.com/codecov/codecov-action
# - name: Upload coverage to Codecov.io
- # uses: Wandalen/wretry.action@v1.3.0
+ # uses: Wandalen/wretry.action@v3.8.0
# with:
# action: codecov/codecov-action@v4
# # Ensure codecov-action throws an error when it fails to upload
diff --git a/.github/workflows/codescan.yml b/.github/workflows/codescan.yml
index 3a563c6fa39c..90d79f9bfd60 100644
--- a/.github/workflows/codescan.yml
+++ b/.github/workflows/codescan.yml
@@ -35,11 +35,11 @@ jobs:
steps:
# https://github.com/actions/checkout
- name: Checkout repository
- uses: actions/checkout@v4
+ uses: actions/checkout@v6
# https://github.com/actions/setup-java
- name: Install JDK
- uses: actions/setup-java@v4
+ uses: actions/setup-java@v5
with:
java-version: 17
distribution: 'temurin'
@@ -47,7 +47,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
# https://github.com/github/codeql-action
- name: Initialize CodeQL
- uses: github/codeql-action/init@v2
+ uses: github/codeql-action/init@v4
with:
# Codescan Javascript as well since a few JS files exist in REST API's interface
languages: java, javascript
@@ -56,8 +56,8 @@ jobs:
# NOTE: Based on testing, this autobuild process works well for DSpace. A custom
# DSpace build w/caching (like in build.yml) was about the same speed as autobuild.
- name: Autobuild
- uses: github/codeql-action/autobuild@v2
+ uses: github/codeql-action/autobuild@v4
# Perform GitHub Code Scanning.
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@v2
+ uses: github/codeql-action/analyze@v4
diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml
index 9d32cb119d41..5732a22d8277 100644
--- a/.github/workflows/docker.yml
+++ b/.github/workflows/docker.yml
@@ -183,10 +183,10 @@ jobs:
steps:
# Checkout our codebase (to get access to Docker Compose scripts)
- name: Checkout codebase
- uses: actions/checkout@v4
+ uses: actions/checkout@v6
# Download Docker image artifacts (which were just built by reusable-docker-build.yml)
- name: Download Docker image artifacts
- uses: actions/download-artifact@v4
+ uses: actions/download-artifact@v8
with:
# Download all amd64 Docker images (TAR files) into the /tmp/docker directory
pattern: docker-image-*-linux-amd64
@@ -205,8 +205,11 @@ jobs:
sleep 10
docker container ls
# Create a test admin account. Load test data from a simple set of AIPs as defined in cli.ingest.yml
+ # NOTE: Before creating test data, we wait for the backend to become responsive by requesting it every 10 sec.
+ # Timeout after 5 minutes. This is done to ensure the backend is fully initialized before we create test data.
- name: Load test data into Backend
run: |
+ timeout 5m wget --retry-connrefused -t 0 --waitretry=10 http://127.0.0.1:8080/server/api
docker compose -f docker-compose-cli.yml run --rm dspace-cli create-administrator -e test@test.edu -f admin -l user -p admin -c en
docker compose -f docker-compose-cli.yml -f dspace/src/main/docker-compose/cli.ingest.yml run --rm dspace-cli
# Verify backend started successfully.
@@ -220,6 +223,19 @@ jobs:
result=$(wget -O- -q http://127.0.0.1:8080/server/api/core/collections)
echo "$result"
echo "$result" | grep -oE "\"Dog in Yard\","
+ # Verify basic backend logging is working.
+ # 1. Access the top communities list. Verify that the "Before request" INFO statement is logged
+ # 2. Access an invalid endpoint (and ignore 404 response). Verify that a "status:404" WARN statement is logged
+ - name: Verify backend is logging properly
+ run: |
+ wget -O/dev/null -q http://127.0.0.1:8080/server/api/core/communities/search/top
+ logs=$(docker compose -f docker-compose.yml logs -n 5 dspace)
+ echo "$logs"
+ echo "$logs" | grep -o "Before request \[GET /server/api/core/communities/search/top\]"
+ wget -O/dev/null -q http://127.0.0.1:8080/server/api/does/not/exist || true
+ logs=$(docker compose -f docker-compose.yml logs -n 5 dspace)
+ echo "$logs"
+ echo "$logs" | grep -o "status:404 exception: The repository type does.not was not found"
# Verify Handle Server can be stared and is working properly
# 1. First generate the "[dspace]/handle-server" folder with the sitebndl.zip
# 2. Start the Handle Server (and wait 20 seconds to let it start up)
diff --git a/.github/workflows/issue_opened.yml b/.github/workflows/issue_opened.yml
index 0a35a6a95044..c8c421d98f47 100644
--- a/.github/workflows/issue_opened.yml
+++ b/.github/workflows/issue_opened.yml
@@ -16,7 +16,7 @@ jobs:
# Only add to project board if issue is flagged as "needs triage" or has no labels
# NOTE: By default we flag new issues as "needs triage" in our issue template
if: (contains(github.event.issue.labels.*.name, 'needs triage') || join(github.event.issue.labels.*.name) == '')
- uses: actions/add-to-project@v1.0.0
+ uses: actions/add-to-project@v1.0.2
# Note, the authentication token below is an ORG level Secret.
# It must be created/recreated manually via a personal access token with admin:org, project, public_repo permissions
# See: https://docs.github.com/en/actions/configuring-and-managing-workflows/authenticating-with-the-github_token#permissions-for-the-github_token
diff --git a/.github/workflows/port_merged_pull_request.yml b/.github/workflows/port_merged_pull_request.yml
index 857f22755e49..676ad45ba263 100644
--- a/.github/workflows/port_merged_pull_request.yml
+++ b/.github/workflows/port_merged_pull_request.yml
@@ -23,11 +23,11 @@ jobs:
if: github.event.pull_request.merged
steps:
# Checkout code
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v6
# Port PR to other branch (ONLY if labeled with "port to")
# See https://github.com/korthout/backport-action
- name: Create backport pull requests
- uses: korthout/backport-action@v2
+ uses: korthout/backport-action@v4
with:
# Trigger based on a "port to [branch]" label on PR
# (This label must specify the branch name to port to)
diff --git a/.github/workflows/pull_request_opened.yml b/.github/workflows/pull_request_opened.yml
index bbac52af2438..e2b6e8ba9c2c 100644
--- a/.github/workflows/pull_request_opened.yml
+++ b/.github/workflows/pull_request_opened.yml
@@ -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@v2.1.0
+ uses: toshimaru/auto-author-assign@v3.0.1
diff --git a/.github/workflows/reusable-docker-build.yml b/.github/workflows/reusable-docker-build.yml
index 0c3261da95da..768adb8f1602 100644
--- a/.github/workflows/reusable-docker-build.yml
+++ b/.github/workflows/reusable-docker-build.yml
@@ -109,13 +109,13 @@ jobs:
# https://github.com/actions/checkout
- name: Checkout codebase
- uses: actions/checkout@v4
+ uses: actions/checkout@v6
# https://github.com/docker/login-action
# NOTE: This login occurs for BOTH non-PRs or PRs. PRs *must* also login to access private images from GHCR
# during the build process
- name: Login to ${{ env.DOCKER_BUILD_REGISTRY }}
- uses: docker/login-action@v3
+ uses: docker/login-action@v4
with:
registry: ${{ env.DOCKER_BUILD_REGISTRY }}
username: ${{ github.repository_owner }}
@@ -123,13 +123,13 @@ jobs:
# https://github.com/docker/setup-buildx-action
- name: Setup Docker Buildx
- uses: docker/setup-buildx-action@v3
+ uses: docker/setup-buildx-action@v4
# https://github.com/docker/metadata-action
# Extract metadata used for Docker images in all build steps below
- name: Extract metadata (tags, labels) from GitHub for Docker image
id: meta_build
- uses: docker/metadata-action@v5
+ uses: docker/metadata-action@v6
with:
images: ${{ env.DOCKER_BUILD_REGISTRY }}/${{ env.IMAGE_NAME }}
tags: ${{ env.IMAGE_TAGS }}
@@ -147,7 +147,7 @@ jobs:
- name: Build and push image to ${{ env.DOCKER_BUILD_REGISTRY }}
if: ${{ ! matrix.isPr }}
id: docker_build
- uses: docker/build-push-action@v5
+ uses: docker/build-push-action@v7
with:
build-contexts: |
${{ inputs.dockerfile_additional_contexts }}
@@ -164,7 +164,7 @@ jobs:
# Use GitHub cache to load cached Docker images and cache the results of this build
# This decreases the number of images we need to fetch from DockerHub
cache-from: type=gha,scope=${{ inputs.build_id }}
- cache-to: type=gha,scope=${{ inputs.build_id }},mode=max
+ cache-to: type=gha,scope=${{ inputs.build_id }},mode=min
# Export the digest of Docker build locally
- name: Export Docker build digest
@@ -178,7 +178,7 @@ jobs:
# (The purpose of the combined manifest is to list both amd64 and arm64 builds under same tag)
- name: Upload Docker build digest to artifact
if: ${{ ! matrix.isPr }}
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v7
with:
name: digests-${{ inputs.build_id }}-${{ env.ARCH_NAME }}
path: /tmp/digests/*
@@ -201,7 +201,7 @@ jobs:
# NOTE: This step cannot be combined with the build above as it's a different type of output.
- name: Build and push image to local TAR file
if: ${{ matrix.arch == 'linux/amd64'}}
- uses: docker/build-push-action@v5
+ uses: docker/build-push-action@v7
with:
build-contexts: |
${{ inputs.dockerfile_additional_contexts }}
@@ -216,7 +216,7 @@ jobs:
# Use GitHub cache to load cached Docker images and cache the results of this build
# This decreases the number of images we need to fetch from DockerHub
cache-from: type=gha,scope=${{ inputs.build_id }}
- cache-to: type=gha,scope=${{ inputs.build_id }},mode=max
+ cache-to: type=gha,scope=${{ inputs.build_id }},mode=min
# Export image to a local TAR file
outputs: type=docker,dest=/tmp/${{ inputs.build_id }}.tar
@@ -224,7 +224,7 @@ jobs:
# This step is only done for AMD64, as that's the only image we use in our automated testing (at this time).
- name: Upload local image TAR to artifact
if: ${{ matrix.arch == 'linux/amd64'}}
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v7
with:
name: docker-image-${{ inputs.build_id }}-${{ env.ARCH_NAME }}
path: /tmp/${{ inputs.build_id }}.tar
@@ -245,7 +245,7 @@ jobs:
- docker-build
steps:
- name: Download Docker build digests
- uses: actions/download-artifact@v4
+ uses: actions/download-artifact@v8
with:
path: /tmp/digests
# Download digests for both AMD64 and ARM64 into same directory
@@ -253,18 +253,18 @@ jobs:
merge-multiple: true
- name: Login to ${{ env.DOCKER_BUILD_REGISTRY }}
- uses: docker/login-action@v3
+ uses: docker/login-action@v4
with:
registry: ${{ env.DOCKER_BUILD_REGISTRY }}
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
- uses: docker/setup-buildx-action@v3
+ uses: docker/setup-buildx-action@v4
- name: Add Docker metadata for image
id: meta
- uses: docker/metadata-action@v5
+ uses: docker/metadata-action@v6
with:
images: ${{ env.DOCKER_BUILD_REGISTRY }}/${{ env.IMAGE_NAME }}
tags: ${{ env.IMAGE_TAGS }}
@@ -298,14 +298,17 @@ jobs:
# 'regctl' is used to more easily copy the image to DockerHub and obtain the digest from DockerHub
# See https://github.com/regclient/regclient/blob/main/docs/regctl.md
- name: Install regctl for Docker registry tools
- uses: regclient/actions/regctl-installer@main
- with:
- release: 'v0.8.0'
+ run: |
+ export REGCTL_VERSION=v0.9.2
+ mkdir -p bin
+ curl -sSLo bin/regctl https://github.com/regclient/regclient/releases/download/${REGCTL_VERSION}/regctl-linux-amd64
+ chmod a+x bin/regctl
+ echo "$(pwd)/bin" >> $GITHUB_PATH
# This recreates Docker tags for DockerHub
- name: Add Docker metadata for image
id: meta_dockerhub
- uses: docker/metadata-action@v5
+ uses: docker/metadata-action@v6
with:
images: ${{ env.IMAGE_NAME }}
tags: ${{ env.IMAGE_TAGS }}
@@ -313,7 +316,7 @@ jobs:
# Login to source registry first, as this is where we are copying *from*
- name: Login to ${{ env.DOCKER_BUILD_REGISTRY }}
- uses: docker/login-action@v3
+ uses: docker/login-action@v4
with:
registry: ${{ env.DOCKER_BUILD_REGISTRY }}
username: ${{ github.repository_owner }}
@@ -321,7 +324,7 @@ jobs:
# Login to DockerHub, since this is where we are copying *to*
- name: Login to DockerHub
- uses: docker/login-action@v3
+ uses: docker/login-action@v4
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
diff --git a/Dockerfile b/Dockerfile
index 382db1ed3784..bf0a938c5192 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -46,26 +46,23 @@ ARG TARGET_DIR=dspace-installer
# COPY the /install directory from 'build' container to /dspace-src in this container
COPY --from=build /install /dspace-src
WORKDIR /dspace-src
-# Create the initial install deployment using ANT
-ENV ANT_VERSION=1.10.13
-ENV ANT_HOME=/tmp/ant-$ANT_VERSION
-ENV PATH=$ANT_HOME/bin:$PATH
-# Download and install 'ant'
-RUN mkdir $ANT_HOME && \
- curl --silent --show-error --location --fail --retry 5 --output /tmp/apache-ant.tar.gz \
- https://archive.apache.org/dist/ant/binaries/apache-ant-${ANT_VERSION}-bin.tar.gz && \
- tar -zx --strip-components=1 -f /tmp/apache-ant.tar.gz -C $ANT_HOME && \
- rm /tmp/apache-ant.tar.gz
+# Install Apache Ant
+RUN apt-get update \
+ && apt-get install -y --no-install-recommends ant \
+ && apt-get purge -y --auto-remove \
+ && rm -rf /var/lib/apt/lists/*
# Run necessary 'ant' deploy scripts
RUN ant init_installation update_configs update_code update_webapps
# Step 3 - Start up DSpace via Runnable JAR
FROM docker.io/eclipse-temurin:${JDK_VERSION}
-# NOTE: DSPACE_INSTALL must align with the "dspace.dir" default configuration.
-ENV DSPACE_INSTALL=/dspace
+# Below syntax may look odd, but it is how to override dspace.cfg settings via env variables.
+# See https://github.com/DSpace/DSpace/blob/main/dspace/config/config-definition.xml
+# "dspace__P__dir" is setting the value of the "dspace.dir" configuration. This is our installation directory.
+ENV dspace__P__dir=/dspace
# Copy the /dspace directory from 'ant_build' container to /dspace in this container
-COPY --from=ant_build /dspace $DSPACE_INSTALL
-WORKDIR $DSPACE_INSTALL
+COPY --from=ant_build /dspace $dspace__P__dir
+WORKDIR $dspace__P__dir
# Need host command for "[dspace]/bin/make-handle-config"
RUN apt-get update \
&& apt-get install -y --no-install-recommends host \
@@ -96,5 +93,5 @@ RUN apt-get update && \
COPY dspace/src/main/docker/cron/postfix.sh /usr/local/bin/postfix.sh
# End UMD Customization
-# On startup, run DSpace Runnable JAR
-ENTRYPOINT ["java", "-jar", "webapps/server-boot.jar", "--dspace.dir=$DSPACE_INSTALL"]
+# On startup, run DSpace Runnable JAR (uses the "dspace.dir" setting defined in "dspace__P__dir" env variable)
+ENTRYPOINT ["java", "-jar", "webapps/server-boot.jar"]
diff --git a/Dockerfile.ant b/Dockerfile.ant
index 4bcec223f186..8d5759dc6d8e 100644
--- a/Dockerfile.ant
+++ b/Dockerfile.ant
@@ -4,13 +4,8 @@
ARG JDK_VERSION=17
FROM docker.io/eclipse-temurin:${JDK_VERSION} AS ant_build
-# Create the initial install deployment using ANT
-ENV ANT_VERSION=1.10.13
-ENV ANT_HOME=/tmp/ant-$ANT_VERSION
-ENV PATH=$ANT_HOME/bin:$PATH
-# Download and install 'ant'
-RUN mkdir $ANT_HOME && \
- curl --silent --show-error --location --fail --retry 5 --output /tmp/apache-ant.tar.gz \
- https://archive.apache.org/dist/ant/binaries/apache-ant-${ANT_VERSION}-bin.tar.gz && \
- tar -zx --strip-components=1 -f /tmp/apache-ant.tar.gz -C $ANT_HOME && \
- rm /tmp/apache-ant.tar.gz
+# Install Apache Ant
+RUN apt-get update \
+ && apt-get install -y --no-install-recommends ant \
+ && apt-get purge -y --auto-remove \
+ && rm -rf /var/lib/apt/lists/*
diff --git a/Dockerfile.cli b/Dockerfile.cli
index 72913d5e5e9d..82ae1a64dfa2 100644
--- a/Dockerfile.cli
+++ b/Dockerfile.cli
@@ -44,25 +44,23 @@ ARG TARGET_DIR=dspace-installer
# COPY the /install directory from 'build' container to /dspace-src in this container
COPY --from=build /install /dspace-src
WORKDIR /dspace-src
-# Create the initial install deployment using ANT
-ENV ANT_VERSION=1.10.13
-ENV ANT_HOME=/tmp/ant-$ANT_VERSION
-ENV PATH=$ANT_HOME/bin:$PATH
-# Download and install 'ant'
-RUN mkdir $ANT_HOME && \
- curl --silent --show-error --location --fail --retry 5 --output /tmp/apache-ant.tar.gz \
- https://archive.apache.org/dist/ant/binaries/apache-ant-${ANT_VERSION}-bin.tar.gz && \
- tar -zx --strip-components=1 -f /tmp/apache-ant.tar.gz -C $ANT_HOME && \
- rm /tmp/apache-ant.tar.gz
+# Install Apache Ant
+RUN apt-get update \
+ && apt-get install -y --no-install-recommends ant \
+ && apt-get purge -y --auto-remove \
+ && rm -rf /var/lib/apt/lists/*
# Run necessary 'ant' deploy scripts
RUN ant init_installation update_configs update_code
# Step 3 - Run jdk
FROM docker.io/eclipse-temurin:${JDK_VERSION}
-# NOTE: DSPACE_INSTALL must align with the "dspace.dir" default configuration.
-ENV DSPACE_INSTALL=/dspace
+# Below syntax may look odd, but it is how to override dspace.cfg settings via env variables.
+# See https://github.com/DSpace/DSpace/blob/main/dspace/config/config-definition.xml
+# "dspace__P__dir" is setting the value of the "dspace.dir" configuration. This is our installation directory.
+ENV dspace__P__dir=/dspace
# Copy the /dspace directory from 'ant_build' container to /dspace in this container
-COPY --from=ant_build /dspace $DSPACE_INSTALL
+COPY --from=ant_build /dspace $dspace__P__dir
+WORKDIR $dspace__P__dir
# Give java extra memory (1GB)
ENV JAVA_OPTS=-Xmx1000m
# Install unzip for AIPs
@@ -70,3 +68,8 @@ RUN apt-get update \
&& apt-get install -y --no-install-recommends unzip \
&& apt-get purge -y --auto-remove \
&& rm -rf /var/lib/apt/lists/*
+
+# On startup, run DSpace commandline script
+ENTRYPOINT ["./bin/dspace"]
+# By default just pass 'help' command to ./bin/dspace
+CMD ["help"]
diff --git a/Dockerfile.dev b/Dockerfile.dev
index 90e1e579a18c..6e5e72e9c4f9 100644
--- a/Dockerfile.dev
+++ b/Dockerfile.dev
@@ -56,11 +56,13 @@ RUN ant init_installation update_configs update_code update_webapps
# Step 3 - Start up DSpace via Runnable JAR
FROM docker.io/eclipse-temurin:${JDK_VERSION}
-# NOTE: DSPACE_INSTALL must align with the "dspace.dir" default configuration.
-ENV DSPACE_INSTALL=/dspace
+# Below syntax may look odd, but it is how to override dspace.cfg settings via env variables.
+# See https://github.com/DSpace/DSpace/blob/main/dspace/config/config-definition.xml
+# "dspace__P__dir" is setting the value of the "dspace.dir" configuration. This is our installation directory.
+ENV dspace__P__dir=/dspace
# Copy the /dspace directory from 'ant_build' container to /dspace in this container
-COPY --from=ant_build /dspace $DSPACE_INSTALL
-WORKDIR $DSPACE_INSTALL
+COPY --from=ant_build /dspace $dspace__P__dir
+WORKDIR $dspace__P__dir
# Need host command for "[dspace]/bin/make-handle-config"
RUN apt-get update \
&& apt-get install -y --no-install-recommends host \
@@ -87,8 +89,9 @@ RUN apt-get update && \
vim \
python3-lxml \
jq && \
- mkfifo /var/spool/postfix/public/pickup && \
ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
+
+COPY dspace/src/main/docker/cron/postfix.sh /usr/local/bin/postfix.sh
# End UMD Customization
-# On startup, run DSpace Runnable JAR
-ENTRYPOINT ["java", "-jar", "webapps/server-boot.jar", "--dspace.dir=$DSPACE_INSTALL"]
+# On startup, run DSpace Runnable JAR (uses the "dspace.dir" setting defined in "dspace__P__dir" env variable)
+ENTRYPOINT ["java", "-jar", "webapps/server-boot.jar"]
diff --git a/Dockerfile.dev-additions b/Dockerfile.dev-additions
index 64bcd62b688c..9d365e3d21e2 100644
--- a/Dockerfile.dev-additions
+++ b/Dockerfile.dev-additions
@@ -58,11 +58,13 @@ RUN ant init_installation update_configs update_code update_webapps
# Step 3 - Start up DSpace via Runnable JAR
FROM docker.io/eclipse-temurin:${JDK_VERSION}
-# NOTE: DSPACE_INSTALL must align with the "dspace.dir" default configuration.
-ENV DSPACE_INSTALL=/dspace
+# Below syntax may look odd, but it is how to override dspace.cfg settings via env variables.
+# See https://github.com/DSpace/DSpace/blob/main/dspace/config/config-definition.xml
+# "dspace__P__dir" is setting the value of the "dspace.dir" configuration. This is our installation directory.
+ENV dspace__P__dir=/dspace
# Copy the /dspace directory from 'ant_build' container to /dspace in this container
-COPY --from=ant_build /dspace $DSPACE_INSTALL
-WORKDIR $DSPACE_INSTALL
+COPY --from=ant_build /dspace $dspace__P__dir
+WORKDIR $dspace__P__dir
# Need host command for "[dspace]/bin/make-handle-config"
# UMD Customization
# Commenting out, because no need for "make-handle-config" in dev environment
@@ -86,9 +88,8 @@ RUN apt-get update && \
jq
# Create the directories needed for Proquest ETD loading
-RUN mkdir -p $DSPACE_INSTALL/proquest/incoming $DSPACE_INSTALL/proquest/processed \
- $DSPACE_INSTALL/proquest/csv $DSPACE_INSTALL/proquest/marc
+RUN mkdir -p $dspace__P__dir/proquest/incoming $dspace__P__dir/proquest/processed \
+ $dspace__P__dir/proquest/csv $dspace__P__dir/proquest/marc
# End UMD Customization
-
-# On startup, run DSpace Runnable JAR
-ENTRYPOINT ["java", "-jar", "webapps/server-boot.jar", "--dspace.dir=$DSPACE_INSTALL"]
+# On startup, run DSpace Runnable JAR (uses the "dspace.dir" setting defined in "dspace__P__dir" env variable)
+ENTRYPOINT ["java", "-jar", "webapps/server-boot.jar"]
diff --git a/Dockerfile.test b/Dockerfile.test
index c9627e439fd7..79e2e5d9eec1 100644
--- a/Dockerfile.test
+++ b/Dockerfile.test
@@ -39,26 +39,23 @@ ARG TARGET_DIR=dspace-installer
# COPY the /install directory from 'build' container to /dspace-src in this container
COPY --from=build /install /dspace-src
WORKDIR /dspace-src
-# Create the initial install deployment using ANT
-ENV ANT_VERSION=1.10.12
-ENV ANT_HOME=/tmp/ant-$ANT_VERSION
-ENV PATH=$ANT_HOME/bin:$PATH
-# Download and install 'ant'
-RUN mkdir $ANT_HOME && \
- curl --silent --show-error --location --fail --retry 5 --output /tmp/apache-ant.tar.gz \
- https://archive.apache.org/dist/ant/binaries/apache-ant-${ANT_VERSION}-bin.tar.gz && \
- tar -zx --strip-components=1 -f /tmp/apache-ant.tar.gz -C $ANT_HOME && \
- rm /tmp/apache-ant.tar.gz
+# Install Apache Ant
+RUN apt-get update \
+ && apt-get install -y --no-install-recommends ant \
+ && apt-get purge -y --auto-remove \
+ && rm -rf /var/lib/apt/lists/*
# Run necessary 'ant' deploy scripts
RUN ant init_installation update_configs update_code update_webapps
# Step 3 - Start up DSpace via Runnable JAR
FROM docker.io/eclipse-temurin:${JDK_VERSION}
-# NOTE: DSPACE_INSTALL must align with the "dspace.dir" default configuration.
-ENV DSPACE_INSTALL=/dspace
+# Below syntax may look odd, but it is how to override dspace.cfg settings via env variables.
+# See https://github.com/DSpace/DSpace/blob/main/dspace/config/config-definition.xml
+# "dspace__P__dir" is setting the value of the "dspace.dir" configuration. This is our installation directory.
+ENV dspace__P__dir=/dspace
# Copy the /dspace directory from 'ant_build' container to /dspace in this container
-COPY --from=ant_build /dspace $DSPACE_INSTALL
-WORKDIR $DSPACE_INSTALL
+COPY --from=ant_build /dspace $dspace__P__dir
+WORKDIR $dspace__P__dir
# Need host command for "[dspace]/bin/make-handle-config"
RUN apt-get update \
&& apt-get install -y --no-install-recommends host \
@@ -70,5 +67,5 @@ EXPOSE 8080 8000
ENV JAVA_OPTS=-Xmx2000m
# enable JVM debugging via JDWP
ENV JAVA_TOOL_OPTIONS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:8000
-# On startup, run DSpace Runnable JAR
-ENTRYPOINT ["java", "-jar", "webapps/server-boot.jar", "--dspace.dir=$DSPACE_INSTALL"]
+# On startup, run DSpace Runnable JAR (uses the "dspace.dir" setting defined in "dspace__P__dir" env variable)
+ENTRYPOINT ["java", "-jar", "webapps/server-boot.jar"]
diff --git a/LICENSES_THIRD_PARTY b/LICENSES_THIRD_PARTY
index 5d99bd7e426c..c65827035560 100644
--- a/LICENSES_THIRD_PARTY
+++ b/LICENSES_THIRD_PARTY
@@ -21,35 +21,34 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines
Apache Software License, Version 2.0:
* Ant-Contrib Tasks (ant-contrib:ant-contrib:1.0b3 - http://ant-contrib.sourceforge.net)
- * AWS SDK for Java - Core (com.amazonaws:aws-java-sdk-core:1.12.785 - https://aws.amazon.com/sdkforjava)
- * AWS Java SDK for AWS KMS (com.amazonaws:aws-java-sdk-kms:1.12.785 - https://aws.amazon.com/sdkforjava)
- * AWS Java SDK for Amazon S3 (com.amazonaws:aws-java-sdk-s3:1.12.785 - https://aws.amazon.com/sdkforjava)
- * JMES Path Query library (com.amazonaws:jmespath-java:1.12.785 - https://aws.amazon.com/sdkforjava)
+ * S3Mock - Testsupport - Testcontainers (com.adobe.testing:s3mock-testcontainers:4.12.4 - https://www.github.com/adobe/S3Mock/s3mock-testsupport-reactor/s3mock-testcontainers)
* Titanium JSON-LD 1.1 (JRE11) (com.apicatalog:titanium-json-ld:1.3.2 - https://github.com/filip26/titanium-json-ld)
* HPPC Collections (com.carrotsearch:hppc:0.8.1 - http://labs.carrotsearch.com/hppc.html/hppc)
* com.drewnoakes:metadata-extractor (com.drewnoakes:metadata-extractor:2.19.0 - https://drewnoakes.com/code/exif/)
* parso (com.epam:parso:2.0.14 - https://github.com/epam/parso)
* Internet Time Utility (com.ethlo.time:itu:1.7.0 - https://github.com/ethlo/itu)
- * ClassMate (com.fasterxml:classmate:1.7.0 - https://github.com/FasterXML/java-classmate)
- * Jackson-annotations (com.fasterxml.jackson.core:jackson-annotations:2.19.1 - https://github.com/FasterXML/jackson)
- * Jackson-core (com.fasterxml.jackson.core:jackson-core:2.19.1 - https://github.com/FasterXML/jackson-core)
- * jackson-databind (com.fasterxml.jackson.core:jackson-databind:2.19.1 - https://github.com/FasterXML/jackson)
- * Jackson dataformat: CBOR (com.fasterxml.jackson.dataformat:jackson-dataformat-cbor:2.17.2 - https://github.com/FasterXML/jackson-dataformats-binary)
+ * ClassMate (com.fasterxml:classmate:1.7.3 - https://github.com/FasterXML/java-classmate)
+ * Jackson-annotations (com.fasterxml.jackson.core:jackson-annotations:2.21 - https://github.com/FasterXML/jackson)
+ * Jackson-core (com.fasterxml.jackson.core:jackson-core:2.21.3 - https://github.com/FasterXML/jackson-core)
+ * jackson-databind (com.fasterxml.jackson.core:jackson-databind:2.21.3 - https://github.com/FasterXML/jackson)
* Jackson dataformat: Smile (com.fasterxml.jackson.dataformat:jackson-dataformat-smile:2.15.2 - https://github.com/FasterXML/jackson-dataformats-binary)
* Jackson-dataformat-TOML (com.fasterxml.jackson.dataformat:jackson-dataformat-toml:2.15.2 - https://github.com/FasterXML/jackson-dataformats-text)
* Jackson-dataformat-YAML (com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.16.2 - https://github.com/FasterXML/jackson-dataformats-text)
- * Jackson datatype: jdk8 (com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.19.1 - https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jdk8)
- * Jackson datatype: JSR310 (com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.19.1 - https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jsr310)
+ * Jackson datatype: jdk8 (com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.21.2 - https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jdk8)
+ * Jackson datatype: JSR310 (com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.21.3 - https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jsr310)
* Jackson Jakarta-RS: base (com.fasterxml.jackson.jakarta.rs:jackson-jakarta-rs-base:2.16.2 - https://github.com/FasterXML/jackson-jakarta-rs-providers/jackson-jakarta-rs-base)
* Jackson Jakarta-RS: JSON (com.fasterxml.jackson.jakarta.rs:jackson-jakarta-rs-json-provider:2.16.2 - https://github.com/FasterXML/jackson-jakarta-rs-providers/jackson-jakarta-rs-json-provider)
* Jackson module: Jakarta XML Bind Annotations (jakarta.xml.bind) (com.fasterxml.jackson.module:jackson-module-jakarta-xmlbind-annotations:2.16.2 - https://github.com/FasterXML/jackson-modules-base)
- * Jackson-module-parameter-names (com.fasterxml.jackson.module:jackson-module-parameter-names:2.19.1 - https://github.com/FasterXML/jackson-modules-java8/jackson-module-parameter-names)
+ * Jackson-module-parameter-names (com.fasterxml.jackson.module:jackson-module-parameter-names:2.21.2 - https://github.com/FasterXML/jackson-modules-java8/jackson-module-parameter-names)
* Java UUID Generator (com.fasterxml.uuid:java-uuid-generator:4.1.0 - https://github.com/cowtowncoder/java-uuid-generator)
* Woodstox (com.fasterxml.woodstox:woodstox-core:6.5.1 - https://github.com/FasterXML/woodstox)
* zjsonpatch (com.flipkart.zjsonpatch:zjsonpatch:0.4.16 - https://github.com/flipkart-incubator/zjsonpatch/)
* Caffeine cache (com.github.ben-manes.caffeine:caffeine:2.9.3 - https://github.com/ben-manes/caffeine)
* Caffeine cache (com.github.ben-manes.caffeine:caffeine:3.1.8 - https://github.com/ben-manes/caffeine)
* JSON.simple (com.github.cliftonlabs:json-simple:3.0.2 - https://cliftonlabs.github.io/json-simple/)
+ * docker-java-api (com.github.docker-java:docker-java-api:3.7.1 - https://github.com/docker-java/docker-java)
+ * docker-java-transport (com.github.docker-java:docker-java-transport:3.7.1 - https://github.com/docker-java/docker-java)
+ * docker-java-transport-zerodep (com.github.docker-java:docker-java-transport-zerodep:3.7.1 - https://github.com/docker-java/docker-java)
* btf (com.github.java-json-tools:btf:1.3 - https://github.com/java-json-tools/btf)
* jackson-coreutils (com.github.java-json-tools:jackson-coreutils:2.0 - https://github.com/java-json-tools/jackson-coreutils)
* jackson-coreutils-equivalence (com.github.java-json-tools:jackson-coreutils-equivalence:1.0 - https://github.com/java-json-tools/jackson-coreutils)
@@ -60,25 +59,26 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines
* uri-template (com.github.java-json-tools:uri-template:0.10 - https://github.com/java-json-tools/uri-template)
* JCIP Annotations under Apache License (com.github.stephenc.jcip:jcip-annotations:1.0-1 - http://stephenc.github.com/jcip-annotations)
* FindBugs-jsr305 (com.google.code.findbugs:jsr305:3.0.2 - http://findbugs.sourceforge.net/)
- * Gson (com.google.code.gson:gson:2.13.1 - https://github.com/google/gson)
- * error-prone annotations (com.google.errorprone:error_prone_annotations:2.38.0 - https://errorprone.info/error_prone_annotations)
+ * Gson (com.google.code.gson:gson:2.14.0 - https://github.com/google/gson)
+ * error-prone annotations (com.google.errorprone:error_prone_annotations:2.42.0 - https://errorprone.info/error_prone_annotations)
* Guava InternalFutureFailureAccess and InternalFutures (com.google.guava:failureaccess:1.0.1 - https://github.com/google/guava/failureaccess)
- * Guava: Google Core Libraries for Java (com.google.guava:guava:32.1.3-jre - https://github.com/google/guava)
+ * Guava InternalFutureFailureAccess and InternalFutures (com.google.guava:failureaccess:1.0.3 - https://github.com/google/guava/failureaccess)
+ * Guava: Google Core Libraries for Java (com.google.guava:guava:33.6.0-jre - https://github.com/google/guava)
* Guava ListenableFuture only (com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava - https://github.com/google/guava/listenablefuture)
* J2ObjC Annotations (com.google.j2objc:j2objc-annotations:1.3 - https://github.com/google/j2objc/)
- * J2ObjC Annotations (com.google.j2objc:j2objc-annotations:2.8 - https://github.com/google/j2objc/)
+ * J2ObjC Annotations (com.google.j2objc:j2objc-annotations:3.1 - https://github.com/google/j2objc/)
* libphonenumber (com.googlecode.libphonenumber:libphonenumber:8.11.1 - https://github.com/google/libphonenumber/)
- * Jackcess (com.healthmarketscience.jackcess:jackcess:4.0.8 - https://jackcess.sourceforge.io)
+ * Jackcess (com.healthmarketscience.jackcess:jackcess:4.0.10 - https://jackcess.sourceforge.io)
* Jackcess Encrypt (com.healthmarketscience.jackcess:jackcess-encrypt:4.0.3 - http://jackcessencrypt.sf.net)
- * json-path (com.jayway.jsonpath:json-path:2.9.0 - https://github.com/jayway/JsonPath)
- * json-path-assert (com.jayway.jsonpath:json-path-assert:2.9.0 - https://github.com/jayway/JsonPath)
+ * json-path (com.jayway.jsonpath:json-path:2.10.0 - https://github.com/jayway/JsonPath)
+ * json-path-assert (com.jayway.jsonpath:json-path-assert:2.10.0 - https://github.com/jayway/JsonPath)
* Disruptor Framework (com.lmax:disruptor:3.4.2 - http://lmax-exchange.github.com/disruptor)
* MaxMind DB Reader (com.maxmind.db:maxmind-db:2.1.0 - http://dev.maxmind.com/)
* MaxMind GeoIP2 API (com.maxmind.geoip2:geoip2:2.17.0 - https://dev.maxmind.com/geoip?lang=en)
* JsonSchemaValidator (com.networknt:json-schema-validator:1.0.76 - https://github.com/networknt/json-schema-validator)
* Nimbus JOSE+JWT (com.nimbusds:nimbus-jose-jwt:9.28 - https://bitbucket.org/connect2id/nimbus-jose-jwt)
* Nimbus JOSE+JWT (com.nimbusds:nimbus-jose-jwt:9.48 - https://bitbucket.org/connect2id/nimbus-jose-jwt)
- * opencsv (com.opencsv:opencsv:5.11.1 - http://opencsv.sf.net)
+ * opencsv (com.opencsv:opencsv:5.12.0 - http://opencsv.sf.net)
* java-libpst (com.pff:java-libpst:0.9.3 - https://github.com/rjohnsondev/java-libpst)
* rome (com.rometools:rome:1.19.0 - http://rometools.com/rome)
* rome-modules (com.rometools:rome-modules:1.19.0 - http://rometools.com/rome-modules)
@@ -88,26 +88,17 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines
* okio (com.squareup.okio:okio:3.6.0 - https://github.com/square/okio/)
* okio (com.squareup.okio:okio-jvm:3.6.0 - https://github.com/square/okio/)
* T-Digest (com.tdunning:t-digest:3.1 - https://github.com/tdunning/t-digest)
- * config (com.typesafe:config:1.3.3 - https://github.com/lightbend/config)
- * ssl-config-core (com.typesafe:ssl-config-core_2.13:0.3.8 - https://github.com/lightbend/ssl-config)
- * akka-actor (com.typesafe.akka:akka-actor_2.13:2.5.31 - https://akka.io/)
- * akka-http-core (com.typesafe.akka:akka-http-core_2.13:10.1.12 - https://akka.io)
- * akka-http (com.typesafe.akka:akka-http_2.13:10.1.12 - https://akka.io)
- * akka-parsing (com.typesafe.akka:akka-parsing_2.13:10.1.12 - https://akka.io)
- * akka-protobuf (com.typesafe.akka:akka-protobuf_2.13:2.5.31 - https://akka.io/)
- * akka-stream (com.typesafe.akka:akka-stream_2.13:2.5.31 - https://akka.io/)
- * scala-logging (com.typesafe.scala-logging:scala-logging_2.13:3.9.2 - https://github.com/lightbend/scala-logging)
* JSON library from Android SDK (com.vaadin.external.google:android-json:0.0.20131108.vaadin1 - http://developer.android.com/sdk)
* SparseBitSet (com.zaxxer:SparseBitSet:1.3 - https://github.com/brettwooldridge/SparseBitSet)
* Apache Commons BeanUtils (commons-beanutils:commons-beanutils:1.11.0 - https://commons.apache.org/proper/commons-beanutils)
- * Apache Commons CLI (commons-cli:commons-cli:1.9.0 - https://commons.apache.org/proper/commons-cli/)
- * Apache Commons Codec (commons-codec:commons-codec:1.18.0 - https://commons.apache.org/proper/commons-codec/)
+ * Apache Commons CLI (commons-cli:commons-cli:1.11.0 - https://commons.apache.org/proper/commons-cli/)
+ * Apache Commons Codec (commons-codec:commons-codec:1.22.0 - https://commons.apache.org/proper/commons-codec/)
* Apache Commons Collections (commons-collections:commons-collections:3.2.2 - http://commons.apache.org/collections/)
* Commons Digester (commons-digester:commons-digester:2.1 - http://commons.apache.org/digester/)
- * Apache Commons IO (commons-io:commons-io:2.19.0 - https://commons.apache.org/proper/commons-io/)
+ * Apache Commons IO (commons-io:commons-io:2.22.0 - https://commons.apache.org/proper/commons-io/)
* Commons Lang (commons-lang:commons-lang:2.6 - http://commons.apache.org/lang/)
- * Apache Commons Logging (commons-logging:commons-logging:1.3.5 - https://commons.apache.org/proper/commons-logging/)
- * Apache Commons Validator (commons-validator:commons-validator:1.9.0 - http://commons.apache.org/proper/commons-validator/)
+ * Apache Commons Logging (commons-logging:commons-logging:1.3.6 - https://commons.apache.org/proper/commons-logging/)
+ * Apache Commons Validator (commons-validator:commons-validator:1.10.1 - https://commons.apache.org/proper/commons-validator/)
* GeoJson POJOs for Jackson (de.grundid.opendatalab:geojson-jackson:1.14 - https://github.com/opendatalab-de/geojson-jackson)
* broker-client (eu.openaire:broker-client:1.1.2 - http://api.openaire.eu/broker/broker-client)
* OpenAIRE Funders Model (eu.openaire:funders-model:2.0.0 - https://api.openaire.eu)
@@ -117,10 +108,10 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines
* Metrics Integration with JMX (io.dropwizard.metrics:metrics-jmx:4.1.5 - https://metrics.dropwizard.io/metrics-jmx)
* JVM Integration for Metrics (io.dropwizard.metrics:metrics-jvm:4.1.5 - https://metrics.dropwizard.io/metrics-jvm)
* SWORD v2 Common Server Library (forked) (io.gdcc:sword2-server:2.0.0 - https://github.com/gdcc/sword2-server)
- * micrometer-commons (io.micrometer:micrometer-commons:1.14.8 - https://github.com/micrometer-metrics/micrometer)
- * micrometer-core (io.micrometer:micrometer-core:1.15.1 - https://github.com/micrometer-metrics/micrometer)
- * micrometer-jakarta9 (io.micrometer:micrometer-jakarta9:1.15.1 - https://github.com/micrometer-metrics/micrometer)
- * micrometer-observation (io.micrometer:micrometer-observation:1.14.8 - https://github.com/micrometer-metrics/micrometer)
+ * micrometer-commons (io.micrometer:micrometer-commons:1.15.11 - https://github.com/micrometer-metrics/micrometer)
+ * micrometer-core (io.micrometer:micrometer-core:1.15.11 - https://github.com/micrometer-metrics/micrometer)
+ * micrometer-jakarta9 (io.micrometer:micrometer-jakarta9:1.15.11 - https://github.com/micrometer-metrics/micrometer)
+ * micrometer-observation (io.micrometer:micrometer-observation:1.15.11 - https://github.com/micrometer-metrics/micrometer)
* Netty/Buffer (io.netty:netty-buffer:4.1.99.Final - https://netty.io/netty-buffer/)
* Netty/Codec (io.netty:netty-codec:4.1.99.Final - https://netty.io/netty-codec/)
* Netty/Codec/HTTP (io.netty:netty-codec-http:4.1.86.Final - https://netty.io/netty-codec-http/)
@@ -168,39 +159,38 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines
* Jakarta Bean Validation API (jakarta.validation:jakarta.validation-api:3.0.2 - https://beanvalidation.org)
* JSR107 API and SPI (javax.cache:cache-api:1.1.1 - https://github.com/jsr107/jsr107spec)
* jdbm (jdbm:jdbm:1.0 - no url defined)
- * Joda-Time (joda-time:joda-time:2.12.7 - https://www.joda.org/joda-time/)
+ * Joda-Time (joda-time:joda-time:2.10.5 - https://www.joda.org/joda-time/)
* Byte Buddy (without dependencies) (net.bytebuddy:byte-buddy:1.11.13 - https://bytebuddy.net/byte-buddy)
* Byte Buddy (without dependencies) (net.bytebuddy:byte-buddy:1.14.11 - https://bytebuddy.net/byte-buddy)
* Byte Buddy agent (net.bytebuddy:byte-buddy-agent:1.11.13 - https://bytebuddy.net/byte-buddy-agent)
* eigenbase-properties (net.hydromatic:eigenbase-properties:1.1.5 - http://github.com/julianhyde/eigenbase-properties)
+ * Java Native Access (net.java.dev.jna:jna:5.18.1 - https://github.com/java-native-access/jna)
* json-unit-core (net.javacrumbs.json-unit:json-unit-core:2.36.0 - https://github.com/lukas-krecan/JsonUnit/json-unit-core)
* "Java Concurrency in Practice" book annotations (net.jcip:jcip-annotations:1.0 - http://jcip.net/)
- * ASM based accessors helper used by json-smart (net.minidev:accessors-smart:2.5.0 - https://urielch.github.io/)
- * ASM based accessors helper used by json-smart (net.minidev:accessors-smart:2.5.2 - https://urielch.github.io/)
- * JSON Small and Fast Parser (net.minidev:json-smart:2.5.0 - https://urielch.github.io/)
- * JSON Small and Fast Parser (net.minidev:json-smart:2.5.2 - https://urielch.github.io/)
+ * ASM based accessors helper used by json-smart (net.minidev:accessors-smart:2.6.0 - https://urielch.github.io/)
+ * JSON Small and Fast Parser (net.minidev:json-smart:2.6.0 - https://urielch.github.io/)
* Abdera Core (org.apache.abdera:abdera-core:1.1.3 - http://abdera.apache.org/abdera-core)
* I18N Libraries (org.apache.abdera:abdera-i18n:1.1.3 - http://abdera.apache.org)
* Abdera Parser (org.apache.abdera:abdera-parser:1.1.3 - http://abdera.apache.org/abdera-parser)
- * Apache Ant Core (org.apache.ant:ant:1.10.15 - https://ant.apache.org/)
- * Apache Ant Launcher (org.apache.ant:ant-launcher:1.10.15 - https://ant.apache.org/)
- * Apache Commons BCEL (org.apache.bcel:bcel:6.10.0 - https://commons.apache.org/proper/commons-bcel)
+ * Apache Ant Core (org.apache.ant:ant:1.10.17 - https://ant.apache.org/)
+ * Apache Ant Launcher (org.apache.ant:ant-launcher:1.10.17 - https://ant.apache.org/)
+ * Apache Commons BCEL (org.apache.bcel:bcel:6.12.0 - https://commons.apache.org/proper/commons-bcel)
* Calcite Core (org.apache.calcite:calcite-core:1.35.0 - https://calcite.apache.org)
* Calcite Linq4j (org.apache.calcite:calcite-linq4j:1.35.0 - https://calcite.apache.org)
* Apache Calcite Avatica (org.apache.calcite.avatica:avatica-core:1.23.0 - https://calcite.apache.org/avatica)
* Apache Calcite Avatica Metrics (org.apache.calcite.avatica:avatica-metrics:1.23.0 - https://calcite.apache.org/avatica)
* Apache Commons Collections (org.apache.commons:commons-collections4:4.5.0 - https://commons.apache.org/proper/commons-collections/)
- * Apache Commons Compress (org.apache.commons:commons-compress:1.27.1 - https://commons.apache.org/proper/commons-compress/)
- * Apache Commons Configuration (org.apache.commons:commons-configuration2:2.12.0 - https://commons.apache.org/proper/commons-configuration/)
- * Apache Commons CSV (org.apache.commons:commons-csv:1.14.0 - https://commons.apache.org/proper/commons-csv/)
- * Apache Commons DBCP (org.apache.commons:commons-dbcp2:2.13.0 - https://commons.apache.org/proper/commons-dbcp/)
+ * Apache Commons Compress (org.apache.commons:commons-compress:1.28.0 - https://commons.apache.org/proper/commons-compress/)
+ * Apache Commons Configuration (org.apache.commons:commons-configuration2:2.15.0 - https://commons.apache.org/proper/commons-configuration/)
+ * Apache Commons CSV (org.apache.commons:commons-csv:1.14.1 - https://commons.apache.org/proper/commons-csv/)
+ * Apache Commons DBCP (org.apache.commons:commons-dbcp2:2.14.0 - https://commons.apache.org/proper/commons-dbcp/)
* Apache Commons Digester (org.apache.commons:commons-digester3:3.2 - http://commons.apache.org/digester/)
* Apache Commons Exec (org.apache.commons:commons-exec:1.3 - http://commons.apache.org/proper/commons-exec/)
- * Apache Commons Exec (org.apache.commons:commons-exec:1.4.0 - https://commons.apache.org/proper/commons-exec/)
- * Apache Commons Lang (org.apache.commons:commons-lang3:3.17.0 - https://commons.apache.org/proper/commons-lang/)
+ * Apache Commons Exec (org.apache.commons:commons-exec:1.6.0 - https://commons.apache.org/proper/commons-exec/)
+ * Apache Commons Lang (org.apache.commons:commons-lang3:3.20.0 - https://commons.apache.org/proper/commons-lang/)
* Apache Commons Math (org.apache.commons:commons-math3:3.6.1 - http://commons.apache.org/proper/commons-math/)
- * Apache Commons Pool (org.apache.commons:commons-pool2:2.12.1 - https://commons.apache.org/proper/commons-pool/)
- * Apache Commons Text (org.apache.commons:commons-text:1.13.1 - https://commons.apache.org/proper/commons-text)
+ * Apache Commons Pool (org.apache.commons:commons-pool2:2.13.1 - https://commons.apache.org/proper/commons-pool/)
+ * Apache Commons Text (org.apache.commons:commons-text:1.15.0 - https://commons.apache.org/proper/commons-text)
* Curator Client (org.apache.curator:curator-client:2.13.0 - http://curator.apache.org/curator-client)
* Curator Framework (org.apache.curator:curator-framework:2.13.0 - http://curator.apache.org/curator-framework)
* Curator Recipes (org.apache.curator:curator-recipes:2.13.0 - http://curator.apache.org/curator-recipes)
@@ -214,13 +204,13 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines
* Apache HttpCore (org.apache.httpcomponents:httpcore:4.4.16 - http://hc.apache.org/httpcomponents-core-ga)
* Apache HttpClient Mime (org.apache.httpcomponents:httpmime:4.5.14 - http://hc.apache.org/httpcomponents-client-ga)
* Apache HttpClient (org.apache.httpcomponents.client5:httpclient5:5.1.3 - https://hc.apache.org/httpcomponents-client-5.0.x/5.1.3/httpclient5/)
- * Apache HttpClient (org.apache.httpcomponents.client5:httpclient5:5.5 - https://hc.apache.org/httpcomponents-client-5.5.x/5.5/httpclient5/)
+ * Apache HttpClient (org.apache.httpcomponents.client5:httpclient5:5.6.1 - https://hc.apache.org/httpcomponents-client-5.5.x/5.6.1/httpclient5/)
* Apache HttpComponents Core HTTP/1.1 (org.apache.httpcomponents.core5:httpcore5:5.1.3 - https://hc.apache.org/httpcomponents-core-5.1.x/5.1.3/httpcore5/)
- * Apache HttpComponents Core HTTP/1.1 (org.apache.httpcomponents.core5:httpcore5:5.3.4 - https://hc.apache.org/httpcomponents-core-5.3.x/5.3.4/httpcore5/)
+ * Apache HttpComponents Core HTTP/1.1 (org.apache.httpcomponents.core5:httpcore5:5.4 - https://hc.apache.org/httpcomponents-core-5.4.x/5.4/httpcore5/)
* Apache HttpComponents Core HTTP/2 (org.apache.httpcomponents.core5:httpcore5-h2:5.1.3 - https://hc.apache.org/httpcomponents-core-5.1.x/5.1.3/httpcore5-h2/)
- * Apache HttpComponents Core HTTP/2 (org.apache.httpcomponents.core5:httpcore5-h2:5.3.4 - https://hc.apache.org/httpcomponents-core-5.3.x/5.3.4/httpcore5-h2/)
- * Apache James :: Mime4j :: Core (org.apache.james:apache-mime4j-core:0.8.12 - http://james.apache.org/mime4j/apache-mime4j-core)
- * Apache James :: Mime4j :: DOM (org.apache.james:apache-mime4j-dom:0.8.12 - http://james.apache.org/mime4j/apache-mime4j-dom)
+ * Apache HttpComponents Core HTTP/2 (org.apache.httpcomponents.core5:httpcore5-h2:5.4 - https://hc.apache.org/httpcomponents-core-5.4.x/5.4/httpcore5-h2/)
+ * Apache James :: Mime4j :: Core (org.apache.james:apache-mime4j-core:0.8.14 - http://james.apache.org/mime4j/apache-mime4j-core)
+ * Apache James :: Mime4j :: DOM (org.apache.james:apache-mime4j-dom:0.8.13 - http://james.apache.org/mime4j/apache-mime4j-dom)
* Apache Jena - Libraries POM (org.apache.jena:apache-jena-libs:4.10.0 - https://jena.apache.org/apache-jena-libs/)
* Apache Jena - ARQ (org.apache.jena:jena-arq:4.10.0 - https://jena.apache.org/jena-arq/)
* Apache Jena - Base (org.apache.jena:jena-base:4.10.0 - https://jena.apache.org/jena-base/)
@@ -242,12 +232,12 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines
* Kerby ASN1 Project (org.apache.kerby:kerby-asn1:1.0.1 - http://directory.apache.org/kerby/kerby-common/kerby-asn1)
* Kerby PKIX Project (org.apache.kerby:kerby-pkix:1.0.1 - http://directory.apache.org/kerby/kerby-pkix)
* Apache Log4j 1.x Compatibility API (org.apache.logging.log4j:log4j-1.2-api:2.17.2 - https://logging.apache.org/log4j/2.x/log4j-1.2-api/)
- * Apache Log4j API (org.apache.logging.log4j:log4j-api:2.24.3 - https://logging.apache.org/log4j/2.x/log4j/log4j-api/)
- * Apache Log4j Core (org.apache.logging.log4j:log4j-core:2.24.3 - https://logging.apache.org/log4j/2.x/log4j/log4j-core/)
+ * Apache Log4j API (org.apache.logging.log4j:log4j-api:2.25.4 - https://logging.apache.org/log4j/2.x/)
+ * Apache Log4j Core (org.apache.logging.log4j:log4j-core:2.25.4 - https://logging.apache.org/log4j/2.x/)
* Apache Log4j JUL Adapter (org.apache.logging.log4j:log4j-jul:2.24.3 - https://logging.apache.org/log4j/2.x/log4j/log4j-jul/)
* Apache Log4j Layout for JSON template (org.apache.logging.log4j:log4j-layout-template-json:2.17.2 - https://logging.apache.org/log4j/2.x/log4j-layout-template-json/)
* Apache Log4j SLF4J Binding (org.apache.logging.log4j:log4j-slf4j-impl:2.17.2 - https://logging.apache.org/log4j/2.x/log4j-slf4j-impl/)
- * SLF4J 2 Provider for Log4j API (org.apache.logging.log4j:log4j-slf4j2-impl:2.24.3 - https://logging.apache.org/log4j/2.x/log4j/log4j-slf4j2-impl/)
+ * SLF4J 2 Provider for Log4j API (org.apache.logging.log4j:log4j-slf4j2-impl:2.25.4 - https://logging.apache.org/log4j/2.x/)
* Apache Log4j Web (org.apache.logging.log4j:log4j-web:2.17.2 - https://logging.apache.org/log4j/2.x/log4j-web/)
* Lucene Common Analyzers (org.apache.lucene:lucene-analyzers-common:8.11.4 - https://lucene.apache.org/lucene-parent/lucene-analyzers-common)
* Lucene ICU Analysis Components (org.apache.lucene:lucene-analyzers-icu:8.11.4 - https://lucene.apache.org/lucene-parent/lucene-analyzers-icu)
@@ -272,48 +262,49 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines
* Lucene Spatial Extras (org.apache.lucene:lucene-spatial-extras:8.11.4 - https://lucene.apache.org/lucene-parent/lucene-spatial-extras)
* Lucene Spatial 3D (org.apache.lucene:lucene-spatial3d:8.11.4 - https://lucene.apache.org/lucene-parent/lucene-spatial3d)
* Lucene Suggest (org.apache.lucene:lucene-suggest:8.11.4 - https://lucene.apache.org/lucene-parent/lucene-suggest)
- * Apache FontBox (org.apache.pdfbox:fontbox:2.0.34 - http://pdfbox.apache.org/)
+ * Apache FontBox (org.apache.pdfbox:fontbox:3.0.7 - http://pdfbox.apache.org/)
* PDFBox JBIG2 ImageIO plugin (org.apache.pdfbox:jbig2-imageio:3.0.4 - https://www.apache.org/jbig2-imageio/)
* Apache JempBox (org.apache.pdfbox:jempbox:1.8.17 - http://www.apache.org/pdfbox-parent/jempbox/)
- * Apache PDFBox (org.apache.pdfbox:pdfbox:2.0.34 - https://www.apache.org/pdfbox-parent/pdfbox/)
- * Apache PDFBox tools (org.apache.pdfbox:pdfbox-tools:2.0.34 - https://www.apache.org/pdfbox-parent/pdfbox-tools/)
- * Apache XmpBox (org.apache.pdfbox:xmpbox:2.0.34 - https://www.apache.org/pdfbox-parent/xmpbox/)
- * Apache POI - Common (org.apache.poi:poi:5.4.1 - https://poi.apache.org/)
- * Apache POI - API based on OPC and OOXML schemas (org.apache.poi:poi-ooxml:5.4.1 - https://poi.apache.org/)
- * Apache POI (org.apache.poi:poi-ooxml-lite:5.4.1 - https://poi.apache.org/)
- * Apache POI (org.apache.poi:poi-scratchpad:5.4.1 - https://poi.apache.org/)
+ * Apache PDFBox (org.apache.pdfbox:pdfbox:3.0.7 - https://www.apache.org/pdfbox-parent/pdfbox/)
+ * Apache PDFBox io (org.apache.pdfbox:pdfbox-io:3.0.7 - https://www.apache.org/pdfbox-parent/pdfbox-io/)
+ * Apache PDFBox tools (org.apache.pdfbox:pdfbox-tools:3.0.7 - https://www.apache.org/pdfbox-parent/pdfbox-tools/)
+ * Apache XmpBox (org.apache.pdfbox:xmpbox:3.0.7 - https://www.apache.org/pdfbox-parent/xmpbox/)
+ * Apache POI - Common (org.apache.poi:poi:5.5.1 - https://poi.apache.org/)
+ * Apache POI - API based on OPC and OOXML schemas (org.apache.poi:poi-ooxml:5.5.1 - https://poi.apache.org/)
+ * Apache POI - OOXML schemas (full) (org.apache.poi:poi-ooxml-full:5.5.1 - https://poi.apache.org/)
+ * Apache POI (org.apache.poi:poi-scratchpad:5.5.1 - https://poi.apache.org/)
* Apache Solr Core (org.apache.solr:solr-core:8.11.4 - https://lucene.apache.org/solr-parent/solr-core)
* Apache Solr Solrj (org.apache.solr:solr-solrj:8.11.4 - https://lucene.apache.org/solr-parent/solr-solrj)
* Apache Standard Taglib Implementation (org.apache.taglibs:taglibs-standard-impl:1.2.5 - http://tomcat.apache.org/taglibs/standard-1.2.5/taglibs-standard-impl)
* Apache Standard Taglib Specification API (org.apache.taglibs:taglibs-standard-spec:1.2.5 - http://tomcat.apache.org/taglibs/standard-1.2.5/taglibs-standard-spec)
* Apache Thrift (org.apache.thrift:libthrift:0.19.0 - http://thrift.apache.org)
- * Apache Tika core (org.apache.tika:tika-core:2.9.4 - https://tika.apache.org/)
- * Apache Tika Apple parser module (org.apache.tika:tika-parser-apple-module:2.9.4 - https://tika.apache.org/tika-parser-apple-module/)
- * Apache Tika audiovideo parser module (org.apache.tika:tika-parser-audiovideo-module:2.9.4 - https://tika.apache.org/tika-parser-audiovideo-module/)
- * Apache Tika cad parser module (org.apache.tika:tika-parser-cad-module:2.9.4 - https://tika.apache.org/tika-parser-cad-module/)
- * Apache Tika code parser module (org.apache.tika:tika-parser-code-module:2.9.4 - https://tika.apache.org/tika-parser-code-module/)
- * Apache Tika crypto parser module (org.apache.tika:tika-parser-crypto-module:2.9.4 - https://tika.apache.org/tika-parser-crypto-module/)
- * Apache Tika digest commons (org.apache.tika:tika-parser-digest-commons:2.9.4 - https://tika.apache.org/tika-parser-digest-commons/)
- * Apache Tika font parser module (org.apache.tika:tika-parser-font-module:2.9.4 - https://tika.apache.org/tika-parser-font-module/)
- * Apache Tika html parser module (org.apache.tika:tika-parser-html-module:2.9.4 - https://tika.apache.org/tika-parser-html-module/)
- * Apache Tika image parser module (org.apache.tika:tika-parser-image-module:2.9.4 - https://tika.apache.org/tika-parser-image-module/)
- * Apache Tika mail commons (org.apache.tika:tika-parser-mail-commons:2.9.4 - https://tika.apache.org/tika-parser-mail-commons/)
- * Apache Tika mail parser module (org.apache.tika:tika-parser-mail-module:2.9.4 - https://tika.apache.org/tika-parser-mail-module/)
- * Apache Tika Microsoft parser module (org.apache.tika:tika-parser-microsoft-module:2.9.4 - https://tika.apache.org/tika-parser-microsoft-module/)
- * Apache Tika miscellaneous office format parser module (org.apache.tika:tika-parser-miscoffice-module:2.9.4 - https://tika.apache.org/tika-parser-miscoffice-module/)
- * Apache Tika news parser module (org.apache.tika:tika-parser-news-module:2.9.4 - https://tika.apache.org/tika-parser-news-module/)
- * Apache Tika OCR parser module (org.apache.tika:tika-parser-ocr-module:2.9.4 - https://tika.apache.org/tika-parser-ocr-module/)
- * Apache Tika PDF parser module (org.apache.tika:tika-parser-pdf-module:2.9.4 - https://tika.apache.org/tika-parser-pdf-module/)
- * Apache Tika package parser module (org.apache.tika:tika-parser-pkg-module:2.9.4 - https://tika.apache.org/tika-parser-pkg-module/)
- * Apache Tika text parser module (org.apache.tika:tika-parser-text-module:2.9.4 - https://tika.apache.org/tika-parser-text-module/)
- * Apache Tika WARC parser module (org.apache.tika:tika-parser-webarchive-module:2.9.4 - https://tika.apache.org/tika-parser-webarchive-module/)
- * Apache Tika XML parser module (org.apache.tika:tika-parser-xml-module:2.9.4 - https://tika.apache.org/tika-parser-xml-module/)
- * Apache Tika XMP commons (org.apache.tika:tika-parser-xmp-commons:2.9.4 - https://tika.apache.org/tika-parser-xmp-commons/)
- * Apache Tika ZIP commons (org.apache.tika:tika-parser-zip-commons:2.9.4 - https://tika.apache.org/tika-parser-zip-commons/)
- * Apache Tika standard parser package (org.apache.tika:tika-parsers-standard-package:2.9.4 - https://tika.apache.org/tika-parsers/tika-parsers-standard/tika-parsers-standard-package/)
- * tomcat-embed-core (org.apache.tomcat.embed:tomcat-embed-core:10.1.42 - https://tomcat.apache.org/)
- * tomcat-embed-el (org.apache.tomcat.embed:tomcat-embed-el:10.1.42 - https://tomcat.apache.org/)
- * tomcat-embed-websocket (org.apache.tomcat.embed:tomcat-embed-websocket:10.1.42 - https://tomcat.apache.org/)
+ * Apache Tika core (org.apache.tika:tika-core:3.3.0 - https://tika.apache.org/)
+ * Apache Tika Apple parser module (org.apache.tika:tika-parser-apple-module:3.3.0 - https://tika.apache.org/tika-parser-apple-module/)
+ * Apache Tika audiovideo parser module (org.apache.tika:tika-parser-audiovideo-module:3.3.0 - https://tika.apache.org/tika-parser-audiovideo-module/)
+ * Apache Tika cad parser module (org.apache.tika:tika-parser-cad-module:3.3.0 - https://tika.apache.org/tika-parser-cad-module/)
+ * Apache Tika code parser module (org.apache.tika:tika-parser-code-module:3.3.0 - https://tika.apache.org/tika-parser-code-module/)
+ * Apache Tika crypto parser module (org.apache.tika:tika-parser-crypto-module:3.3.0 - https://tika.apache.org/tika-parser-crypto-module/)
+ * Apache Tika digest commons (org.apache.tika:tika-parser-digest-commons:3.3.0 - https://tika.apache.org/tika-parser-digest-commons/)
+ * Apache Tika font parser module (org.apache.tika:tika-parser-font-module:3.3.0 - https://tika.apache.org/tika-parser-font-module/)
+ * Apache Tika html parser module (org.apache.tika:tika-parser-html-module:3.3.0 - https://tika.apache.org/tika-parser-html-module/)
+ * Apache Tika image parser module (org.apache.tika:tika-parser-image-module:3.3.0 - https://tika.apache.org/tika-parser-image-module/)
+ * Apache Tika mail commons (org.apache.tika:tika-parser-mail-commons:3.3.0 - https://tika.apache.org/tika-parser-mail-commons/)
+ * Apache Tika mail parser module (org.apache.tika:tika-parser-mail-module:3.3.0 - https://tika.apache.org/tika-parser-mail-module/)
+ * Apache Tika Microsoft parser module (org.apache.tika:tika-parser-microsoft-module:3.3.0 - https://tika.apache.org/tika-parser-microsoft-module/)
+ * Apache Tika miscellaneous office format parser module (org.apache.tika:tika-parser-miscoffice-module:3.3.0 - https://tika.apache.org/tika-parser-miscoffice-module/)
+ * Apache Tika news parser module (org.apache.tika:tika-parser-news-module:3.3.0 - https://tika.apache.org/tika-parser-news-module/)
+ * Apache Tika OCR parser module (org.apache.tika:tika-parser-ocr-module:3.3.0 - https://tika.apache.org/tika-parser-ocr-module/)
+ * Apache Tika PDF parser module (org.apache.tika:tika-parser-pdf-module:3.3.0 - https://tika.apache.org/tika-parser-pdf-module/)
+ * Apache Tika package parser module (org.apache.tika:tika-parser-pkg-module:3.3.0 - https://tika.apache.org/tika-parser-pkg-module/)
+ * Apache Tika text parser module (org.apache.tika:tika-parser-text-module:3.3.0 - https://tika.apache.org/tika-parser-text-module/)
+ * Apache Tika WARC parser module (org.apache.tika:tika-parser-webarchive-module:3.3.0 - https://tika.apache.org/tika-parser-webarchive-module/)
+ * Apache Tika XML parser module (org.apache.tika:tika-parser-xml-module:3.3.0 - https://tika.apache.org/tika-parser-xml-module/)
+ * Apache Tika XMP commons (org.apache.tika:tika-parser-xmp-commons:3.3.0 - https://tika.apache.org/tika-parser-xmp-commons/)
+ * Apache Tika ZIP commons (org.apache.tika:tika-parser-zip-commons:3.3.0 - https://tika.apache.org/tika-parser-zip-commons/)
+ * Apache Tika standard parser package (org.apache.tika:tika-parsers-standard-package:3.3.0 - https://tika.apache.org/tika-parsers/tika-parsers-standard/tika-parsers-standard-package/)
+ * tomcat-embed-core (org.apache.tomcat.embed:tomcat-embed-core:10.1.54 - https://tomcat.apache.org/)
+ * tomcat-embed-el (org.apache.tomcat.embed:tomcat-embed-el:10.1.54 - https://tomcat.apache.org/)
+ * tomcat-embed-websocket (org.apache.tomcat.embed:tomcat-embed-websocket:10.1.54 - https://tomcat.apache.org/)
* Apache Velocity - Engine (org.apache.velocity:velocity-engine-core:2.4.1 - http://velocity.apache.org/engine/devel/velocity-engine-core/)
* Apache Velocity - JSR 223 Scripting (org.apache.velocity:velocity-engine-scripting:2.3 - http://velocity.apache.org/engine/devel/velocity-engine-scripting/)
* Apache Velocity Tools - Generic tools (org.apache.velocity.tools:velocity-tools-generic:3.1 - https://velocity.apache.org/tools/devel/velocity-tools-generic/)
@@ -323,12 +314,11 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines
* Apache ZooKeeper - Server (org.apache.zookeeper:zookeeper:3.6.2 - http://zookeeper.apache.org/zookeeper)
* Apache ZooKeeper - Jute (org.apache.zookeeper:zookeeper-jute:3.6.2 - http://zookeeper.apache.org/zookeeper-jute)
* org.apiguardian:apiguardian-api (org.apiguardian:apiguardian-api:1.1.2 - https://github.com/apiguardian-team/apiguardian)
- * AssertJ Core (org.assertj:assertj-core:3.27.3 - https://assertj.github.io/doc/#assertj-core)
+ * AssertJ Core (org.assertj:assertj-core:3.27.7 - https://assertj.github.io/doc/#assertj-core)
* Evo Inflector (org.atteo:evo-inflector:1.3 - http://atteo.org/static/evo-inflector)
* attoparser (org.attoparser:attoparser:2.0.7.RELEASE - https://www.attoparser.org)
* Awaitility (org.awaitility:awaitility:4.2.2 - http://awaitility.org)
* jose4j (org.bitbucket.b_c:jose4j:0.6.5 - https://bitbucket.org/b_c/jose4j/)
- * TagSoup (org.ccil.cowan.tagsoup:tagsoup:1.2.1 - http://home.ccil.org/~cowan/XML/tagsoup/)
* Woodstox (org.codehaus.woodstox:wstx-asl:3.2.6 - http://woodstox.codehaus.org)
* jems (org.dmfs:jems:1.18 - https://github.com/dmfs/jems)
* rfc3986-uri (org.dmfs:rfc3986-uri:0.8.1 - https://github.com/dmfs/uri-toolkit)
@@ -344,124 +334,151 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines
* Jetty :: Asynchronous HTTP Client (org.eclipse.jetty:jetty-client:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-client)
* Jetty :: Continuation (org.eclipse.jetty:jetty-continuation:9.4.15.v20190215 - http://www.eclipse.org/jetty)
* Jetty :: Continuation (org.eclipse.jetty:jetty-continuation:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-continuation)
- * Jetty :: Deployers (org.eclipse.jetty:jetty-deploy:9.4.57.v20241219 - https://jetty.org/jetty-deploy/)
- * Jetty :: Http Utility (org.eclipse.jetty:jetty-http:9.4.57.v20241219 - https://jetty.org/jetty-http/)
- * Jetty :: IO Utility (org.eclipse.jetty:jetty-io:9.4.57.v20241219 - https://jetty.org/jetty-io/)
+ * Jetty :: Deployers (org.eclipse.jetty:jetty-deploy:9.4.58.v20250814 - https://jetty.org/jetty-deploy/)
+ * Jetty :: Http Utility (org.eclipse.jetty:jetty-http:9.4.58.v20250814 - https://jetty.org/jetty-http/)
+ * Jetty :: IO Utility (org.eclipse.jetty:jetty-io:9.4.58.v20250814 - https://jetty.org/jetty-io/)
* Jetty :: JMX Management (org.eclipse.jetty:jetty-jmx:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-jmx)
* Jetty :: JNDI Naming (org.eclipse.jetty:jetty-jndi:9.4.15.v20190215 - http://www.eclipse.org/jetty)
* Jetty :: Plus (org.eclipse.jetty:jetty-plus:9.4.15.v20190215 - http://www.eclipse.org/jetty)
* Jetty :: Rewrite Handler (org.eclipse.jetty:jetty-rewrite:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-rewrite)
* Jetty :: Security (org.eclipse.jetty:jetty-security:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-security)
- * Jetty :: Security (org.eclipse.jetty:jetty-security:9.4.57.v20241219 - https://jetty.org/jetty-security/)
- * Jetty :: Server Core (org.eclipse.jetty:jetty-server:9.4.57.v20241219 - https://jetty.org/jetty-server/)
- * Jetty :: Servlet Handling (org.eclipse.jetty:jetty-servlet:9.4.57.v20241219 - https://jetty.org/jetty-servlet/)
+ * Jetty :: Security (org.eclipse.jetty:jetty-security:9.4.58.v20250814 - https://jetty.org/jetty-security/)
+ * Jetty :: Server Core (org.eclipse.jetty:jetty-server:9.4.58.v20250814 - https://jetty.org/jetty-server/)
+ * Jetty :: Servlet Handling (org.eclipse.jetty:jetty-servlet:9.4.58.v20250814 - https://jetty.org/jetty-servlet/)
* Jetty :: Utility Servlets and Filters (org.eclipse.jetty:jetty-servlets:9.4.15.v20190215 - http://www.eclipse.org/jetty)
* Jetty :: Utility Servlets and Filters (org.eclipse.jetty:jetty-servlets:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-servlets)
- * Jetty :: Utilities (org.eclipse.jetty:jetty-util:9.4.57.v20241219 - https://jetty.org/jetty-util/)
- * Jetty :: Utilities :: Ajax(JSON) (org.eclipse.jetty:jetty-util-ajax:9.4.57.v20241219 - https://jetty.org/jetty-util-ajax/)
- * Jetty :: Webapp Application Support (org.eclipse.jetty:jetty-webapp:9.4.57.v20241219 - https://jetty.org/jetty-webapp/)
+ * Jetty :: Utilities (org.eclipse.jetty:jetty-util:9.4.58.v20250814 - https://jetty.org/jetty-util/)
+ * Jetty :: Utilities :: Ajax(JSON) (org.eclipse.jetty:jetty-util-ajax:9.4.58.v20250814 - https://jetty.org/jetty-util-ajax/)
+ * Jetty :: Webapp Application Support (org.eclipse.jetty:jetty-webapp:9.4.58.v20250814 - https://jetty.org/jetty-webapp/)
* Jetty :: XML utilities (org.eclipse.jetty:jetty-xml:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-xml)
- * Jetty :: XML utilities (org.eclipse.jetty:jetty-xml:9.4.57.v20241219 - https://jetty.org/jetty-xml/)
+ * Jetty :: XML utilities (org.eclipse.jetty:jetty-xml:9.4.58.v20250814 - https://jetty.org/jetty-xml/)
* Jetty :: ALPN :: API (org.eclipse.jetty.alpn:alpn-api:1.1.3.v20160715 - http://www.eclipse.org/jetty/alpn-api)
* Jetty :: HTTP2 :: Client (org.eclipse.jetty.http2:http2-client:9.4.53.v20231009 - https://eclipse.org/jetty/http2-parent/http2-client)
- * Jetty :: HTTP2 :: Common (org.eclipse.jetty.http2:http2-common:9.4.57.v20241219 - https://jetty.org/http2-parent/http2-common/)
+ * Jetty :: HTTP2 :: Common (org.eclipse.jetty.http2:http2-common:9.4.58.v20250814 - https://jetty.org/http2-parent/http2-common/)
* Jetty :: HTTP2 :: HPACK (org.eclipse.jetty.http2:http2-hpack:9.4.53.v20231009 - https://eclipse.org/jetty/http2-parent/http2-hpack)
* Jetty :: HTTP2 :: HTTP Client Transport (org.eclipse.jetty.http2:http2-http-client-transport:9.4.53.v20231009 - https://eclipse.org/jetty/http2-parent/http2-http-client-transport)
* Jetty :: HTTP2 :: Server (org.eclipse.jetty.http2:http2-server:9.4.15.v20190215 - https://eclipse.org/jetty/http2-parent/http2-server)
* Jetty :: HTTP2 :: Server (org.eclipse.jetty.http2:http2-server:9.4.53.v20231009 - https://eclipse.org/jetty/http2-parent/http2-server)
* Jetty :: Schemas (org.eclipse.jetty.toolchain:jetty-schemas:3.1.2 - https://eclipse.org/jetty/jetty-schemas)
- * Ehcache (org.ehcache:ehcache:3.10.8 - http://ehcache.org)
+ * Ehcache (org.ehcache:ehcache:3.12.0 - http://ehcache.org)
* flyway-core (org.flywaydb:flyway-core:10.22.0 - https://flywaydb.org/flyway-core)
* flyway-database-postgresql (org.flywaydb:flyway-database-postgresql:10.22.0 - https://flywaydb.org/flyway-database-postgresql)
* Ogg and Vorbis for Java, Core (org.gagravarr:vorbis-java-core:0.8 - https://github.com/Gagravarr/VorbisJava)
* Apache Tika plugin for Ogg, Vorbis and FLAC (org.gagravarr:vorbis-java-tika:0.8 - https://github.com/Gagravarr/VorbisJava)
- * jersey-core-client (org.glassfish.jersey.core:jersey-client:3.1.10 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client)
- * jersey-core-common (org.glassfish.jersey.core:jersey-common:3.1.10 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-common)
- * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:3.1.10 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2)
+ * jersey-core-client (org.glassfish.jersey.core:jersey-client:3.1.11 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client)
+ * jersey-core-common (org.glassfish.jersey.core:jersey-common:3.1.11 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-common)
+ * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:3.1.11 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2)
* jersey-media-multipart (org.glassfish.jersey.media:jersey-media-multipart:3.1.3 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-media-multipart)
- * Hibernate Validator Engine (org.hibernate.validator:hibernate-validator:8.0.2.Final - http://hibernate.org/validator/hibernate-validator)
- * Hibernate Validator Portable Extension (org.hibernate.validator:hibernate-validator-cdi:8.0.2.Final - http://hibernate.org/validator/hibernate-validator-cdi)
+ * Hibernate Validator Engine (org.hibernate.validator:hibernate-validator:8.0.3.Final - https://hibernate.org/validator)
+ * Hibernate Validator Portable Extension (org.hibernate.validator:hibernate-validator-cdi:8.0.3.Final - https://hibernate.org/validator)
* org.immutables.value-annotations (org.immutables:value-annotations:2.9.2 - http://immutables.org/value-annotations)
- * leveldb (org.iq80.leveldb:leveldb:0.12 - http://github.com/dain/leveldb/leveldb)
- * leveldb-api (org.iq80.leveldb:leveldb-api:0.12 - http://github.com/dain/leveldb/leveldb-api)
* Javassist (org.javassist:javassist:3.30.2-GA - https://www.javassist.org/)
- * JBoss Logging 3 (org.jboss.logging:jboss-logging:3.6.1.Final - http://www.jboss.org)
+ * JBoss Logging 3 (org.jboss.logging:jboss-logging:3.5.0.Final - http://www.jboss.org)
* JDOM (org.jdom:jdom2:2.0.6.1 - http://www.jdom.org)
- * IntelliJ IDEA Annotations (org.jetbrains:annotations:13.0 - http://www.jetbrains.org)
+ * JetBrains Java Annotations (org.jetbrains:annotations:17.0.0 - https://github.com/JetBrains/java-annotations)
* Kotlin Stdlib (org.jetbrains.kotlin:kotlin-stdlib:1.8.21 - https://kotlinlang.org/)
* Kotlin Stdlib Common (org.jetbrains.kotlin:kotlin-stdlib-common:1.8.21 - https://kotlinlang.org/)
* Kotlin Stdlib Jdk7 (org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.21 - https://kotlinlang.org/)
* Kotlin Stdlib Jdk8 (org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.21 - https://kotlinlang.org/)
+ * JSpecify annotations (org.jspecify:jspecify:1.0.0 - http://jspecify.org/)
* Proj4J (org.locationtech.proj4j:proj4j:1.1.5 - https://github.com/locationtech/proj4j)
* Spatial4J (org.locationtech.spatial4j:spatial4j:0.7 - https://projects.eclipse.org/projects/locationtech.spatial4j)
* MockServer Java Client (org.mock-server:mockserver-client-java:5.15.0 - https://www.mock-server.com)
* MockServer Core (org.mock-server:mockserver-core:5.15.0 - https://www.mock-server.com)
* MockServer JUnit 4 Integration (org.mock-server:mockserver-junit-rule:5.15.0 - https://www.mock-server.com)
* MockServer & Proxy Netty (org.mock-server:mockserver-netty:5.15.0 - https://www.mock-server.com)
- * jwarc (org.netpreserve:jwarc:0.31.1 - https://github.com/iipc/jwarc)
+ * jwarc (org.netpreserve:jwarc:0.35.0 - https://github.com/iipc/jwarc)
* Objenesis (org.objenesis:objenesis:3.2 - http://objenesis.org/objenesis)
- * org.opentest4j:opentest4j (org.opentest4j:opentest4j:1.3.0 - https://github.com/ota4j-team/opentest4j)
* org.roaringbitmap:RoaringBitmap (org.roaringbitmap:RoaringBitmap:1.0.0 - https://github.com/RoaringBitmap/RoaringBitmap)
* RRD4J (org.rrd4j:rrd4j:3.5 - https://github.com/rrd4j/rrd4j/)
- * Scala Library (org.scala-lang:scala-library:2.13.2 - https://www.scala-lang.org/)
- * Scala Compiler (org.scala-lang:scala-reflect:2.13.0 - https://www.scala-lang.org/)
- * scala-collection-compat (org.scala-lang.modules:scala-collection-compat_2.13:2.1.6 - http://www.scala-lang.org/)
- * scala-java8-compat (org.scala-lang.modules:scala-java8-compat_2.13:0.9.0 - http://www.scala-lang.org/)
- * scala-parser-combinators (org.scala-lang.modules:scala-parser-combinators_2.13:1.1.2 - http://www.scala-lang.org/)
- * scala-xml (org.scala-lang.modules:scala-xml_2.13:1.3.0 - http://www.scala-lang.org/)
* JSONassert (org.skyscreamer:jsonassert:1.5.3 - https://github.com/skyscreamer/JSONassert)
* JCL 1.2 implemented over SLF4J (org.slf4j:jcl-over-slf4j:2.0.17 - http://www.slf4j.org)
- * Spring AOP (org.springframework:spring-aop:6.2.8 - https://github.com/spring-projects/spring-framework)
- * Spring Beans (org.springframework:spring-beans:6.2.8 - https://github.com/spring-projects/spring-framework)
- * Spring Context (org.springframework:spring-context:6.2.8 - https://github.com/spring-projects/spring-framework)
- * Spring Context Support (org.springframework:spring-context-support:6.2.8 - https://github.com/spring-projects/spring-framework)
- * Spring Core (org.springframework:spring-core:6.2.8 - https://github.com/spring-projects/spring-framework)
- * Spring Expression Language (SpEL) (org.springframework:spring-expression:6.2.8 - https://github.com/spring-projects/spring-framework)
- * Spring Commons Logging Bridge (org.springframework:spring-jcl:6.2.8 - https://github.com/spring-projects/spring-framework)
- * Spring JDBC (org.springframework:spring-jdbc:6.2.8 - https://github.com/spring-projects/spring-framework)
- * Spring Object/Relational Mapping (org.springframework:spring-orm:6.2.8 - https://github.com/spring-projects/spring-framework)
- * Spring TestContext Framework (org.springframework:spring-test:6.2.8 - https://github.com/spring-projects/spring-framework)
- * Spring Transaction (org.springframework:spring-tx:6.2.8 - https://github.com/spring-projects/spring-framework)
- * Spring Web (org.springframework:spring-web:6.2.8 - https://github.com/spring-projects/spring-framework)
- * Spring Web MVC (org.springframework:spring-webmvc:6.2.8 - https://github.com/spring-projects/spring-framework)
- * spring-boot (org.springframework.boot:spring-boot:3.5.3 - https://spring.io/projects/spring-boot)
- * spring-boot-actuator (org.springframework.boot:spring-boot-actuator:3.5.3 - https://spring.io/projects/spring-boot)
- * spring-boot-actuator-autoconfigure (org.springframework.boot:spring-boot-actuator-autoconfigure:3.5.3 - https://spring.io/projects/spring-boot)
- * spring-boot-autoconfigure (org.springframework.boot:spring-boot-autoconfigure:3.5.3 - https://spring.io/projects/spring-boot)
- * spring-boot-starter (org.springframework.boot:spring-boot-starter:3.5.3 - https://spring.io/projects/spring-boot)
- * spring-boot-starter-actuator (org.springframework.boot:spring-boot-starter-actuator:3.5.3 - https://spring.io/projects/spring-boot)
- * spring-boot-starter-aop (org.springframework.boot:spring-boot-starter-aop:3.5.3 - https://spring.io/projects/spring-boot)
- * spring-boot-starter-cache (org.springframework.boot:spring-boot-starter-cache:3.5.3 - https://spring.io/projects/spring-boot)
- * spring-boot-starter-data-rest (org.springframework.boot:spring-boot-starter-data-rest:3.5.3 - https://spring.io/projects/spring-boot)
- * spring-boot-starter-json (org.springframework.boot:spring-boot-starter-json:3.5.3 - https://spring.io/projects/spring-boot)
- * spring-boot-starter-log4j2 (org.springframework.boot:spring-boot-starter-log4j2:3.5.3 - https://spring.io/projects/spring-boot)
- * spring-boot-starter-security (org.springframework.boot:spring-boot-starter-security:3.5.3 - https://spring.io/projects/spring-boot)
- * spring-boot-starter-test (org.springframework.boot:spring-boot-starter-test:3.5.3 - https://spring.io/projects/spring-boot)
- * spring-boot-starter-thymeleaf (org.springframework.boot:spring-boot-starter-thymeleaf:3.5.3 - https://spring.io/projects/spring-boot)
- * spring-boot-starter-tomcat (org.springframework.boot:spring-boot-starter-tomcat:3.5.3 - https://spring.io/projects/spring-boot)
- * spring-boot-starter-web (org.springframework.boot:spring-boot-starter-web:3.5.3 - https://spring.io/projects/spring-boot)
- * spring-boot-test (org.springframework.boot:spring-boot-test:3.5.3 - https://spring.io/projects/spring-boot)
- * spring-boot-test-autoconfigure (org.springframework.boot:spring-boot-test-autoconfigure:3.5.3 - https://spring.io/projects/spring-boot)
- * Spring Data Core (org.springframework.data:spring-data-commons:3.5.1 - https://spring.io/projects/spring-data)
- * Spring Data REST - Core (org.springframework.data:spring-data-rest-core:4.5.1 - https://www.spring.io/spring-data/spring-data-rest-parent/spring-data-rest-core)
- * Spring Data REST - WebMVC (org.springframework.data:spring-data-rest-webmvc:4.5.1 - https://www.spring.io/spring-data/spring-data-rest-parent/spring-data-rest-webmvc)
- * Spring HATEOAS (org.springframework.hateoas:spring-hateoas:2.5.1 - https://github.com/spring-projects/spring-hateoas)
+ * Spring AOP (org.springframework:spring-aop:6.2.18 - https://github.com/spring-projects/spring-framework)
+ * Spring Beans (org.springframework:spring-beans:6.2.18 - https://github.com/spring-projects/spring-framework)
+ * Spring Context (org.springframework:spring-context:6.2.18 - https://github.com/spring-projects/spring-framework)
+ * Spring Context Support (org.springframework:spring-context-support:6.2.18 - https://github.com/spring-projects/spring-framework)
+ * Spring Core (org.springframework:spring-core:6.2.18 - https://github.com/spring-projects/spring-framework)
+ * Spring Expression Language (SpEL) (org.springframework:spring-expression:6.2.18 - https://github.com/spring-projects/spring-framework)
+ * Spring JDBC (org.springframework:spring-jdbc:6.2.18 - https://github.com/spring-projects/spring-framework)
+ * Spring Object/Relational Mapping (org.springframework:spring-orm:6.2.18 - https://github.com/spring-projects/spring-framework)
+ * Spring TestContext Framework (org.springframework:spring-test:6.2.18 - https://github.com/spring-projects/spring-framework)
+ * Spring Transaction (org.springframework:spring-tx:6.2.18 - https://github.com/spring-projects/spring-framework)
+ * Spring Web (org.springframework:spring-web:6.2.18 - https://github.com/spring-projects/spring-framework)
+ * Spring Web MVC (org.springframework:spring-webmvc:6.2.18 - https://github.com/spring-projects/spring-framework)
+ * spring-boot (org.springframework.boot:spring-boot:3.5.14 - https://spring.io/projects/spring-boot)
+ * spring-boot-actuator (org.springframework.boot:spring-boot-actuator:3.5.14 - https://spring.io/projects/spring-boot)
+ * spring-boot-actuator-autoconfigure (org.springframework.boot:spring-boot-actuator-autoconfigure:3.5.14 - https://spring.io/projects/spring-boot)
+ * spring-boot-autoconfigure (org.springframework.boot:spring-boot-autoconfigure:3.5.14 - https://spring.io/projects/spring-boot)
+ * spring-boot-starter (org.springframework.boot:spring-boot-starter:3.5.14 - https://spring.io/projects/spring-boot)
+ * spring-boot-starter-actuator (org.springframework.boot:spring-boot-starter-actuator:3.5.14 - https://spring.io/projects/spring-boot)
+ * spring-boot-starter-aop (org.springframework.boot:spring-boot-starter-aop:3.5.14 - https://spring.io/projects/spring-boot)
+ * spring-boot-starter-cache (org.springframework.boot:spring-boot-starter-cache:3.5.14 - https://spring.io/projects/spring-boot)
+ * spring-boot-starter-data-rest (org.springframework.boot:spring-boot-starter-data-rest:3.5.14 - https://spring.io/projects/spring-boot)
+ * spring-boot-starter-json (org.springframework.boot:spring-boot-starter-json:3.5.14 - https://spring.io/projects/spring-boot)
+ * spring-boot-starter-log4j2 (org.springframework.boot:spring-boot-starter-log4j2:3.5.14 - https://spring.io/projects/spring-boot)
+ * spring-boot-starter-security (org.springframework.boot:spring-boot-starter-security:3.5.14 - https://spring.io/projects/spring-boot)
+ * spring-boot-starter-test (org.springframework.boot:spring-boot-starter-test:3.5.14 - https://spring.io/projects/spring-boot)
+ * spring-boot-starter-thymeleaf (org.springframework.boot:spring-boot-starter-thymeleaf:3.5.14 - https://spring.io/projects/spring-boot)
+ * spring-boot-starter-tomcat (org.springframework.boot:spring-boot-starter-tomcat:3.5.14 - https://spring.io/projects/spring-boot)
+ * spring-boot-starter-web (org.springframework.boot:spring-boot-starter-web:3.5.14 - https://spring.io/projects/spring-boot)
+ * spring-boot-test (org.springframework.boot:spring-boot-test:3.5.14 - https://spring.io/projects/spring-boot)
+ * spring-boot-test-autoconfigure (org.springframework.boot:spring-boot-test-autoconfigure:3.5.14 - https://spring.io/projects/spring-boot)
+ * Spring Data Core (org.springframework.data:spring-data-commons:3.5.11 - https://spring.io/projects/spring-data)
+ * Spring Data REST - Core (org.springframework.data:spring-data-rest-core:4.5.11 - https://www.spring.io/spring-data/spring-data-rest-parent/spring-data-rest-core)
+ * Spring Data REST - WebMVC (org.springframework.data:spring-data-rest-webmvc:4.5.11 - https://www.spring.io/spring-data/spring-data-rest-parent/spring-data-rest-webmvc)
+ * Spring HATEOAS (org.springframework.hateoas:spring-hateoas:2.5.2 - https://github.com/spring-projects/spring-hateoas)
+ * spring-ldap-core (org.springframework.ldap:spring-ldap-core:3.3.7 - https://spring.io/projects/spring-ldap)
* Spring Plugin - Core (org.springframework.plugin:spring-plugin-core:3.0.0 - https://github.com/spring-projects/spring-plugin/spring-plugin-core)
- * spring-security-config (org.springframework.security:spring-security-config:6.5.1 - https://spring.io/projects/spring-security)
- * spring-security-core (org.springframework.security:spring-security-core:6.5.1 - https://spring.io/projects/spring-security)
- * spring-security-crypto (org.springframework.security:spring-security-crypto:6.5.1 - https://spring.io/projects/spring-security)
- * spring-security-test (org.springframework.security:spring-security-test:6.5.1 - https://spring.io/projects/spring-security)
- * spring-security-web (org.springframework.security:spring-security-web:6.5.1 - https://spring.io/projects/spring-security)
- * thymeleaf (org.thymeleaf:thymeleaf:3.1.3.RELEASE - http://www.thymeleaf.org/thymeleaf-lib/thymeleaf)
- * thymeleaf-spring6 (org.thymeleaf:thymeleaf-spring6:3.1.3.RELEASE - http://www.thymeleaf.org/thymeleaf-lib/thymeleaf-spring6)
+ * spring-security-config (org.springframework.security:spring-security-config:6.5.10 - https://spring.io/projects/spring-security)
+ * spring-security-core (org.springframework.security:spring-security-core:6.5.10 - https://spring.io/projects/spring-security)
+ * spring-security-crypto (org.springframework.security:spring-security-crypto:6.5.10 - https://spring.io/projects/spring-security)
+ * spring-security-test (org.springframework.security:spring-security-test:6.5.10 - https://spring.io/projects/spring-security)
+ * spring-security-web (org.springframework.security:spring-security-web:6.5.10 - https://spring.io/projects/spring-security)
+ * thymeleaf (org.thymeleaf:thymeleaf:3.1.5.RELEASE - http://www.thymeleaf.org/thymeleaf-lib/thymeleaf)
+ * thymeleaf-spring6 (org.thymeleaf:thymeleaf-spring6:3.1.5.RELEASE - http://www.thymeleaf.org/thymeleaf-lib/thymeleaf-spring6)
* unbescape (org.unbescape:unbescape:1.1.6.RELEASE - http://www.unbescape.org)
* snappy-java (org.xerial.snappy:snappy-java:1.1.10.1 - https://github.com/xerial/snappy-java)
* xml-matchers (org.xmlmatchers:xml-matchers:0.10 - http://code.google.com/p/xml-matchers/)
- * org.xmlunit:xmlunit-core (org.xmlunit:xmlunit-core:2.10.2 - https://www.xmlunit.org/)
+ * org.xmlunit:xmlunit-core (org.xmlunit:xmlunit-core:2.10.4 - https://www.xmlunit.org/)
+ * org.xmlunit:xmlunit-core (org.xmlunit:xmlunit-core:2.11.0 - https://www.xmlunit.org/)
* org.xmlunit:xmlunit-placeholders (org.xmlunit:xmlunit-placeholders:2.9.1 - https://www.xmlunit.org/xmlunit-placeholders/)
* SnakeYAML (org.yaml:snakeyaml:2.4 - https://bitbucket.org/snakeyaml/snakeyaml)
+ * AWS Java SDK :: Annotations (software.amazon.awssdk:annotations:2.43.2 - https://aws.amazon.com/sdkforjava/core/annotations)
+ * AWS Java SDK :: Arns (software.amazon.awssdk:arns:2.43.2 - https://aws.amazon.com/sdkforjava)
+ * AWS Java SDK :: Auth (software.amazon.awssdk:auth:2.43.2 - https://aws.amazon.com/sdkforjava)
+ * AWS Java SDK :: AWS Core (software.amazon.awssdk:aws-core:2.43.2 - https://aws.amazon.com/sdkforjava)
+ * AWS Java SDK :: Core :: Protocols :: AWS Query Protocol (software.amazon.awssdk:aws-query-protocol:2.43.2 - https://aws.amazon.com/sdkforjava)
+ * AWS Java SDK :: Core :: Protocols :: AWS Xml Protocol (software.amazon.awssdk:aws-xml-protocol:2.43.2 - https://aws.amazon.com/sdkforjava)
+ * AWS Java SDK :: Checksums (software.amazon.awssdk:checksums:2.43.2 - https://aws.amazon.com/sdkforjava)
+ * AWS Java SDK :: Checksums SPI (software.amazon.awssdk:checksums-spi:2.43.2 - https://aws.amazon.com/sdkforjava)
+ * AWS Java SDK :: AWS CRT Core (software.amazon.awssdk:crt-core:2.43.2 - https://aws.amazon.com/sdkforjava)
+ * AWS Java SDK :: Endpoints SPI (software.amazon.awssdk:endpoints-spi:2.43.2 - https://aws.amazon.com/sdkforjava/core/endpoints-spi)
+ * AWS Java SDK :: HTTP Auth (software.amazon.awssdk:http-auth:2.43.2 - https://aws.amazon.com/sdkforjava)
+ * AWS Java SDK :: HTTP Auth AWS (software.amazon.awssdk:http-auth-aws:2.43.2 - https://aws.amazon.com/sdkforjava)
+ * AWS Java SDK :: HTTP Auth Event Stream (software.amazon.awssdk:http-auth-aws-eventstream:2.43.2 - https://aws.amazon.com/sdkforjava)
+ * AWS Java SDK :: HTTP Auth SPI (software.amazon.awssdk:http-auth-spi:2.43.2 - https://aws.amazon.com/sdkforjava)
+ * AWS Java SDK :: HTTP Client Interface (software.amazon.awssdk:http-client-spi:2.43.2 - https://aws.amazon.com/sdkforjava/http-client-spi)
+ * AWS Java SDK :: Identity SPI (software.amazon.awssdk:identity-spi:2.43.2 - https://aws.amazon.com/sdkforjava)
+ * AWS Java SDK :: Core :: Protocols :: Json Utils (software.amazon.awssdk:json-utils:2.43.2 - https://aws.amazon.com/sdkforjava)
+ * AWS Java SDK :: Metrics SPI (software.amazon.awssdk:metrics-spi:2.43.2 - https://aws.amazon.com/sdkforjava/core/metrics-spi)
+ * AWS Java SDK :: Profiles (software.amazon.awssdk:profiles:2.43.2 - https://aws.amazon.com/sdkforjava)
+ * AWS Java SDK :: Core :: Protocols :: Protocol Core (software.amazon.awssdk:protocol-core:2.43.2 - https://aws.amazon.com/sdkforjava)
+ * AWS Java SDK :: Regions (software.amazon.awssdk:regions:2.43.2 - https://aws.amazon.com/sdkforjava/core/regions)
+ * AWS Java SDK :: Retries (software.amazon.awssdk:retries:2.43.2 - https://aws.amazon.com/sdkforjava/core/retries)
+ * AWS Java SDK :: Retries API (software.amazon.awssdk:retries-spi:2.43.2 - https://aws.amazon.com/sdkforjava/core/retries-spi)
+ * AWS Java SDK :: Services :: Amazon S3 (software.amazon.awssdk:s3:2.43.2 - https://aws.amazon.com/sdkforjava)
+ * AWS Java SDK :: SDK Core (software.amazon.awssdk:sdk-core:2.43.2 - https://aws.amazon.com/sdkforjava)
+ * AWS Java SDK :: Third Party :: Jackson-core (software.amazon.awssdk:third-party-jackson-core:2.43.2 - https://aws.amazon.com/sdkforjava)
+ * AWS Java SDK :: Utilities (software.amazon.awssdk:utils:2.43.2 - https://aws.amazon.com/sdkforjava/utils)
+ * AWS Java SDK :: Utils Lite (software.amazon.awssdk:utils-lite:2.43.2 - https://aws.amazon.com/sdkforjava)
+ * software.amazon.awssdk.crt:aws-crt (software.amazon.awssdk.crt:aws-crt:0.45.2 - https://github.com/awslabs/aws-crt-java)
+ * AWS Event Stream (software.amazon.eventstream:eventstream:1.0.1 - https://github.com/awslabs/aws-eventstream-java)
* Xerces2-j (xerces:xercesImpl:2.12.2 - https://xerces.apache.org/xerces2-j/)
+ BSD 2-Clause License:
+
+ * zstd-jni (com.github.luben:zstd-jni:1.5.7-4 - https://github.com/luben/zstd-jni)
+
BSD License:
* Adobe XMPCore (com.adobe.xmp:xmpcore:6.1.11 - https://www.adobe.com/devnet/xmp/library/eula-xmp-library-java.html)
@@ -473,15 +490,15 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines
* Protocol Buffers [Core] (com.google.protobuf:protobuf-java:3.24.3 - https://developers.google.com/protocol-buffers/protobuf-java/)
* JZlib (com.jcraft:jzlib:1.1.3 - http://www.jcraft.com/jzlib/)
* jmustache (com.samskivert:jmustache:1.15 - http://github.com/samskivert/jmustache)
- * dnsjava (dnsjava:dnsjava:3.6.3 - https://github.com/dnsjava/dnsjava)
- * jaxen (jaxen:jaxen:2.0.0 - http://www.cafeconleche.org/jaxen/jaxen)
+ * dnsjava (dnsjava:dnsjava:3.6.4 - https://github.com/dnsjava/dnsjava)
+ * jaxen (jaxen:jaxen:2.0.1 - https://jaxen-xpath.github.io/jaxen/jaxen/)
* ANTLR 4 Runtime (org.antlr:antlr4-runtime:4.13.2 - https://www.antlr.org/antlr4-runtime/)
* commons-compiler (org.codehaus.janino:commons-compiler:3.1.8 - http://janino-compiler.github.io/commons-compiler/)
* janino (org.codehaus.janino:janino:3.1.8 - http://janino-compiler.github.io/janino/)
* Stax2 API (org.codehaus.woodstox:stax2-api:4.2.1 - http://github.com/FasterXML/stax2-api)
* Hamcrest Date (org.exparity:hamcrest-date:2.0.8 - https://github.com/exparity/hamcrest-date)
- * jersey-core-client (org.glassfish.jersey.core:jersey-client:3.1.10 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client)
- * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:3.1.10 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2)
+ * jersey-core-client (org.glassfish.jersey.core:jersey-client:3.1.11 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client)
+ * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:3.1.11 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2)
* jersey-media-multipart (org.glassfish.jersey.media:jersey-media-multipart:3.1.3 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-media-multipart)
* Hamcrest (org.hamcrest:hamcrest:2.2 - http://hamcrest.org/JavaHamcrest/)
* Hamcrest Core (org.hamcrest:hamcrest-core:2.2 - http://hamcrest.org/JavaHamcrest/)
@@ -491,39 +508,34 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines
* asm-analysis (org.ow2.asm:asm-analysis:8.0.1 - http://asm.ow2.io/)
* asm-commons (org.ow2.asm:asm-commons:8.0.1 - http://asm.ow2.io/)
* asm-tree (org.ow2.asm:asm-tree:8.0.1 - http://asm.ow2.io/)
- * PostgreSQL JDBC Driver (org.postgresql:postgresql:42.7.7 - https://jdbc.postgresql.org)
+ * PostgreSQL JDBC Driver (org.postgresql:postgresql:42.7.11 - https://jdbc.postgresql.org)
* Reflections (org.reflections:reflections:0.9.12 - http://github.com/ronmamo/reflections)
* JMatIO (org.tallison:jmatio:1.5 - https://github.com/tballison/jmatio)
- * XZ for Java (org.tukaani:xz:1.10 - https://tukaani.org/xz/java.html)
+ * XZ for Java (org.tukaani:xz:1.12 - https://tukaani.org/xz/java.html)
* XMLUnit for Java (xmlunit:xmlunit:1.3 - http://xmlunit.sourceforge.net/)
- CC0:
-
- * reactive-streams (org.reactivestreams:reactive-streams:1.0.2 - http://www.reactive-streams.org/)
-
Common Development and Distribution License (CDDL):
* JavaMail API (no providers) (com.sun.mail:mailapi:1.6.2 - http://javaee.github.io/javamail/mailapi)
* Old JAXB Core (com.sun.xml.bind:jaxb-core:2.3.0.1 - http://jaxb.java.net/jaxb-bundles/jaxb-core)
* Old JAXB Runtime (com.sun.xml.bind:jaxb-impl:2.3.1 - http://jaxb.java.net/jaxb-bundles/jaxb-impl)
* Jakarta Annotations API (jakarta.annotation:jakarta.annotation-api:2.1.1 - https://projects.eclipse.org/projects/ee4j.ca)
- * Jakarta Mail API (jakarta.mail:jakarta.mail-api:2.1.3 - https://projects.eclipse.org/projects/ee4j/jakarta.mail-api)
+ * Jakarta Mail API (jakarta.mail:jakarta.mail-api:2.1.5 - https://projects.eclipse.org/projects/ee4j/jakarta.mail-api)
* Jakarta Servlet (jakarta.servlet:jakarta.servlet-api:6.1.0 - https://projects.eclipse.org/projects/ee4j.servlet)
* jakarta.transaction API (jakarta.transaction:jakarta.transaction-api:2.0.1 - https://projects.eclipse.org/projects/ee4j.jta)
* JavaBeans Activation Framework API jar (javax.activation:javax.activation-api:1.2.0 - http://java.net/all/javax.activation-api/)
- * javax.annotation API (javax.annotation:javax.annotation-api:1.3 - http://jcp.org/en/jsr/detail?id=250)
* Java Servlet API (javax.servlet:javax.servlet-api:3.1.0 - http://servlet-spec.java.net)
* javax.transaction API (javax.transaction:javax.transaction-api:1.3 - http://jta-spec.java.net)
* jaxb-api (javax.xml.bind:jaxb-api:2.3.1 - https://github.com/javaee/jaxb-spec/jaxb-api)
- * JHighlight (org.codelibs:jhighlight:1.1.0 - https://github.com/codelibs/jhighlight)
- * Angus Mail default provider (org.eclipse.angus:jakarta.mail:2.0.3 - http://eclipse-ee4j.github.io/angus-mail/jakarta.mail)
+ * JHighlight (org.codelibs:jhighlight:1.1.1 - https://github.com/codelibs/jhighlight)
+ * Angus Mail default provider (org.eclipse.angus:jakarta.mail:2.0.5 - http://eclipse-ee4j.github.io/angus-mail/jakarta.mail)
* HK2 API module (org.glassfish.hk2:hk2-api:3.0.6 - https://github.com/eclipse-ee4j/glassfish-hk2/hk2-api)
* ServiceLocator Default Implementation (org.glassfish.hk2:hk2-locator:3.0.6 - https://github.com/eclipse-ee4j/glassfish-hk2/hk2-locator)
* HK2 Implementation Utilities (org.glassfish.hk2:hk2-utils:3.0.6 - https://github.com/eclipse-ee4j/glassfish-hk2/hk2-utils)
* OSGi resource locator (org.glassfish.hk2:osgi-resource-locator:1.0.3 - https://projects.eclipse.org/projects/ee4j/osgi-resource-locator)
* aopalliance version 1.0 repackaged as a module (org.glassfish.hk2.external:aopalliance-repackaged:3.0.6 - https://github.com/eclipse-ee4j/glassfish-hk2/external/aopalliance-repackaged)
- * jersey-core-client (org.glassfish.jersey.core:jersey-client:3.1.10 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client)
- * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:3.1.10 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2)
+ * jersey-core-client (org.glassfish.jersey.core:jersey-client:3.1.11 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client)
+ * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:3.1.11 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2)
* jersey-media-multipart (org.glassfish.jersey.media:jersey-media-multipart:3.1.3 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-media-multipart)
Cordra (Version 2) License Agreement:
@@ -538,17 +550,17 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines
Eclipse Distribution License, Version 1.0:
* istack common utility code runtime (com.sun.istack:istack-commons-runtime:4.1.2 - https://projects.eclipse.org/projects/ee4j/istack-commons/istack-commons-runtime)
- * Jakarta Activation API (jakarta.activation:jakarta.activation-api:2.1.3 - https://github.com/jakartaee/jaf-api)
- * Jakarta Mail API (jakarta.mail:jakarta.mail-api:2.1.3 - https://projects.eclipse.org/projects/ee4j/jakarta.mail-api)
+ * Jakarta Activation API (jakarta.activation:jakarta.activation-api:2.1.4 - https://github.com/jakartaee/jaf-api)
+ * Jakarta Mail API (jakarta.mail:jakarta.mail-api:2.1.5 - https://projects.eclipse.org/projects/ee4j/jakarta.mail-api)
* Jakarta Persistence API (jakarta.persistence:jakarta.persistence-api:3.1.0 - https://github.com/eclipse-ee4j/jpa-api)
- * Jakarta XML Binding API (jakarta.xml.bind:jakarta.xml.bind-api:4.0.2 - https://github.com/jakartaee/jaxb-api/jakarta.xml.bind-api)
- * Angus Activation Registries (org.eclipse.angus:angus-activation:2.0.2 - https://github.com/eclipse-ee4j/angus-activation/angus-activation)
- * Angus Mail default provider (org.eclipse.angus:jakarta.mail:2.0.3 - http://eclipse-ee4j.github.io/angus-mail/jakarta.mail)
- * JAXB Core (org.glassfish.jaxb:jaxb-core:4.0.5 - https://eclipse-ee4j.github.io/jaxb-ri/)
- * JAXB Runtime (org.glassfish.jaxb:jaxb-runtime:4.0.5 - https://eclipse-ee4j.github.io/jaxb-ri/)
- * TXW2 Runtime (org.glassfish.jaxb:txw2:4.0.5 - https://eclipse-ee4j.github.io/jaxb-ri/)
- * jersey-core-client (org.glassfish.jersey.core:jersey-client:3.1.10 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client)
- * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:3.1.10 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2)
+ * Jakarta XML Binding API (jakarta.xml.bind:jakarta.xml.bind-api:4.0.5 - https://github.com/jakartaee/jaxb-api/jakarta.xml.bind-api)
+ * Angus Activation Registries (org.eclipse.angus:angus-activation:2.0.3 - https://github.com/eclipse-ee4j/angus-activation/angus-activation)
+ * Angus Mail default provider (org.eclipse.angus:jakarta.mail:2.0.5 - http://eclipse-ee4j.github.io/angus-mail/jakarta.mail)
+ * JAXB Core (org.glassfish.jaxb:jaxb-core:4.0.8 - https://eclipse-ee4j.github.io/jaxb-ri/)
+ * JAXB Runtime (org.glassfish.jaxb:jaxb-runtime:4.0.8 - https://eclipse-ee4j.github.io/jaxb-ri/)
+ * TXW2 Runtime (org.glassfish.jaxb:txw2:4.0.8 - https://eclipse-ee4j.github.io/jaxb-ri/)
+ * jersey-core-client (org.glassfish.jersey.core:jersey-client:3.1.11 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client)
+ * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:3.1.11 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2)
* jersey-media-multipart (org.glassfish.jersey.media:jersey-media-multipart:3.1.3 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-media-multipart)
* MIME streaming extension (org.jvnet.mimepull:mimepull:1.9.15 - https://github.com/eclipse-ee4j/metro-mimepull)
* org.locationtech.jts:jts-core (org.locationtech.jts:jts-core:1.19.0 - https://www.locationtech.org/projects/technology.jts/jts-modules/jts-core)
@@ -557,16 +569,16 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines
Eclipse Public License:
* System Rules (com.github.stefanbirkner:system-rules:1.19.0 - http://stefanbirkner.github.io/system-rules/)
- * H2 Database Engine (com.h2database:h2:2.3.232 - https://h2database.com)
+ * H2 Database Engine (com.h2database:h2:2.4.240 - https://h2database.com)
* Jakarta Annotations API (jakarta.annotation:jakarta.annotation-api:2.1.1 - https://projects.eclipse.org/projects/ee4j.ca)
- * Jakarta Mail API (jakarta.mail:jakarta.mail-api:2.1.3 - https://projects.eclipse.org/projects/ee4j/jakarta.mail-api)
+ * Jakarta Mail API (jakarta.mail:jakarta.mail-api:2.1.5 - https://projects.eclipse.org/projects/ee4j/jakarta.mail-api)
* Jakarta Persistence API (jakarta.persistence:jakarta.persistence-api:3.1.0 - https://github.com/eclipse-ee4j/jpa-api)
* Jakarta Servlet (jakarta.servlet:jakarta.servlet-api:6.1.0 - https://projects.eclipse.org/projects/ee4j.servlet)
* jakarta.transaction API (jakarta.transaction:jakarta.transaction-api:2.0.1 - https://projects.eclipse.org/projects/ee4j.jta)
* Jakarta RESTful WS API (jakarta.ws.rs:jakarta.ws.rs-api:3.1.0 - https://github.com/eclipse-ee4j/jaxrs-api)
* JUnit (junit:junit:4.13.2 - http://junit.org)
- * AspectJ Weaver (org.aspectj:aspectjweaver:1.9.24 - https://www.eclipse.org/aspectj/)
- * Angus Mail default provider (org.eclipse.angus:jakarta.mail:2.0.3 - http://eclipse-ee4j.github.io/angus-mail/jakarta.mail)
+ * AspectJ Weaver (org.aspectj:aspectjweaver:1.9.25.1 - https://www.eclipse.org/aspectj/)
+ * Angus Mail default provider (org.eclipse.angus:jakarta.mail:2.0.5 - http://eclipse-ee4j.github.io/angus-mail/jakarta.mail)
* Jetty :: Apache JSP Implementation (org.eclipse.jetty:apache-jsp:9.4.15.v20190215 - http://www.eclipse.org/jetty)
* Apache :: JSTL module (org.eclipse.jetty:apache-jstl:9.4.15.v20190215 - http://tomcat.apache.org/taglibs/standard/)
* Jetty :: ALPN :: Client (org.eclipse.jetty:jetty-alpn-client:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-client)
@@ -579,27 +591,27 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines
* Jetty :: Asynchronous HTTP Client (org.eclipse.jetty:jetty-client:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-client)
* Jetty :: Continuation (org.eclipse.jetty:jetty-continuation:9.4.15.v20190215 - http://www.eclipse.org/jetty)
* Jetty :: Continuation (org.eclipse.jetty:jetty-continuation:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-continuation)
- * Jetty :: Deployers (org.eclipse.jetty:jetty-deploy:9.4.57.v20241219 - https://jetty.org/jetty-deploy/)
- * Jetty :: Http Utility (org.eclipse.jetty:jetty-http:9.4.57.v20241219 - https://jetty.org/jetty-http/)
- * Jetty :: IO Utility (org.eclipse.jetty:jetty-io:9.4.57.v20241219 - https://jetty.org/jetty-io/)
+ * Jetty :: Deployers (org.eclipse.jetty:jetty-deploy:9.4.58.v20250814 - https://jetty.org/jetty-deploy/)
+ * Jetty :: Http Utility (org.eclipse.jetty:jetty-http:9.4.58.v20250814 - https://jetty.org/jetty-http/)
+ * Jetty :: IO Utility (org.eclipse.jetty:jetty-io:9.4.58.v20250814 - https://jetty.org/jetty-io/)
* Jetty :: JMX Management (org.eclipse.jetty:jetty-jmx:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-jmx)
* Jetty :: JNDI Naming (org.eclipse.jetty:jetty-jndi:9.4.15.v20190215 - http://www.eclipse.org/jetty)
* Jetty :: Plus (org.eclipse.jetty:jetty-plus:9.4.15.v20190215 - http://www.eclipse.org/jetty)
* Jetty :: Rewrite Handler (org.eclipse.jetty:jetty-rewrite:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-rewrite)
* Jetty :: Security (org.eclipse.jetty:jetty-security:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-security)
- * Jetty :: Security (org.eclipse.jetty:jetty-security:9.4.57.v20241219 - https://jetty.org/jetty-security/)
- * Jetty :: Server Core (org.eclipse.jetty:jetty-server:9.4.57.v20241219 - https://jetty.org/jetty-server/)
- * Jetty :: Servlet Handling (org.eclipse.jetty:jetty-servlet:9.4.57.v20241219 - https://jetty.org/jetty-servlet/)
+ * Jetty :: Security (org.eclipse.jetty:jetty-security:9.4.58.v20250814 - https://jetty.org/jetty-security/)
+ * Jetty :: Server Core (org.eclipse.jetty:jetty-server:9.4.58.v20250814 - https://jetty.org/jetty-server/)
+ * Jetty :: Servlet Handling (org.eclipse.jetty:jetty-servlet:9.4.58.v20250814 - https://jetty.org/jetty-servlet/)
* Jetty :: Utility Servlets and Filters (org.eclipse.jetty:jetty-servlets:9.4.15.v20190215 - http://www.eclipse.org/jetty)
* Jetty :: Utility Servlets and Filters (org.eclipse.jetty:jetty-servlets:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-servlets)
- * Jetty :: Utilities (org.eclipse.jetty:jetty-util:9.4.57.v20241219 - https://jetty.org/jetty-util/)
- * Jetty :: Utilities :: Ajax(JSON) (org.eclipse.jetty:jetty-util-ajax:9.4.57.v20241219 - https://jetty.org/jetty-util-ajax/)
- * Jetty :: Webapp Application Support (org.eclipse.jetty:jetty-webapp:9.4.57.v20241219 - https://jetty.org/jetty-webapp/)
+ * Jetty :: Utilities (org.eclipse.jetty:jetty-util:9.4.58.v20250814 - https://jetty.org/jetty-util/)
+ * Jetty :: Utilities :: Ajax(JSON) (org.eclipse.jetty:jetty-util-ajax:9.4.58.v20250814 - https://jetty.org/jetty-util-ajax/)
+ * Jetty :: Webapp Application Support (org.eclipse.jetty:jetty-webapp:9.4.58.v20250814 - https://jetty.org/jetty-webapp/)
* Jetty :: XML utilities (org.eclipse.jetty:jetty-xml:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-xml)
- * Jetty :: XML utilities (org.eclipse.jetty:jetty-xml:9.4.57.v20241219 - https://jetty.org/jetty-xml/)
+ * Jetty :: XML utilities (org.eclipse.jetty:jetty-xml:9.4.58.v20250814 - https://jetty.org/jetty-xml/)
* Jetty :: ALPN :: API (org.eclipse.jetty.alpn:alpn-api:1.1.3.v20160715 - http://www.eclipse.org/jetty/alpn-api)
* Jetty :: HTTP2 :: Client (org.eclipse.jetty.http2:http2-client:9.4.53.v20231009 - https://eclipse.org/jetty/http2-parent/http2-client)
- * Jetty :: HTTP2 :: Common (org.eclipse.jetty.http2:http2-common:9.4.57.v20241219 - https://jetty.org/http2-parent/http2-common/)
+ * Jetty :: HTTP2 :: Common (org.eclipse.jetty.http2:http2-common:9.4.58.v20250814 - https://jetty.org/http2-parent/http2-common/)
* Jetty :: HTTP2 :: HPACK (org.eclipse.jetty.http2:http2-hpack:9.4.53.v20231009 - https://eclipse.org/jetty/http2-parent/http2-hpack)
* Jetty :: HTTP2 :: HTTP Client Transport (org.eclipse.jetty.http2:http2-http-client-transport:9.4.53.v20231009 - https://eclipse.org/jetty/http2-parent/http2-http-client-transport)
* Jetty :: HTTP2 :: Server (org.eclipse.jetty.http2:http2-server:9.4.15.v20190215 - https://eclipse.org/jetty/http2-parent/http2-server)
@@ -611,13 +623,10 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines
* HK2 Implementation Utilities (org.glassfish.hk2:hk2-utils:3.0.6 - https://github.com/eclipse-ee4j/glassfish-hk2/hk2-utils)
* OSGi resource locator (org.glassfish.hk2:osgi-resource-locator:1.0.3 - https://projects.eclipse.org/projects/ee4j/osgi-resource-locator)
* aopalliance version 1.0 repackaged as a module (org.glassfish.hk2.external:aopalliance-repackaged:3.0.6 - https://github.com/eclipse-ee4j/glassfish-hk2/external/aopalliance-repackaged)
- * jersey-core-client (org.glassfish.jersey.core:jersey-client:3.1.10 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client)
- * jersey-core-common (org.glassfish.jersey.core:jersey-common:3.1.10 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-common)
- * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:3.1.10 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2)
+ * jersey-core-client (org.glassfish.jersey.core:jersey-client:3.1.11 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client)
+ * jersey-core-common (org.glassfish.jersey.core:jersey-common:3.1.11 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-common)
+ * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:3.1.11 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2)
* jersey-media-multipart (org.glassfish.jersey.media:jersey-media-multipart:3.1.3 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-media-multipart)
- * JUnit Platform Commons (org.junit.platform:junit-platform-commons:1.11.4 - https://junit.org/junit5/)
- * JUnit Platform Engine API (org.junit.platform:junit-platform-engine:1.11.4 - https://junit.org/junit5/)
- * JUnit Vintage Engine (org.junit.vintage:junit-vintage-engine:5.11.4 - https://junit.org/junit5/)
* org.locationtech.jts:jts-core (org.locationtech.jts:jts-core:1.19.0 - https://www.locationtech.org/projects/technology.jts/jts-modules/jts-core)
* org.locationtech.jts.io:jts-io-common (org.locationtech.jts.io:jts-io-common:1.19.0 - https://www.locationtech.org/projects/technology.jts/jts-modules/jts-io/jts-io-common)
@@ -640,14 +649,14 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines
* msg-simple (com.github.java-json-tools:msg-simple:1.2 - https://github.com/java-json-tools/msg-simple)
* uri-template (com.github.java-json-tools:uri-template:0.10 - https://github.com/java-json-tools/uri-template)
* FindBugs-Annotations (com.google.code.findbugs:annotations:3.0.1u2 - http://findbugs.sourceforge.net/)
- * JHighlight (org.codelibs:jhighlight:1.1.0 - https://github.com/codelibs/jhighlight)
+ * JHighlight (org.codelibs:jhighlight:1.1.1 - https://github.com/codelibs/jhighlight)
* Hibernate Commons Annotations (org.hibernate.common:hibernate-commons-annotations:6.0.6.Final - http://hibernate.org)
- * Hibernate ORM - hibernate-core (org.hibernate.orm:hibernate-core:6.4.8.Final - https://hibernate.org/orm)
- * Hibernate ORM - hibernate-jcache (org.hibernate.orm:hibernate-jcache:6.4.8.Final - https://hibernate.org/orm)
- * Hibernate ORM - hibernate-jpamodelgen (org.hibernate.orm:hibernate-jpamodelgen:6.4.8.Final - https://hibernate.org/orm)
+ * Hibernate ORM - hibernate-core (org.hibernate.orm:hibernate-core:6.4.10.Final - https://hibernate.org/orm)
+ * Hibernate ORM - hibernate-jcache (org.hibernate.orm:hibernate-jcache:6.4.10.Final - https://hibernate.org/orm)
+ * Hibernate ORM - hibernate-jpamodelgen (org.hibernate.orm:hibernate-jpamodelgen:6.4.10.Final - https://hibernate.org/orm)
* im4java (org.im4java:im4java:1.4.0 - http://sourceforge.net/projects/im4java/)
* Javassist (org.javassist:javassist:3.30.2-GA - https://www.javassist.org/)
- * XOM (xom:xom:1.3.9 - https://xom.nu)
+ * XOM (xom:xom:1.4.1 - https://xom.nu)
Go License:
@@ -661,28 +670,34 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines
* Simple Magic (com.j256.simplemagic:simplemagic:1.17 - https://256stuff.com/sources/simplemagic/)
+ LGPL-2.1-or-later:
+
+ * Java Native Access (net.java.dev.jna:jna:5.18.1 - https://github.com/java-native-access/jna)
+
MIT License:
* dexx (com.github.andrewoma.dexx:collection:0.7 - https://github.com/andrewoma/dexx)
- * better-files (com.github.pathikrit:better-files_2.13:3.9.1 - https://github.com/pathikrit/better-files)
* Java SemVer (com.github.zafarkhaja:java-semver:0.9.0 - https://github.com/zafarkhaja/jsemver)
- * dd-plist (com.googlecode.plist:dd-plist:1.28 - http://www.github.com/3breadt/dd-plist)
+ * dd-plist (com.googlecode.plist:dd-plist:1.29 - http://www.github.com/3breadt/dd-plist)
* DigitalCollections: IIIF API Library (de.digitalcollections.iiif:iiif-apis:0.3.11 - https://github.com/dbmdz/iiif-apis)
- * s3mock (io.findify:s3mock_2.13:0.2.6 - https://github.com/findify/s3mock)
* ClassGraph (io.github.classgraph:classgraph:4.8.165 - https://github.com/classgraph/classgraph)
* JOpt Simple (net.sf.jopt-simple:jopt-simple:5.0.4 - http://jopt-simple.github.io/jopt-simple)
- * Bouncy Castle JavaMail S/MIME APIs (org.bouncycastle:bcmail-jdk18on:1.80 - https://www.bouncycastle.org/download/bouncy-castle-java/)
+ * Bouncy Castle JavaMail Jakarta S/MIME APIs (org.bouncycastle:bcjmail-jdk18on:1.83 - https://www.bouncycastle.org/download/bouncy-castle-java/)
* Bouncy Castle PKIX, CMS, EAC, TSP, PKCS, OCSP, CMP, and CRMF APIs (org.bouncycastle:bcpkix-jdk18on:1.81 - https://www.bouncycastle.org/download/bouncy-castle-java/)
* Bouncy Castle Provider (org.bouncycastle:bcprov-jdk18on:1.81 - https://www.bouncycastle.org/download/bouncy-castle-java/)
* Bouncy Castle ASN.1 Extension and Utility APIs (org.bouncycastle:bcutil-jdk18on:1.81 - https://www.bouncycastle.org/download/bouncy-castle-java/)
* org.brotli:dec (org.brotli:dec:0.1.2 - http://brotli.org/dec)
- * Checker Qual (org.checkerframework:checker-qual:3.49.5 - https://checkerframework.org/)
- * jersey-core-client (org.glassfish.jersey.core:jersey-client:3.1.10 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client)
- * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:3.1.10 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2)
+ * Checker Qual (org.checkerframework:checker-qual:3.23.0 - https://checkerframework.org)
+ * Checker Qual (org.checkerframework:checker-qual:3.37.0 - https://checkerframework.org/)
+ * jersey-core-client (org.glassfish.jersey.core:jersey-client:3.1.11 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client)
+ * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:3.1.11 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2)
* jersey-media-multipart (org.glassfish.jersey.media:jersey-media-multipart:3.1.3 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-media-multipart)
+ * jsoup Java HTML Parser (org.jsoup:jsoup:1.22.1 - https://jsoup.org/)
* mockito-core (org.mockito:mockito-core:3.12.4 - https://github.com/mockito/mockito)
* mockito-inline (org.mockito:mockito-inline:3.12.4 - https://github.com/mockito/mockito)
+ * Duct Tape (org.rnorth.duct-tape:duct-tape:1.0.8 - https://github.com/rnorth/duct-tape)
* SLF4J API Module (org.slf4j:slf4j-api:2.0.17 - http://www.slf4j.org)
+ * Testcontainers Core (org.testcontainers:testcontainers:2.0.5 - https://java.testcontainers.org)
* HAL Browser (org.webjars:hal-browser:ad9b865 - http://webjars.org)
* toastr (org.webjars.bowergithub.codeseven:toastr:2.1.4 - http://webjars.org)
* backbone (org.webjars.bowergithub.jashkenas:backbone:1.4.1 - https://www.webjars.org)
@@ -690,31 +705,39 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines
* jquery (org.webjars.bowergithub.jquery:jquery-dist:3.7.1 - https://www.webjars.org)
* urijs (org.webjars.bowergithub.medialize:uri.js:1.19.11 - https://www.webjars.org)
* bootstrap (org.webjars.bowergithub.twbs:bootstrap:4.6.2 - https://www.webjars.org)
- * core-js (org.webjars.npm:core-js:3.42.0 - https://www.webjars.org)
+ * core-js (org.webjars.npm:core-js:3.49.0 - https://www.webjars.org)
* @json-editor/json-editor (org.webjars.npm:json-editor__json-editor:2.15.2 - https://www.webjars.org)
+ MIT-0:
+
+ * reactive-streams (org.reactivestreams:reactive-streams:1.0.4 - http://www.reactive-streams.org/)
+
Mozilla Public License:
* juniversalchardet (com.github.albfernandez:juniversalchardet:2.5.0 - https://github.com/albfernandez/juniversalchardet)
- * H2 Database Engine (com.h2database:h2:2.3.232 - https://h2database.com)
+ * H2 Database Engine (com.h2database:h2:2.4.240 - https://h2database.com)
* Saxon-HE (net.sf.saxon:Saxon-HE:9.9.1-8 - http://www.saxonica.com/)
* Javassist (org.javassist:javassist:3.30.2-GA - https://www.javassist.org/)
* Mozilla Rhino (org.mozilla:rhino:1.7.7.2 - https://developer.mozilla.org/en/Rhino)
Public Domain:
- * jersey-core-client (org.glassfish.jersey.core:jersey-client:3.1.10 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client)
- * jersey-core-common (org.glassfish.jersey.core:jersey-common:3.1.10 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-common)
- * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:3.1.10 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2)
+ * jersey-core-client (org.glassfish.jersey.core:jersey-client:3.1.11 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client)
+ * jersey-core-common (org.glassfish.jersey.core:jersey-common:3.1.11 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-common)
+ * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:3.1.11 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2)
* jersey-media-multipart (org.glassfish.jersey.media:jersey-media-multipart:3.1.3 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-media-multipart)
* HdrHistogram (org.hdrhistogram:HdrHistogram:2.2.2 - http://hdrhistogram.github.io/HdrHistogram/)
* JSON in Java (org.json:json:20231013 - https://github.com/douglascrockford/JSON-java)
* LatencyUtils (org.latencyutils:LatencyUtils:2.0.3 - http://latencyutils.github.io/LatencyUtils/)
* Reflections (org.reflections:reflections:0.9.12 - http://github.com/ronmamo/reflections)
+ The Apache Software License, version 2.0:
+
+ * picocli (info.picocli:picocli:4.7.7 - https://picocli.info)
+
UnRar License:
- * Java Unrar (com.github.junrar:junrar:7.5.5 - https://github.com/junrar/junrar)
+ * Java Unrar (com.github.junrar:junrar:7.5.8 - https://github.com/junrar/junrar)
Unicode/ICU License:
@@ -722,12 +745,12 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines
W3C license:
- * jersey-core-client (org.glassfish.jersey.core:jersey-client:3.1.10 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client)
- * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:3.1.10 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2)
+ * jersey-core-client (org.glassfish.jersey.core:jersey-client:3.1.11 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client)
+ * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:3.1.11 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2)
* jersey-media-multipart (org.glassfish.jersey.media:jersey-media-multipart:3.1.3 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-media-multipart)
jQuery license:
- * jersey-core-client (org.glassfish.jersey.core:jersey-client:3.1.10 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client)
- * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:3.1.10 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2)
+ * jersey-core-client (org.glassfish.jersey.core:jersey-client:3.1.11 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client)
+ * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:3.1.11 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2)
* jersey-media-multipart (org.glassfish.jersey.media:jersey-media-multipart:3.1.3 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-media-multipart)
diff --git a/README.md b/README.md
index 1d93abe49948..53e7e8cc3461 100644
--- a/README.md
+++ b/README.md
@@ -65,7 +65,7 @@ Additional support options are at https://wiki.lyrasis.org/display/DSPACE/Suppor
DSpace also has an active service provider network. If you'd rather hire a service provider to
install, upgrade, customize, or host DSpace, then we recommend getting in touch with one of our
-[Registered Service Providers](http://www.dspace.org/service-providers).
+[Registered Service Providers](https://dspace.org/registered-service-providers/).
## Issue Tracker
diff --git a/checkstyle-suppressions.xml b/checkstyle-suppressions.xml
index 46bd9ca80d62..963165f75cef 100644
--- a/checkstyle-suppressions.xml
+++ b/checkstyle-suppressions.xml
@@ -8,4 +8,5 @@
on JMockIt Expectations blocks and similar. See https://github.com/checkstyle/checkstyle/issues/3739 -->
+
diff --git a/docker-compose-cli.yml b/docker-compose-cli.yml
index 942354f4d5c8..565d9b6bde91 100644
--- a/docker-compose-cli.yml
+++ b/docker-compose-cli.yml
@@ -18,24 +18,18 @@ services:
# See https://github.com/DSpace/DSpace/blob/main/dspace/config/config-definition.xml
# __P__ => "." (e.g. dspace__P__dir => dspace.dir)
# __D__ => "-" (e.g. google__D__metadata => google-metadata)
- # dspace.dir: Must match with Dockerfile's DSPACE_INSTALL directory.
- dspace__P__dir: /dspace
# db.url: Ensure we are using the 'dspacedb' image for our database
# UMD Customization
- db__P__url: 'jdbc:postgresql://dspacedb:5432/drum'
+ db__P__url: {db__P__url:-jdbc:postgresql://dspacedb:5432/drum}
# End UMD Customization
# solr.server: Ensure we are using the 'dspacesolr' image for Solr
- solr__P__server: http://dspacesolr:8983/solr
+ solr__P__server: ${solr__P__server:-http://dspacesolr:8983/solr}
volumes:
# Keep DSpace assetstore directory between reboots
- assetstore:/dspace/assetstore
# Mount local [src]/dspace/config/ to container. This syncs your local configs with container
# NOTE: Environment variables specified above will OVERRIDE any configs in local.cfg or dspace.cfg
- ./dspace/config:/dspace/config
- entrypoint: /dspace/bin/dspace
- command: help
- tty: true
- stdin_open: true
volumes:
assetstore:
diff --git a/docker-compose.yml b/docker-compose.yml
index 36ff1cf6f63b..5f0c0fd6d263 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -5,6 +5,8 @@ networks:
# Define a custom subnet for our DSpace network, so that we can easily trust requests from host to container.
# If you customize this value, be sure to customize the 'proxies.trusted.ipranges' env variable below.
- subnet: 172.23.0.0/16
+ # Explicitly set external=false because this script creates the network.
+ external: false
services:
# UMD Customization
# Nginx server configuration for supporting HTTPS connections from the
@@ -29,26 +31,31 @@ services:
# See https://github.com/DSpace/DSpace/blob/main/dspace/config/config-definition.xml
# __P__ => "." (e.g. dspace__P__dir => dspace.dir)
# __D__ => "-" (e.g. google__D__metadata => google-metadata)
- # dspace.dir: Must match with Dockerfile's DSPACE_INSTALL directory.
- dspace__P__dir: /dspace
- # Uncomment to set a non-default value for dspace.server.url or dspace.ui.url
+ # Uncomment to set a non-default value for dspace.dir, dspace.server.url or dspace.ui.url
+ # dspace__P__dir: /dspace
# UMD Customization
dspace__P__server__P__url: https://api.drum-local.lib.umd.edu/server
dspace__P__ui__P__url: https://drum-local.lib.umd.edu:4000
# End UMD Customization
- dspace__P__name: 'DSpace Started with Docker Compose'
+ # Set SSR URL to the Docker container name so that UI can contact container directly in Production mode.
+ # (This is necessary for docker-compose-angular.yml as it uses production mode by default)
+ dspace__P__server__P__ssr__P__url: ${dspace__P__server__P__ssr__P__url:-http://dspace:8080/server}
+ dspace__P__name: ${dspace__P__name:-DSpace Started with Docker Compose}
+ # UMD Customization
+ # Customization for Matomo - remove when updating to DSpace 9 or later
+ # matomo.tracker.url: Ensure we are using the 'matomo' image for Matomo
+ matomo__P__tracker__P__url: ${matomo__P__tracker__P__url:-http://matomo}
+ # End UMD Customization
# db.url: Ensure we are using the 'dspacedb' image for our database
# UMD Customization
- db__P__url: 'jdbc:postgresql://dspacedb:5432/drum'
+ db__P__url: ${db__P__url:-jdbc:postgresql://dspacedb:5432/drum}
# End UMD Customization
# solr.server: Ensure we are using the 'dspacesolr' image for Solr
- solr__P__server: http://dspacesolr:8983/solr
- # matomo.tracker.url: Ensure we are using the 'matomo' image for Matomo
- matomo__P__tracker__P__url: http://matomo
+ solr__P__server: ${solr__P__server:-http://dspacesolr:8983/solr}
# proxies.trusted.ipranges: This setting is required for a REST API running in Docker to trust requests
# from the host machine. This IP range MUST correspond to the 'dspacenet' subnet defined above.
- proxies__P__trusted__P__ipranges: '172.23.0'
- LOGGING_CONFIG: /dspace/config/log4j2-container.xml
+ proxies__P__trusted__P__ipranges: ${proxies__P__trusted__P__ipranges:-172.23.0}
+ LOGGING_CONFIG: ${LOGGING_CONFIG:-/dspace/config/log4j2-container.xml}
# UMD Customization
JPDA_OPTS: "-agentlib:jdwp=transport=dt_socket,address=0.0.0.0:8000,server=y,suspend=n"
# End UMD Customization
@@ -69,8 +76,6 @@ services:
target: 8080
- published: 8000
target: 8000
- stdin_open: true
- tty: true
volumes:
# Keep DSpace assetstore directory between reboots
- assetstore:/dspace/assetstore
@@ -88,7 +93,7 @@ services:
while (! /dev/null 2>&1; do sleep 1; done;
/dspace/bin/dspace database migrate
# UMD Customization
- java $${JPDA_OPTS} -jar /dspace/webapps/server-boot.jar --dspace.dir=/dspace
+ java $${JPDA_OPTS} -jar /dspace/webapps/server-boot.jar
# End UMD Customization
# DSpace PostgreSQL database container
dspacedb:
@@ -112,8 +117,6 @@ services:
ports:
- published: 5432
target: 5432
- stdin_open: true
- tty: true
volumes:
# Keep Postgres data directory between reboots
- pgdata:/pgdata
@@ -136,8 +139,6 @@ services:
ports:
- published: 8983
target: 8983
- stdin_open: true
- tty: true
working_dir: /var/solr/data
volumes:
# Keep Solr data directory between reboots
diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml
index 3803df9ec322..2a17be8981c4 100644
--- a/dspace-api/pom.xml
+++ b/dspace-api/pom.xml
@@ -12,7 +12,7 @@
org.dspacedspace-parent
- 8.1-drum-3-SNAPSHOT
+ 8.4-drum-0-SNAPSHOT..
@@ -99,24 +99,10 @@
-
- org.codehaus.mojo
- build-helper-maven-plugin
- 3.6.1
-
-
- validate
-
- maven-version
-
-
-
-
-
org.codehaus.mojobuildnumber-maven-plugin
- 3.2.1
+ 3.3.0UNKNOWN_REVISION
@@ -400,16 +386,13 @@
${hibernate-validator.version}
+
+ org.springframework.ldap
+ spring-ldap-core
+ org.springframeworkspring-orm
-
-
-
- org.springframework
- spring-jcl
-
-
@@ -438,6 +421,11 @@
org.bouncycastlebcprov-jdk15on
+
+
+ javax.annotation
+ javax.annotation-api
+
@@ -651,7 +639,7 @@
dnsjavadnsjava
- 3.6.3
+ 3.6.4
@@ -660,6 +648,7 @@
1.1.1
+
com.google.guavaguava
@@ -736,9 +725,25 @@
- com.amazonaws
- aws-java-sdk-s3
- 1.12.785
+ software.amazon.awssdk
+ s3
+ 2.43.2
+
+
+ software.amazon.awssdk
+ netty-nio-client
+
+
+ software.amazon.awssdk
+ apache-client
+
+
+
+
+
+ software.amazon.awssdk.crt
+ aws-crt
+ 0.45.2com.opencsvopencsv
- 5.11.1
+ 5.12.0
@@ -791,14 +797,14 @@
org.xmlunitxmlunit-core
- 2.10.2
+ 2.11.0testorg.apache.bcelbcel
- 6.10.0
+ 6.12.0test
@@ -851,21 +857,26 @@
- io.findify
- s3mock_2.13
- 0.2.6
+ com.adobe.testing
+ s3mock-testcontainers
+ 4.12.4test
- com.amazonawsl
- aws-java-sdk-s3
-
-
- com.amazonaws
- aws-java-sdk-s3
+ org.testcontainers
+ testcontainers
+
+
+ org.testcontainers
+ testcontainers
+ 2.0.5
+ test
+ com.squareup.okhttp3
diff --git a/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/BulkAccessControl.java b/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/BulkAccessControl.java
index 30f68efaf3cb..59e75059c94f 100644
--- a/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/BulkAccessControl.java
+++ b/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/BulkAccessControl.java
@@ -18,6 +18,7 @@
import java.sql.SQLException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
+import java.time.ZoneOffset;
import java.util.Arrays;
import java.util.Date;
import java.util.Iterator;
@@ -154,7 +155,7 @@ public void internalRun() throws Exception {
}
ObjectMapper mapper = new ObjectMapper();
- mapper.setTimeZone(TimeZone.getTimeZone("UTC"));
+ mapper.setTimeZone(TimeZone.getTimeZone(ZoneOffset.UTC));
BulkAccessControlInput accessControl;
context = new Context(Context.Mode.BATCH_EDIT);
setEPerson(context);
diff --git a/dspace-api/src/main/java/org/dspace/app/bulkedit/DSpaceCSV.java b/dspace-api/src/main/java/org/dspace/app/bulkedit/DSpaceCSV.java
index 3533a2397b3d..89caa6c15286 100644
--- a/dspace-api/src/main/java/org/dspace/app/bulkedit/DSpaceCSV.java
+++ b/dspace-api/src/main/java/org/dspace/app/bulkedit/DSpaceCSV.java
@@ -25,6 +25,7 @@
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
+import org.dspace.app.util.MetadataExposureServiceImpl;
import org.dspace.authority.AuthorityValue;
import org.dspace.authority.factory.AuthorityServiceFactory;
import org.dspace.authority.service.AuthorityValueService;
@@ -321,20 +322,7 @@ protected void init() {
// Set the metadata fields to ignore
ignore = new HashMap<>();
- // Specify default values
- String[] defaultValues =
- new String[] {
- "dc.date.accessioned", "dc.date.available", "dc.date.updated", "dc.description.provenance"
- };
- String[] toIgnoreArray =
- DSpaceServicesFactory.getInstance()
- .getConfigurationService()
- .getArrayProperty("bulkedit.ignore-on-export", defaultValues);
- for (String toIgnoreString : toIgnoreArray) {
- if (!"".equals(toIgnoreString.trim())) {
- ignore.put(toIgnoreString.trim(), toIgnoreString.trim());
- }
- }
+ getConfiguredIgnoreFields();
}
/**
@@ -352,6 +340,40 @@ public boolean hasActions() {
return false;
}
+ /**
+ * Sets the ignored fields with 'bulkedit.ignore-on-export'
+ *
+ * Also adds 'metadata.hide.*' fields to ignored if 'bulkedit.ignore-on-export.include-metadata-hide' is true
+ */
+ private void getConfiguredIgnoreFields() {
+ // Specify default values
+ String[] defaultValues =
+ new String[] {
+ "dc.date.accessioned", "dc.date.available", "dc.date.updated", "dc.description.provenance"
+ };
+ String[] toIgnoreArray =
+ DSpaceServicesFactory.getInstance()
+ .getConfigurationService()
+ .getArrayProperty("bulkedit.ignore-on-export", defaultValues);
+
+ boolean ignoreHiddenMetadata = DSpaceServicesFactory.getInstance().getConfigurationService()
+ .getBooleanProperty("bulkedit.ignore-on-export.include-metadata-hide", true);
+ if (ignoreHiddenMetadata) {
+ List hiddenMetadata = DSpaceServicesFactory.getInstance().getConfigurationService()
+ .getPropertyKeys(MetadataExposureServiceImpl.CONFIG_PREFIX);
+ for (String hiddenMetadataKey : hiddenMetadata) {
+ String key = hiddenMetadataKey.split(MetadataExposureServiceImpl.CONFIG_PREFIX)[1];
+ ignore.put(key.trim(), key.trim());
+ }
+ }
+
+ for (String toIgnoreString : toIgnoreArray) {
+ if (!"".equals(toIgnoreString.trim())) {
+ ignore.put(toIgnoreString.trim(), toIgnoreString.trim());
+ }
+ }
+ }
+
/**
* Set the value separator for multiple values stored in one csv value.
*
diff --git a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataExportSearch.java b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataExportSearch.java
index e4bbe335d63e..689df4701a96 100644
--- a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataExportSearch.java
+++ b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataExportSearch.java
@@ -14,6 +14,8 @@
import java.util.List;
import java.util.UUID;
+import org.apache.commons.cli.DefaultParser;
+import org.apache.commons.cli.DefaultParser.Builder;
import org.apache.commons.cli.ParseException;
import org.dspace.content.Item;
import org.dspace.content.MetadataDSpaceCsvExportServiceImpl;
@@ -167,4 +169,14 @@ public IndexableObject resolveScope(Context context, String id) throws SQLExcept
}
return scopeObj;
}
+
+ @Override
+ protected StepResult parse(String[] args) throws ParseException {
+ commandLine = new DefaultParser().parse(getScriptConfiguration().getOptions(), args);
+ Builder builder = new DefaultParser().builder();
+ builder.setStripLeadingAndTrailingQuotes(false);
+ commandLine = builder.build().parse(getScriptConfiguration().getOptions(), args);
+ setup();
+ return StepResult.Continue;
+ }
}
diff --git a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImport.java b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImport.java
index e8cf42b47c1b..37a19c6435d2 100644
--- a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImport.java
+++ b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImport.java
@@ -358,6 +358,16 @@ public List runImport(Context c, boolean change,
// Process each change
rowCount = 1;
+
+ int maxItems = configurationService.getIntProperty("bulkedit.import.max.items", 1000);
+ int numItems = toImport.size();
+ if (numItems > maxItems && maxItems > 0) {
+ throw new MetadataImportException(
+ "Import contains " + numItems + " items, which exceeds the configured "
+ + "maximum of " + maxItems + ". You can change this limit by setting "
+ + "'bulkedit.import.max.items' in your local configuration.");
+ }
+
for (DSpaceCSVLine line : toImport) {
// Resolve target references to other items
populateRefAndRowMap(line, line.getID());
@@ -494,7 +504,7 @@ public List runImport(Context c, boolean change,
// Check it has an owning collection
List collections = line.get("collection");
- if (collections == null) {
+ if (collections == null || collections.isEmpty()) {
throw new MetadataImportException(
"New items must have a 'collection' assigned in the form of a handle");
}
diff --git a/dspace-api/src/main/java/org/dspace/app/checker/ChecksumChecker.java b/dspace-api/src/main/java/org/dspace/app/checker/ChecksumChecker.java
index ec024c345263..160d23e32204 100644
--- a/dspace-api/src/main/java/org/dspace/app/checker/ChecksumChecker.java
+++ b/dspace-api/src/main/java/org/dspace/app/checker/ChecksumChecker.java
@@ -98,7 +98,7 @@ public static void main(String[] args) throws SQLException {
options.addOption("h", "help", false, "Help");
options.addOption("d", "duration", true, "Checking duration");
options.addOption("c", "count", true, "Check count");
- options.addOption("a", "handle", true, "Specify a handle to check");
+ options.addOption("i", "handle", true, "Specify a handle to check");
options.addOption("v", "verbose", false, "Report all processing");
Option option;
@@ -106,7 +106,7 @@ public static void main(String[] args) throws SQLException {
option = Option.builder("b")
.longOpt("bitstream-ids")
.hasArgs()
- .desc("Space separated list of bitstream ids")
+ .desc("Space separated list of bitstream UUIDs")
.build();
options.addOption(option);
@@ -132,6 +132,17 @@ public static void main(String[] args) throws SQLException {
try {
context = new Context();
+ int mutuallyExclusiveOpts = 0;
+ for (char c : new char[]{'l', 'L', 'd', 'b', 'i','c'}) {
+ if (line.hasOption(c)) {
+ mutuallyExclusiveOpts++;
+ }
+ }
+ if (mutuallyExclusiveOpts > 1) {
+ System.err.println("Please use only one option of -l, -L, -d, -b, -i, or -c");
+ LOG.error("Please use only one option of -l, -L, -d, -b, -i, or -c");
+ System.exit(1);
+ }
// Prune stage
if (line.hasOption('p')) {
@@ -169,13 +180,13 @@ public static void main(String[] args) throws SQLException {
bitstreams.add(bitstreamService.find(context, UUID.fromString(ids[i])));
} catch (NumberFormatException nfe) {
System.err.println("The following argument: " + ids[i]
- + " is not an integer");
+ + " is not an UUID");
System.exit(0);
}
}
dispatcher = new IteratorDispatcher(bitstreams.iterator());
- } else if (line.hasOption('a')) {
- dispatcher = new HandleDispatcher(context, line.getOptionValue('a'));
+ } else if (line.hasOption('i')) {
+ dispatcher = new HandleDispatcher(context, line.getOptionValue('i'));
} else if (line.hasOption('d')) {
// run checker process for specified duration
try {
@@ -185,6 +196,8 @@ public static void main(String[] args) throws SQLException {
+ Utils.parseDuration(line
.getOptionValue('d'))));
} catch (Exception e) {
+ System.err.println("Couldn't parse " + line.getOptionValue('d')
+ + " as a duration");
LOG.fatal("Couldn't parse " + line.getOptionValue('d')
+ " as a duration: ", e);
System.exit(0);
@@ -228,18 +241,24 @@ public static void main(String[] args) throws SQLException {
private static void printHelp(Options options) {
HelpFormatter myhelp = new HelpFormatter();
- myhelp.printHelp("Checksum Checker\n", options);
- System.out.println("\nSpecify a duration for checker process, using s(seconds),"
- + "m(minutes), or h(hours): ChecksumChecker -d 30s"
- + " OR ChecksumChecker -d 30m"
- + " OR ChecksumChecker -d 2h");
- System.out.println("\nSpecify bitstream IDs: ChecksumChecker -b 13 15 17 20");
- System.out.println("\nLoop once through all bitstreams: "
- + "ChecksumChecker -l");
- System.out.println("\nLoop continuously through all bitstreams: ChecksumChecker -L");
- System.out.println("\nCheck a defined number of bitstreams: ChecksumChecker -c 10");
- System.out.println("\nReport all processing (verbose)(default reports only errors): ChecksumChecker -v");
- System.out.println("\nDefault (no arguments) is equivalent to '-c 1'");
+ myhelp.printHelp("checker\n", options);
+ System.out.println("\nChecksum Checker usage examples:");
+ System.out.println("\nThe following options are mutually exclusive:");
+ System.out.println(" - Specify a duration for checker process, using s(seconds),"
+ + "m(minutes), or h(hours): checker -d 30s"
+ + " OR checker -d 30m"
+ + " OR checker -d 2h");
+ System.out.println(" - Specify bitstream UUIDs: checker -b 550e8400-e29b-41d4-a716-446655440000"
+ + " f3f2e850-b5d4-11ef-ac7e-96584d5248b2");
+ System.out.println(" - Specify handle: checker -i 12345/100");
+ System.out.println(" - Loop once through all bitstreams: "
+ + "checker -l");
+ System.out.println(" - Loop continuously through all bitstreams: checker -L");
+ System.out.println(" - Check a defined number of bitstreams: checker -c 10");
+ System.out.println("\nThe following options can be used in combination with others above:");
+ System.out.println(" - Report all processing to checker.log (by default logs only errors): checker -v");
+ System.out.println(" - Prune old results from the database: checker -p");
+ System.out.println("\nDefault (no arguments) is equivalent to 'checker -c 1'\n");
System.exit(0);
}
diff --git a/dspace-api/src/main/java/org/dspace/app/itemexport/ItemExportServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/itemexport/ItemExportServiceImpl.java
index 9eaabc20e862..d50b44fd8d4c 100644
--- a/dspace-api/src/main/java/org/dspace/app/itemexport/ItemExportServiceImpl.java
+++ b/dspace-api/src/main/java/org/dspace/app/itemexport/ItemExportServiceImpl.java
@@ -352,7 +352,7 @@ protected void writeHandle(Context c, Item i, File destDir)
/**
* Create the 'collections' file. List handles of all Collections which
- * contain this Item. The "owning" Collection is listed first.
+ * contain this Item. The "owning" Collection is listed first.
*
* @param item list collections holding this Item.
* @param destDir write the file here.
@@ -363,12 +363,14 @@ protected void writeCollections(Item item, File destDir)
File outFile = new File(destDir, "collections");
if (outFile.createNewFile()) {
try (PrintWriter out = new PrintWriter(new FileWriter(outFile))) {
- String ownerHandle = item.getOwningCollection().getHandle();
- out.println(ownerHandle);
+ Collection owningCollection = item.getOwningCollection();
+ // The owning collection is null for workspace and workflow items
+ if (owningCollection != null) {
+ out.println(owningCollection.getHandle());
+ }
for (Collection collection : item.getCollections()) {
- String collectionHandle = collection.getHandle();
- if (!collectionHandle.equals(ownerHandle)) {
- out.println(collectionHandle);
+ if (!collection.equals(owningCollection)) {
+ out.println(collection.getHandle());
}
}
}
diff --git a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImport.java b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImport.java
index b32de11f7a7f..33487bc8e35a 100644
--- a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImport.java
+++ b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImport.java
@@ -22,6 +22,7 @@
import org.apache.commons.cli.ParseException;
import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.tika.Tika;
import org.dspace.app.itemimport.factory.ItemImportServiceFactory;
@@ -333,33 +334,38 @@ protected void process(Context context, ItemImportService itemImportService,
protected void readZip(Context context, ItemImportService itemImportService) throws Exception {
Optional optionalFileStream = Optional.empty();
Optional validationFileStream = Optional.empty();
- if (!remoteUrl) {
- // manage zip via upload
- optionalFileStream = handler.getFileStream(context, zipfilename);
- validationFileStream = handler.getFileStream(context, zipfilename);
- } else {
- // manage zip via remote url
- optionalFileStream = Optional.ofNullable(new URL(zipfilename).openStream());
- validationFileStream = Optional.ofNullable(new URL(zipfilename).openStream());
- }
+ try {
+ if (!remoteUrl) {
+ // manage zip via upload
+ optionalFileStream = handler.getFileStream(context, zipfilename);
+ validationFileStream = handler.getFileStream(context, zipfilename);
+ } else {
+ // manage zip via remote url
+ optionalFileStream = Optional.ofNullable(new URL(zipfilename).openStream());
+ validationFileStream = Optional.ofNullable(new URL(zipfilename).openStream());
+ }
- if (validationFileStream.isPresent()) {
- // validate zip file
if (validationFileStream.isPresent()) {
- validateZip(validationFileStream.get());
+ // validate zip file
+ if (validationFileStream.isPresent()) {
+ validateZip(validationFileStream.get());
+ }
+
+ workFile = new File(itemImportService.getTempWorkDir() + File.separator
+ + zipfilename + "-" + context.getCurrentUser().getID());
+ FileUtils.copyInputStreamToFile(optionalFileStream.get(), workFile);
+ } else {
+ throw new IllegalArgumentException(
+ "Error reading file, the file couldn't be found for filename: " + zipfilename);
}
- workFile = new File(itemImportService.getTempWorkDir() + File.separator
- + zipfilename + "-" + context.getCurrentUser().getID());
- FileUtils.copyInputStreamToFile(optionalFileStream.get(), workFile);
- } else {
- throw new IllegalArgumentException(
- "Error reading file, the file couldn't be found for filename: " + zipfilename);
+ workDir = new File(itemImportService.getTempWorkDir() + File.separator + TEMP_DIR
+ + File.separator + context.getCurrentUser().getID());
+ sourcedir = itemImportService.unzip(workFile, workDir.getAbsolutePath());
+ } finally {
+ optionalFileStream.ifPresent(IOUtils::closeQuietly);
+ validationFileStream.ifPresent(IOUtils::closeQuietly);
}
-
- workDir = new File(itemImportService.getTempWorkDir() + File.separator + TEMP_DIR
- + File.separator + context.getCurrentUser().getID());
- sourcedir = itemImportService.unzip(workFile, workDir.getAbsolutePath());
}
/**
diff --git a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportCLI.java b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportCLI.java
index 98d2469b7155..bd29aa97fe48 100644
--- a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportCLI.java
+++ b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportCLI.java
@@ -17,6 +17,7 @@
import java.util.UUID;
import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.dspace.app.itemimport.service.ItemImportService;
import org.dspace.content.Collection;
@@ -111,7 +112,11 @@ protected void readZip(Context context, ItemImportService itemImportService) thr
// validate zip file
InputStream validationFileStream = new FileInputStream(myZipFile);
- validateZip(validationFileStream);
+ try {
+ validateZip(validationFileStream);
+ } finally {
+ IOUtils.closeQuietly(validationFileStream);
+ }
workDir = new File(itemImportService.getTempWorkDir() + File.separator + TEMP_DIR
+ File.separator + context.getCurrentUser().getID());
@@ -120,22 +125,28 @@ protected void readZip(Context context, ItemImportService itemImportService) thr
} else {
// manage zip via remote url
Optional optionalFileStream = Optional.ofNullable(new URL(zipfilename).openStream());
- if (optionalFileStream.isPresent()) {
- // validate zip file via url
- Optional validationFileStream = Optional.ofNullable(new URL(zipfilename).openStream());
- if (validationFileStream.isPresent()) {
- validateZip(validationFileStream.get());
+ Optional validationFileStream = Optional.ofNullable(new URL(zipfilename).openStream());
+ try {
+ if (optionalFileStream.isPresent()) {
+ // validate zip file via url
+
+ if (validationFileStream.isPresent()) {
+ validateZip(validationFileStream.get());
+ }
+
+ workFile = new File(itemImportService.getTempWorkDir() + File.separator
+ + zipfilename + "-" + context.getCurrentUser().getID());
+ FileUtils.copyInputStreamToFile(optionalFileStream.get(), workFile);
+ workDir = new File(itemImportService.getTempWorkDir() + File.separator + TEMP_DIR
+ + File.separator + context.getCurrentUser().getID());
+ sourcedir = itemImportService.unzip(workFile, workDir.getAbsolutePath());
+ } else {
+ throw new IllegalArgumentException(
+ "Error reading file, the file couldn't be found for filename: " + zipfilename);
}
-
- workFile = new File(itemImportService.getTempWorkDir() + File.separator
- + zipfilename + "-" + context.getCurrentUser().getID());
- FileUtils.copyInputStreamToFile(optionalFileStream.get(), workFile);
- workDir = new File(itemImportService.getTempWorkDir() + File.separator + TEMP_DIR
- + File.separator + context.getCurrentUser().getID());
- sourcedir = itemImportService.unzip(workFile, workDir.getAbsolutePath());
- } else {
- throw new IllegalArgumentException(
- "Error reading file, the file couldn't be found for filename: " + zipfilename);
+ } finally {
+ optionalFileStream.ifPresent(IOUtils::closeQuietly);
+ validationFileStream.ifPresent(IOUtils::closeQuietly);
}
}
}
diff --git a/dspace-api/src/main/java/org/dspace/app/mediafilter/BrandedPreviewJPEGFilter.java b/dspace-api/src/main/java/org/dspace/app/mediafilter/BrandedPreviewJPEGFilter.java
index 7b082c6c21a4..483e4f5f6ea2 100644
--- a/dspace-api/src/main/java/org/dspace/app/mediafilter/BrandedPreviewJPEGFilter.java
+++ b/dspace-api/src/main/java/org/dspace/app/mediafilter/BrandedPreviewJPEGFilter.java
@@ -7,9 +7,7 @@
*/
package org.dspace.app.mediafilter;
-import java.awt.image.BufferedImage;
import java.io.InputStream;
-import javax.imageio.ImageIO;
import org.dspace.content.Item;
import org.dspace.services.ConfigurationService;
@@ -63,27 +61,20 @@ public String getDescription() {
@Override
public InputStream getDestinationStream(Item currentItem, InputStream source, boolean verbose)
throws Exception {
- // read in bitstream's image
- BufferedImage buf = ImageIO.read(source);
-
// get config params
ConfigurationService configurationService
= DSpaceServicesFactory.getInstance().getConfigurationService();
- float xmax = (float) configurationService
- .getIntProperty("webui.preview.maxwidth");
- float ymax = (float) configurationService
- .getIntProperty("webui.preview.maxheight");
- boolean blurring = (boolean) configurationService
- .getBooleanProperty("webui.preview.blurring");
- boolean hqscaling = (boolean) configurationService
- .getBooleanProperty("webui.preview.hqscaling");
+ int xmax = configurationService.getIntProperty("webui.preview.maxwidth");
+ int ymax = configurationService.getIntProperty("webui.preview.maxheight");
+ boolean blurring = configurationService.getBooleanProperty("webui.preview.blurring");
+ boolean hqscaling = configurationService.getBooleanProperty("webui.preview.hqscaling");
int brandHeight = configurationService.getIntProperty("webui.preview.brand.height");
String brandFont = configurationService.getProperty("webui.preview.brand.font");
int brandFontPoint = configurationService.getIntProperty("webui.preview.brand.fontpoint");
JPEGFilter jpegFilter = new JPEGFilter();
- return jpegFilter
- .getThumbDim(currentItem, buf, verbose, xmax, ymax, blurring, hqscaling, brandHeight, brandFontPoint,
- brandFont);
+ return jpegFilter.getThumb(
+ currentItem, source, verbose, xmax, ymax, blurring, hqscaling, brandHeight, brandFontPoint, brandFont
+ );
}
}
diff --git a/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickThumbnailFilter.java b/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickThumbnailFilter.java
index 7543410a7968..28bfc72dc110 100644
--- a/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickThumbnailFilter.java
+++ b/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickThumbnailFilter.java
@@ -105,7 +105,7 @@ public File getThumbnailFile(File f, boolean verbose)
ConvertCmd cmd = new ConvertCmd();
IMOperation op = new IMOperation();
op.autoOrient();
- op.addImage(f.getAbsolutePath());
+ op.addImage(f.getAbsolutePath() + "[0]");
op.thumbnail(configurationService.getIntProperty("thumbnail.maxwidth", DEFAULT_WIDTH),
configurationService.getIntProperty("thumbnail.maxheight", DEFAULT_HEIGHT));
op.addImage(f2.getAbsolutePath());
diff --git a/dspace-api/src/main/java/org/dspace/app/mediafilter/JPEGFilter.java b/dspace-api/src/main/java/org/dspace/app/mediafilter/JPEGFilter.java
index 502f71eb5ca8..2ccc2afbb2d2 100644
--- a/dspace-api/src/main/java/org/dspace/app/mediafilter/JPEGFilter.java
+++ b/dspace-api/src/main/java/org/dspace/app/mediafilter/JPEGFilter.java
@@ -8,19 +8,32 @@
package org.dspace.app.mediafilter;
import java.awt.Color;
+import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Transparency;
+import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.awt.image.BufferedImageOp;
import java.awt.image.ConvolveOp;
import java.awt.image.Kernel;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
import java.io.InputStream;
import javax.imageio.ImageIO;
+import com.drew.imaging.ImageMetadataReader;
+import com.drew.imaging.ImageProcessingException;
+import com.drew.metadata.Metadata;
+import com.drew.metadata.MetadataException;
+import com.drew.metadata.exif.ExifIFD0Directory;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
import org.dspace.content.Item;
import org.dspace.services.ConfigurationService;
import org.dspace.services.factory.DSpaceServicesFactory;
@@ -33,6 +46,8 @@
* @author Jason Sherman jsherman@usao.edu
*/
public class JPEGFilter extends MediaFilter implements SelfRegisterInputFormats {
+ private static final Logger log = LogManager.getLogger(JPEGFilter.class);
+
@Override
public String getFilteredName(String oldFilename) {
return oldFilename + ".jpg";
@@ -62,6 +77,115 @@ public String getDescription() {
return "Generated Thumbnail";
}
+ /**
+ * Gets the rotation angle from image's metadata using ImageReader.
+ * This method consumes the InputStream, so you need to be careful to don't reuse the same InputStream after
+ * computing the rotation angle.
+ *
+ * @param buf InputStream of the image file
+ * @return Rotation angle in degrees (0, 90, 180, or 270)
+ */
+ public static int getImageRotationUsingImageReader(InputStream buf) {
+ try {
+ Metadata metadata = ImageMetadataReader.readMetadata(buf);
+ ExifIFD0Directory directory = metadata.getFirstDirectoryOfType(ExifIFD0Directory.class);
+ if (directory != null && directory.containsTag(ExifIFD0Directory.TAG_ORIENTATION)) {
+ return convertRotationToDegrees(directory.getInt(ExifIFD0Directory.TAG_ORIENTATION));
+ }
+ } catch (MetadataException | ImageProcessingException | IOException e) {
+ log.error("Error reading image metadata", e);
+ }
+ return 0;
+ }
+
+ public static int convertRotationToDegrees(int valueNode) {
+ // Common orientation values:
+ // 1 = Normal (0°)
+ // 6 = Rotated 90° CW
+ // 3 = Rotated 180°
+ // 8 = Rotated 270° CW
+ switch (valueNode) {
+ case 6:
+ return 90;
+ case 3:
+ return 180;
+ case 8:
+ return 270;
+ default:
+ return 0;
+ }
+ }
+
+ /**
+ * Rotates an image by the specified angle
+ *
+ * @param image The original image
+ * @param angle The rotation angle in degrees
+ * @return Rotated image
+ */
+ public static BufferedImage rotateImage(BufferedImage image, int angle) {
+ if (angle == 0) {
+ return image;
+ }
+
+ double radians = Math.toRadians(angle);
+ double sin = Math.abs(Math.sin(radians));
+ double cos = Math.abs(Math.cos(radians));
+
+ int newWidth = (int) Math.round(image.getWidth() * cos + image.getHeight() * sin);
+ int newHeight = (int) Math.round(image.getWidth() * sin + image.getHeight() * cos);
+
+ BufferedImage rotated = new BufferedImage(newWidth, newHeight, image.getType());
+ Graphics2D g2d = rotated.createGraphics();
+ AffineTransform at = new AffineTransform();
+
+ at.translate(newWidth / 2, newHeight / 2);
+ at.rotate(radians);
+ at.translate(-image.getWidth() / 2, -image.getHeight() / 2);
+
+ g2d.setTransform(at);
+ g2d.drawImage(image, 0, 0, null);
+ g2d.dispose();
+
+ return rotated;
+ }
+
+ /**
+ * Calculates scaled dimension while maintaining aspect ratio
+ *
+ * @param imgSize Original image dimensions
+ * @param boundary Maximum allowed dimensions
+ * @return New dimensions that fit within boundary while preserving aspect ratio
+ */
+ private Dimension getScaledDimension(Dimension imgSize, Dimension boundary) {
+
+ int originalWidth = imgSize.width;
+ int originalHeight = imgSize.height;
+ int boundWidth = boundary.width;
+ int boundHeight = boundary.height;
+ int newWidth = originalWidth;
+ int newHeight = originalHeight;
+
+
+ // First check if we need to scale width
+ if (originalWidth > boundWidth) {
+ // Scale width to fit
+ newWidth = boundWidth;
+ // Scale height to maintain aspect ratio
+ newHeight = (newWidth * originalHeight) / originalWidth;
+ }
+
+ // Then check if we need to scale even with the new height
+ if (newHeight > boundHeight) {
+ // Scale height to fit instead
+ newHeight = boundHeight;
+ newWidth = (newHeight * originalWidth) / originalHeight;
+ }
+
+ return new Dimension(newWidth, newHeight);
+ }
+
+
/**
* @param currentItem item
* @param source source input stream
@@ -72,10 +196,65 @@ public String getDescription() {
@Override
public InputStream getDestinationStream(Item currentItem, InputStream source, boolean verbose)
throws Exception {
- // read in bitstream's image
- BufferedImage buf = ImageIO.read(source);
+ return getThumb(currentItem, source, verbose);
+ }
- return getThumb(currentItem, buf, verbose);
+ public InputStream getThumb(Item currentItem, InputStream source, boolean verbose)
+ throws Exception {
+ // get config params
+ final ConfigurationService configurationService
+ = DSpaceServicesFactory.getInstance().getConfigurationService();
+ int xmax = configurationService
+ .getIntProperty("thumbnail.maxwidth");
+ int ymax = configurationService
+ .getIntProperty("thumbnail.maxheight");
+ boolean blurring = (boolean) configurationService
+ .getBooleanProperty("thumbnail.blurring");
+ boolean hqscaling = (boolean) configurationService
+ .getBooleanProperty("thumbnail.hqscaling");
+
+ return getThumb(currentItem, source, verbose, xmax, ymax, blurring, hqscaling, 0, 0, null);
+ }
+
+ protected InputStream getThumb(
+ Item currentItem,
+ InputStream source,
+ boolean verbose,
+ int xmax,
+ int ymax,
+ boolean blurring,
+ boolean hqscaling,
+ int brandHeight,
+ int brandFontPoint,
+ String brandFont
+ ) throws Exception {
+
+ File tempFile = File.createTempFile("temp", ".tmp");
+ tempFile.deleteOnExit();
+
+ // Write to temp file
+ try (FileOutputStream fos = new FileOutputStream(tempFile)) {
+ byte[] buffer = new byte[4096];
+ int len;
+ while ((len = source.read(buffer)) != -1) {
+ fos.write(buffer, 0, len);
+ }
+ }
+
+ int rotation = 0;
+ try (FileInputStream fis = new FileInputStream(tempFile)) {
+ rotation = getImageRotationUsingImageReader(fis);
+ }
+
+ try (FileInputStream fis = new FileInputStream(tempFile)) {
+ // read in bitstream's image
+ BufferedImage buf = ImageIO.read(fis);
+
+ return getThumbDim(
+ currentItem, buf, verbose, xmax, ymax, blurring, hqscaling, brandHeight, brandFontPoint, rotation,
+ brandFont
+ );
+ }
}
public InputStream getThumb(Item currentItem, BufferedImage buf, boolean verbose)
@@ -83,25 +262,28 @@ public InputStream getThumb(Item currentItem, BufferedImage buf, boolean verbose
// get config params
final ConfigurationService configurationService
= DSpaceServicesFactory.getInstance().getConfigurationService();
- float xmax = (float) configurationService
+ int xmax = configurationService
.getIntProperty("thumbnail.maxwidth");
- float ymax = (float) configurationService
+ int ymax = configurationService
.getIntProperty("thumbnail.maxheight");
boolean blurring = (boolean) configurationService
.getBooleanProperty("thumbnail.blurring");
boolean hqscaling = (boolean) configurationService
.getBooleanProperty("thumbnail.hqscaling");
- return getThumbDim(currentItem, buf, verbose, xmax, ymax, blurring, hqscaling, 0, 0, null);
+ return getThumbDim(currentItem, buf, verbose, xmax, ymax, blurring, hqscaling, 0, 0, 0, null);
}
- public InputStream getThumbDim(Item currentItem, BufferedImage buf, boolean verbose, float xmax, float ymax,
+ public InputStream getThumbDim(Item currentItem, BufferedImage buf, boolean verbose, int xmax, int ymax,
boolean blurring, boolean hqscaling, int brandHeight, int brandFontPoint,
- String brandFont)
+ int rotation, String brandFont)
throws Exception {
- // now get the image dimensions
- float xsize = (float) buf.getWidth(null);
- float ysize = (float) buf.getHeight(null);
+
+ // Rotate the image if needed
+ BufferedImage correctedImage = rotateImage(buf, rotation);
+
+ int xsize = correctedImage.getWidth();
+ int ysize = correctedImage.getHeight();
// if verbose flag is set, print out dimensions
// to STDOUT
@@ -109,86 +291,63 @@ public InputStream getThumbDim(Item currentItem, BufferedImage buf, boolean verb
System.out.println("original size: " + xsize + "," + ysize);
}
- // scale by x first if needed
- if (xsize > xmax) {
- // calculate scaling factor so that xsize * scale = new size (max)
- float scale_factor = xmax / xsize;
+ // Calculate new dimensions while maintaining aspect ratio
+ Dimension newDimension = getScaledDimension(
+ new Dimension(xsize, ysize),
+ new Dimension(xmax, ymax)
+ );
- // if verbose flag is set, print out extracted text
- // to STDOUT
- if (verbose) {
- System.out.println("x scale factor: " + scale_factor);
- }
-
- // now reduce x size
- // and y size
- xsize = xsize * scale_factor;
- ysize = ysize * scale_factor;
-
- // if verbose flag is set, print out extracted text
- // to STDOUT
- if (verbose) {
- System.out.println("size after fitting to maximum width: " + xsize + "," + ysize);
- }
- }
-
- // scale by y if needed
- if (ysize > ymax) {
- float scale_factor = ymax / ysize;
-
- // now reduce x size
- // and y size
- xsize = xsize * scale_factor;
- ysize = ysize * scale_factor;
- }
// if verbose flag is set, print details to STDOUT
if (verbose) {
- System.out.println("size after fitting to maximum height: " + xsize + ", "
- + ysize);
+ System.out.println("size after fitting to maximum height: " + newDimension.width + ", "
+ + newDimension.height);
}
+ xsize = newDimension.width;
+ ysize = newDimension.height;
+
// create an image buffer for the thumbnail with the new xsize, ysize
- BufferedImage thumbnail = new BufferedImage((int) xsize, (int) ysize,
- BufferedImage.TYPE_INT_RGB);
+ BufferedImage thumbnail = new BufferedImage(xsize, ysize, BufferedImage.TYPE_INT_RGB);
// Use blurring if selected in config.
// a little blur before scaling does wonders for keeping moire in check.
if (blurring) {
// send the buffered image off to get blurred.
- buf = getBlurredInstance((BufferedImage) buf);
+ correctedImage = getBlurredInstance(correctedImage);
}
// Use high quality scaling method if selected in config.
// this has a definite performance penalty.
if (hqscaling) {
// send the buffered image off to get an HQ downscale.
- buf = getScaledInstance((BufferedImage) buf, (int) xsize, (int) ysize,
- (Object) RenderingHints.VALUE_INTERPOLATION_BICUBIC, (boolean) true);
+ correctedImage = getScaledInstance(correctedImage, xsize, ysize,
+ RenderingHints.VALUE_INTERPOLATION_BICUBIC, true);
}
// now render the image into the thumbnail buffer
Graphics2D g2d = thumbnail.createGraphics();
- g2d.drawImage(buf, 0, 0, (int) xsize, (int) ysize, null);
+ g2d.drawImage(correctedImage, 0, 0, xsize, ysize, null);
if (brandHeight != 0) {
ConfigurationService configurationService
= DSpaceServicesFactory.getInstance().getConfigurationService();
- Brand brand = new Brand((int) xsize, brandHeight, new Font(brandFont, Font.PLAIN, brandFontPoint), 5);
+ Brand brand = new Brand(xsize, brandHeight, new Font(brandFont, Font.PLAIN, brandFontPoint), 5);
BufferedImage brandImage = brand.create(configurationService.getProperty("webui.preview.brand"),
configurationService.getProperty("webui.preview.brand.abbrev"),
currentItem == null ? "" : "hdl:" + currentItem.getHandle());
- g2d.drawImage(brandImage, (int) 0, (int) ysize, (int) xsize, (int) 20, null);
+ g2d.drawImage(brandImage, 0, ysize, xsize, 20, null);
}
- // now create an input stream for the thumbnail buffer and return it
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
-
- ImageIO.write(thumbnail, "jpeg", baos);
- // now get the array
- ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+ ByteArrayInputStream bais;
+ // now create an input stream for the thumbnail buffer and return it
+ try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
+ ImageIO.write(thumbnail, "jpeg", baos);
+ // now get the array
+ bais = new ByteArrayInputStream(baos.toByteArray());
+ }
return bais; // hope this gets written out before its garbage collected!
}
diff --git a/dspace-api/src/main/java/org/dspace/app/mediafilter/PDFBoxThumbnail.java b/dspace-api/src/main/java/org/dspace/app/mediafilter/PDFBoxThumbnail.java
index 94c463b2808f..eb23e9daa085 100644
--- a/dspace-api/src/main/java/org/dspace/app/mediafilter/PDFBoxThumbnail.java
+++ b/dspace-api/src/main/java/org/dspace/app/mediafilter/PDFBoxThumbnail.java
@@ -83,6 +83,7 @@ public InputStream getDestinationStream(Item currentItem, InputStream source, bo
// Generate thumbnail derivative and return as IO stream.
JPEGFilter jpegFilter = new JPEGFilter();
+
return jpegFilter.getThumb(currentItem, buf, verbose);
}
}
diff --git a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemEmailNotifier.java b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemEmailNotifier.java
index c489fb4b3ff0..49b82210dc23 100644
--- a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemEmailNotifier.java
+++ b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemEmailNotifier.java
@@ -9,7 +9,9 @@
package org.dspace.app.requestitem;
import java.io.IOException;
+import java.io.InputStream;
import java.sql.SQLException;
+import java.util.ArrayList;
import java.util.List;
import jakarta.annotation.ManagedBean;
@@ -186,6 +188,7 @@ public void sendResponse(Context context, RequestItem ri, String subject,
email.setSubject(subject);
email.addRecipient(ri.getReqEmail());
// Attach bitstreams.
+ List bitstreamInputStreams = new ArrayList<>();
try {
if (ri.isAccept_request()) {
if (ri.isAllfiles()) {
@@ -200,11 +203,13 @@ public void sendResponse(Context context, RequestItem ri, String subject,
// #8636 Anyone receiving the email can respond to the
// request without authenticating into DSpace
context.turnOffAuthorisationSystem();
+ InputStream is = bitstreamService.retrieve(context, bitstream);
email.addAttachment(
- bitstreamService.retrieve(context, bitstream),
+ is,
bitstream.getName(),
bitstream.getFormat(context).getMIMEType());
context.restoreAuthSystemState();
+ bitstreamInputStreams.add(is);
}
}
}
@@ -212,10 +217,12 @@ public void sendResponse(Context context, RequestItem ri, String subject,
Bitstream bitstream = ri.getBitstream();
// #8636 Anyone receiving the email can respond to the request without authenticating into DSpace
context.turnOffAuthorisationSystem();
- email.addAttachment(bitstreamService.retrieve(context, bitstream),
+ InputStream is = bitstreamService.retrieve(context, bitstream);
+ email.addAttachment(is,
bitstream.getName(),
bitstream.getFormat(context).getMIMEType());
context.restoreAuthSystemState();
+ bitstreamInputStreams.add(is);
}
email.send();
} else {
@@ -231,6 +238,10 @@ public void sendResponse(Context context, RequestItem ri, String subject,
LOG.warn(LogHelper.getHeader(context,
"error_mailing_requestItem", e.getMessage()));
throw new IOException("Reply not sent: " + e.getMessage());
+ } finally {
+ for (InputStream bitstreamInputStream : bitstreamInputStreams) {
+ bitstreamInputStream.close();
+ }
}
LOG.info(LogHelper.getHeader(context,
"sent_attach_requestItem", "token={}"), ri.getToken());
diff --git a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemServiceImpl.java
index b915cfedd346..d6d0225655e0 100644
--- a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemServiceImpl.java
+++ b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemServiceImpl.java
@@ -11,6 +11,7 @@
import java.util.Date;
import java.util.Iterator;
import java.util.List;
+import java.util.UUID;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -96,6 +97,11 @@ public Iterator findByItem(Context context, Item item) throws SQLEx
return requestItemDAO.findByItem(context, item);
}
+ @Override
+ public Iterator findByBitstreamId(Context context, UUID bitstreamId) throws SQLException {
+ return requestItemDAO.findByBitstreamId(context, bitstreamId);
+ }
+
@Override
public void update(Context context, RequestItem requestItem) {
try {
diff --git a/dspace-api/src/main/java/org/dspace/app/requestitem/dao/RequestItemDAO.java b/dspace-api/src/main/java/org/dspace/app/requestitem/dao/RequestItemDAO.java
index b36ae58e0ca1..9c6954fe6be1 100644
--- a/dspace-api/src/main/java/org/dspace/app/requestitem/dao/RequestItemDAO.java
+++ b/dspace-api/src/main/java/org/dspace/app/requestitem/dao/RequestItemDAO.java
@@ -9,6 +9,7 @@
import java.sql.SQLException;
import java.util.Iterator;
+import java.util.UUID;
import org.dspace.app.requestitem.RequestItem;
import org.dspace.content.Item;
@@ -26,7 +27,7 @@
*/
public interface RequestItemDAO extends GenericDAO {
/**
- * Fetch a request named by its unique token (passed in emails).
+ * Fetch a request named by its unique approval token (passed in emails).
*
* @param context the current DSpace context.
* @param token uniquely identifies the request.
@@ -36,4 +37,17 @@ public interface RequestItemDAO extends GenericDAO {
public RequestItem findByToken(Context context, String token) throws SQLException;
public Iterator findByItem(Context context, Item item) throws SQLException;
+
+ /**
+ * Retrieve all requests (as iterator) for a given bitstream UUID
+ * A UUID parameter is used here rather than Bitstream object, to make it usable
+ * in situations even when a bitstream object no longer exists, but orphaned
+ * entries need to be found by their (previous) bitstream UUID.
+ *
+ * @param context current DSpace context
+ * @param bitstreamId the bitstream UUID to search for
+ * @return the matching requests (or empty iterator)
+ */
+ public Iterator findByBitstreamId(Context context, UUID bitstreamId) throws SQLException;
+
}
diff --git a/dspace-api/src/main/java/org/dspace/app/requestitem/dao/impl/RequestItemDAOImpl.java b/dspace-api/src/main/java/org/dspace/app/requestitem/dao/impl/RequestItemDAOImpl.java
index c76bd50d1910..d6c6c1b231b7 100644
--- a/dspace-api/src/main/java/org/dspace/app/requestitem/dao/impl/RequestItemDAOImpl.java
+++ b/dspace-api/src/main/java/org/dspace/app/requestitem/dao/impl/RequestItemDAOImpl.java
@@ -9,6 +9,7 @@
import java.sql.SQLException;
import java.util.Iterator;
+import java.util.UUID;
import jakarta.persistence.Query;
import jakarta.persistence.criteria.CriteriaBuilder;
@@ -52,4 +53,11 @@ public Iterator findByItem(Context context, Item item) throws SQLEx
Query query = createQuery(context, criteriaQuery);
return iterate(query);
}
+
+ @Override
+ public Iterator findByBitstreamId(Context context, UUID bitstreamId) throws SQLException {
+ Query query = createQuery(context, "FROM RequestItem WHERE bitstream.id = :bitstreamId");
+ query.setParameter("bitstreamId", bitstreamId);
+ return iterate(query);
+ }
}
diff --git a/dspace-api/src/main/java/org/dspace/app/requestitem/service/RequestItemService.java b/dspace-api/src/main/java/org/dspace/app/requestitem/service/RequestItemService.java
index efac3b18bc7c..5c27ba9acb55 100644
--- a/dspace-api/src/main/java/org/dspace/app/requestitem/service/RequestItemService.java
+++ b/dspace-api/src/main/java/org/dspace/app/requestitem/service/RequestItemService.java
@@ -10,6 +10,7 @@
import java.sql.SQLException;
import java.util.Iterator;
import java.util.List;
+import java.util.UUID;
import org.dspace.app.requestitem.RequestItem;
import org.dspace.content.Bitstream;
@@ -40,7 +41,7 @@ public interface RequestItemService {
* @return the token of the request item
* @throws SQLException if database error
*/
- public String createRequest(Context context, Bitstream bitstream, Item item,
+ String createRequest(Context context, Bitstream bitstream, Item item,
boolean allFiles, String reqEmail, String reqName, String reqMessage)
throws SQLException;
@@ -49,35 +50,51 @@ public String createRequest(Context context, Bitstream bitstream, Item item,
*
* @param context current DSpace session.
* @return all item requests.
- * @throws java.sql.SQLException passed through.
+ * @throws SQLException passed through.
*/
- public List findAll(Context context)
+ List findAll(Context context)
throws SQLException;
/**
- * Retrieve a request by its token.
+ * Retrieve a request by its approver token.
*
* @param context current DSpace session.
- * @param token the token identifying the request.
+ * @param token the token identifying the request to be approved.
* @return the matching request, or null if not found.
*/
- public RequestItem findByToken(Context context, String token);
+ RequestItem findByToken(Context context, String token);
/**
- * Retrieve a request based on the item.
+ * Retrieve all requests (as iterator) for a given item
* @param context current DSpace session.
* @param item the item to find requests for.
- * @return the matching requests, or null if not found.
+ * @return the matching requests (or empty iterator)
*/
- public Iterator findByItem(Context context, Item item) throws SQLException;
+ Iterator findByItem(Context context, Item item) throws SQLException;
+
/**
- * Save updates to the record. Only accept_request, and decision_date are set-able.
+ * Retrieve all requests (as iterator) for a given bitstream UUID
+ * A UUID parameter is used here rather than Bitstream object, to make it usable
+ * in situations even when a bitstream object no longer exists, but orphaned
+ * entries need to be found by their (previous) bitstream UUID.
+ *
+ * @param context current DSpace context
+ * @param bitstreamId the bitstream UUID to search for
+ * @return the matching requests (or empty iterator)
+ */
+ Iterator findByBitstreamId(Context context, UUID bitstreamId) throws SQLException;
+
+ /**
+ * Save updates to the record. Only accept_request, decision_date, access_period are settable.
+ *
+ * Note: the "is settable" rules mentioned here are enforced in RequestItemRest with annotations meaning that
+ * these JSON properties are considered READ-ONLY by the core DSpaceRestRepository methods
*
* @param context The relevant DSpace Context.
* @param requestItem requested item
*/
- public void update(Context context, RequestItem requestItem);
+ void update(Context context, RequestItem requestItem);
/**
* Remove the record from the database.
@@ -85,7 +102,7 @@ public List findAll(Context context)
* @param context current DSpace context.
* @param request record to be removed.
*/
- public void delete(Context context, RequestItem request);
+ void delete(Context context, RequestItem request);
/**
* Is there at least one valid READ resource policy for this object?
@@ -94,6 +111,6 @@ public List findAll(Context context)
* @return true if a READ policy applies.
* @throws SQLException passed through.
*/
- public boolean isRestricted(Context context, DSpaceObject o)
+ boolean isRestricted(Context context, DSpaceObject o)
throws SQLException;
}
diff --git a/dspace-api/src/main/java/org/dspace/app/util/DSpaceObjectUtilsImpl.java b/dspace-api/src/main/java/org/dspace/app/util/DSpaceObjectUtilsImpl.java
index e3f2b0ea5faa..33621abd529d 100644
--- a/dspace-api/src/main/java/org/dspace/app/util/DSpaceObjectUtilsImpl.java
+++ b/dspace-api/src/main/java/org/dspace/app/util/DSpaceObjectUtilsImpl.java
@@ -15,12 +15,15 @@
import org.dspace.content.factory.ContentServiceFactory;
import org.dspace.content.service.DSpaceObjectService;
import org.dspace.core.Context;
+import org.dspace.handle.service.HandleService;
import org.springframework.beans.factory.annotation.Autowired;
public class DSpaceObjectUtilsImpl implements DSpaceObjectUtils {
@Autowired
private ContentServiceFactory contentServiceFactory;
+ @Autowired
+ private HandleService handleService;
/**
* Retrieve a DSpaceObject from its uuid. As this method need to iterate over all the different services that
@@ -44,4 +47,32 @@ public DSpaceObject findDSpaceObject(Context context, UUID uuid) throws SQLExcep
}
return null;
}
+
+ /**
+ * Retrieve a DSpaceObject from its uuid or handle. As this method need to iterate over all the different services
+ * that support concrete class of DSpaceObject it has poor performance. Please consider the use of the direct
+ * service (ItemService, CommunityService, etc.) if you know in advance the type of DSpaceObject that you are
+ * looking for
+ *
+ * @param context DSpace context
+ * @param id the uuid or handle to lookup
+ * @return the DSpaceObject if any with the supplied uuid or handle
+ * @throws SQLException
+ */
+ public DSpaceObject findDSpaceObject(Context context, String id) throws SQLException {
+ DSpaceObject dso = handleService.resolveToObject(context, id);
+ // if the id did not resolve to a handle, check if it is a uuid
+ if (dso == null) {
+ UUID uuid = null;
+ try {
+ uuid = UUID.fromString(id);
+ } catch (IllegalArgumentException iae) {
+ // nothing to do here. We check later fo empty uuids anyway
+ }
+ if (uuid != null) {
+ dso = findDSpaceObject(context, uuid);
+ }
+ }
+ return dso;
+ }
}
diff --git a/dspace-api/src/main/java/org/dspace/app/util/MetadataExposureServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/util/MetadataExposureServiceImpl.java
index 681867371a06..9a281d65775b 100644
--- a/dspace-api/src/main/java/org/dspace/app/util/MetadataExposureServiceImpl.java
+++ b/dspace-api/src/main/java/org/dspace/app/util/MetadataExposureServiceImpl.java
@@ -63,7 +63,7 @@ public class MetadataExposureServiceImpl implements MetadataExposureService {
protected Map> hiddenElementSets = null;
protected Map>> hiddenElementMaps = null;
- protected final String CONFIG_PREFIX = "metadata.hide.";
+ public static final String CONFIG_PREFIX = "metadata.hide.";
@Autowired(required = true)
protected AuthorizeService authorizeService;
diff --git a/dspace-api/src/main/java/org/dspace/app/util/service/DSpaceObjectUtils.java b/dspace-api/src/main/java/org/dspace/app/util/service/DSpaceObjectUtils.java
index e6a97004ef60..8088a4ca4de5 100644
--- a/dspace-api/src/main/java/org/dspace/app/util/service/DSpaceObjectUtils.java
+++ b/dspace-api/src/main/java/org/dspace/app/util/service/DSpaceObjectUtils.java
@@ -30,4 +30,17 @@ public interface DSpaceObjectUtils {
* @throws SQLException
*/
public DSpaceObject findDSpaceObject(Context context, UUID uuid) throws SQLException;
+
+ /**
+ * Retrieve a DSpaceObject from its uuid or handle. As this method need to iterate over all the different services
+ * that support concrete class of DSpaceObject it has poor performance. Please consider the use of the direct
+ * service (ItemService, CommunityService, etc.) if you know in advance the type of DSpaceObject that you are
+ * looking for
+ *
+ * @param context DSpace context
+ * @param id the uuid or handle to lookup
+ * @return the DSpaceObject if any with the supplied uuid or handle
+ * @throws SQLException
+ */
+ public DSpaceObject findDSpaceObject(Context context, String id) throws SQLException;
}
diff --git a/dspace-api/src/main/java/org/dspace/authenticate/AuthenticationMethod.java b/dspace-api/src/main/java/org/dspace/authenticate/AuthenticationMethod.java
index d316cb636f87..a7140f244dfa 100644
--- a/dspace-api/src/main/java/org/dspace/authenticate/AuthenticationMethod.java
+++ b/dspace-api/src/main/java/org/dspace/authenticate/AuthenticationMethod.java
@@ -54,7 +54,7 @@ public interface AuthenticationMethod {
public static final int BAD_CREDENTIALS = 2;
/**
- * Not allowed to login this way without X.509 certificate.
+ * Not allowed to login this way without a certificate.
*/
public static final int CERT_REQUIRED = 3;
@@ -124,8 +124,8 @@ public boolean allowSetPassword(Context context,
* Predicate, is this an implicit authentication method.
* An implicit method gets credentials from the environment (such as
* an HTTP request or even Java system properties) rather than the
- * explicit username and password. For example, a method that reads
- * the X.509 certificates in an HTTPS request is implicit.
+ * explicit username and password. For example, a method that provides
+ * IP-based authentication is implicit.
*
* @return true if this method uses implicit authentication.
*/
@@ -166,7 +166,7 @@ public List getSpecialGroups(Context context, HttpServletRequest request)
* otherwise
*/
public default boolean areSpecialGroupsApplicable(Context context, HttpServletRequest request) {
- return getName().equals(context.getAuthenticationMethod());
+ return getName().equals(context.getAuthenticationMethod()) || isUsed(context, request);
}
/**
@@ -188,7 +188,7 @@ public default boolean areSpecialGroupsApplicable(Context context, HttpServletRe
*
Meaning:
* SUCCESS - authenticated OK.
* BAD_CREDENTIALS - user exists, but credentials (e.g. passwd) don't match
- * CERT_REQUIRED - not allowed to login this way without X.509 cert.
+ * CERT_REQUIRED - not allowed to login this way without a cert.
* NO_SUCH_USER - user not found using this method.
* BAD_ARGS - user/pw not appropriate for this method
* @throws SQLException if database error
diff --git a/dspace-api/src/main/java/org/dspace/authenticate/AuthenticationServiceImpl.java b/dspace-api/src/main/java/org/dspace/authenticate/AuthenticationServiceImpl.java
index 2b07f73c489c..c0dde49b13a3 100644
--- a/dspace-api/src/main/java/org/dspace/authenticate/AuthenticationServiceImpl.java
+++ b/dspace-api/src/main/java/org/dspace/authenticate/AuthenticationServiceImpl.java
@@ -38,11 +38,11 @@
* Configuration
* The stack of authentication methods is defined by one property in the DSpace configuration:
*
- * plugin.sequence.org.dspace.eperson.AuthenticationMethod = a list of method class names
+ * plugin.sequence.org.dspace.authenticate.AuthenticationMethod = a list of method class names
* e.g.
- * plugin.sequence.org.dspace.eperson.AuthenticationMethod = \
- * org.dspace.eperson.X509Authentication, \
- * org.dspace.eperson.PasswordAuthentication
+ * plugin.sequence.org.dspace.authenticate.AuthenticationMethod = \
+ * org.dspace.authenticate.IPAuthentication, \
+ * org.dspace.authenticate.PasswordAuthentication
*
Meaning:
* SUCCESS - authenticated OK.
* BAD_CREDENTIALS - user exists, but credentials (e.g. passwd) don't match
- * CERT_REQUIRED - not allowed to login this way without X.509 cert.
+ * CERT_REQUIRED - not allowed to login this way without a cert.
* NO_SUCH_USER - user not found using this method.
* BAD_ARGS - user/pw not appropriate for this method
*/
@@ -238,12 +234,18 @@ public int authenticate(Context context,
String idField = configurationService.getProperty("authentication-ldap.id_field");
String dn = "";
- // If adminUser is blank and anonymous search is not allowed, then we can't search so construct the DN
- // instead of searching it
if ((StringUtils.isBlank(adminUser) || StringUtils.isBlank(adminPassword)) && !anonymousSearch) {
- dn = idField + "=" + netid + "," + objectContext;
+ try {
+ dn = LdapNameBuilder.newInstance(objectContext)
+ .add(idField, netid)
+ .build()
+ .toString();
+ } catch (Exception e) {
+ log.warn("Failed to build DN for user " + netid, e);
+ return BAD_ARGS;
+ }
} else {
- dn = ldap.getDNOfUser(adminUser, adminPassword, context, netid);
+ dn = ldap.getDNOfUser(context, netid);
}
// Check a DN was found
@@ -322,7 +324,7 @@ public int authenticate(Context context,
log.info(LogHelper.getHeader(context,
"type=ldap-login", "type=ldap_but_already_email"));
context.turnOffAuthorisationSystem();
- setEpersonAttributes(context, eperson, ldap, Optional.of(netid));
+ setEpersonAttributes(context, eperson, ldap, Optional.of(netid), email);
ePersonService.update(context, eperson);
context.dispatchEvents();
context.restoreAuthSystemState();
@@ -339,7 +341,7 @@ public int authenticate(Context context,
try {
context.turnOffAuthorisationSystem();
eperson = ePersonService.create(context);
- setEpersonAttributes(context, eperson, ldap, Optional.of(netid));
+ setEpersonAttributes(context, eperson, ldap, Optional.of(netid), email);
eperson.setCanLogIn(true);
authenticationService.initEPerson(context, request, eperson);
ePersonService.update(context, eperson);
@@ -381,11 +383,24 @@ public int authenticate(Context context,
* Update eperson's attributes
*/
private void setEpersonAttributes(Context context, EPerson eperson, SpeakerToLDAP ldap, Optional netid)
- throws SQLException {
+ throws SQLException {
+ setEpersonAttributes(context, eperson, ldap, netid, null);
+ }
+
+ /**
+ * Update eperson's attributes
+ */
+ private void setEpersonAttributes(Context context, EPerson eperson, SpeakerToLDAP ldap, Optional netid,
+ String email)
+ throws SQLException {
+ // Set email address: first try LDAP email, then fallback to provided email parameter
if (StringUtils.isNotEmpty(ldap.ldapEmail)) {
eperson.setEmail(ldap.ldapEmail);
+ } else if (StringUtils.isNotEmpty(email)) {
+ eperson.setEmail(email);
}
+
if (StringUtils.isNotEmpty(ldap.ldapGivenName)) {
eperson.setFirstName(context, ldap.ldapGivenName);
}
@@ -429,6 +444,7 @@ private static class SpeakerToLDAP {
final String ldap_group_field;
final boolean useTLS;
+ private LdapTemplate ldapTemplate;
SpeakerToLDAP(Logger thelog) {
ConfigurationService configurationService
@@ -445,252 +461,112 @@ private static class SpeakerToLDAP {
ldap_phone_field = configurationService.getProperty("authentication-ldap.phone_field");
ldap_group_field = configurationService.getProperty("authentication-ldap.login.groupmap.attribute");
useTLS = configurationService.getBooleanProperty("authentication-ldap.starttls", false);
- }
- protected String getDNOfUser(String adminUser, String adminPassword, Context context, String netid) {
- // The resultant DN
- String resultDN;
+ setupSpringLdap(configurationService);
+ }
- // The search scope to use (default to 0)
- int ldap_search_scope_value = 0;
- try {
- ldap_search_scope_value = Integer.parseInt(ldap_search_scope.trim());
- } catch (NumberFormatException e) {
- // Log the error if it has been set but is invalid
- if (ldap_search_scope != null) {
- log.warn(LogHelper.getHeader(context,
- "ldap_authentication", "invalid search scope: " + ldap_search_scope));
- }
+ private void setupSpringLdap(ConfigurationService cfg) {
+ LdapContextSource contextSource = new LdapContextSource();
+ if (StringUtils.isBlank(ldap_provider_url)) {
+ throw new IllegalStateException(
+ "LDAP provider URL is empty! Please check 'authentication-ldap.provider_url' in your configuration."
+ );
}
+ contextSource.setUrl(ldap_provider_url);
- // Set up environment for creating initial context
- @SuppressWarnings("UseOfObsoleteCollectionType")
- Hashtable env = new Hashtable<>();
- env.put(javax.naming.Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
- env.put(javax.naming.Context.PROVIDER_URL, ldap_provider_url);
-
- LdapContext ctx = null;
- StartTlsResponse startTLSResponse = null;
+ String adminUser = cfg.getProperty("authentication-ldap.search.user");
+ String adminPass = cfg.getProperty("authentication-ldap.search.password");
- try {
- if ((adminUser != null) && (!adminUser.trim().equals("")) &&
- (adminPassword != null) && (!adminPassword.trim().equals(""))) {
- if (useTLS) {
- ctx = new InitialLdapContext(env, null);
- // start TLS
- startTLSResponse = (StartTlsResponse) ctx
- .extendedOperation(new StartTlsRequest());
-
- startTLSResponse.negotiate();
-
- // perform simple client authentication
- ctx.addToEnvironment(javax.naming.Context.SECURITY_AUTHENTICATION, "simple");
- ctx.addToEnvironment(javax.naming.Context.SECURITY_PRINCIPAL,
- adminUser);
- ctx.addToEnvironment(javax.naming.Context.SECURITY_CREDENTIALS,
- adminPassword);
- } else {
- // Use admin credentials for search// Authenticate
- env.put(javax.naming.Context.SECURITY_AUTHENTICATION, "simple");
- env.put(javax.naming.Context.SECURITY_PRINCIPAL, adminUser);
- env.put(javax.naming.Context.SECURITY_CREDENTIALS, adminPassword);
- }
- } else {
- // Use anonymous authentication
- env.put(javax.naming.Context.SECURITY_AUTHENTICATION, "none");
- }
+ if (StringUtils.isNotBlank(adminUser) && StringUtils.isNotBlank(adminPass)) {
+ contextSource.setUserDn(adminUser);
+ contextSource.setPassword(adminPass);
+ } else {
+ contextSource.setAnonymousReadOnly(true);
+ }
- if (ctx == null) {
- // Create initial context
- ctx = new InitialLdapContext(env, null);
- }
+ contextSource.setPooled(true);
+ contextSource.afterPropertiesSet();
+ this.ldapTemplate = new LdapTemplate(contextSource);
+ this.ldapTemplate.setIgnorePartialResultException(true);
+ }
- Attributes matchAttrs = new BasicAttributes(true);
- matchAttrs.put(new BasicAttribute(ldap_id_field, netid));
+ protected String getDNOfUser(Context context, String netid) {
+ try {
+ EqualsFilter filter = new EqualsFilter(ldap_id_field, netid);
- // look up attributes
- 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) {
- searchName = ldap_search_context;
- } else {
- searchName = ldap_provider_url + ldap_search_context;
- }
- @SuppressWarnings("BanJNDI")
- NamingEnumeration answer = ctx.search(
- searchName,
- "(&({0}={1}))", new Object[] {ldap_id_field,
- netid}, ctrls);
-
- while (answer.hasMoreElements()) {
- SearchResult sr = answer.next();
- if (StringUtils.isEmpty(ldap_search_context)) {
- resultDN = sr.getName();
- } else {
- resultDN = (sr.getName() + "," + ldap_search_context);
- }
+ log.debug("Searching for user using Spring LDAP filter: {}", filter.toString());
- String attlist[] = {ldap_email_field, ldap_givenname_field,
- ldap_surname_field, ldap_phone_field, ldap_group_field};
- Attributes atts = sr.getAttributes();
- Attribute att;
+ List foundDNs = ldapTemplate.search(
+ LdapQueryBuilder.query().base(ldap_search_context).filter(filter),
+ (ContextMapper) (originalCtx) -> {
+ DirContextOperations ctx = (DirContextOperations) originalCtx;
- if (attlist[0] != null) {
- att = atts.get(attlist[0]);
- if (att != null) {
- ldapEmail = (String) att.get();
- }
+ if (ldap_email_field != null) {
+ this.ldapEmail = ctx.getStringAttribute(ldap_email_field);
}
- if (attlist[1] != null) {
- att = atts.get(attlist[1]);
- if (att != null) {
- ldapGivenName = (String) att.get();
- }
+ if (ldap_givenname_field != null) {
+ this.ldapGivenName = ctx.getStringAttribute(ldap_givenname_field);
}
- if (attlist[2] != null) {
- att = atts.get(attlist[2]);
- if (att != null) {
- ldapSurname = (String) att.get();
- }
+ if (ldap_surname_field != null) {
+ this.ldapSurname = ctx.getStringAttribute(ldap_surname_field);
}
- if (attlist[3] != null) {
- att = atts.get(attlist[3]);
- if (att != null) {
- ldapPhone = (String) att.get();
- }
+ if (ldap_phone_field != null) {
+ this.ldapPhone = ctx.getStringAttribute(ldap_phone_field);
}
- if (attlist[4] != null) {
- att = atts.get(attlist[4]);
- if (att != null) {
- // loop through all groups returned by LDAP
- ldapGroup = new ArrayList<>();
- for (NamingEnumeration val = att.getAll(); val.hasMoreElements(); ) {
- ldapGroup.add((String) val.next());
- }
+ if (ldap_group_field != null) {
+ String[] groups = ctx.getStringAttributes(ldap_group_field);
+ if (groups != null) {
+ this.ldapGroup = new ArrayList<>(Arrays.asList(groups));
}
}
-
- if (answer.hasMoreElements()) {
- // Oh dear - more than one match
- // Ambiguous user, can't continue
-
- } else {
- log.debug(LogHelper.getHeader(context, "got DN", resultDN));
- return resultDN;
- }
+ return ctx.getNameInNamespace();
}
- } catch (NamingException e) {
- // if the lookup fails go ahead and create a new record for them because the authentication
- // succeeded
- log.warn(LogHelper.getHeader(context,
- "ldap_attribute_lookup", "type=failed_search "
- + e));
- }
- } catch (NamingException | IOException e) {
- log.warn(LogHelper.getHeader(context,
- "ldap_authentication", "type=failed_auth " + e));
- } finally {
- // Close the context when we're done
- try {
- if (startTLSResponse != null) {
- startTLSResponse.close();
- }
- if (ctx != null) {
- ctx.close();
- }
- } catch (NamingException | IOException e) {
- // ignore
+ );
+
+ if (foundDNs.isEmpty()) {
+ log.debug(LogHelper.getHeader(context, "getDNOfUser", "no DN found for user " + netid));
+ return null;
+ } else if (foundDNs.size() > 1) {
+ log.warn(LogHelper.getHeader(context, "getDNOfUser", "multiple DN found for user " + netid));
+ return null;
}
+ String resultDN = foundDNs.get(0);
+ log.debug(LogHelper.getHeader(context, "got DN", resultDN));
+ return resultDN;
+ } catch (Exception e) {
+ log.warn(LogHelper.getHeader(context, "ldap_authentication", "type=failed_search " + e));
+ return null;
}
-
- // No DN match found
- return null;
}
/**
* contact the ldap server and attempt to authenticate
*/
- protected boolean ldapAuthenticate(String netid, String password,
- Context context) {
- if (!password.equals("")) {
-
- LdapContext ctx = null;
- StartTlsResponse startTLSResponse = null;
-
-
- // Set up environment for creating initial context
- @SuppressWarnings("UseOfObsoleteCollectionType")
- Hashtable env = new Hashtable<>();
- env.put(javax.naming.Context.INITIAL_CONTEXT_FACTORY,
- "com.sun.jndi.ldap.LdapCtxFactory");
- env.put(javax.naming.Context.PROVIDER_URL, ldap_provider_url);
-
- try {
- if (useTLS) {
- ctx = new InitialLdapContext(env, null);
- // start TLS
- startTLSResponse = (StartTlsResponse) ctx
- .extendedOperation(new StartTlsRequest());
-
- startTLSResponse.negotiate();
-
- // perform simple client authentication
- ctx.addToEnvironment(javax.naming.Context.SECURITY_AUTHENTICATION, "simple");
- ctx.addToEnvironment(javax.naming.Context.SECURITY_PRINCIPAL,
- netid);
- ctx.addToEnvironment(javax.naming.Context.SECURITY_CREDENTIALS,
- password);
- ctx.addToEnvironment(javax.naming.Context.AUTHORITATIVE, "true");
- ctx.addToEnvironment(javax.naming.Context.REFERRAL, "follow");
- // dummy operation to check if authentication has succeeded
- @SuppressWarnings("BanJNDI")
- Attributes trash = ctx.getAttributes("");
- } else if (!useTLS) {
- // Authenticate
- env.put(javax.naming.Context.SECURITY_AUTHENTICATION, "Simple");
- env.put(javax.naming.Context.SECURITY_PRINCIPAL, netid);
- env.put(javax.naming.Context.SECURITY_CREDENTIALS, password);
- env.put(javax.naming.Context.AUTHORITATIVE, "true");
- env.put(javax.naming.Context.REFERRAL, "follow");
-
- // Try to bind
- ctx = new InitialLdapContext(env, null);
- }
- } catch (NamingException | IOException e) {
- // something went wrong (like wrong password) so return false
- log.warn(LogHelper.getHeader(context,
- "ldap_authentication", "type=failed_auth " + e));
- return false;
- } finally {
- // Close the context when we're done
- try {
- if (startTLSResponse != null) {
- startTLSResponse.close();
- }
- if (ctx != null) {
- ctx.close();
- }
- } catch (NamingException | IOException e) {
- // ignore
- }
- }
- } else {
+ protected boolean ldapAuthenticate(String dn, String password, Context context) {
+ if (StringUtils.isBlank(password)) {
return false;
}
- return true;
+ try {
+ boolean authenticated = ldapTemplate.authenticate(
+ LdapUtils.newLdapName(dn),
+ new PresentFilter(ldap_id_field).toString(),
+ password
+ );
+ return authenticated;
+
+ } catch (Exception e) {
+ log.warn(LogHelper.getHeader(context, "ldap_authentication", "type=failed_auth " + e));
+ return false;
+ }
}
}
+
/**
* Returns the URL of an external login page which is not applicable for this authn method.
*
diff --git a/dspace-api/src/main/java/org/dspace/authenticate/OrcidAuthenticationBean.java b/dspace-api/src/main/java/org/dspace/authenticate/OrcidAuthenticationBean.java
index 590bbf6cf0ef..be2111fad9bc 100644
--- a/dspace-api/src/main/java/org/dspace/authenticate/OrcidAuthenticationBean.java
+++ b/dspace-api/src/main/java/org/dspace/authenticate/OrcidAuthenticationBean.java
@@ -120,7 +120,7 @@ public String loginPageURL(Context context, HttpServletRequest request, HttpServ
@Override
public boolean isUsed(Context context, HttpServletRequest request) {
- return request.getAttribute(ORCID_AUTH_ATTRIBUTE) != null;
+ return request != null && request.getAttribute(ORCID_AUTH_ATTRIBUTE) != null;
}
@Override
diff --git a/dspace-api/src/main/java/org/dspace/authenticate/PasswordAuthentication.java b/dspace-api/src/main/java/org/dspace/authenticate/PasswordAuthentication.java
index 8e030305c957..035a235422a6 100644
--- a/dspace-api/src/main/java/org/dspace/authenticate/PasswordAuthentication.java
+++ b/dspace-api/src/main/java/org/dspace/authenticate/PasswordAuthentication.java
@@ -188,7 +188,7 @@ public List getSpecialGroups(Context context, HttpServletRequest request)
*
Meaning:
* SUCCESS - authenticated OK.
* BAD_CREDENTIALS - user exists, but password doesn't match
- * CERT_REQUIRED - not allowed to login this way without X.509 cert.
+ * CERT_REQUIRED - not allowed to login this way without a cert.
* NO_SUCH_USER - no EPerson with matching email address.
* BAD_ARGS - missing username, or user matched but cannot login.
* @throws SQLException if database error
@@ -213,7 +213,7 @@ public int authenticate(Context context,
// cannot login this way
return BAD_ARGS;
} else if (eperson.getRequireCertificate()) {
- // this user can only login with x.509 certificate
+ // this user can only login with a certificate
log.warn(LogHelper.getHeader(context, "authenticate",
"rejecting PasswordAuthentication because " + username + " requires " +
"certificate."));
diff --git a/dspace-api/src/main/java/org/dspace/authenticate/ShibAuthentication.java b/dspace-api/src/main/java/org/dspace/authenticate/ShibAuthentication.java
index 24d8266012d4..13a5ae6d0dfd 100644
--- a/dspace-api/src/main/java/org/dspace/authenticate/ShibAuthentication.java
+++ b/dspace-api/src/main/java/org/dspace/authenticate/ShibAuthentication.java
@@ -160,7 +160,7 @@ public class ShibAuthentication implements AuthenticationMethod {
* SUCCESS - authenticated OK.
* BAD_CREDENTIALS - user exists, but credentials (e.g. passwd)
* don't match
- * CERT_REQUIRED - not allowed to login this way without X.509 cert.
+ * CERT_REQUIRED - not allowed to login this way without a cert.
*
* NO_SUCH_USER - user not found using this method.
* BAD_ARGS - user/pw not appropriate for this method
@@ -417,8 +417,7 @@ public boolean allowSetPassword(Context context,
* Predicate, is this an implicit authentication method. An implicit method
* gets credentials from the environment (such as an HTTP request or even
* Java system properties) rather than the explicit username and password.
- * For example, a method that reads the X.509 certificates in an HTTPS
- * request is implicit.
+ * For example, a method that provides IP-based authentication is implicit.
*
* @return true if this method uses implicit authentication.
*/
@@ -871,7 +870,7 @@ protected void updateEPerson(Context context, HttpServletRequest request, EPerso
String[] nameParts = MetadataFieldName.parse(field);
ePersonService.setMetadataSingleValue(context, eperson,
- nameParts[0], nameParts[1], nameParts[2], value, null);
+ nameParts[0], nameParts[1], nameParts[2], null, value);
log.debug("Updated the eperson's '{}' metadata using header: '{}' = '{}'.",
field, header, value);
}
@@ -917,7 +916,7 @@ protected int swordCompatibility(Context context, String username, String passwo
" is not allowed to login.");
return BAD_ARGS;
} else if (eperson.getRequireCertificate()) {
- // this user can only login with x.509 certificate
+ // this user can only login with a certificate
log.error(
"Shibboleth-based password authentication failed for user " + username + " because the eperson object" +
" requires a certificate to authenticate..");
diff --git a/dspace-api/src/main/java/org/dspace/authenticate/X509Authentication.java b/dspace-api/src/main/java/org/dspace/authenticate/X509Authentication.java
deleted file mode 100644
index 55843c710760..000000000000
--- a/dspace-api/src/main/java/org/dspace/authenticate/X509Authentication.java
+++ /dev/null
@@ -1,616 +0,0 @@
-/**
- * The contents of this file are subject to the license and copyright
- * detailed in the LICENSE and NOTICE files at the root of the source
- * tree and available online at
- *
- * http://www.dspace.org/license/
- */
-package org.dspace.authenticate;
-
-import java.io.BufferedInputStream;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.security.GeneralSecurityException;
-import java.security.KeyStore;
-import java.security.Principal;
-import java.security.PublicKey;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateException;
-import java.security.cert.CertificateFactory;
-import java.security.cert.X509Certificate;
-import java.sql.SQLException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Enumeration;
-import java.util.List;
-import java.util.StringTokenizer;
-
-import jakarta.servlet.http.HttpServletRequest;
-import jakarta.servlet.http.HttpServletResponse;
-import jakarta.servlet.http.HttpSession;
-import org.apache.commons.lang3.ArrayUtils;
-import org.apache.logging.log4j.Logger;
-import org.dspace.authenticate.factory.AuthenticateServiceFactory;
-import org.dspace.authenticate.service.AuthenticationService;
-import org.dspace.authorize.AuthorizeException;
-import org.dspace.core.Context;
-import org.dspace.core.LogHelper;
-import org.dspace.eperson.EPerson;
-import org.dspace.eperson.Group;
-import org.dspace.eperson.factory.EPersonServiceFactory;
-import org.dspace.eperson.service.EPersonService;
-import org.dspace.eperson.service.GroupService;
-import org.dspace.services.ConfigurationService;
-import org.dspace.services.factory.DSpaceServicesFactory;
-
-/**
- * Implicit authentication method that gets credentials from the X.509 client
- * certificate supplied by the HTTPS client when connecting to this server. The
- * email address in that certificate is taken as the authenticated user name
- * with no further checking, so be sure your HTTP server (e.g. Tomcat) is
- * configured correctly to accept only client certificates it can validate.
- *
- * See the AuthenticationMethod interface for more details.
- *
- * Configuration:
- *
- *
- * x509.keystore.path =
- *
- * path to Java keystore file
- *
- * keystore.password =
- *
- * password to access the keystore
- *
- * ca.cert =
- *
- * path to certificate file for CA whose client certs to accept.
- *
- * autoregister =
- *
- * "true" if E-Person is created automatically for unknown new users.
- *
- * groups =
- *
- * comma-delimited list of special groups to add user to if authenticated.
- *
- * emaildomain =
- *
- * email address domain (after the 'at' symbol) to match before allowing
- * membership in special groups.
- *
- *
- *
- * Only one of the "keystore.path" or "ca.cert"
- * options is required. If you supply a keystore, then all of the "trusted"
- * certificates in the keystore represent CAs whose client certificates will be
- * accepted. The ca.cert option only allows a single CA to be
- * named.
- *
- * You can configure both a keystore and a CA cert, and both will be
- * used.
- *
- * The autoregister configuration parameter determines what the
- * canSelfRegister() method returns. It also allows an EPerson
- * record to be created automatically when the presented certificate is
- * acceptable but there is no corresponding EPerson.
- *
- * @author Larry Stone
- * @version $Revision$
- */
-public class X509Authentication implements AuthenticationMethod {
-
- /**
- * log4j category
- */
- private static Logger log = org.apache.logging.log4j.LogManager.getLogger(X509Authentication.class);
-
- /**
- * public key of CA to check client certs against.
- */
- private static PublicKey caPublicKey = null;
-
- /**
- * key store for CA certs if we use that
- */
- private static KeyStore caCertKeyStore = null;
-
- private static String loginPageTitle = null;
-
- private static String loginPageURL = null;
-
- protected AuthenticationService authenticationService = AuthenticateServiceFactory.getInstance()
- .getAuthenticationService();
- protected EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService();
- protected GroupService groupService = EPersonServiceFactory.getInstance().getGroupService();
- protected ConfigurationService configurationService =
- DSpaceServicesFactory.getInstance().getConfigurationService();
-
- private static final String X509_AUTHENTICATED = "x509.authenticated";
-
-
- /**
- * Initialization: Set caPublicKey and/or keystore. This loads the
- * information needed to check if a client cert presented is valid and
- * acceptable.
- */
- static {
- ConfigurationService configurationService =
- DSpaceServicesFactory.getInstance().getConfigurationService();
- /*
- * allow identification of alternative entry points for certificate
- * authentication when selected by the user rather than implicitly.
- */
- loginPageTitle = configurationService
- .getProperty("authentication-x509.chooser.title.key");
- loginPageURL = configurationService
- .getProperty("authentication-x509.chooser.uri");
-
- String keystorePath = configurationService
- .getProperty("authentication-x509.keystore.path");
- String keystorePassword = configurationService
- .getProperty("authentication-x509.keystore.password");
- String caCertPath = configurationService
- .getProperty("authentication-x509.ca.cert");
-
- // First look for keystore full of trusted certs.
- if (keystorePath != null) {
- FileInputStream fis = null;
- if (keystorePassword == null) {
- keystorePassword = "";
- }
- try {
- KeyStore ks = KeyStore.getInstance("JKS");
- fis = new FileInputStream(keystorePath);
- ks.load(fis, keystorePassword.toCharArray());
- caCertKeyStore = ks;
- } catch (IOException e) {
- log
- .error("X509Authentication: Failed to load CA keystore, file="
- + keystorePath + ", error=" + e.toString());
- } catch (GeneralSecurityException e) {
- log
- .error("X509Authentication: Failed to extract CA keystore, file="
- + keystorePath + ", error=" + e.toString());
- } finally {
- if (fis != null) {
- try {
- fis.close();
- } catch (IOException ioe) {
- // ignore
- }
- }
- }
- }
-
- // Second, try getting public key out of CA cert, if that's configured.
- if (caCertPath != null) {
- InputStream is = null;
- FileInputStream fis = null;
- try {
- fis = new FileInputStream(caCertPath);
- is = new BufferedInputStream(fis);
- X509Certificate cert = (X509Certificate) CertificateFactory
- .getInstance("X.509").generateCertificate(is);
- if (cert != null) {
- caPublicKey = cert.getPublicKey();
- }
- } catch (IOException e) {
- log.error("X509Authentication: Failed to load CA cert, file="
- + caCertPath + ", error=" + e.toString());
- } catch (CertificateException e) {
- log
- .error("X509Authentication: Failed to extract CA cert, file="
- + caCertPath + ", error=" + e.toString());
- } finally {
- if (is != null) {
- try {
- is.close();
- } catch (IOException ioe) {
- // ignore
- }
- }
-
- if (fis != null) {
- try {
- fis.close();
- } catch (IOException ioe) {
- // ignore
- }
- }
- }
- }
- }
-
- /**
- * Return the email address from certificate, or null if an
- * email address cannot be found in the certificate.
- *
- * Note that the certificate parsing has only been tested with certificates
- * granted by the MIT Certification Authority, and may not work elsewhere.
- *
- * @param certificate -
- * An X509 certificate object
- * @return - The email address found in certificate, or null if an email
- * address cannot be found in the certificate.
- */
- private static String getEmail(X509Certificate certificate)
- throws SQLException {
- Principal principal = certificate.getSubjectDN();
-
- if (principal == null) {
- return null;
- }
-
- String dn = principal.getName();
- if (dn == null) {
- return null;
- }
-
- StringTokenizer tokenizer = new StringTokenizer(dn, ",");
- String token = null;
- while (tokenizer.hasMoreTokens()) {
- int len = "emailaddress=".length();
-
- token = (String) tokenizer.nextToken();
-
- if (token.toLowerCase().startsWith("emailaddress=")) {
- // Make sure the token actually contains something
- if (token.length() <= len) {
- return null;
- }
-
- return token.substring(len).toLowerCase();
- }
- }
-
- return null;
- }
-
- /**
- * Verify CERTIFICATE against KEY. Return true if and only if CERTIFICATE is
- * valid and can be verified against KEY.
- *
- * @param context The current DSpace context
- * @param certificate -
- * An X509 certificate object
- * @return - True if CERTIFICATE is valid and can be verified against KEY,
- * false otherwise.
- */
- private static boolean isValid(Context context, X509Certificate certificate) {
- if (certificate == null) {
- return false;
- }
-
- // This checks that current time is within cert's validity window:
- try {
- certificate.checkValidity();
- } catch (CertificateException e) {
- log.info(LogHelper.getHeader(context, "authentication",
- "X.509 Certificate is EXPIRED or PREMATURE: "
- + e.toString()));
- return false;
- }
-
- // Try CA public key, if available.
- if (caPublicKey != null) {
- try {
- certificate.verify(caPublicKey);
- return true;
- } catch (GeneralSecurityException e) {
- log.info(LogHelper.getHeader(context, "authentication",
- "X.509 Certificate FAILED SIGNATURE check: "
- + e.toString()));
- }
- }
-
- // Try it with keystore, if available.
- if (caCertKeyStore != null) {
- try {
- Enumeration ke = caCertKeyStore.aliases();
-
- while (ke.hasMoreElements()) {
- String alias = (String) ke.nextElement();
- if (caCertKeyStore.isCertificateEntry(alias)) {
- Certificate ca = caCertKeyStore.getCertificate(alias);
- try {
- certificate.verify(ca.getPublicKey());
- return true;
- } catch (CertificateException ce) {
- // ignore
- }
- }
- }
- log
- .info(LogHelper
- .getHeader(context, "authentication",
- "Keystore method FAILED SIGNATURE check on client cert."));
- } catch (GeneralSecurityException e) {
- log.info(LogHelper.getHeader(context, "authentication",
- "X.509 Certificate FAILED SIGNATURE check: "
- + e.toString()));
- }
-
- }
- return false;
- }
-
- /**
- * Predicate, can new user automatically create EPerson. Checks
- * configuration value. You'll probably want this to be true to take
- * advantage of a Web certificate infrastructure with many more users than
- * are already known by DSpace.
- *
- * @throws SQLException if database error
- */
- @Override
- public boolean canSelfRegister(Context context, HttpServletRequest request,
- String username) throws SQLException {
- return configurationService
- .getBooleanProperty("authentication-x509.autoregister");
- }
-
- /**
- * Nothing extra to initialize.
- *
- * @throws SQLException if database error
- */
- @Override
- public void initEPerson(Context context, HttpServletRequest request,
- EPerson eperson) throws SQLException {
- }
-
- /**
- * We don't use EPerson password so there is no reason to change it.
- *
- * @throws SQLException if database error
- */
- @Override
- public boolean allowSetPassword(Context context,
- HttpServletRequest request, String username) throws SQLException {
- return false;
- }
-
- /**
- * Returns true, this is an implicit method.
- */
- @Override
- public boolean isImplicit() {
- return true;
- }
-
- /**
- * Returns a list of group names that the user should be added to upon
- * successful authentication, configured in dspace.cfg.
- *
- * @return List of special groups configured for this authenticator
- */
- private List getX509Groups() {
- List groupNames = new ArrayList();
-
- String[] groups = configurationService
- .getArrayProperty("authentication-x509.groups");
-
- if (ArrayUtils.isNotEmpty(groups)) {
- for (String group : groups) {
- groupNames.add(group.trim());
- }
- }
-
- return groupNames;
- }
-
- /**
- * Checks for configured email domain required to grant special groups
- * membership. If no email domain is configured to verify, special group
- * membership is simply granted.
- *
- * @param request -
- * The current request object
- * @param email -
- * The email address from the x509 certificate
- */
- private void setSpecialGroupsFlag(HttpServletRequest request, String email) {
- String emailDomain = null;
- emailDomain = (String) request
- .getAttribute("authentication.x509.emaildomain");
-
- HttpSession session = request.getSession(true);
-
- if (null != emailDomain && !"".equals(emailDomain)) {
- if (email.substring(email.length() - emailDomain.length()).equals(
- emailDomain)) {
- session.setAttribute("x509Auth", Boolean.TRUE);
- }
- } else {
- // No configured email domain to verify. Just flag
- // as authenticated so special groups are granted.
- session.setAttribute("x509Auth", Boolean.TRUE);
- }
- }
-
- /**
- * Return special groups configured in dspace.cfg for X509 certificate
- * authentication.
- *
- * @param context context
- * @param request object potentially containing the cert
- * @return An int array of group IDs
- * @throws SQLException if database error
- */
- @Override
- public List getSpecialGroups(Context context, HttpServletRequest request)
- throws SQLException {
- if (request == null) {
- return Collections.EMPTY_LIST;
- }
-
- Boolean authenticated = false;
- HttpSession session = request.getSession(false);
- authenticated = (Boolean) session.getAttribute("x509Auth");
- authenticated = (null == authenticated) ? false : authenticated;
-
- if (authenticated) {
- List groupNames = getX509Groups();
- List groups = new ArrayList<>();
-
- if (groupNames != null) {
- for (String groupName : groupNames) {
- if (groupName != null) {
- Group group = groupService.findByName(context, groupName);
- if (group != null) {
- groups.add(group);
- } else {
- log.warn(LogHelper.getHeader(context,
- "configuration_error", "unknown_group="
- + groupName));
- }
- }
- }
- }
-
- return groups;
- }
-
- return Collections.EMPTY_LIST;
- }
-
- /**
- * X509 certificate authentication. The client certificate is obtained from
- * the ServletRequest object.
- *
- *
If the certificate is valid, and corresponds to an existing EPerson,
- * and the user is allowed to login, return success.
- *
If the user is matched but is not allowed to login, it fails.
- *
If the certificate is valid, but there is no corresponding EPerson,
- * the "authentication.x509.autoregister" configuration
- * parameter is checked (via canSelfRegister())
- *
- *
If it's true, a new EPerson record is created for the certificate,
- * and the result is success.
- *
If it's false, return that the user was unknown.
- *
- *
- *
- *
- * @return One of: SUCCESS, BAD_CREDENTIALS, NO_SUCH_USER, BAD_ARGS
- * @throws SQLException if database error
- */
- @Override
- public int authenticate(Context context, String username, String password,
- String realm, HttpServletRequest request) throws SQLException {
- // Obtain the certificate from the request, if any
- X509Certificate[] certs = null;
- if (request != null) {
- certs = (X509Certificate[]) request
- .getAttribute("jakarta.servlet.request.X509Certificate");
- }
-
- if ((certs == null) || (certs.length == 0)) {
- return BAD_ARGS;
- } else {
- // We have a cert -- check it and get username from it.
- try {
- if (!isValid(context, certs[0])) {
- log
- .warn(LogHelper
- .getHeader(context, "authenticate",
- "type=x509certificate, status=BAD_CREDENTIALS (not valid)"));
- return BAD_CREDENTIALS;
- }
-
- // And it's valid - try and get an e-person
- String email = getEmail(certs[0]);
- EPerson eperson = null;
- if (email != null) {
- eperson = ePersonService.findByEmail(context, email);
- }
- if (eperson == null) {
- // Cert is valid, but no record.
- if (email != null
- && canSelfRegister(context, request, null)) {
- // Register the new user automatically
- log.info(LogHelper.getHeader(context, "autoregister",
- "from=x.509, email=" + email));
-
- // TEMPORARILY turn off authorisation
- context.turnOffAuthorisationSystem();
- eperson = ePersonService.create(context);
- eperson.setEmail(email);
- eperson.setCanLogIn(true);
- authenticationService.initEPerson(context, request,
- eperson);
- ePersonService.update(context, eperson);
- context.dispatchEvents();
- context.restoreAuthSystemState();
- context.setCurrentUser(eperson);
- request.setAttribute(X509_AUTHENTICATED, true);
- setSpecialGroupsFlag(request, email);
- return SUCCESS;
- } else {
- // No auto-registration for valid certs
- log
- .warn(LogHelper
- .getHeader(context, "authenticate",
- "type=cert_but_no_record, cannot auto-register"));
- return NO_SUCH_USER;
- }
- } else if (!eperson.canLogIn()) { // make sure this is a login account
- log.warn(LogHelper.getHeader(context, "authenticate",
- "type=x509certificate, email=" + email
- + ", canLogIn=false, rejecting."));
- return BAD_ARGS;
- } else {
- log.info(LogHelper.getHeader(context, "login",
- "type=x509certificate"));
- context.setCurrentUser(eperson);
- request.setAttribute(X509_AUTHENTICATED, true);
- setSpecialGroupsFlag(request, email);
- return SUCCESS;
- }
- } catch (AuthorizeException ce) {
- log.warn(LogHelper.getHeader(context, "authorize_exception",
- ""), ce);
- }
-
- return BAD_ARGS;
- }
- }
-
- /**
- * Returns URL of password-login servlet.
- *
- * @param context DSpace context, will be modified (EPerson set) upon success.
- * @param request The HTTP request that started this operation, or null if not
- * applicable.
- * @param response The HTTP response from the servlet method.
- * @return fully-qualified URL
- */
- @Override
- public String loginPageURL(Context context, HttpServletRequest request,
- HttpServletResponse response) {
- return loginPageURL;
- }
-
- @Override
- public String getName() {
- return "x509";
- }
-
- @Override
- public boolean isUsed(final Context context, final HttpServletRequest request) {
- if (request != null &&
- context.getCurrentUser() != null &&
- request.getAttribute(X509_AUTHENTICATED) != null) {
- return true;
- }
- return false;
- }
-
- @Override
- public boolean canChangePassword(Context context, EPerson ePerson, String currentPassword) {
- return false;
- }
-}
diff --git a/dspace-api/src/main/java/org/dspace/authenticate/service/AuthenticationService.java b/dspace-api/src/main/java/org/dspace/authenticate/service/AuthenticationService.java
index 45ad8932daec..8409f1e27d05 100644
--- a/dspace-api/src/main/java/org/dspace/authenticate/service/AuthenticationService.java
+++ b/dspace-api/src/main/java/org/dspace/authenticate/service/AuthenticationService.java
@@ -29,11 +29,11 @@
* Configuration
* The stack of authentication methods is defined by one property in the DSpace configuration:
*
- * plugin.sequence.org.dspace.eperson.AuthenticationMethod = a list of method class names
+ * plugin.sequence.org.dspace.authenticate.AuthenticationMethod = a list of method class names
* e.g.
- * plugin.sequence.org.dspace.eperson.AuthenticationMethod = \
- * org.dspace.eperson.X509Authentication, \
- * org.dspace.eperson.PasswordAuthentication
+ * plugin.sequence.org.dspace.authenticate.AuthenticationMethod = \
+ * org.dspace.authenticate.IPAuthentication, \
+ * org.dspace.authenticate.PasswordAuthentication
*
*
* The "stack" is always traversed in order, with the methods
@@ -64,7 +64,7 @@ public interface AuthenticationService {
*
Meaning:
* SUCCESS - authenticated OK.
* BAD_CREDENTIALS - user exists, but credentials (e.g. password) don't match
- * CERT_REQUIRED - not allowed to login this way without X.509 cert.
+ * CERT_REQUIRED - not allowed to login this way without a cert.
* NO_SUCH_USER - user not found using this method.
* BAD_ARGS - user/password not appropriate for this method
*/
@@ -91,7 +91,7 @@ public int authenticate(Context context,
*
Meaning:
* SUCCESS - authenticated OK.
* BAD_CREDENTIALS - user exists, but credentials (e.g. password) don't match
- * CERT_REQUIRED - not allowed to login this way without X.509 cert.
+ * CERT_REQUIRED - not allowed to login this way without a cert.
* NO_SUCH_USER - user not found using this method.
* BAD_ARGS - user/password not appropriate for this method
*/
diff --git a/dspace-api/src/main/java/org/dspace/authority/orcid/Orcidv3SolrAuthorityImpl.java b/dspace-api/src/main/java/org/dspace/authority/orcid/Orcidv3SolrAuthorityImpl.java
index 494daa97734a..312a00c146af 100644
--- a/dspace-api/src/main/java/org/dspace/authority/orcid/Orcidv3SolrAuthorityImpl.java
+++ b/dspace-api/src/main/java/org/dspace/authority/orcid/Orcidv3SolrAuthorityImpl.java
@@ -10,6 +10,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
@@ -20,6 +21,7 @@
import org.apache.logging.log4j.Logger;
import org.dspace.authority.AuthorityValue;
import org.dspace.authority.SolrAuthorityInterface;
+import org.dspace.external.OrcidConnectionException;
import org.dspace.external.OrcidRestConnector;
import org.dspace.external.provider.orcid.xml.XMLtoBio;
import org.dspace.orcid.model.factory.OrcidFactoryUtils;
@@ -142,9 +144,15 @@ public Person getBio(String id) {
return null;
}
initializeAccessToken();
- InputStream bioDocument = orcidRestConnector.get(id + ((id.endsWith("/person")) ? "" : "/person"), accessToken);
- XMLtoBio converter = new XMLtoBio();
- return converter.convertSinglePerson(bioDocument);
+ try {
+ InputStream bioDocument = orcidRestConnector.get(id + ((id.endsWith("/person")) ? "" : "/person"),
+ accessToken);
+ XMLtoBio converter = new XMLtoBio();
+ return converter.convertSinglePerson(bioDocument);
+ } catch (OrcidConnectionException e) {
+ log.error("Error retrieving ORCID bio for ID=" + id, e);
+ return null;
+ }
}
@@ -167,29 +175,35 @@ public List queryBio(String text, int start, int rows) {
// Check / init access token
initializeAccessToken();
- String searchPath = "search?q=" + URLEncoder.encode(text) + "&start=" + start + "&rows=" + rows;
+ String searchPath = "search?q=" + URLEncoder.encode(text, StandardCharsets.UTF_8) + "&start=" + start +
+ "&rows=" + rows;
log.debug("queryBio searchPath=" + searchPath + " accessToken=" + accessToken);
- InputStream bioDocument = orcidRestConnector.get(searchPath, accessToken);
- XMLtoBio converter = new XMLtoBio();
- List results = converter.convert(bioDocument);
- List bios = new LinkedList<>();
- for (Result result : results) {
- OrcidIdentifier orcidIdentifier = result.getOrcidIdentifier();
- if (orcidIdentifier != null) {
- log.debug("Found OrcidId=" + orcidIdentifier.toString());
- String orcid = orcidIdentifier.getPath();
- Person bio = getBio(orcid);
- if (bio != null) {
- bios.add(bio);
+ try {
+ InputStream bioDocument = orcidRestConnector.get(searchPath, accessToken);
+ XMLtoBio converter = new XMLtoBio();
+ List results = converter.convert(bioDocument);
+ List bios = new LinkedList<>();
+ for (Result result : results) {
+ OrcidIdentifier orcidIdentifier = result.getOrcidIdentifier();
+ if (orcidIdentifier != null) {
+ log.debug("Found OrcidId=" + orcidIdentifier);
+ String orcid = orcidIdentifier.getPath();
+ Person bio = getBio(orcid);
+ if (bio != null) {
+ bios.add(bio);
+ }
}
}
+ try {
+ bioDocument.close();
+ } catch (IOException e) {
+ log.error(e.getMessage(), e);
+ }
+ return bios;
+ } catch (OrcidConnectionException e) {
+ log.error("Error searching ORCID for query=" + text, e);
+ return Collections.emptyList();
}
- try {
- bioDocument.close();
- } catch (IOException e) {
- log.error(e.getMessage(), e);
- }
- return bios;
}
/**
diff --git a/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java b/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java
index f2692cf394fc..bb94198f7961 100644
--- a/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java
+++ b/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java
@@ -16,6 +16,7 @@
import java.util.Arrays;
import java.util.Date;
import java.util.List;
+import java.util.Objects;
import java.util.UUID;
import org.apache.commons.collections4.CollectionUtils;
@@ -48,6 +49,7 @@
import org.dspace.eperson.EPerson;
import org.dspace.eperson.Group;
import org.dspace.eperson.service.GroupService;
+import org.dspace.services.ConfigurationService;
import org.dspace.workflow.WorkflowItemService;
import org.springframework.beans.factory.annotation.Autowired;
@@ -84,6 +86,8 @@ public class AuthorizeServiceImpl implements AuthorizeService {
protected WorkflowItemService workflowItemService;
@Autowired(required = true)
private SearchService searchService;
+ @Autowired(required = true)
+ private ConfigurationService configurationService;
protected AuthorizeServiceImpl() {
@@ -508,17 +512,26 @@ public List getPoliciesActionFilter(Context c, DSpaceObject o,
return resourcePolicyService.find(c, o, actionID);
}
+ @Override
+ public void inheritPolicies(Context c, DSpaceObject src, DSpaceObject dest)
+ throws SQLException, AuthorizeException {
+ inheritPolicies(c, src, dest, false);
+ }
+
@Override
public void inheritPolicies(Context c, DSpaceObject src,
- DSpaceObject dest) throws SQLException, AuthorizeException {
+ DSpaceObject dest, boolean includeCustom) throws SQLException, AuthorizeException {
// find all policies for the source object
List policies = getPolicies(c, src);
- //Only inherit non-ADMIN policies (since ADMIN policies are automatically inherited)
- //and non-custom policies as these are manually applied when appropriate
+ // Only inherit non-ADMIN policies (since ADMIN policies are automatically inherited)
+ // and non-custom policies (usually applied manually?) UNLESS specified otherwise with includCustom
+ // (for example, item.addBundle() will inherit custom policies to enforce access conditions)
List nonAdminPolicies = new ArrayList<>();
for (ResourcePolicy rp : policies) {
- if (rp.getAction() != Constants.ADMIN && !StringUtils.equals(rp.getRpType(), ResourcePolicy.TYPE_CUSTOM)) {
+ if (rp.getAction() != Constants.ADMIN && (!StringUtils.equals(rp.getRpType(), ResourcePolicy.TYPE_CUSTOM)
+ || (includeCustom && StringUtils.equals(rp.getRpType(), ResourcePolicy.TYPE_CUSTOM)
+ && isNotAlreadyACustomRPOfThisTypeOnDSO(c, dest)))) {
nonAdminPolicies.add(rp);
}
}
@@ -729,15 +742,15 @@ public List getPoliciesActionFilterExceptRpType(Context c, DSpac
/**
* Checks that the context's current user is a community admin in the site by querying the solr database.
+ * This query doesn't use authorization inheritance because direct community admin is enough to perform this check.
*
* @param context context with the current user
* @return true if the current user is a community admin in the site
* false when this is not the case, or an exception occurred
- * @throws java.sql.SQLException passed through.
*/
@Override
- public boolean isCommunityAdmin(Context context) throws SQLException {
- return performCheck(context, RESOURCE_TYPE_FIELD + ":" + IndexableCommunity.TYPE);
+ public boolean isCommunityAdmin(Context context) {
+ return performCheck(context, RESOURCE_TYPE_FIELD + ":" + IndexableCommunity.TYPE, false);
}
/**
@@ -746,11 +759,10 @@ public boolean isCommunityAdmin(Context context) throws SQLException {
* @param context context with the current user
* @return true if the current user is a collection admin in the site
* false when this is not the case, or an exception occurred
- * @throws java.sql.SQLException passed through.
*/
@Override
- public boolean isCollectionAdmin(Context context) throws SQLException {
- return performCheck(context, RESOURCE_TYPE_FIELD + ":" + IndexableCollection.TYPE);
+ public boolean isCollectionAdmin(Context context) {
+ return performCheck(context, RESOURCE_TYPE_FIELD + ":" + IndexableCollection.TYPE, true);
}
/**
@@ -759,26 +771,26 @@ public boolean isCollectionAdmin(Context context) throws SQLException {
* @param context context with the current user
* @return true if the current user is an item admin in the site
* false when this is not the case, or an exception occurred
- * @throws java.sql.SQLException passed through.
*/
@Override
- public boolean isItemAdmin(Context context) throws SQLException {
- return performCheck(context, RESOURCE_TYPE_FIELD + ":" + IndexableItem.TYPE);
+ public boolean isItemAdmin(Context context) {
+ return performCheck(context, RESOURCE_TYPE_FIELD + ":" + IndexableItem.TYPE, true);
}
/**
* Checks that the context's current user is a community or collection admin in the site.
+ * This query doesn't use authorization inheritance because direct community/collection admin is enough to
+ * perform this check.
*
* @param context context with the current user
* @return true if the current user is a community or collection admin in the site
* false when this is not the case, or an exception occurred
- * @throws java.sql.SQLException passed through.
*/
@Override
- public boolean isComColAdmin(Context context) throws SQLException {
+ public boolean isComColAdmin(Context context) {
return performCheck(context,
"(" + RESOURCE_TYPE_FIELD + ":" + IndexableCommunity.TYPE + " OR " +
- RESOURCE_TYPE_FIELD + ":" + IndexableCollection.TYPE + ")");
+ RESOURCE_TYPE_FIELD + ":" + IndexableCollection.TYPE + ")", false);
}
/**
@@ -793,11 +805,30 @@ public boolean isComColAdmin(Context context) throws SQLException {
*/
@Override
public List findAdminAuthorizedCommunity(Context context, String query, int offset, int limit)
- throws SearchServiceException, SQLException {
+ throws SearchServiceException {
+ return findAuthorizedCommunityByAction(context, query, Constants.ADMIN, offset, limit);
+ }
+
+ /**
+ * Finds communities for which the logged in user has the rights specified by the action parameter.
+ *
+ * @param context the context whose user is checked against
+ * @param query the optional extra query
+ * @param action the action to check for
+ * @param offset the offset for pagination
+ * @param limit the amount of dso's to return
+ * @return a list of communities for which the logged in user has the rights specified by the action
+ * @throws SearchServiceException
+ */
+ @Override
+ public List findAuthorizedCommunityByAction(Context context, String query, int action, int offset,
+ int limit)
+ throws SearchServiceException {
List communities = new ArrayList<>();
+ query = searchService.formatAutoCompleteQuery(query, "dc.title_sort");
query = formatCustomQuery(query);
DiscoverResult discoverResult = getDiscoverResult(context, query + RESOURCE_TYPE_FIELD + ":" +
- IndexableCommunity.TYPE,
+ IndexableCommunity.TYPE, action, true,
offset, limit, null, null);
for (IndexableObject solrCollections : discoverResult.getIndexableObjects()) {
Community community = ((IndexableCommunity) solrCollections).getIndexedObject();
@@ -816,10 +847,26 @@ public List findAdminAuthorizedCommunity(Context context, String quer
*/
@Override
public long countAdminAuthorizedCommunity(Context context, String query)
- throws SearchServiceException, SQLException {
+ throws SearchServiceException {
+ return countAuthorizedCommunityByAction(context, query, Constants.ADMIN);
+ }
+
+ /**
+ * Counts communities for which the current user has the rights specified by the action parameter.
+ *
+ * @param context context with the current user
+ * @param query the query for which to filter the results more
+ * @param action the action to check for
+ * @return the matching communities
+ * @throws SearchServiceException
+ */
+ @Override
+ public long countAuthorizedCommunityByAction(Context context, String query, int action)
+ throws SearchServiceException {
+ query = searchService.formatAutoCompleteQuery(query, "dc.title_sort");
query = formatCustomQuery(query);
DiscoverResult discoverResult = getDiscoverResult(context, query + RESOURCE_TYPE_FIELD + ":" +
- IndexableCommunity.TYPE,
+ IndexableCommunity.TYPE, action, true,
null, 0, null, null);
return discoverResult.getTotalSearchResults();
}
@@ -836,15 +883,34 @@ public long countAdminAuthorizedCommunity(Context context, String query)
*/
@Override
public List findAdminAuthorizedCollection(Context context, String query, int offset, int limit)
- throws SearchServiceException, SQLException {
+ throws SearchServiceException {
+ return findAuthorizedCollectionByAction(context, query, Constants.ADMIN, offset, limit);
+ }
+
+ /**
+ * Finds collections for which the logged in user has the rights specified by the action parameter.
+ *
+ * @param context the context whose user is checked against
+ * @param query the optional extra query
+ * @param action the action to check for
+ * @param offset the offset for pagination
+ * @param limit the amount of dso's to return
+ * @return a list of collections for which the logged in user has the rights specified by the action
+ * @throws SearchServiceException
+ */
+ @Override
+ public List findAuthorizedCollectionByAction(Context context, String query,
+ int action, int offset, int limit)
+ throws SearchServiceException {
List collections = new ArrayList<>();
if (context.getCurrentUser() == null) {
return collections;
}
+ query = searchService.formatAutoCompleteQuery(query, "dc.title_sort");
query = formatCustomQuery(query);
DiscoverResult discoverResult = getDiscoverResult(context, query + RESOURCE_TYPE_FIELD + ":" +
- IndexableCollection.TYPE,
+ IndexableCollection.TYPE, action, true,
offset, limit, CollectionService.SOLR_SORT_FIELD, SORT_ORDER.asc);
for (IndexableObject solrCollections : discoverResult.getIndexableObjects()) {
Collection collection = ((IndexableCollection) solrCollections).getIndexedObject();
@@ -863,31 +929,44 @@ public List findAdminAuthorizedCollection(Context context, String qu
*/
@Override
public long countAdminAuthorizedCollection(Context context, String query)
- throws SearchServiceException, SQLException {
+ throws SearchServiceException {
+ return countAuthorizedCollectionByAction(context, query, Constants.ADMIN);
+ }
+
+ /**
+ * Counts collections for which the current user has the rights specified by the action parameter.
+ *
+ * @param context context with the current user
+ * @param query the query for which to filter the results more
+ * @param action the action to check for
+ * @return the matching collections
+ * @throws SearchServiceException
+ */
+ @Override
+ public long countAuthorizedCollectionByAction(Context context, String query, int action)
+ throws SearchServiceException {
+ query = searchService.formatAutoCompleteQuery(query, "dc.title_sort");
query = formatCustomQuery(query);
DiscoverResult discoverResult = getDiscoverResult(context, query + RESOURCE_TYPE_FIELD + ":" +
- IndexableCollection.TYPE,
- null, 0, null, null);
+ IndexableCollection.TYPE, action,
+ true, null, 0, null, null);
return discoverResult.getTotalSearchResults();
}
@Override
public boolean isAccountManager(Context context) {
- try {
- return (canCommunityAdminManageAccounts() && isCommunityAdmin(context)
- || canCollectionAdminManageAccounts() && isCollectionAdmin(context));
- } catch (SQLException e) {
- throw new RuntimeException(e);
- }
+ return (canCommunityAdminManageAccounts() && isCommunityAdmin(context)
+ || canCollectionAdminManageAccounts() && isCollectionAdmin(context));
}
- private boolean performCheck(Context context, String query) throws SQLException {
+ private boolean performCheck(Context context, String query, boolean inheritAuthorizations) {
if (context.getCurrentUser() == null) {
return false;
}
try {
- DiscoverResult discoverResult = getDiscoverResult(context, query, null, null, null, null);
+ DiscoverResult discoverResult = getDiscoverResult(context, query, Constants.ADMIN, inheritAuthorizations,
+ null, 0, null, null);
if (discoverResult.getTotalSearchResults() > 0) {
return true;
}
@@ -899,16 +978,11 @@ private boolean performCheck(Context context, String query) throws SQLException
return false;
}
- private DiscoverResult getDiscoverResult(Context context, String query, Integer offset, Integer limit,
- String sortField, SORT_ORDER sortOrder)
- throws SearchServiceException, SQLException {
- String groupQuery = getGroupToQuery(groupService.allMemberGroups(context, context.getCurrentUser()));
+ private DiscoverResult getDiscoverResult(Context context, String query, int action, boolean inheritAuthorizations,
+ Integer offset, Integer limit, String sortField, SORT_ORDER sortOrder)
+ throws SearchServiceException {
DiscoverQuery discoverQuery = new DiscoverQuery();
- if (!this.isAdmin(context)) {
- query = query + " AND (" +
- "admin:e" + context.getCurrentUser().getID() + groupQuery + ")";
- }
discoverQuery.setQuery(query);
if (offset != null) {
discoverQuery.setStart(offset);
@@ -919,28 +993,113 @@ private DiscoverResult getDiscoverResult(Context context, String query, Integer
if (sortField != null && sortOrder != null) {
discoverQuery.setSortField(sortField, sortOrder);
}
+ discoverQuery.addRequiredAuthorization(action);
+ discoverQuery.setInheritAuthorizations(inheritAuthorizations);
return searchService.search(context, discoverQuery);
}
- private String getGroupToQuery(List groups) {
- StringBuilder groupQuery = new StringBuilder();
+ private String formatCustomQuery(String query) {
+ if (StringUtils.isBlank(query)) {
+ return "";
+ } else {
+ return query + " AND ";
+ }
+ }
- if (groups != null) {
- for (Group group: groups) {
- groupQuery.append(" OR admin:g");
- groupQuery.append(group.getID());
+ /**
+ * Add the default policies, which have not been already added to the given DSpace object
+ *
+ * @param context The relevant DSpace Context.
+ * @param dso The DSpace Object to add policies to
+ * @param defaultCollectionPolicies list of policies
+ * @throws SQLException An exception that provides information on a database access error or other errors.
+ * @throws AuthorizeException Exception indicating the current user of the context does not have permission
+ * to perform a particular action.
+ */
+ @Override
+ public void addDefaultPoliciesNotInPlace(Context context, DSpaceObject dso,
+ List defaultCollectionPolicies) throws SQLException, AuthorizeException {
+ boolean appendMode = configurationService
+ .getBooleanProperty("core.authorization.installitem.inheritance-read.append-mode", false);
+ for (ResourcePolicy defaultPolicy : defaultCollectionPolicies) {
+ if (!isAnIdenticalPolicyAlreadyInPlace(context, dso, defaultPolicy.getGroup(), Constants.READ,
+ defaultPolicy.getID()) &&
+ (!appendMode && isNotAlreadyACustomRPOfThisTypeOnDSO(context, dso) ||
+ appendMode && shouldBeAppended(context, dso, defaultPolicy))) {
+ ResourcePolicy newPolicy = resourcePolicyService.clone(context, defaultPolicy);
+ newPolicy.setdSpaceObject(dso);
+ newPolicy.setAction(Constants.READ);
+ newPolicy.setRpType(ResourcePolicy.TYPE_INHERITED);
+ resourcePolicyService.update(context, newPolicy);
}
}
+ }
- return groupQuery.toString();
+ /**
+ * Add a list of custom policies if there are already NO custom policies in place
+ *
+ */
+ @Override
+ public void addCustomPoliciesNotInPlace(Context context, DSpaceObject dso, List customPolicies)
+ throws SQLException, AuthorizeException {
+ boolean customPoliciesAlreadyInPlace =
+ findPoliciesByDSOAndType(context, dso, ResourcePolicy.TYPE_CUSTOM).size() > 0;
+ if (!customPoliciesAlreadyInPlace) {
+ addPolicies(context, customPolicies, dso);
+ }
}
- private String formatCustomQuery(String query) {
- if (StringUtils.isBlank(query)) {
- return "";
- } else {
- return query + " AND ";
+ /**
+ * Check whether or not there is already an RP on the given dso, which has actionId={@link Constants.READ} and
+ * resourceTypeId={@link ResourcePolicy.TYPE_CUSTOM}
+ *
+ * @param context DSpace context
+ * @param dso DSpace object to check for custom read RP
+ * @return True if there is no RP on the item with custom read RP, otherwise false
+ * @throws SQLException If something goes wrong retrieving the RP on the DSO
+ */
+ private boolean isNotAlreadyACustomRPOfThisTypeOnDSO(Context context, DSpaceObject dso) throws SQLException {
+ return isNotAlreadyACustomRPOfThisTypeOnDSO(context, dso, Constants.READ);
+ }
+
+ private boolean isNotAlreadyACustomRPOfThisTypeOnDSO(Context context, DSpaceObject dso, int action)
+ throws SQLException {
+ List rps = resourcePolicyService.find(context, dso, action);
+ for (ResourcePolicy rp : rps) {
+ if (rp.getRpType() != null && rp.getRpType().equals(ResourcePolicy.TYPE_CUSTOM)) {
+ return false;
+ }
}
+ return true;
+ }
+
+ /**
+ * Check if the provided default policy should be appended or not to the final
+ * item. If an item has at least one custom READ policy any anonymous READ
+ * policy with empty start/end date should be skipped
+ *
+ * @param context DSpace context
+ * @param dso DSpace object to check for custom read RP
+ * @param defaultPolicy The policy to check
+ * @return
+ * @throws SQLException If something goes wrong retrieving the RP on the DSO
+ */
+ private boolean shouldBeAppended(Context context, DSpaceObject dso, ResourcePolicy defaultPolicy)
+ throws SQLException {
+ boolean hasCustomPolicy = resourcePolicyService.find(context, dso, Constants.READ)
+ .stream()
+ .filter(rp -> (Objects.nonNull(rp.getRpType()) &&
+ Objects.equals(rp.getRpType(), ResourcePolicy.TYPE_CUSTOM)))
+ .findFirst()
+ .isPresent();
+
+ boolean isAnonymousGroup = Objects.nonNull(defaultPolicy.getGroup())
+ && StringUtils.equals(defaultPolicy.getGroup().getName(), Group.ANONYMOUS);
+
+ boolean datesAreNull = Objects.isNull(defaultPolicy.getStartDate())
+ && Objects.isNull(defaultPolicy.getEndDate());
+
+ return !(hasCustomPolicy && isAnonymousGroup && datesAreNull);
}
}
diff --git a/dspace-api/src/main/java/org/dspace/authorize/dao/impl/ResourcePolicyDAOImpl.java b/dspace-api/src/main/java/org/dspace/authorize/dao/impl/ResourcePolicyDAOImpl.java
index 3b09f9cf300b..1f9e5ea26677 100644
--- a/dspace-api/src/main/java/org/dspace/authorize/dao/impl/ResourcePolicyDAOImpl.java
+++ b/dspace-api/src/main/java/org/dspace/authorize/dao/impl/ResourcePolicyDAOImpl.java
@@ -182,7 +182,7 @@ public List findByEPersonGroupTypeIdAction(Context context, EPer
compareEpersonOrGroups
)
);
- return list(context, criteriaQuery, false, ResourcePolicy.class, 1, -1);
+ return list(context, criteriaQuery, false, ResourcePolicy.class, -1, -1);
}
@Override
diff --git a/dspace-api/src/main/java/org/dspace/authorize/service/AuthorizeService.java b/dspace-api/src/main/java/org/dspace/authorize/service/AuthorizeService.java
index e0a94833d76c..95e4ec1ee627 100644
--- a/dspace-api/src/main/java/org/dspace/authorize/service/AuthorizeService.java
+++ b/dspace-api/src/main/java/org/dspace/authorize/service/AuthorizeService.java
@@ -322,6 +322,19 @@ public void addPolicy(Context c, DSpaceObject o, int actionID, Group g, String t
*/
public List getPoliciesActionFilterExceptRpType(Context c, DSpaceObject o, int actionID,
String rpType) throws SQLException;
+ /**
+ * Add policies to an object to match those from a previous object
+ *
+ * @param c context
+ * @param src source of policies
+ * @param dest destination of inherited policies
+ * @param includeCustom whether TYPE_CUSTOM policies should be inherited
+ * @throws SQLException if there's a database problem
+ * @throws AuthorizeException if the current user is not authorized to add these policies
+ */
+ public void inheritPolicies(Context c, DSpaceObject src, DSpaceObject dest, boolean includeCustom)
+ throws SQLException, AuthorizeException;
+
/**
* Add policies to an object to match those from a previous object
*
@@ -546,6 +559,20 @@ void switchPoliciesAction(Context context, DSpaceObject dso, int fromAction, int
List findAdminAuthorizedCommunity(Context context, String query, int offset, int limit)
throws SearchServiceException, SQLException;
+ /**
+ * Finds communities for which the logged in user has the rights specified by the action parameter.
+ *
+ * @param context the context whose user is checked against
+ * @param query the optional extra query
+ * @param action the action to check for
+ * @param offset the offset for pagination
+ * @param limit the amount of dso's to return
+ * @return a list of communities for which the logged in user has the rights specified by the action
+ * @throws SearchServiceException
+ */
+ List findAuthorizedCommunityByAction(Context context, String query, int action, int offset, int limit)
+ throws SearchServiceException, SQLException;
+
/**
* Counts communities for which the current user is admin, AND which match the query.
*
@@ -558,6 +585,18 @@ List findAdminAuthorizedCommunity(Context context, String query, int
long countAdminAuthorizedCommunity(Context context, String query)
throws SearchServiceException, SQLException;
+ /**
+ * Counts communities for which the current user has the rights specified by the action parameter.
+ *
+ * @param context context with the current user
+ * @param query the query for which to filter the results more
+ * @param action the action to check for
+ * @return the matching communities
+ * @throws SearchServiceException
+ */
+ long countAuthorizedCommunityByAction(Context context, String query, int action)
+ throws SearchServiceException;
+
/**
* Finds collections for which the current user is admin, AND which match the query.
*
@@ -567,10 +606,24 @@ long countAdminAuthorizedCommunity(Context context, String query)
* @param limit used for pagination of the results
* @return the matching collections
* @throws SearchServiceException
- * @throws SQLException
*/
List findAdminAuthorizedCollection(Context context, String query, int offset, int limit)
- throws SearchServiceException, SQLException;
+ throws SearchServiceException;
+
+ /**
+ * Finds collections for which the current user has the rights specified by the action parameter.
+ *
+ * @param context context with the current user
+ * @param query the query for which to filter the results more
+ * @param action the action to check for
+ * @param offset used for pagination of the results
+ * @param limit used for pagination of the results
+ * @return the matching collections
+ * @throws SearchServiceException
+ */
+ List findAuthorizedCollectionByAction(Context context, String query, int action, int offset,
+ int limit)
+ throws SearchServiceException;
/**
* Counts collections for which the current user is admin, AND which match the query.
@@ -582,7 +635,19 @@ List findAdminAuthorizedCollection(Context context, String query, in
* @throws SQLException
*/
long countAdminAuthorizedCollection(Context context, String query)
- throws SearchServiceException, SQLException;
+ throws SearchServiceException;
+
+ /**
+ * Counts collections for which the current user has the rights specified by the action parameter.
+ *
+ * @param context context with the current user
+ * @param query the query for which to filter the results more
+ * @param action the action to check for
+ * @return the number of matching collections
+ * @throws SearchServiceException
+ */
+ long countAuthorizedCollectionByAction(Context context, String query, int action)
+ throws SearchServiceException;
/**
* Returns true if the current user can manage accounts.
@@ -604,4 +669,10 @@ long countAdminAuthorizedCollection(Context context, String query)
public void replaceAllPolicies(Context context, DSpaceObject source, DSpaceObject dest)
throws SQLException, AuthorizeException;
+ public void addDefaultPoliciesNotInPlace(Context context, DSpaceObject dso,
+ List defaultCollectionPolicies) throws SQLException, AuthorizeException;
+
+ public void addCustomPoliciesNotInPlace(Context context, DSpaceObject dso,
+ List defaultCollectionPolicies) throws SQLException, AuthorizeException;
+
}
diff --git a/dspace-api/src/main/java/org/dspace/browse/BrowseDAO.java b/dspace-api/src/main/java/org/dspace/browse/BrowseDAO.java
index 03130e39e78b..26983303a152 100644
--- a/dspace-api/src/main/java/org/dspace/browse/BrowseDAO.java
+++ b/dspace-api/src/main/java/org/dspace/browse/BrowseDAO.java
@@ -396,4 +396,6 @@ public interface BrowseDAO {
public void setStartsWith(String startsWith);
public String getStartsWith();
+
+ public void setDateStartsWith(String dateStartsWith);
}
diff --git a/dspace-api/src/main/java/org/dspace/browse/BrowseEngine.java b/dspace-api/src/main/java/org/dspace/browse/BrowseEngine.java
index be7a34086a46..f7f5f2ff7df7 100644
--- a/dspace-api/src/main/java/org/dspace/browse/BrowseEngine.java
+++ b/dspace-api/src/main/java/org/dspace/browse/BrowseEngine.java
@@ -203,12 +203,8 @@ private BrowseInfo browseByItem(BrowserScope bs)
// get the table name that we are going to be getting our data from
dao.setTable(browseIndex.getTableName());
- if (scope.getBrowseIndex() != null && OrderFormat.TITLE.equals(scope.getBrowseIndex().getDataType())) {
- // For browsing by title, apply the same normalization applied to indexed titles
- dao.setStartsWith(normalizeJumpToValue(scope.getStartsWith()));
- } else {
- dao.setStartsWith(StringUtils.lowerCase(scope.getStartsWith()));
- }
+ // Set startsWith or dateStartsWith params on SolrBrowseDAO
+ addStartsWithParams(bs);
// tell the browse query whether we are ascending or descending on the value
dao.setAscending(scope.isAscending());
@@ -367,6 +363,30 @@ private BrowseInfo browseByItem(BrowserScope bs)
}
}
+ private void addStartsWithParams(BrowserScope bs) throws BrowseException {
+ if (StringUtils.isNotBlank(scope.getStartsWith())) {
+ boolean isDateBrowse = bs.getSortOption().getType().equals("date");
+ if (!isDateBrowse) {
+ if (scope.getBrowseIndex() != null
+ && OrderFormat.TITLE.equals(scope.getBrowseIndex().getDataType())) {
+ // For browsing by title, apply the same normalization applied to indexed titles
+ dao.setStartsWith(normalizeJumpToValue(scope.getStartsWith()));
+ } else {
+ dao.setStartsWith(StringUtils.lowerCase(scope.getStartsWith()));
+ }
+ // clear the old date starts with
+ dao.setDateStartsWith(null);
+ } else {
+ // For "date" sort browses ({@code webui.itemlist.sort-option.*} config):
+ // sets a date specific filter where the startsWith query is the start date,
+ // eg `fq=bi_sort_*_sort:+["1940-02" TO + ]`
+ dao.setDateStartsWith(scope.getStartsWith().trim());
+ // clear the old non date starts with
+ dao.setStartsWith(null);
+ }
+ }
+ }
+
/**
* Browse the archive by single values (such as the name of an author). This
* produces a BrowseInfo object that contains Strings as the results of
diff --git a/dspace-api/src/main/java/org/dspace/browse/ItemCountDAOSolr.java b/dspace-api/src/main/java/org/dspace/browse/ItemCountDAOSolr.java
index e4d0079fe20b..723b49d63aea 100644
--- a/dspace-api/src/main/java/org/dspace/browse/ItemCountDAOSolr.java
+++ b/dspace-api/src/main/java/org/dspace/browse/ItemCountDAOSolr.java
@@ -7,121 +7,62 @@
*/
package org.dspace.browse;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
+import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
+import org.apache.solr.client.solrj.SolrClient;
+import org.apache.solr.client.solrj.SolrQuery;
+import org.apache.solr.client.solrj.response.QueryResponse;
import org.dspace.content.Collection;
import org.dspace.content.Community;
import org.dspace.content.DSpaceObject;
import org.dspace.core.Context;
-import org.dspace.discovery.DiscoverFacetField;
-import org.dspace.discovery.DiscoverQuery;
-import org.dspace.discovery.DiscoverResult;
-import org.dspace.discovery.DiscoverResult.FacetResult;
-import org.dspace.discovery.SearchService;
-import org.dspace.discovery.SearchServiceException;
-import org.dspace.discovery.configuration.DiscoveryConfigurationParameters;
+import org.dspace.discovery.SolrSearchCore;
import org.dspace.discovery.indexobject.IndexableItem;
import org.springframework.beans.factory.annotation.Autowired;
/**
* Discovery (Solr) driver implementing ItemCountDAO interface to look up item
- * count information in communities and collections. Caching operations are
- * intentionally not implemented because Solr already is our cache.
+ * count information in communities and collections.
+ *
+ * Counts are computed by querying Solr for archived, non-withdrawn, discoverable
+ * items using {@code location.comm} / {@code location.coll} filters.
+ * The query returns only {@code numFound} (rows=0), making it very fast.
*/
public class ItemCountDAOSolr implements ItemCountDAO {
- /**
- * Log4j logger
- */
- private static Logger log = org.apache.logging.log4j.LogManager.getLogger(ItemCountDAOSolr.class);
-
- /**
- * Hold the communities item count obtained from SOLR after the first query. This only works
- * well if the ItemCountDAO lifecycle is bound to the request lifecycle as
- * it is now. If we switch to a Spring-based instantiation we should mark
- * this bean as prototype
- **/
- private Map communitiesCount = null;
-
- /**
- * Hold the collection item count obtained from SOLR after the first query
- **/
- private Map collectionsCount = null;
+ private static final Logger log = LogManager.getLogger(ItemCountDAOSolr.class);
- /**
- * Solr search service
- */
@Autowired
- protected SearchService searchService;
+ private SolrSearchCore solrSearchCore;
- /**
- * Get the count of the items in the given container.
- *
- * @param context DSpace context
- * @param dso DspaceObject
- * @return count
- */
@Override
public int getCount(Context context, DSpaceObject dso) {
- loadCount(context);
- Integer val = null;
+ String locationFilter;
if (dso instanceof Collection) {
- val = collectionsCount.get(dso.getID().toString());
+ locationFilter = "location.coll:" + dso.getID().toString();
} else if (dso instanceof Community) {
- val = communitiesCount.get(dso.getID().toString());
- }
-
- if (val != null) {
- return val;
+ locationFilter = "location.comm:" + dso.getID().toString();
} else {
return 0;
}
- }
- /**
- * make sure that the counts are actually fetched from Solr (if haven't been
- * cached in a Map yet)
- *
- * @param context DSpace Context
- */
- private void loadCount(Context context) {
- if (communitiesCount != null || collectionsCount != null) {
- return;
- }
-
- communitiesCount = new HashMap<>();
- collectionsCount = new HashMap<>();
-
- DiscoverQuery query = new DiscoverQuery();
- query.setFacetMinCount(1);
- query.addFacetField(new DiscoverFacetField("location.comm",
- DiscoveryConfigurationParameters.TYPE_STANDARD, -1,
- DiscoveryConfigurationParameters.SORT.COUNT));
- query.addFacetField(new DiscoverFacetField("location.coll",
- DiscoveryConfigurationParameters.TYPE_STANDARD, -1,
- DiscoveryConfigurationParameters.SORT.COUNT));
- query.addFilterQueries("search.resourcetype:" + IndexableItem.TYPE); // count only items
- query.addFilterQueries("NOT(discoverable:false)"); // only discoverable
- query.addFilterQueries("withdrawn:false"); // only not withdrawn
- query.addFilterQueries("archived:true"); // only archived
- query.setMaxResults(0);
-
- DiscoverResult sResponse;
try {
- sResponse = searchService.search(context, query);
- List commCount = sResponse.getFacetResult("location.comm");
- List collCount = sResponse.getFacetResult("location.coll");
- for (FacetResult c : commCount) {
- communitiesCount.put(c.getAsFilterQuery(), (int) c.getCount());
- }
- for (FacetResult c : collCount) {
- collectionsCount.put(c.getAsFilterQuery(), (int) c.getCount());
+ SolrClient solr = solrSearchCore.getSolr();
+ if (solr == null) {
+ return 0;
}
- } catch (SearchServiceException e) {
- log.error("Could not initialize Community/Collection Item Counts from Solr: ", e);
+ SolrQuery query = new SolrQuery("*:*");
+ query.addFilterQuery(locationFilter);
+ query.addFilterQuery("search.resourcetype:" + IndexableItem.TYPE);
+ query.addFilterQuery("NOT(discoverable:false)");
+ query.addFilterQuery("withdrawn:false");
+ query.addFilterQuery("archived:true");
+ query.setRows(0);
+ QueryResponse response = solr.query(query, solrSearchCore.REQUEST_METHOD);
+ return (int) response.getResults().getNumFound();
+ } catch (Exception e) {
+ log.error("Error counting items in Solr for {}: ", dso.getID(), e);
}
+ return 0;
}
}
diff --git a/dspace-api/src/main/java/org/dspace/browse/SolrBrowseDAO.java b/dspace-api/src/main/java/org/dspace/browse/SolrBrowseDAO.java
index a0a7725fa13a..32841e6c4b34 100644
--- a/dspace-api/src/main/java/org/dspace/browse/SolrBrowseDAO.java
+++ b/dspace-api/src/main/java/org/dspace/browse/SolrBrowseDAO.java
@@ -11,6 +11,7 @@
import static org.dspace.discovery.SearchUtils.RESOURCE_TYPE_FIELD;
import java.io.Serializable;
+import java.time.YearMonth;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
@@ -99,6 +100,8 @@ public int compare(Object o1, Object o2) {
private String startsWith = null;
+ private String dateStartsWith = null;
+
/**
* field to look for value in
*/
@@ -221,10 +224,33 @@ private DiscoverResult getSolrResponse() throws BrowseException {
} else if (valuePartial) {
query.addFilterQueries("{!field f=" + facetField + "_partial}" + value);
}
+
if (StringUtils.isNotBlank(startsWith) && orderField != null) {
query.addFilterQueries(
"bi_" + orderField + "_sort:" + ClientUtils.escapeQueryChars(startsWith) + "*");
}
+ if (StringUtils.isNotBlank(dateStartsWith)) {
+ if (!ascending) {
+ String raw = dateStartsWith.trim();
+ String upperBound;
+ if (raw.length() == 4) { // YYYY
+ upperBound = raw + "-12-31";
+ } else if (raw.length() == 7) { // YYYY-MM
+ YearMonth ym = YearMonth.parse(raw);
+ upperBound = ym.atEndOfMonth().toString();
+ } else { // YYYY-MM-DD
+ upperBound = raw;
+ }
+ query.addFilterQueries("bi_" + orderField + "_sort" + ": [* TO \"" + upperBound + "\"]");
+ } else {
+ query.addFilterQueries("bi_" + orderField + "_sort" + ": [\"" + dateStartsWith + "\" TO *]");
+ }
+ }
+ if (StringUtils.isNotBlank(startsWith) && StringUtils.isNotBlank(dateStartsWith)) {
+ log.warn(String.format("dateStartsWith %s and startsWith %s both given, only one should " +
+ "be given since different type of sort filterquery applied depending on which is not blank",
+ dateStartsWith, startsWith));
+ }
// filter on item to be sure to don't include any other object
// indexed in the Discovery Search core
query.addFilterQueries("search.resourcetype:" + IndexableItem.TYPE);
@@ -466,6 +492,11 @@ public int getLimit() {
return limit;
}
+ @Override
+ public void setDateStartsWith(String dateStartsWith) {
+ this.dateStartsWith = dateStartsWith;
+ }
+
/*
* (non-Javadoc)
*
diff --git a/dspace-api/src/main/java/org/dspace/checker/CheckerCommand.java b/dspace-api/src/main/java/org/dspace/checker/CheckerCommand.java
index c82eb365277d..9ad9f553b445 100644
--- a/dspace-api/src/main/java/org/dspace/checker/CheckerCommand.java
+++ b/dspace-api/src/main/java/org/dspace/checker/CheckerCommand.java
@@ -131,13 +131,7 @@ public void process() throws SQLException {
collector.collect(context, info);
}
- // UMD Customization
- // This change was provided to DSpace in Pull Request 10508
- // This customization markers can be removed once the
- // application has been upgraded to a DSpace version containing
- // the pull request.
context.commit();
- // End UMD Customization
bitstream = dispatcher.next();
}
}
diff --git a/dspace-api/src/main/java/org/dspace/checker/DailyReportEmailer.java b/dspace-api/src/main/java/org/dspace/checker/DailyReportEmailer.java
index 50ef4baa98e3..481b055fbb7d 100644
--- a/dspace-api/src/main/java/org/dspace/checker/DailyReportEmailer.java
+++ b/dspace-api/src/main/java/org/dspace/checker/DailyReportEmailer.java
@@ -75,6 +75,7 @@ public void sendReport(File attachment, int numberOfBitstreams)
email.setContent("Checker Report", "report is attached ...");
email.addAttachment(attachment, "checksum_checker_report.txt");
email.addRecipient(configurationService.getProperty("mail.admin"));
+ log.info("Sending checker report email to " + configurationService.getProperty("mail.admin"));
email.send();
}
}
@@ -109,18 +110,19 @@ public static void main(String[] args) {
Options options = new Options();
options.addOption("h", "help", false, "Help");
- options.addOption("d", "Deleted", false,
- "Send E-mail report for all bitstreams set as deleted for today");
- options.addOption("m", "Missing", false,
- "Send E-mail report for all bitstreams not found in assetstore for today");
- options.addOption("c", "Changed", false,
- "Send E-mail report for all bitstreams where checksum has been changed for today");
- options.addOption("a", "All", false,
- "Send all E-mail reports");
- options.addOption("u", "Unchecked", false,
- "Send the Unchecked bitstream report");
- options.addOption("n", "Not Processed", false,
- "Send E-mail report for all bitstreams set to longer be processed for today");
+ options.addOption("d", "deleted", false,
+ "Send email report for all bitstreams set as deleted for today");
+ options.addOption("m", "missing", false,
+ "Send email report for all bitstreams not found in assetstore for today");
+ options.addOption("c", "changed", false,
+ "Send email report for all bitstreams where checksum has been changed for today");
+ options.addOption("a", "all", false,
+ "Send all email reports (used by default)");
+ options.addOption("u", "unchecked", false,
+ "Send the unchecked (i.e. recently added) bitstream email report");
+ options.addOption("n", "not-processed", false,
+ "Send email report for all bitstreams set to no longer be processed for today (includes"
+ + " bitstreams marked as deleted or not found)");
try {
line = parser.parse(options, args);
@@ -133,13 +135,15 @@ public static void main(String[] args) {
if (line.hasOption('h')) {
HelpFormatter myhelp = new HelpFormatter();
- myhelp.printHelp("Checksum Reporter\n", options);
- System.out.println("\nSend Deleted bitstream email report: DailyReportEmailer -d");
- System.out.println("\nSend Missing bitstreams email report: DailyReportEmailer -m");
- System.out.println("\nSend Checksum Changed email report: DailyReportEmailer -c");
- System.out.println("\nSend bitstream not to be processed email report: DailyReportEmailer -n");
- System.out.println("\nSend Un-checked bitstream report: DailyReportEmailer -u");
- System.out.println("\nSend All email reports: DailyReportEmailer");
+ myhelp.printHelp("checker-emailer\n", options);
+ System.out.println("\nChecksum Checker Reporter usage examples:\n");
+ System.out.println(" - Send all email reports: checker-emailer -a");
+ System.out.println(" - Send deleted bitstream email report: checker-emailer -d");
+ System.out.println(" - Send missing bitstreams email report: checker-emailer -m");
+ System.out.println(" - Send checksum changed email report: checker-emailer -c");
+ System.out.println(" - Send bitstream not to be processed email report: checker-emailer -n");
+ System.out.println(" - Send unchecked bitstream email report: checker-emailer -u");
+ System.out.println("\nDefault (no arguments) is equivalent to 'checker-emailer -a'\n");
System.exit(0);
}
@@ -191,7 +195,9 @@ public static void main(String[] args) {
writer.write("\n--------------------------------- Report Spacer ---------------------------\n\n");
numBitstreams += reporter.getBitstreamNotFoundReport(context, yesterday, tomorrow, writer);
writer.write("\n--------------------------------- Report Spacer ---------------------------\n\n");
- numBitstreams += reporter.getNotToBeProcessedReport(context, yesterday, tomorrow, writer);
+ // not to be processed report includes deleted and not found bitstreams so it is not necessary to
+ // include the sum in the counter
+ reporter.getNotToBeProcessedReport(context, yesterday, tomorrow, writer);
writer.write("\n--------------------------------- Report Spacer ---------------------------\n\n");
numBitstreams += reporter.getUncheckedBitstreamsReport(context, writer);
writer.write("\n--------------------------------- End Report ---------------------------\n\n");
diff --git a/dspace-api/src/main/java/org/dspace/checker/MostRecentChecksumServiceImpl.java b/dspace-api/src/main/java/org/dspace/checker/MostRecentChecksumServiceImpl.java
index d267171aa0d9..9ee777a3e15b 100644
--- a/dspace-api/src/main/java/org/dspace/checker/MostRecentChecksumServiceImpl.java
+++ b/dspace-api/src/main/java/org/dspace/checker/MostRecentChecksumServiceImpl.java
@@ -90,61 +90,16 @@ public List findBitstreamResultTypeReport(Context context, D
/**
* Queries the bitstream table for bitstream IDs that are not yet in the
* most_recent_checksum table, and inserts them into the
- * most_recent_checksum and checksum_history tables.
- *
+ * most_recent_checksum table.
* @param context Context
* @throws SQLException if database error
*/
@Override
public void updateMissingBitstreams(Context context) throws SQLException {
-// "insert into most_recent_checksum ( "
-// + "bitstream_id, to_be_processed, expected_checksum, current_checksum, "
-// + "last_process_start_date, last_process_end_date, "
-// + "checksum_algorithm, matched_prev_checksum, result ) "
-// + "select bitstream.bitstream_id, "
-// + "CASE WHEN bitstream.deleted = false THEN true ELSE false END, "
-// + "CASE WHEN bitstream.checksum IS NULL THEN '' ELSE bitstream.checksum END, "
-// + "CASE WHEN bitstream.checksum IS NULL THEN '' ELSE bitstream.checksum END, "
-// + "?, ?, CASE WHEN bitstream.checksum_algorithm IS NULL "
-// + "THEN 'MD5' ELSE bitstream.checksum_algorithm END, true, "
-// + "CASE WHEN bitstream.deleted = true THEN 'BITSTREAM_MARKED_DELETED' else 'CHECKSUM_MATCH' END "
-// + "from bitstream where not exists( "
-// + "select 'x' from most_recent_checksum "
-// + "where most_recent_checksum.bitstream_id = bitstream.bitstream_id )";
-
- List unknownBitstreams = bitstreamService.findBitstreamsWithNoRecentChecksum(context);
- for (Bitstream bitstream : unknownBitstreams) {
- log.info(bitstream + " " + bitstream.getID().toString() + " " + bitstream.getName());
-
- MostRecentChecksum mostRecentChecksum = new MostRecentChecksum();
- mostRecentChecksum.setBitstream(bitstream);
- //Only process if our bitstream isn't deleted
- mostRecentChecksum.setToBeProcessed(!bitstream.isDeleted());
- if (bitstream.getChecksum() == null) {
- mostRecentChecksum.setCurrentChecksum("");
- mostRecentChecksum.setExpectedChecksum("");
- } else {
- mostRecentChecksum.setCurrentChecksum(bitstream.getChecksum());
- mostRecentChecksum.setExpectedChecksum(bitstream.getChecksum());
- }
- mostRecentChecksum.setProcessStartDate(new Date());
- mostRecentChecksum.setProcessEndDate(new Date());
- if (bitstream.getChecksumAlgorithm() == null) {
- mostRecentChecksum.setChecksumAlgorithm("MD5");
- } else {
- mostRecentChecksum.setChecksumAlgorithm(bitstream.getChecksumAlgorithm());
- }
- mostRecentChecksum.setMatchedPrevChecksum(true);
- ChecksumResult checksumResult;
- if (bitstream.isDeleted()) {
- checksumResult = checksumResultService.findByCode(context, ChecksumResultCode.BITSTREAM_MARKED_DELETED);
- } else {
- checksumResult = checksumResultService.findByCode(context, ChecksumResultCode.CHECKSUM_MATCH);
- }
- mostRecentChecksum.setChecksumResult(checksumResult);
- mostRecentChecksumDAO.create(context, mostRecentChecksum);
- mostRecentChecksumDAO.save(context, mostRecentChecksum);
- }
+ log.info("Retrieving missing bitsreams (bitstream IDs that are not yet in most_recent_checksum table)...");
+ int updated = mostRecentChecksumDAO.updateMissingBitstreams(context);
+ log.info("Updated most_recent_checksum for " + updated + " bitstreams.");
+ log.info("Missing bitsreams processing done.");
}
@Override
diff --git a/dspace-api/src/main/java/org/dspace/checker/SimpleReporterServiceImpl.java b/dspace-api/src/main/java/org/dspace/checker/SimpleReporterServiceImpl.java
index ddefb28e1b57..6c69764fdc79 100644
--- a/dspace-api/src/main/java/org/dspace/checker/SimpleReporterServiceImpl.java
+++ b/dspace-api/src/main/java/org/dspace/checker/SimpleReporterServiceImpl.java
@@ -70,6 +70,7 @@ public int getDeletedBitstreamReport(Context context, Date startDate, Date endDa
osw.write("\n");
osw.write(msg("deleted-bitstream-intro"));
+ osw.write(" ");
osw.write(applyDateFormatShort(startDate));
osw.write(" ");
osw.write(msg("date-range-to"));
@@ -111,7 +112,6 @@ public int getChangedChecksumReport(Context context, Date startDate, Date endDat
osw.write("\n");
osw.write(msg("checksum-did-not-match"));
osw.write(" ");
- osw.write("\n");
osw.write(applyDateFormatShort(startDate));
osw.write(" ");
osw.write(msg("date-range-to"));
diff --git a/dspace-api/src/main/java/org/dspace/checker/dao/MostRecentChecksumDAO.java b/dspace-api/src/main/java/org/dspace/checker/dao/MostRecentChecksumDAO.java
index 56485c9b4b4b..73a81e4d9b4f 100644
--- a/dspace-api/src/main/java/org/dspace/checker/dao/MostRecentChecksumDAO.java
+++ b/dspace-api/src/main/java/org/dspace/checker/dao/MostRecentChecksumDAO.java
@@ -33,6 +33,8 @@ public List findByNotProcessedInDateRange(Context context, D
public List findByResultTypeInDateRange(Context context, Date startDate, Date endDate,
ChecksumResultCode resultCode) throws SQLException;
+ public int updateMissingBitstreams(Context context) throws SQLException;
+
public void deleteByBitstream(Context context, Bitstream bitstream) throws SQLException;
public MostRecentChecksum getOldestRecord(Context context) throws SQLException;
diff --git a/dspace-api/src/main/java/org/dspace/checker/dao/impl/MostRecentChecksumDAOImpl.java b/dspace-api/src/main/java/org/dspace/checker/dao/impl/MostRecentChecksumDAOImpl.java
index 669621aeeb58..dd07f3e2e9fa 100644
--- a/dspace-api/src/main/java/org/dspace/checker/dao/impl/MostRecentChecksumDAOImpl.java
+++ b/dspace-api/src/main/java/org/dspace/checker/dao/impl/MostRecentChecksumDAOImpl.java
@@ -56,8 +56,8 @@ public List findByNotProcessedInDateRange(Context context, D
criteriaQuery.where(criteriaBuilder.and(
criteriaBuilder.equal(mostRecentChecksumRoot.get(MostRecentChecksum_.toBeProcessed), false),
criteriaBuilder
- .lessThanOrEqualTo(mostRecentChecksumRoot.get(MostRecentChecksum_.processStartDate), startDate),
- criteriaBuilder.greaterThan(mostRecentChecksumRoot.get(MostRecentChecksum_.processStartDate), endDate)
+ .lessThanOrEqualTo(mostRecentChecksumRoot.get(MostRecentChecksum_.processStartDate), endDate),
+ criteriaBuilder.greaterThan(mostRecentChecksumRoot.get(MostRecentChecksum_.processStartDate), startDate)
)
);
List orderList = new LinkedList<>();
@@ -66,6 +66,24 @@ public List findByNotProcessedInDateRange(Context context, D
return list(context, criteriaQuery, false, MostRecentChecksum.class, -1, -1);
}
+ @Override
+ public int updateMissingBitstreams(Context context) throws SQLException {
+ String hql = "INSERT INTO MostRecentChecksum(bitstream, toBeProcessed, expectedChecksum, currentChecksum, " +
+ "processStartDate, processEndDate, checksumAlgorithm, matchedPrevChecksum, checksumResult) " +
+ "SELECT b, " +
+ "CASE WHEN deleted = false THEN true ELSE false END, " +
+ "CASE WHEN checksum IS NULL THEN '' ELSE checksum END, " +
+ "CASE WHEN checksum IS NULL THEN '' ELSE checksum END, " +
+ "current_timestamp(), current_timestamp(), " +
+ "CASE WHEN checksumAlgorithm IS NULL THEN 'MD5' ELSE checksumAlgorithm END, " +
+ "CAST(1 AS boolean), " +
+ "(SELECT cr FROM ChecksumResult AS cr WHERE " +
+ "(resultCode = 'BITSTREAM_MARKED_DELETED' AND b.deleted = true) " +
+ "OR (resultCode = 'CHECKSUM_MATCH' AND b.deleted = false)) " +
+ "FROM Bitstream AS b WHERE NOT EXISTS(SELECT 'x' FROM MostRecentChecksum AS c WHERE c.bitstream = b)";
+ Query query = createQuery(context, hql);
+ return query.executeUpdate();
+ }
@Override
public MostRecentChecksum findByBitstream(Context context, Bitstream bitstream) throws SQLException {
diff --git a/dspace-api/src/main/java/org/dspace/checker/service/SimpleReporterService.java b/dspace-api/src/main/java/org/dspace/checker/service/SimpleReporterService.java
index 1dc56c20a3de..f3e0b43d8899 100644
--- a/dspace-api/src/main/java/org/dspace/checker/service/SimpleReporterService.java
+++ b/dspace-api/src/main/java/org/dspace/checker/service/SimpleReporterService.java
@@ -72,7 +72,8 @@ public int getBitstreamNotFoundReport(Context context, Date startDate, Date endD
/**
* The bitstreams that were set to not be processed report for the specified
- * date range.
+ * date range. This includes bitstreams that are marked as deleted and bitstreams
+ * that are not found from the assetstore.
*
* @param context context
* @param startDate the start date range.
diff --git a/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java
index 880a72d0a6c7..4363e0c76bf5 100644
--- a/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java
+++ b/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java
@@ -21,6 +21,8 @@
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.Logger;
+import org.dspace.app.requestitem.RequestItem;
+import org.dspace.app.requestitem.service.RequestItemService;
import org.dspace.authorize.AuthorizeException;
import org.dspace.authorize.service.AuthorizeService;
import org.dspace.content.dao.BitstreamDAO;
@@ -65,6 +67,8 @@ public class BitstreamServiceImpl extends DSpaceObjectServiceImpl imp
protected BundleService bundleService;
@Autowired(required = true)
protected BitstreamStorageService bitstreamStorageService;
+ @Autowired(required = true)
+ protected RequestItemService requestItemService;
protected BitstreamServiceImpl() {
super();
@@ -289,6 +293,13 @@ public void delete(Context context, Bitstream bitstream) throws SQLException, Au
//Remove all bundles from the bitstream object, clearing the connection in 2 ways
bundles.clear();
+ // Remove any RequestItem entities associated with this bitstream ensuring there are no requests referencing
+ // a deleted bitstream
+ Iterator requestItems = requestItemService.findByBitstreamId(context, bitstream.getID());
+ while (requestItems.hasNext()) {
+ requestItemService.delete(context, requestItems.next());
+ }
+
// Remove policies only after the bitstream has been updated (otherwise the current user has not WRITE rights)
authorizeService.removeAllPolicies(context, bitstream);
}
@@ -350,6 +361,28 @@ public void expunge(Context context, Bitstream bitstream) throws SQLException, A
throw new IllegalStateException("Bitstream " + bitstream.getID().toString()
+ " must be deleted before it can be removed from the database.");
}
+
+ // Defensively remove any remaining bundle2bitstream references.
+ // Normally delete() already cleans these up, but orphaned rows from
+ // historical bugs can cause FK constraint violations on hard-delete.
+ final List bundles = bitstream.getBundles();
+ for (Bundle bundle : bundles) {
+ if (bitstream.equals(bundle.getPrimaryBitstream())) {
+ bundle.unsetPrimaryBitstreamID();
+ }
+ bundle.removeBitstream(bitstream);
+ }
+ bundles.clear();
+
+ // Remove any orphaned request items referencing this bitstream
+ Iterator requestItems = requestItemService.findByBitstreamId(context, bitstream.getID());
+ while (requestItems.hasNext()) {
+ requestItemService.delete(context, requestItems.next());
+ }
+
+ // Remove any remaining authorization policies
+ authorizeService.removeAllPolicies(context, bitstream);
+
bitstreamDAO.delete(context, bitstream);
}
diff --git a/dspace-api/src/main/java/org/dspace/content/BundleServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/BundleServiceImpl.java
index 3ba90c8cc2ae..8234846ed4f3 100644
--- a/dspace-api/src/main/java/org/dspace/content/BundleServiceImpl.java
+++ b/dspace-api/src/main/java/org/dspace/content/BundleServiceImpl.java
@@ -583,4 +583,8 @@ public Bundle findByLegacyId(Context context, int id) throws SQLException {
public int countTotal(Context context) throws SQLException {
return bundleDAO.countRows(context);
}
+
+ public int countBitstreams(Context context, Bundle bundle) throws SQLException {
+ return bundleDAO.countBitstreams(context, bundle);
+ }
}
diff --git a/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java
index f5ef4f4b14a4..2f3fd827bfab 100644
--- a/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java
+++ b/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java
@@ -13,18 +13,21 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
+import java.util.HashSet;
import java.util.Iterator;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.Objects;
+import java.util.Queue;
import java.util.Set;
import java.util.UUID;
+import java.util.stream.Collectors;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.Logger;
-import org.apache.solr.client.solrj.util.ClientUtils;
import org.dspace.app.util.AuthorizeUtil;
import org.dspace.authorize.AuthorizeConfiguration;
import org.dspace.authorize.AuthorizeException;
@@ -838,6 +841,86 @@ public List findAuthorized(Context context, Community community, int
return myResults;
}
+ @Override
+ public List findAuthorized(Context context, Community community, List actions)
+ throws SQLException {
+
+ List myCollections = new ArrayList<>();
+ EPerson eperson = context.getCurrentUser();
+
+ //If eperson is Administrator return all colls or if a community is not null only the community's collections
+ if (authorizeService.isAdmin(context, eperson)) {
+ if (community != null) {
+ return community.getCollections();
+ }
+ myCollections = this.findAll(context);
+ return myCollections;
+ }
+
+ //Get the collections of the eperson where is is admin of a community
+ List directGroups = new ArrayList<>(eperson.getGroups()); // direct membership
+ Queue queue = new LinkedList<>(directGroups);
+ while (!queue.isEmpty()) {
+ Group current = queue.poll();
+ List parents = current.getParentGroups();
+
+ for (Group parent : parents) {
+ if (directGroups.add(parent)) {
+ queue.add(parent);
+ }
+ }
+ }
+
+ List resourcePolicies = resourcePolicyService
+ .find(context, eperson, directGroups, Constants.ADMIN, Constants.COMMUNITY);
+ List uuids = resourcePolicies.stream()
+ .map(policy -> policy.getdSpaceObject().getID())
+ .toList();
+
+ List communities = uuids.stream()
+ .map(uuid -> {
+ try {
+ return communityService.find(context, uuid);
+ } catch (SQLException e) {
+ return null; //ignore that uuid
+ }
+ })
+ .filter(Objects::nonNull)
+ .toList();
+
+ Set allCommunities = new HashSet<>(communities);
+ Set allCommAdminCollections = communities.stream()
+ .flatMap(cm -> cm.getCollections().stream())
+ .collect(Collectors.toSet());
+ Queue queueComm = new LinkedList<>(communities);
+
+ while (!queueComm.isEmpty()) {
+ Community com = queueComm.poll();
+ List childrenComms = com.getSubcommunities();
+ for (Community childComm : childrenComms) {
+ if (allCommunities.add(childComm)) {
+ queueComm.add(childComm);
+ allCommAdminCollections.addAll(childComm.getCollections());
+ }
+ }
+ }
+
+ //Now get the collection when the eperson can deposit or is admin or is in a group with those privileges
+ myCollections = collectionDAO.findAuthorizedByEPerson(context, eperson, actions);
+ Set allCollections = new HashSet<>(myCollections);
+ //Join EPerson Community Admin Collections with Collection Admins
+ allCollections.addAll(allCommAdminCollections);
+
+ List collsAllowed = new ArrayList<>(allCollections);
+
+ //A community is passed, only the community's collections will be used and existing in eperson Authorizations
+ if (community != null) {
+ collsAllowed.retainAll(community.getCollections());
+ }
+
+ return collsAllowed;
+ }
+
@Override
public Collection findByGroup(Context context, Group group) throws SQLException {
return collectionDAO.findByGroup(context, group);
@@ -949,7 +1032,7 @@ public String getDefaultReadGroupName(Collection collection, String typeOfGroupS
@Override
public List findCollectionsWithSubmit(String q, Context context, Community community,
- int offset, int limit) throws SQLException, SearchServiceException {
+ int offset, int limit) throws SearchServiceException {
List collections = new ArrayList<>();
DiscoverQuery discoverQuery = new DiscoverQuery();
@@ -966,8 +1049,8 @@ public List findCollectionsWithSubmit(String q, Context context, Com
}
@Override
- public int countCollectionsWithSubmit(String q, Context context, Community community)
- throws SQLException, SearchServiceException {
+ public int countCollectionsWithSubmit(Context context, String q, Community community)
+ throws SearchServiceException {
DiscoverQuery discoverQuery = new DiscoverQuery();
discoverQuery.setMaxResults(0);
@@ -989,29 +1072,12 @@ public int countCollectionsWithSubmit(String q, Context context, Community commu
* terms. The terms are used to make also a prefix query on SOLR
* so it can be used to implement an autosuggest feature over the collection name
* @return discovery search result objects
- * @throws SQLException if something goes wrong
* @throws SearchServiceException if search error
*/
private DiscoverResult retrieveCollectionsWithSubmit(Context context, DiscoverQuery discoverQuery,
String entityType, Community community, String q)
- throws SQLException, SearchServiceException {
-
- StringBuilder query = new StringBuilder();
- EPerson currentUser = context.getCurrentUser();
- if (!authorizeService.isAdmin(context)) {
- String userId = "";
- if (currentUser != null) {
- userId = currentUser.getID().toString();
- }
- query.append("submit:(e").append(userId);
+ throws SearchServiceException {
- Set groups = groupService.allMemberGroupsSet(context, currentUser);
- for (Group group : groups) {
- query.append(" OR g").append(group.getID());
- }
- query.append(")");
- discoverQuery.addFilterQueries(query.toString());
- }
if (Objects.nonNull(community)) {
discoverQuery.addFilterQueries("location.comm:" + community.getID().toString());
}
@@ -1019,12 +1085,10 @@ private DiscoverResult retrieveCollectionsWithSubmit(Context context, DiscoverQu
discoverQuery.addFilterQueries("search.entitytype:" + entityType);
}
if (StringUtils.isNotBlank(q)) {
- StringBuilder buildQuery = new StringBuilder();
- String escapedQuery = ClientUtils.escapeQueryChars(q);
- buildQuery.append("(").append(escapedQuery).append(" OR dc.title_sort:*")
- .append(escapedQuery).append("*").append(")");
- discoverQuery.setQuery(buildQuery.toString());
+ q = searchService.formatAutoCompleteQuery(q, "dc.title_sort");
+ discoverQuery.setQuery(q);
}
+ discoverQuery.addRequiredAuthorization(Constants.ADD);
DiscoverResult resp = searchService.search(context, discoverQuery);
return resp;
}
@@ -1064,8 +1128,8 @@ public Collection retrieveCollectionWithSubmitByCommunityAndEntityType(Context c
context.turnOffAuthorisationSystem();
List collections;
try {
- collections = findCollectionsWithSubmit(null, context, community, entityType, 0, 1);
- } catch (SQLException | SearchServiceException e) {
+ collections = findCollectionsWithSubmit(context, null, community, entityType, 0, 1);
+ } catch (SearchServiceException e) {
throw new RuntimeException(e);
}
context.restoreAuthSystemState();
@@ -1085,8 +1149,8 @@ public Collection retrieveCollectionWithSubmitByCommunityAndEntityType(Context c
}
@Override
- public List findCollectionsWithSubmit(String q, Context context, Community community, String entityType,
- int offset, int limit) throws SQLException, SearchServiceException {
+ public List findCollectionsWithSubmit(Context context, String q, Community community, String entityType,
+ int offset, int limit) throws SearchServiceException {
List collections = new ArrayList<>();
DiscoverQuery discoverQuery = new DiscoverQuery();
discoverQuery.setDSpaceObjectFilter(IndexableCollection.TYPE);
@@ -1103,8 +1167,8 @@ public List findCollectionsWithSubmit(String q, Context context, Com
}
@Override
- public int countCollectionsWithSubmit(String q, Context context, Community community, String entityType)
- throws SQLException, SearchServiceException {
+ public int countCollectionsWithSubmit(Context context, String q, Community community, String entityType)
+ throws SearchServiceException {
DiscoverQuery discoverQuery = new DiscoverQuery();
discoverQuery.setMaxResults(0);
discoverQuery.setDSpaceObjectFilter(IndexableCollection.TYPE);
diff --git a/dspace-api/src/main/java/org/dspace/content/EntityType.java b/dspace-api/src/main/java/org/dspace/content/EntityType.java
index 720e0c492ca7..70b054c94d18 100644
--- a/dspace-api/src/main/java/org/dspace/content/EntityType.java
+++ b/dspace-api/src/main/java/org/dspace/content/EntityType.java
@@ -9,6 +9,7 @@
import java.util.Objects;
+import jakarta.persistence.Cacheable;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
@@ -19,6 +20,8 @@
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.dspace.core.ReloadableEntity;
+import org.hibernate.annotations.Cache;
+import org.hibernate.annotations.CacheConcurrencyStrategy;
/**
* Class representing an EntityType
@@ -26,6 +29,8 @@
* This also has a label that will be used to identify what kind of EntityType this object is
*/
@Entity
+@Cacheable
+@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
@Table(name = "entity_type")
public class EntityType implements ReloadableEntity {
diff --git a/dspace-api/src/main/java/org/dspace/content/EntityTypeServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/EntityTypeServiceImpl.java
index 7df892cd56f5..1442c676a0c9 100644
--- a/dspace-api/src/main/java/org/dspace/content/EntityTypeServiceImpl.java
+++ b/dspace-api/src/main/java/org/dspace/content/EntityTypeServiceImpl.java
@@ -16,6 +16,7 @@
import java.util.Set;
import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.response.FacetField;
@@ -28,6 +29,7 @@
import org.dspace.content.service.EntityTypeService;
import org.dspace.core.Constants;
import org.dspace.core.Context;
+import org.dspace.discovery.SearchService;
import org.dspace.discovery.SolrSearchCore;
import org.dspace.discovery.indexobject.IndexableCollection;
import org.dspace.eperson.EPerson;
@@ -49,6 +51,9 @@ public class EntityTypeServiceImpl implements EntityTypeService {
@Autowired
protected SolrSearchCore solrSearchCore;
+ @Autowired
+ protected SearchService searchService;
+
@Override
public EntityType findByEntityType(Context context, String entityType) throws SQLException {
return entityTypeDAO.findByEntityType(context, entityType);
@@ -126,26 +131,34 @@ public List getSubmitAuthorizedTypes(Context context)
throws SQLException, SolrServerException, IOException {
List types = new ArrayList<>();
StringBuilder query = null;
- EPerson currentUser = context.getCurrentUser();
if (!authorizeService.isAdmin(context)) {
- String userId = "";
+ EPerson currentUser = context.getCurrentUser();
+ StringBuilder epersonAndGroupClause = new StringBuilder();
if (currentUser != null) {
- userId = currentUser.getID().toString();
- query = new StringBuilder();
- query.append("submit:(e").append(userId);
+ epersonAndGroupClause.append("e").append(currentUser.getID());
}
-
+ //Retrieve all the groups the current user is a member of
Set groups = groupService.allMemberGroupsSet(context, currentUser);
for (Group group : groups) {
- if (query == null) {
- query = new StringBuilder();
- query.append("submit:(g");
+ if (!epersonAndGroupClause.isEmpty()) {
+ epersonAndGroupClause.append(" OR g").append(group.getID());
} else {
- query.append(" OR g");
+ epersonAndGroupClause.append("g").append(group.getID());
}
- query.append(group.getID());
}
- query.append(")");
+
+ if (epersonAndGroupClause.isEmpty()) {
+ // No user or groups, no authorized types
+ return new ArrayList<>();
+ }
+ query = new StringBuilder();
+ query.append("submit:(").append(epersonAndGroupClause).append(")");
+ query.append(" OR ").append("admin:(").append(epersonAndGroupClause).append(")");
+ String locations = searchService.createLocationQueryForAdministrableDSOs(epersonAndGroupClause.toString());
+ if (StringUtils.isNotBlank(locations)) {
+ query.append(" OR ");
+ query.append(locations);
+ }
}
SolrQuery sQuery = new SolrQuery("*:*");
diff --git a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java
index 157b891486f0..f8ea21b3cccb 100644
--- a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java
+++ b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java
@@ -22,7 +22,6 @@
import java.util.UUID;
import java.util.function.Supplier;
import java.util.stream.Collectors;
-import java.util.stream.Stream;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
@@ -480,7 +479,7 @@ public void addBundle(Context context, Item item, Bundle bundle) throws SQLExcep
// now add authorization policies from owning item
// hmm, not very "multiple-inclusion" friendly
- authorizeService.inheritPolicies(context, item, bundle);
+ authorizeService.inheritPolicies(context, item, bundle, true);
// Add the bundle to in-memory list
item.addBundle(bundle);
@@ -1046,8 +1045,8 @@ public void adjustBundleBitstreamPolicies(Context context, Item item, Collection
// if come from InstallItem: remove all submission/workflow policies
authorizeService.removeAllPoliciesByDSOAndType(context, mybundle, ResourcePolicy.TYPE_SUBMISSION);
authorizeService.removeAllPoliciesByDSOAndType(context, mybundle, ResourcePolicy.TYPE_WORKFLOW);
- addCustomPoliciesNotInPlace(context, mybundle, defaultItemPolicies);
- addDefaultPoliciesNotInPlace(context, mybundle, defaultCollectionBundlePolicies);
+ authorizeService.addCustomPoliciesNotInPlace(context, mybundle, defaultItemPolicies);
+ authorizeService.addDefaultPoliciesNotInPlace(context, mybundle, defaultCollectionBundlePolicies);
for (Bitstream bitstream : mybundle.getBitstreams()) {
// If collection has default READ policies, remove the bundle's READ policies.
@@ -1093,8 +1092,8 @@ private void removeAllPoliciesAndAddDefault(Context context, Bitstream bitstream
throws SQLException, AuthorizeException {
authorizeService.removeAllPoliciesByDSOAndType(context, bitstream, ResourcePolicy.TYPE_SUBMISSION);
authorizeService.removeAllPoliciesByDSOAndType(context, bitstream, ResourcePolicy.TYPE_WORKFLOW);
- addCustomPoliciesNotInPlace(context, bitstream, defaultItemPolicies);
- addDefaultPoliciesNotInPlace(context, bitstream, defaultCollectionPolicies);
+ authorizeService.addCustomPoliciesNotInPlace(context, bitstream, defaultItemPolicies);
+ authorizeService.addDefaultPoliciesNotInPlace(context, bitstream, defaultCollectionPolicies);
}
@Override
@@ -1132,7 +1131,7 @@ public void adjustItemPolicies(Context context, Item item, Collection collection
authorizeService.removeAllPoliciesByDSOAndType(context, item, ResourcePolicy.TYPE_WORKFLOW);
// add default policies only if not already in place
- addDefaultPoliciesNotInPlace(context, item, defaultCollectionPolicies);
+ authorizeService.addDefaultPoliciesNotInPlace(context, item, defaultCollectionPolicies);
} finally {
context.restoreAuthSystemState();
}
@@ -1142,11 +1141,6 @@ public void adjustItemPolicies(Context context, Item item, Collection collection
public void move(Context context, Item item, Collection from, Collection to)
throws SQLException, AuthorizeException, IOException {
- // If the two collections are the same, do nothing.
- if (from.equals(to)) {
- return;
- }
-
// Use the normal move method, and default to not inherit permissions
this.move(context, item, from, to, false);
}
@@ -1161,6 +1155,11 @@ public void move(Context context, Item item, Collection from, Collection to, boo
authorizeService.authorizeAction(context, item, Constants.WRITE);
}
+ // If the two collections are the same, do nothing.
+ if (from.equals(to)) {
+ return;
+ }
+
// Move the Item from one Collection to the other
collectionService.addItem(context, to, item);
collectionService.removeItem(context, from, item);
@@ -1261,43 +1260,41 @@ public boolean canEdit(Context context, Item item) throws SQLException {
*
* @param context DSpace context
* @param discoverQuery
+ * @param q query string
* @return discovery search result objects
- * @throws SQLException if something goes wrong
* @throws SearchServiceException if search error
*/
- private DiscoverResult retrieveItemsWithEdit(Context context, DiscoverQuery discoverQuery)
- throws SQLException, SearchServiceException {
- EPerson currentUser = context.getCurrentUser();
- if (!authorizeService.isAdmin(context)) {
- String userId = currentUser != null ? "e" + currentUser.getID().toString() : "e";
- Stream groupIds = groupService.allMemberGroupsSet(context, currentUser).stream()
- .map(group -> "g" + group.getID());
- String query = Stream.concat(Stream.of(userId), groupIds)
- .collect(Collectors.joining(" OR ", "edit:(", ")"));
- discoverQuery.addFilterQueries(query);
- }
+ private DiscoverResult retrieveItemsWithEdit(Context context, DiscoverQuery discoverQuery, String q)
+ throws SearchServiceException {
+ if (StringUtils.isNotBlank(q)) {
+ // Although not all items will have a metadata dc.title, we use it for autocomplete because it is the
+ // most common. Ideally, we should use a field that all indexed items have
+ q = searchService.formatAutoCompleteQuery(q, "dc.title_sort");
+ discoverQuery.setQuery(q);
+ }
+ discoverQuery.addRequiredAuthorization(Constants.WRITE);
return searchService.search(context, discoverQuery);
}
@Override
- public List findItemsWithEdit(Context context, int offset, int limit)
- throws SQLException, SearchServiceException {
+ public List findItemsWithEdit(Context context, String q, int offset, int limit)
+ throws SearchServiceException {
DiscoverQuery discoverQuery = new DiscoverQuery();
discoverQuery.setDSpaceObjectFilter(IndexableItem.TYPE);
discoverQuery.setStart(offset);
discoverQuery.setMaxResults(limit);
- DiscoverResult resp = retrieveItemsWithEdit(context, discoverQuery);
+ DiscoverResult resp = retrieveItemsWithEdit(context, discoverQuery, q);
return resp.getIndexableObjects().stream()
.map(solrItems -> ((IndexableItem) solrItems).getIndexedObject())
.collect(Collectors.toList());
}
@Override
- public int countItemsWithEdit(Context context) throws SQLException, SearchServiceException {
+ public int countItemsWithEdit(Context context, String q) throws SearchServiceException {
DiscoverQuery discoverQuery = new DiscoverQuery();
discoverQuery.setMaxResults(0);
discoverQuery.setDSpaceObjectFilter(IndexableItem.TYPE);
- DiscoverResult resp = retrieveItemsWithEdit(context, discoverQuery);
+ DiscoverResult resp = retrieveItemsWithEdit(context, discoverQuery, q);
return (int) resp.getTotalSearchResults();
}
@@ -1322,91 +1319,7 @@ public boolean isInProgressSubmission(Context context, Item item) throws SQLExce
*/
- /**
- * Add the default policies, which have not been already added to the given DSpace object
- *
- * @param context The relevant DSpace Context.
- * @param dso The DSpace Object to add policies to
- * @param defaultCollectionPolicies list of policies
- * @throws SQLException An exception that provides information on a database access error or other errors.
- * @throws AuthorizeException Exception indicating the current user of the context does not have permission
- * to perform a particular action.
- */
- protected void addDefaultPoliciesNotInPlace(Context context, DSpaceObject dso,
- List defaultCollectionPolicies) throws SQLException, AuthorizeException {
- boolean appendMode = configurationService
- .getBooleanProperty("core.authorization.installitem.inheritance-read.append-mode", false);
- for (ResourcePolicy defaultPolicy : defaultCollectionPolicies) {
- if (!authorizeService
- .isAnIdenticalPolicyAlreadyInPlace(context, dso, defaultPolicy.getGroup(), Constants.READ,
- defaultPolicy.getID()) &&
- (!appendMode && isNotAlreadyACustomRPOfThisTypeOnDSO(context, dso) ||
- appendMode && shouldBeAppended(context, dso, defaultPolicy))) {
- ResourcePolicy newPolicy = resourcePolicyService.clone(context, defaultPolicy);
- newPolicy.setdSpaceObject(dso);
- newPolicy.setAction(Constants.READ);
- newPolicy.setRpType(ResourcePolicy.TYPE_INHERITED);
- resourcePolicyService.update(context, newPolicy);
- }
- }
- }
-
- private void addCustomPoliciesNotInPlace(Context context, DSpaceObject dso, List customPolicies)
- throws SQLException, AuthorizeException {
- boolean customPoliciesAlreadyInPlace = authorizeService
- .findPoliciesByDSOAndType(context, dso, ResourcePolicy.TYPE_CUSTOM).size() > 0;
- if (!customPoliciesAlreadyInPlace) {
- authorizeService.addPolicies(context, customPolicies, dso);
- }
- }
-
- /**
- * Check whether or not there is already an RP on the given dso, which has actionId={@link Constants.READ} and
- * resourceTypeId={@link ResourcePolicy.TYPE_CUSTOM}
- *
- * @param context DSpace context
- * @param dso DSpace object to check for custom read RP
- * @return True if there is no RP on the item with custom read RP, otherwise false
- * @throws SQLException If something goes wrong retrieving the RP on the DSO
- */
- private boolean isNotAlreadyACustomRPOfThisTypeOnDSO(Context context, DSpaceObject dso) throws SQLException {
- List readRPs = resourcePolicyService.find(context, dso, Constants.READ);
- for (ResourcePolicy readRP : readRPs) {
- if (readRP.getRpType() != null && readRP.getRpType().equals(ResourcePolicy.TYPE_CUSTOM)) {
- return false;
- }
- }
- return true;
- }
- /**
- * Check if the provided default policy should be appended or not to the final
- * item. If an item has at least one custom READ policy any anonymous READ
- * policy with empty start/end date should be skipped
- *
- * @param context DSpace context
- * @param dso DSpace object to check for custom read RP
- * @param defaultPolicy The policy to check
- * @return
- * @throws SQLException If something goes wrong retrieving the RP on the DSO
- */
- private boolean shouldBeAppended(Context context, DSpaceObject dso, ResourcePolicy defaultPolicy)
- throws SQLException {
- boolean hasCustomPolicy = resourcePolicyService.find(context, dso, Constants.READ)
- .stream()
- .filter(rp -> (Objects.nonNull(rp.getRpType()) &&
- Objects.equals(rp.getRpType(), ResourcePolicy.TYPE_CUSTOM)))
- .findFirst()
- .isPresent();
-
- boolean isAnonimousGroup = Objects.nonNull(defaultPolicy.getGroup())
- && StringUtils.equals(defaultPolicy.getGroup().getName(), Group.ANONYMOUS);
-
- boolean datesAreNull = Objects.isNull(defaultPolicy.getStartDate())
- && Objects.isNull(defaultPolicy.getEndDate());
-
- return !(hasCustomPolicy && isAnonimousGroup && datesAreNull);
- }
/**
* Returns an iterator of Items possessing the passed metadata field, or only
diff --git a/dspace-api/src/main/java/org/dspace/content/RelationshipType.java b/dspace-api/src/main/java/org/dspace/content/RelationshipType.java
index ba5f0531e97e..f00b21421e30 100644
--- a/dspace-api/src/main/java/org/dspace/content/RelationshipType.java
+++ b/dspace-api/src/main/java/org/dspace/content/RelationshipType.java
@@ -7,6 +7,7 @@
*/
package org.dspace.content;
+import jakarta.persistence.Cacheable;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
@@ -20,6 +21,8 @@
import jakarta.persistence.Table;
import org.dspace.core.Context;
import org.dspace.core.ReloadableEntity;
+import org.hibernate.annotations.Cache;
+import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.JdbcTypeCode;
import org.hibernate.type.SqlTypes;
@@ -32,6 +35,8 @@
* The cardinality properties describe how many of each relations this relationshipType can support
*/
@Entity
+@Cacheable
+@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
@Table(name = "relationship_type")
public class RelationshipType implements ReloadableEntity {
diff --git a/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java b/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java
index 444332df97d2..3aea294b2ba1 100644
--- a/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java
+++ b/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java
@@ -34,23 +34,25 @@
* from {@code ${dspace.dir}/config/controlled-vocabularies/*.xml} and turns
* them into autocompleting authorities.
*
- * Configuration: This MUST be configured as a self-named plugin, e.g.: {@code
- * plugin.selfnamed.org.dspace.content.authority.ChoiceAuthority = \
+ *
Configuration: This MUST be configured as a self-named plugin, e.g.: {@code
+ * plugin.selfnamed.org.dspace.content.authority.ChoiceAuthority =
* org.dspace.content.authority.DSpaceControlledVocabulary
* }
*
- * It AUTOMATICALLY configures a plugin instance for each XML file in the
+ *
It AUTOMATICALLY configures a plugin instance for each XML file in the
* controlled vocabularies directory. The name of the plugin is the basename of
* the file; e.g., {@code ${dspace.dir}/config/controlled-vocabularies/nsi.xml}
* would generate a plugin called "nsi".
*
- * Each configured plugin comes with three configuration options: {@code
- * vocabulary.plugin._plugin_.hierarchy.store =
- * # Store entire hierarchy along with selected value. Default: TRUE
- * vocabulary.plugin._plugin_.hierarchy.suggest =
- * # Display entire hierarchy in the suggestion list. Default: TRUE
- * vocabulary.plugin._plugin_.delimiter = ""
- * # Delimiter to use when building hierarchy strings. Default: "::"
+ *
Each configured plugin comes with three configuration options:
+ *
+ *
{@code vocabulary.plugin._plugin_.hierarchy.store =
+ * # Store entire hierarchy along with selected value. Default: TRUE}
+ *
{@code vocabulary.plugin._plugin_.hierarchy.suggest =
+ * # Display entire hierarchy in the suggestion list. Default: TRUE}
+ *
{@code vocabulary.plugin._plugin_.delimiter = ""
+ * # Delimiter to use when building hierarchy strings. Default: "::"}
+ *
* }
*
* @author Michael B. Klein
@@ -58,11 +60,23 @@
public class DSpaceControlledVocabulary extends SelfNamedPlugin implements HierarchicalAuthority {
- private static Logger log = org.apache.logging.log4j.LogManager.getLogger(DSpaceControlledVocabulary.class);
- protected static String xpathTemplate = "//node[contains(translate(@label,'ABCDEFGHIJKLMNOPQRSTUVWXYZ'," +
- "'abcdefghijklmnopqrstuvwxyz'),'%s')]";
- protected static String idTemplate = "//node[@id = '%s']";
- protected static String labelTemplate = "//node[@label = '%s']";
+ private static final Logger log = org.apache.logging.log4j.LogManager.getLogger();
+ protected static final String xpathTemplate;
+ static {
+ StringBuilder upper = new StringBuilder();
+ StringBuilder lower = new StringBuilder();
+ for (int cp = 'A'; cp <= Character.MAX_CODE_POINT; cp++) {
+ if (Character.isLetter(cp) && Character.isUpperCase(cp)) {
+ int lcp = Character.toLowerCase(cp);
+ upper.appendCodePoint(cp);
+ lower.appendCodePoint(lcp);
+ }
+ }
+ xpathTemplate = "//node[contains(translate(@label,'" + upper + "','" + lower + "'),%s)]";
+ }
+ protected static String idTemplate = "//node[@id = %s]";
+ protected static String idTemplateQuoted = "//node[@id = '%s']";
+ protected static String labelTemplate = "//node[@label = %s]";
protected static String idParentTemplate = "//node[@id = '%s']/parent::isComposedBy/parent::node";
protected static String rootTemplate = "/node";
protected static String pluginNames[] = null;
@@ -106,7 +120,7 @@ public boolean accept(File dir, String name) {
File.separator + "config" +
File.separator + "controlled-vocabularies";
String[] xmlFiles = (new File(vocabulariesPath)).list(new xmlFilter());
- List names = new ArrayList();
+ List names = new ArrayList<>();
for (String filename : xmlFiles) {
names.add((new File(filename)).getName().replace(".xml", ""));
}
@@ -162,14 +176,23 @@ protected String buildString(Node node) {
public Choices getMatches(String text, int start, int limit, String locale) {
init();
log.debug("Getting matches for '" + text + "'");
- String xpathExpression = "";
String[] textHierarchy = text.split(hierarchyDelimiter, -1);
+ StringBuilder xpathExpressionBuilder = new StringBuilder();
for (int i = 0; i < textHierarchy.length; i++) {
- xpathExpression += String.format(xpathTemplate, textHierarchy[i].replaceAll("'", "'").toLowerCase());
+ xpathExpressionBuilder.append(String.format(xpathTemplate, "$var" + i));
}
+ String xpathExpression = xpathExpressionBuilder.toString();
XPath xpath = XPathFactory.newInstance().newXPath();
- int total = 0;
- List choices = new ArrayList();
+ xpath.setXPathVariableResolver(variableName -> {
+ String varName = variableName.getLocalPart();
+ if (varName.startsWith("var")) {
+ int index = Integer.parseInt(varName.substring(3));
+ return textHierarchy[index].toLowerCase();
+ }
+ throw new IllegalArgumentException("Unexpected variable: " + varName);
+ });
+ int total;
+ List choices;
try {
NodeList results = (NodeList) xpath.evaluate(xpathExpression, vocabulary, XPathConstants.NODESET);
total = results.getLength();
@@ -185,14 +208,23 @@ public Choices getMatches(String text, int start, int limit, String locale) {
@Override
public Choices getBestMatch(String text, String locale) {
init();
- log.debug("Getting best matches for '" + text + "'");
- String xpathExpression = "";
+ log.debug("Getting best matches for {}'", text);
String[] textHierarchy = text.split(hierarchyDelimiter, -1);
+ StringBuilder xpathExpressionBuilder = new StringBuilder();
for (int i = 0; i < textHierarchy.length; i++) {
- xpathExpression += String.format(labelTemplate, textHierarchy[i].replaceAll("'", "'"));
+ xpathExpressionBuilder.append(String.format(labelTemplate, "$var" + i));
}
+ String xpathExpression = xpathExpressionBuilder.toString();
XPath xpath = XPathFactory.newInstance().newXPath();
- List choices = new ArrayList();
+ xpath.setXPathVariableResolver(variableName -> {
+ String varName = variableName.getLocalPart();
+ if (varName.startsWith("var")) {
+ int index = Integer.parseInt(varName.substring(3));
+ return textHierarchy[index];
+ }
+ throw new IllegalArgumentException("Unexpected variable: " + varName);
+ });
+ List choices;
try {
NodeList results = (NodeList) xpath.evaluate(xpathExpression, vocabulary, XPathConstants.NODESET);
choices = getChoicesFromNodeList(results, 0, 1);
@@ -240,7 +272,7 @@ public Choices getTopChoices(String authorityName, int start, int limit, String
@Override
public Choices getChoicesByParent(String authorityName, String parentId, int start, int limit, String locale) {
init();
- String xpathExpression = String.format(idTemplate, parentId);
+ String xpathExpression = String.format(idTemplateQuoted, parentId);
return getChoicesByXpath(xpathExpression, start, limit);
}
@@ -264,15 +296,12 @@ public Integer getPreloadLevel() {
}
private boolean isRootElement(Node node) {
- if (node != null && node.getOwnerDocument().getDocumentElement().equals(node)) {
- return true;
- }
- return false;
+ return node != null && node.getOwnerDocument().getDocumentElement().equals(node);
}
private Node getNode(String key) throws XPathExpressionException {
init();
- String xpathExpression = String.format(idTemplate, key);
+ String xpathExpression = String.format(idTemplateQuoted, key);
Node node = getNodeFromXPath(xpathExpression);
return node;
}
@@ -284,7 +313,7 @@ private Node getNodeFromXPath(String xpathExpression) throws XPathExpressionExce
}
private List getChoicesFromNodeList(NodeList results, int start, int limit) {
- List choices = new ArrayList();
+ List choices = new ArrayList<>();
for (int i = 0; i < results.getLength(); i++) {
if (i < start) {
continue;
@@ -303,17 +332,17 @@ private List getChoicesFromNodeList(NodeList results, int start, int lim
private Map addOtherInformation(String parentCurr, String noteCurr,
List childrenCurr, String authorityCurr) {
- Map extras = new HashMap();
+ Map extras = new HashMap<>();
if (StringUtils.isNotBlank(parentCurr)) {
extras.put("parent", parentCurr);
}
if (StringUtils.isNotBlank(noteCurr)) {
extras.put("note", noteCurr);
}
- if (childrenCurr.size() > 0) {
- extras.put("hasChildren", "true");
- } else {
+ if (childrenCurr.isEmpty()) {
extras.put("hasChildren", "false");
+ } else {
+ extras.put("hasChildren", "true");
}
extras.put("id", authorityCurr);
return extras;
@@ -368,7 +397,7 @@ private String getNote(Node node) {
}
private List getChildren(Node node) {
- List children = new ArrayList();
+ List children = new ArrayList<>();
NodeList childNodes = node.getChildNodes();
for (int ci = 0; ci < childNodes.getLength(); ci++) {
Node firstChild = childNodes.item(ci);
@@ -391,7 +420,7 @@ private List getChildren(Node node) {
private boolean isSelectable(Node node) {
Node selectableAttr = node.getAttributes().getNamedItem("selectable");
if (null != selectableAttr) {
- return Boolean.valueOf(selectableAttr.getNodeValue());
+ return Boolean.parseBoolean(selectableAttr.getNodeValue());
} else { // Default is true
return true;
}
@@ -418,7 +447,7 @@ private String getAuthority(Node node) {
}
private Choices getChoicesByXpath(String xpathExpression, int start, int limit) {
- List choices = new ArrayList();
+ List choices = new ArrayList<>();
XPath xpath = XPathFactory.newInstance().newXPath();
try {
Node parentNode = (Node) xpath.evaluate(xpathExpression, vocabulary, XPathConstants.NODE);
diff --git a/dspace-api/src/main/java/org/dspace/content/crosswalk/CrosswalkMetadataValidator.java b/dspace-api/src/main/java/org/dspace/content/crosswalk/CrosswalkMetadataValidator.java
index b1be458a255e..1bcc1c9ba9fb 100644
--- a/dspace-api/src/main/java/org/dspace/content/crosswalk/CrosswalkMetadataValidator.java
+++ b/dspace-api/src/main/java/org/dspace/content/crosswalk/CrosswalkMetadataValidator.java
@@ -107,9 +107,12 @@ public MetadataField checkMetadata(Context context, String schema, String elemen
e.printStackTrace();
}
} else if (!fieldChoice.equals("ignore")) {
- throw new CrosswalkException(
- "The '" + element + "." + qualifier + "' element has not been defined in this DSpace " +
- "instance. ");
+ throw new CrosswalkException(String.format(
+ "The '%s.%s%s' element has not been defined in this DSpace instance.",
+ mdSchema.getName(),
+ element,
+ qualifier == null ? "" : ("." + qualifier)
+ ));
}
}
}
diff --git a/dspace-api/src/main/java/org/dspace/content/crosswalk/LicenseStreamDisseminationCrosswalk.java b/dspace-api/src/main/java/org/dspace/content/crosswalk/LicenseStreamDisseminationCrosswalk.java
index 46858747870d..b1854bfd85ae 100644
--- a/dspace-api/src/main/java/org/dspace/content/crosswalk/LicenseStreamDisseminationCrosswalk.java
+++ b/dspace-api/src/main/java/org/dspace/content/crosswalk/LicenseStreamDisseminationCrosswalk.java
@@ -8,6 +8,7 @@
package org.dspace.content.crosswalk;
import java.io.IOException;
+import java.io.InputStream;
import java.io.OutputStream;
import java.sql.SQLException;
@@ -56,7 +57,12 @@ public void disseminate(Context context, DSpaceObject dso, OutputStream out)
Bitstream licenseBs = PackageUtils.findDepositLicense(context, (Item) dso);
if (licenseBs != null) {
- Utils.copy(bitstreamService.retrieve(context, licenseBs), out);
+ try (final InputStream bitInputStream = bitstreamService.retrieve(context, licenseBs)) {
+ Utils.copy(bitInputStream, out);
+ } catch (Exception e) {
+ log.warn("Could not retrieve license file for Item with UUID={}. " +
+ "Leaving it out of generated package. Error{}", dso.getID(), e.getMessage());
+ }
}
}
}
diff --git a/dspace-api/src/main/java/org/dspace/content/crosswalk/OREIngestionCrosswalk.java b/dspace-api/src/main/java/org/dspace/content/crosswalk/OREIngestionCrosswalk.java
index f756aae22577..9e890a6046fa 100644
--- a/dspace-api/src/main/java/org/dspace/content/crosswalk/OREIngestionCrosswalk.java
+++ b/dspace-api/src/main/java/org/dspace/content/crosswalk/OREIngestionCrosswalk.java
@@ -11,6 +11,8 @@
import java.io.IOException;
import java.io.InputStream;
import java.net.ConnectException;
+import java.net.URI;
+import java.net.URISyntaxException;
import java.net.URL;
import java.sql.SQLException;
import java.text.NumberFormat;
@@ -18,6 +20,8 @@
import java.util.Date;
import java.util.HashSet;
import java.util.List;
+import java.util.Locale;
+import java.util.Objects;
import java.util.Set;
import org.apache.logging.log4j.Logger;
@@ -34,6 +38,8 @@
import org.dspace.content.service.ItemService;
import org.dspace.core.Constants;
import org.dspace.core.Context;
+import org.dspace.services.ConfigurationService;
+import org.dspace.services.factory.DSpaceServicesFactory;
import org.jdom2.Attribute;
import org.jdom2.Document;
import org.jdom2.Element;
@@ -76,6 +82,7 @@ public class OREIngestionCrosswalk
.getBitstreamFormatService();
protected BundleService bundleService = ContentServiceFactory.getInstance().getBundleService();
protected ItemService itemService = ContentServiceFactory.getInstance().getItemService();
+ protected ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService();
@Override
@@ -173,9 +180,13 @@ public void ingest(Context context, DSpaceObject dso, Element root, boolean crea
try {
// Make sure the url string escapes all the oddball characters
String processedURL = encodeForURL(href);
- // Generate a requeset for the aggregated resource
- ARurl = new URL(processedURL);
- in = ARurl.openStream();
+ if (validResourceUri(entryId, processedURL)) {
+ // Generate a request for the aggregated resource
+ ARurl = new URL(processedURL);
+ in = ARurl.openStream();
+ } else {
+ throw new FileNotFoundException("Failed to validate " + processedURL);
+ }
} catch (FileNotFoundException fe) {
log.error("The provided URI failed to return a resource: " + href);
} catch (ConnectException fe) {
@@ -219,17 +230,17 @@ public void ingest(Context context, DSpaceObject dso, Element root, boolean crea
* @param sourceString source unescaped string
*/
private String encodeForURL(String sourceString) {
- Character lowalpha[] = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i',
+ Character[] lowalpha = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i',
'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',
's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
- Character upalpha[] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I',
+ Character[] upalpha = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I',
'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R',
'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'};
- Character digit[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
- Character mark[] = {'-', '_', '.', '!', '~', '*', '\'', '(', ')'};
+ Character[] digit = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
+ Character[] mark = {'-', '_', '.', '!', '~', '*', '\'', '(', ')'};
// reserved
- Character reserved[] = {';', '/', '?', ':', '@', '&', '=', '+', '$', ',', '%', '#'};
+ Character[] reserved = {';', '/', '?', ':', '@', '&', '=', '+', '$', ',', '%', '#'};
Set URLcharsSet = new HashSet();
URLcharsSet.addAll(Arrays.asList(lowalpha));
@@ -251,4 +262,61 @@ private String encodeForURL(String sourceString) {
return processedString.toString();
}
+ /**
+ * Validate a resource URI against the host and scheme of the remote OAI endpoint, or a configured
+ * list of allowed prefixes.
+ * This still implicitly "trusts" the remote OAI server, but will reject resource URIs with a totally
+ * different hostname to avoid downloading malicious resources from a compromised endpoint.
+ * Even if the URL prefix validation is disabled, schemes will still be enforced to http(s) so file:/// and
+ * other unwanted schemes cannot be used
+ * @param entryUrl the entryId of the parent ORE resource
+ * @param resourceUrl the resource URL of the aggregated ORE resource
+ * @return result of the validation
+ */
+ private boolean validResourceUri(String entryUrl, String resourceUrl) {
+ try {
+ Set allowedSchemes = Set.of("http", "https");
+ URI entryUri = new URI(entryUrl).normalize();
+ URI resourceUri = new URI(resourceUrl).normalize();
+ String scheme = resourceUri.getScheme();
+
+ if (scheme == null ||
+ !allowedSchemes.contains(scheme.toLowerCase(Locale.ROOT))) {
+ log.warn("Illegal scheme requested for ORE resource: {}", resourceUri);
+ return false;
+ }
+
+ if (configurationService.getBooleanProperty("oai.harvester.ore.file.validateUrlPrefix", false)) {
+ for (String allowedPrefix : configurationService
+ .getArrayProperty("oai.harvester.ore.file.allowedUrlPrefix")) {
+ URI allowedUri = new URI(allowedPrefix).normalize();
+ // Return true on the first allowed prefix match
+ if (Objects.equals(resourceUri.getScheme(), allowedUri.getScheme())
+ && Objects.equals(resourceUri.getHost().toLowerCase(Locale.ROOT),
+ allowedUri.getHost().toLowerCase(Locale.ROOT))) {
+ return true;
+ }
+ }
+
+ // If no allowed prefixes were matched, we require scheme + host to match the remote OAI server
+ if (!Objects.equals(entryUri.getScheme(), resourceUri.getScheme())) {
+ log.warn("Illegal scheme requested for ORE resource: {}", resourceUri);
+ return false;
+ }
+ if (!Objects.equals(
+ entryUri.getHost().toLowerCase(Locale.ROOT),
+ resourceUri.getHost().toLowerCase(Locale.ROOT))) {
+ log.warn("Illegal host requested for ORE resource: {}", resourceUri);
+ return false;
+ }
+ }
+
+ return true;
+
+ } catch (URISyntaxException e) {
+ log.warn("Could not validate ORE resource URI: {}", resourceUrl);
+ return false;
+ }
+ }
+
}
diff --git a/dspace-api/src/main/java/org/dspace/content/crosswalk/PREMISCrosswalk.java b/dspace-api/src/main/java/org/dspace/content/crosswalk/PREMISCrosswalk.java
index 39b6c8f29c80..6b6c0fd7c5a4 100644
--- a/dspace-api/src/main/java/org/dspace/content/crosswalk/PREMISCrosswalk.java
+++ b/dspace-api/src/main/java/org/dspace/content/crosswalk/PREMISCrosswalk.java
@@ -20,9 +20,7 @@
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.Bitstream;
import org.dspace.content.BitstreamFormat;
-import org.dspace.content.Bundle;
import org.dspace.content.DSpaceObject;
-import org.dspace.content.Item;
import org.dspace.content.factory.ContentServiceFactory;
import org.dspace.content.service.BitstreamFormatService;
import org.dspace.content.service.BitstreamService;
@@ -224,29 +222,17 @@ public Element disseminateElement(Context context, DSpaceObject dso)
// c. made-up name based on sequence ID and extension.
String sid = String.valueOf(bitstream.getSequenceID());
String baseUrl = configurationService.getProperty("dspace.ui.url");
- String handle = null;
- // get handle of parent Item of this bitstream, if there is one:
- List bn = bitstream.getBundles();
- if (bn.size() > 0) {
- List bi = bn.get(0).getItems();
- if (bi.size() > 0) {
- handle = bi.get(0).getHandle();
- }
- }
// get or make up name for bitstream:
String bsName = bitstream.getName();
if (bsName == null) {
List ext = bitstream.getFormat(context).getExtensions();
bsName = "bitstream_" + sid + (ext.size() > 0 ? ext.get(0) : "");
}
- if (handle != null && baseUrl != null) {
+ if (baseUrl != null) {
oiv.setText(baseUrl
- + "/bitstream/"
- + URLEncoder.encode(handle, "UTF-8")
- + "/"
- + sid
- + "/"
- + URLEncoder.encode(bsName, "UTF-8"));
+ + "/bitstreams/"
+ + bitstream.getID()
+ + "/download");
} else {
oiv.setText(URLEncoder.encode(bsName, "UTF-8"));
}
diff --git a/dspace-api/src/main/java/org/dspace/content/dao/BundleDAO.java b/dspace-api/src/main/java/org/dspace/content/dao/BundleDAO.java
index da7435d46643..99abd84eabc9 100644
--- a/dspace-api/src/main/java/org/dspace/content/dao/BundleDAO.java
+++ b/dspace-api/src/main/java/org/dspace/content/dao/BundleDAO.java
@@ -22,4 +22,6 @@
*/
public interface BundleDAO extends DSpaceObjectLegacySupportDAO {
int countRows(Context context) throws SQLException;
+
+ int countBitstreams(Context context, Bundle bundle) throws SQLException;
}
diff --git a/dspace-api/src/main/java/org/dspace/content/dao/CollectionDAO.java b/dspace-api/src/main/java/org/dspace/content/dao/CollectionDAO.java
index 6bb65bbb46d8..13bcf5f52c02 100644
--- a/dspace-api/src/main/java/org/dspace/content/dao/CollectionDAO.java
+++ b/dspace-api/src/main/java/org/dspace/content/dao/CollectionDAO.java
@@ -48,6 +48,18 @@ public List findAll(Context context, MetadataField order, Integer li
List findAuthorizedByGroup(Context context, EPerson ePerson, List actions) throws SQLException;
+ /**
+ * Get all authorized collections of the current EPerson
+ *
+ * @param context DSpace context object
+ * @param ePerson the current EPerson
+ * @param actions list of actionsID ADD, READ, etc.
+ * @return the collections the eperson is defined
+ * @throws SQLException if database error
+ */
+ List findAuthorizedByEPerson(Context context, EPerson ePerson, List actions)
+ throws SQLException;
+
List findCollectionsWithSubscribers(Context context) throws SQLException;
int countRows(Context context) throws SQLException;
diff --git a/dspace-api/src/main/java/org/dspace/content/dao/impl/BitstreamDAOImpl.java b/dspace-api/src/main/java/org/dspace/content/dao/impl/BitstreamDAOImpl.java
index 25f102f6def4..66a775e39d80 100644
--- a/dspace-api/src/main/java/org/dspace/content/dao/impl/BitstreamDAOImpl.java
+++ b/dspace-api/src/main/java/org/dspace/content/dao/impl/BitstreamDAOImpl.java
@@ -178,7 +178,7 @@ public int countDeleted(Context context) throws SQLException {
@Override
public int countWithNoPolicy(Context context) throws SQLException {
Query query = createQuery(context,
- "SELECT count(bit.id) from Bitstream bit where bit.deleted<>true and bit.id not in" +
+ "SELECT count(bit.id) from Bitstream bit where bit.deleted<>true and bit not in" +
" (select res.dSpaceObject from ResourcePolicy res where res.resourceTypeId = " +
":typeId )");
query.setParameter("typeId", Constants.BITSTREAM);
diff --git a/dspace-api/src/main/java/org/dspace/content/dao/impl/BundleDAOImpl.java b/dspace-api/src/main/java/org/dspace/content/dao/impl/BundleDAOImpl.java
index 991636108495..ded13687aa7d 100644
--- a/dspace-api/src/main/java/org/dspace/content/dao/impl/BundleDAOImpl.java
+++ b/dspace-api/src/main/java/org/dspace/content/dao/impl/BundleDAOImpl.java
@@ -9,6 +9,7 @@
import java.sql.SQLException;
+import jakarta.persistence.Query;
import org.dspace.content.Bundle;
import org.dspace.content.dao.BundleDAO;
import org.dspace.core.AbstractHibernateDSODAO;
@@ -31,4 +32,13 @@ protected BundleDAOImpl() {
public int countRows(Context context) throws SQLException {
return count(createQuery(context, "SELECT count(*) from Bundle"));
}
+
+ @Override
+ public int countBitstreams(Context context, Bundle bundle) throws SQLException {
+ Query query = createQuery(
+ context, "SELECT count(bi.id) from Bundle bu join bu.bitstreams bi where bu.id = :bundleID"
+ );
+ query.setParameter("bundleID", bundle.getID());
+ return count(query);
+ }
}
diff --git a/dspace-api/src/main/java/org/dspace/content/dao/impl/CollectionDAOImpl.java b/dspace-api/src/main/java/org/dspace/content/dao/impl/CollectionDAOImpl.java
index 841da319f0b2..e47b1ed4a02b 100644
--- a/dspace-api/src/main/java/org/dspace/content/dao/impl/CollectionDAOImpl.java
+++ b/dspace-api/src/main/java/org/dspace/content/dao/impl/CollectionDAOImpl.java
@@ -10,8 +10,13 @@
import java.sql.SQLException;
import java.util.AbstractMap;
import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import java.util.Queue;
+import java.util.Set;
+import java.util.UUID;
import jakarta.persistence.Query;
import jakarta.persistence.criteria.CriteriaBuilder;
@@ -19,6 +24,7 @@
import jakarta.persistence.criteria.Join;
import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Root;
+import org.apache.logging.log4j.Logger;
import org.dspace.authorize.ResourcePolicy;
import org.dspace.authorize.ResourcePolicy_;
import org.dspace.content.Collection;
@@ -40,6 +46,11 @@
* @author kevinvandevelde at atmire.com
*/
public class CollectionDAOImpl extends AbstractHibernateDSODAO implements CollectionDAO {
+ /**
+ * log4j logger
+ */
+ private static Logger log = org.apache.logging.log4j.LogManager.getLogger(CollectionDAOImpl.class);
+
protected CollectionDAOImpl() {
super();
}
@@ -157,9 +168,103 @@ public List findAuthorizedByGroup(Context context, EPerson ePerson,
}
+ /**
+ * Get all authorized collections of the current EPerson
+ *
+ * @param context DSpace context object
+ * @param ePerson the current EPerson
+ * @param actions list of actionsID ADD, READ, etc.
+ * @return the collections the eperson is defined
+ * @throws SQLException if database error
+ */
+ @Override
+ public List findAuthorizedByEPerson(Context context, EPerson ePerson, List actions)
+ throws SQLException {
+
+ //NOTE steps 1) and 2) removes the need of WITH RECURSIVE and a NativeQuery
+
+ // 1) Get all groups a eperson belongs
+ /*ArrayList<>(ePerson.getGroups()) - This ensures you have a concrete copy and can modify it safely.
+ instead if List directGroups = ePerson.getGroups();
+ Also - Can be done using this query:
+ List directGroups = createQuery(context, """
+ SELECT g
+ FROM Group g
+ JOIN g.epeople e
+ WHERE e.id = :epersonId
+ """)
+ .setParameter("epersonId", ePerson.getID())
+ .getResultList();
+ */
+ List directGroups = new ArrayList<>(ePerson.getGroups()); // direct membership
+
+ // 2) Expand hierarquy of groups in memory (recursively)
+ Set allGroups = new HashSet<>(directGroups);
+ Queue queue = new LinkedList<>(directGroups);
+
+ /*
+ * Using the query avoids the change of the getParentGroups visibility in Group
+ * The List parents = current.getParentGroups() could be achieved using:
+ * List parents = createQuery(context,"""
+ SELECT g
+ FROM Group g
+ JOIN g.groups child
+ WHERE child = :child
+ """)
+ */
+ // //current.getMemberGroups()- Making public getParentGroups in Group Class (why it isn't already public?)
+ while (!queue.isEmpty()) {
+ Group current = queue.poll();
+ List parents = current.getParentGroups();
+
+ for (Group parent : parents) {
+ if (allGroups.add(parent)) {
+ queue.add(parent);
+ }
+ }
+ }
+
+ CriteriaBuilder cb = getCriteriaBuilder(context);
+ CriteriaQuery cq = getCriteriaQuery(cb, Collection.class);
+ Root collectionRoot = cq.from(Collection.class);
+
+ // Join to ResourcePolicy using metamodel
+ Join rpJoin = collectionRoot.join("resourcePolicies");
+ // Use metamodel for typesafe access
+ cq.select(collectionRoot).distinct(true);
+
+ List predicates = new ArrayList<>(actions.size());
+ // WHERE rp.resourceTypeId = :resourceType
+ predicates.add(cb.equal(rpJoin.get(ResourcePolicy_.resourceTypeId), Constants.COLLECTION));
+ // AND (:hasActions = false OR rp.actionId IN :actionIds)
+ if (actions != null && !actions.isEmpty()) {
+ predicates.add(rpJoin.get(ResourcePolicy_.actionId).in(actions));
+ }
+
+ // AND (rp.eperson.id = :epersonId OR (:hasGroups = true AND rp.epersonGroup.id IN :groupIds))
+ Predicate epersonPredicate = cb.equal(
+ rpJoin.get(ResourcePolicy_.eperson), ePerson
+ );
+ // Using only groups instead of groupsIDs
+ Predicate groupPredicate = cb.disjunction(); // false by default
+ if (allGroups != null && !allGroups.isEmpty()) {
+ groupPredicate = rpJoin.get(ResourcePolicy_.epersonGroup).in(allGroups);
+ }
+
+ // Combine access condition
+ Predicate accessPredicate = cb.or(epersonPredicate, groupPredicate);
+ predicates.add(accessPredicate);
+
+ // Apply WHERE clause
+ cq.where(cb.and(predicates.toArray(new Predicate[0])));
+
+ // Execute
+ return list(context, cq, true, Collection.class, -1, -1);
+ }
+
@Override
public List findCollectionsWithSubscribers(Context context) throws SQLException {
- return list(createQuery(context, "SELECT DISTINCT c FROM Collection c JOIN Subscription s ON c.id = " +
+ return list(createQuery(context, "SELECT DISTINCT c FROM Collection c JOIN Subscription s ON c = " +
"s.dSpaceObject"));
}
@@ -172,15 +277,26 @@ public int countRows(Context context) throws SQLException {
@SuppressWarnings("unchecked")
public List> getCollectionsWithBitstreamSizesTotal(Context context)
throws SQLException {
- String q = "select col as collection, sum(bit.sizeBytes) as totalBytes from Item i join i.collections col " +
- "join i.bundles bun join bun.bitstreams bit group by col";
+ String q = "select col.id, sum(bit.sizeBytes) as totalBytes from Item i join i.collections col " +
+ "join i.bundles bun join bun.bitstreams bit group by col.id";
Query query = createQuery(context, q);
+ CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context);
+
List