Skip to content

Commit fdf3245

Browse files
authored
Merge pull request DSpace#11739 from bram-atmire/11621-java-21-only
Update minimum Java requirement to JDK 21
2 parents 541fc18 + 6b33049 commit fdf3245

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+729
-371
lines changed

.github/workflows/build.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ jobs:
2424
# NOTE: Unit Tests include a retry for occasionally failing tests
2525
# - surefire.rerunFailingTestsCount => try again for flakey tests, and keep track of/report on number of retries
2626
- type: "Unit Tests"
27-
java: 17
27+
java: 21
2828
mvnflags: "-DskipUnitTests=false -Dsurefire.rerunFailingTestsCount=2"
2929
resultsdir: "**/target/surefire-reports/**"
3030
# NOTE: ITs skip all code validation checks, as they are already done by Unit Test job.
@@ -34,7 +34,7 @@ jobs:
3434
# - xml.skip => Skip all XML/XSLT validation by xml-maven-plugin
3535
# - failsafe.rerunFailingTestsCount => try again for flakey tests, and keep track of/report on number of retries
3636
- type: "Integration Tests"
37-
java: 17
37+
java: 21
3838
mvnflags: "-DskipIntegrationTests=false -Denforcer.skip=true -Dcheckstyle.skip=true -Dlicense.skip=true -Dxml.skip=true -Dfailsafe.rerunFailingTestsCount=2"
3939
resultsdir: "**/target/failsafe-reports/**"
4040
# Do NOT exit immediately if one matrix job fails

.github/workflows/codescan.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ jobs:
4141
- name: Install JDK
4242
uses: actions/setup-java@v4
4343
with:
44-
java-version: 17
44+
java-version: 21
4545
distribution: 'temurin'
4646

4747
# Initializes the CodeQL tools for scanning.

.github/workflows/reusable-docker-build.yml

Lines changed: 60 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -122,8 +122,13 @@ jobs:
122122
password: ${{ secrets.GITHUB_TOKEN }}
123123

124124
# https://github.com/docker/setup-buildx-action
125+
# For PR builds of images that depend on dspace-dependencies (dspace-prod, dspace-test, dspace-cli),
126+
# use the 'docker' driver so buildx can access images loaded into the local Docker daemon.
127+
# For all other builds, use the default 'docker-container' driver.
125128
- name: Setup Docker Buildx
126129
uses: docker/setup-buildx-action@v3
130+
with:
131+
driver: ${{ (matrix.isPr && (inputs.build_id == 'dspace-prod' || inputs.build_id == 'dspace-test' || inputs.build_id == 'dspace-cli')) && 'docker' || 'docker-container' }}
127132

128133
# https://github.com/docker/metadata-action
129134
# Extract metadata used for Docker images in all build steps below
@@ -135,6 +140,29 @@ jobs:
135140
tags: ${{ env.IMAGE_TAGS }}
136141
flavor: ${{ env.TAGS_FLAVOR }}
137142

143+
#--------------------------------------------------------------------
144+
# For PR builds that depend on dspace-dependencies (dspace-prod, dspace-cli,
145+
# dspace-test), download and load the dspace-dependencies image from the
146+
# artifact uploaded by the dspace-dependencies job. This ensures that
147+
# PR builds use the freshly built dependencies image rather than pulling
148+
# an outdated version from the registry.
149+
# NOTE: Only these three build_ids use dspace-dependencies as a base image.
150+
# Other builds (dspace-solr, dspace-postgres-loadsql) have independent Dockerfiles.
151+
#--------------------------------------------------------------------
152+
# https://github.com/actions/download-artifact
153+
- name: Download dspace-dependencies image artifact
154+
if: ${{ matrix.isPr && (inputs.build_id == 'dspace-prod' || inputs.build_id == 'dspace-test' || inputs.build_id == 'dspace-cli') && matrix.arch == 'linux/amd64' }}
155+
uses: actions/download-artifact@v4
156+
with:
157+
name: docker-image-dspace-dependencies-linux-amd64
158+
path: /tmp/docker
159+
160+
- name: Load dspace-dependencies image into Docker
161+
if: ${{ matrix.isPr && (inputs.build_id == 'dspace-prod' || inputs.build_id == 'dspace-test' || inputs.build_id == 'dspace-cli') && matrix.arch == 'linux/amd64' }}
162+
run: |
163+
docker image load --input /tmp/docker/dspace-dependencies.tar
164+
docker image ls | grep dspace-dependencies || true
165+
138166
#--------------------------------------------------------------------
139167
# First, for all branch commits (non-PRs) we build the image & upload
140168
# to GitHub Container Registry (GHCR). After uploading the image
@@ -199,27 +227,53 @@ jobs:
199227
# Build local image (again) and store in a TAR file in /tmp directory
200228
# This step is only done for AMD64, as that's the only image we use in our automated testing (at this time).
201229
# NOTE: This step cannot be combined with the build above as it's a different type of output.
202-
- name: Build and push image to local TAR file
203-
if: ${{ matrix.arch == 'linux/amd64'}}
230+
# For non-PR builds OR builds that don't depend on dspace-dependencies, use docker-container driver
231+
# with direct tarball export. This includes: dspace-dependencies, dspace-solr, dspace-postgres-loadsql
232+
- name: Build and push image to local TAR file (docker-container driver)
233+
if: ${{ matrix.arch == 'linux/amd64' && (!matrix.isPr || (inputs.build_id != 'dspace-prod' && inputs.build_id != 'dspace-test' && inputs.build_id != 'dspace-cli')) }}
204234
uses: docker/build-push-action@v5
205235
with:
206236
build-contexts: |
207237
${{ inputs.dockerfile_additional_contexts }}
208238
context: ${{ inputs.dockerfile_context }}
209239
file: ${{ inputs.dockerfile_path }}
210-
# Tell DSpace's Docker files to use the build registry instead of DockerHub
240+
# Use GHCR as build registry to avoid DockerHub rate limits
211241
build-args:
212242
DOCKER_REGISTRY=${{ env.DOCKER_BUILD_REGISTRY }}
213243
platforms: ${{ matrix.arch }}
214244
tags: ${{ steps.meta_build.outputs.tags }}
215245
labels: ${{ steps.meta_build.outputs.labels }}
216-
# Use GitHub cache to load cached Docker images and cache the results of this build
217-
# This decreases the number of images we need to fetch from DockerHub
246+
# Cache Docker layers in GitHub Actions cache to speed up builds
247+
# and reduce pulls from DockerHub
218248
cache-from: type=gha,scope=${{ inputs.build_id }}
219249
cache-to: type=gha,scope=${{ inputs.build_id }},mode=min
220-
# Export image to a local TAR file
250+
# Export to TAR for use in automated testing jobs
221251
outputs: type=docker,dest=/tmp/${{ inputs.build_id }}.tar
222252

253+
# For PR builds of images that depend on dspace-dependencies (dspace-prod, dspace-cli, dspace-test),
254+
# use docker driver which can access the locally loaded dspace-dependencies image.
255+
# The docker driver doesn't support tarball export, so we use 'load: true' and then 'docker save'.
256+
- name: Build image (docker driver for local dependencies access)
257+
if: ${{ matrix.arch == 'linux/amd64' && matrix.isPr && (inputs.build_id == 'dspace-prod' || inputs.build_id == 'dspace-test' || inputs.build_id == 'dspace-cli') }}
258+
uses: docker/build-push-action@v5
259+
with:
260+
build-contexts: |
261+
${{ inputs.dockerfile_additional_contexts }}
262+
context: ${{ inputs.dockerfile_context }}
263+
file: ${{ inputs.dockerfile_path }}
264+
# Use GHCR as build registry to avoid DockerHub rate limits
265+
build-args:
266+
DOCKER_REGISTRY=${{ env.DOCKER_BUILD_REGISTRY }}
267+
tags: ${{ steps.meta_build.outputs.tags }}
268+
labels: ${{ steps.meta_build.outputs.labels }}
269+
# Load into local Docker daemon (docker driver doesn't support direct TAR export)
270+
load: true
271+
272+
- name: Save image to TAR file (for docker driver builds)
273+
if: ${{ matrix.arch == 'linux/amd64' && matrix.isPr && (inputs.build_id == 'dspace-prod' || inputs.build_id == 'dspace-test' || inputs.build_id == 'dspace-cli') }}
274+
run: |
275+
docker save -o /tmp/${{ inputs.build_id }}.tar ${{ steps.meta_build.outputs.tags }}
276+
223277
# Upload the local docker image (in TAR file) to a build Artifact
224278
# This step is only done for AMD64, as that's the only image we use in our automated testing (at this time).
225279
- name: Upload local image TAR to artifact

Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
#
44
# - note: default tag for branch: dspace/dspace: dspace/dspace:latest
55

6-
# This Dockerfile uses JDK17 by default.
6+
# This Dockerfile uses JDK21 by default.
77
# To build with other versions, use "--build-arg JDK_VERSION=[value]"
8-
ARG JDK_VERSION=17
8+
ARG JDK_VERSION=21
99
# The Docker version tag to build from
1010
ARG DSPACE_VERSION=latest
1111
# The Docker registry to use for DSpace images. Defaults to "docker.io"

Dockerfile.cli

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
#
44
# - note: default tag for branch: dspace/dspace-cli: dspace/dspace-cli:latest
55

6-
# This Dockerfile uses JDK17 by default.
6+
# This Dockerfile uses JDK21 by default.
77
# To build with other versions, use "--build-arg JDK_VERSION=[value]"
8-
ARG JDK_VERSION=17
8+
ARG JDK_VERSION=21
99
# The Docker version tag to build from
1010
ARG DSPACE_VERSION=latest
1111
# The Docker registry to use for DSpace images. Defaults to "docker.io"

Dockerfile.dependencies

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
# The purpose of this image is to make the build for dspace/dspace run faster
33
#
44

5-
# This Dockerfile uses JDK17 by default.
5+
# This Dockerfile uses JDK21 by default.
66
# To build with other versions, use "--build-arg JDK_VERSION=[value]"
7-
ARG JDK_VERSION=17
7+
ARG JDK_VERSION=21
88

99
# Step 1 - Download all Dependencies
1010
FROM docker.io/maven:3-eclipse-temurin-${JDK_VERSION} AS build

Dockerfile.test

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55
#
66
# This image is meant for TESTING/DEVELOPMENT ONLY as it deploys the old v6 REST API under HTTP (not HTTPS)
77

8-
# This Dockerfile uses JDK17 by default.
8+
# This Dockerfile uses JDK21 by default.
99
# To build with other versions, use "--build-arg JDK_VERSION=[value]"
10-
ARG JDK_VERSION=17
10+
ARG JDK_VERSION=21
1111
# The Docker version tag to build from
1212
ARG DSPACE_VERSION=latest
1313
# The Docker registry to use for DSpace images. Defaults to "docker.io"

dspace-api/pom.xml

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -528,7 +528,7 @@
528528
</dependency>
529529
<dependency>
530530
<groupId>org.mockito</groupId>
531-
<artifactId>mockito-inline</artifactId>
531+
<artifactId>mockito-core</artifactId>
532532
<scope>test</scope>
533533
</dependency>
534534
<dependency>
@@ -769,11 +769,13 @@
769769
<version>20231013</version>
770770
</dependency>
771771

772-
<!-- Useful for testing command-line tools -->
772+
<!-- Useful for testing command-line tools that call System.exit() -->
773+
<!-- Note: system-lambda replaces system-rules for Java 21 compatibility -->
774+
<!-- (system-rules uses SecurityManager which is removed in Java 21) -->
773775
<dependency>
774776
<groupId>com.github.stefanbirkner</groupId>
775-
<artifactId>system-rules</artifactId>
776-
<version>1.19.0</version>
777+
<artifactId>system-lambda</artifactId>
778+
<version>1.2.1</version>
777779
<scope>test</scope>
778780
</dependency>
779781

dspace-api/src/main/java/org/dspace/administer/CreateAdministrator.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ protected void negotiateAdministratorDetails(CommandLine line)
191191

192192
if (language != null) {
193193
language = language.trim();
194-
language = I18nUtil.getSupportedLocale(new Locale(language)).getLanguage();
194+
language = I18nUtil.getSupportedLocale(Locale.of(language)).getLanguage();
195195
}
196196
}
197197

dspace-api/src/main/java/org/dspace/app/packager/Packager.java

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -126,14 +126,39 @@ public class Packager {
126126
protected boolean submit = true;
127127
protected boolean userInteractionEnabled = true;
128128

129-
// die from illegal command line
129+
/**
130+
* Signal a usage error and exit.
131+
* Throws PackagerExitException instead of calling System.exit() directly
132+
* to allow for proper testing when called via reflection.
133+
*
134+
* @param msg the error message to display
135+
*/
130136
protected static void usageError(String msg) {
131137
System.out.println(msg);
132138
System.out.println(" (run with -h flag for details)");
133-
System.exit(1);
139+
throw new PackagerExitException(1, msg);
134140
}
135141

142+
/**
143+
* Main entry point. When called via ScriptLauncher, exceptions will be
144+
* caught and handled by the launcher. For success, the method returns normally.
145+
* For errors, PackagerExitException is thrown and propagates up.
146+
*
147+
* @param argv command line arguments
148+
* @throws Exception if an error occurs
149+
*/
136150
public static void main(String[] argv) throws Exception {
151+
runPackager(argv);
152+
}
153+
154+
/**
155+
* Main packager logic, separated from main() to allow testing.
156+
* Throws PackagerExitException instead of calling System.exit() directly.
157+
*
158+
* @param argv command line arguments
159+
* @throws Exception if an error occurs
160+
*/
161+
protected static void runPackager(String[] argv) throws Exception {
137162
Options options = new Options();
138163
options.addOption("p", "parent", true,
139164
"Handle(s) of parent Community or Collection into which to ingest object (repeatable)");
@@ -236,7 +261,7 @@ public static void main(String[] argv) throws Exception {
236261
System.out.println(" " + pn[i]);
237262
}
238263
}
239-
System.exit(0);
264+
return;
240265
}
241266

242267
//look for flag to disable all user interaction
@@ -303,7 +328,7 @@ public static void main(String[] argv) throws Exception {
303328
System.err.println("Error - missing a REQUIRED argument or option.\n");
304329
HelpFormatter myhelp = new HelpFormatter();
305330
myhelp.printHelp("PackageManager [options] package-file|-\n", options);
306-
System.exit(0);
331+
return;
307332
}
308333

309334
// find the EPerson, assign to context
@@ -362,13 +387,16 @@ public static void main(String[] argv) throws Exception {
362387

363388
//commit all changes & exit successfully
364389
context.complete();
365-
System.exit(0);
390+
return;
391+
} catch (PackagerExitException e) {
392+
// Re-throw PackagerExitException as-is
393+
throw e;
366394
} catch (Exception e) {
367395
// abort all operations
368396
e.printStackTrace();
369397
context.abort();
370398
System.out.println(e);
371-
System.exit(1);
399+
throw new PackagerExitException(1);
372400
}
373401
}
374402

@@ -409,13 +437,16 @@ public static void main(String[] argv) throws Exception {
409437

410438
//commit all changes & exit successfully
411439
context.complete();
412-
System.exit(0);
440+
return;
441+
} catch (PackagerExitException e) {
442+
// Re-throw PackagerExitException as-is
443+
throw e;
413444
} catch (Exception e) {
414445
// abort all operations
415446
e.printStackTrace();
416447
context.abort();
417448
System.out.println(e);
418-
System.exit(1);
449+
throw new PackagerExitException(1);
419450
}
420451
} else {
421452
// else, if DISSEMINATE mode
@@ -438,7 +469,7 @@ public static void main(String[] argv) throws Exception {
438469
//disseminate the requested object
439470
myPackager.disseminate(context, dip, dso, pkgParams, sourceFile);
440471
}
441-
System.exit(0);
472+
return;
442473
}
443474

444475
/**
@@ -469,7 +500,7 @@ protected void ingest(Context context, PackageIngester sip, PackageParameters pk
469500

470501
if (!pkgFile.exists()) {
471502
System.out.println("\nERROR: Package located at " + sourceFile + " does not exist!");
472-
System.exit(1);
503+
throw new PackagerExitException(1, "Package file does not exist: " + sourceFile);
473504
}
474505

475506
System.out.println("\nIngesting package located at " + sourceFile);
@@ -665,7 +696,7 @@ protected void replace(Context context, PackageIngester sip, PackageParameters p
665696

666697
if (!pkgFile.exists()) {
667698
System.out.println("\nPackage located at " + sourceFile + " does not exist!");
668-
System.exit(1);
699+
throw new PackagerExitException(1, "Package file does not exist: " + sourceFile);
669700
}
670701

671702
System.out.println("\nReplacing DSpace object(s) with package located at " + sourceFile);

0 commit comments

Comments
 (0)