Compare commits

...

19 Commits

Author SHA1 Message Date
dependabot[bot]
2a4ac311c2 Bump com.amazonaws:aws-java-sdk-s3 from 1.12.791 to 1.12.792
Bumps [com.amazonaws:aws-java-sdk-s3](https://github.com/aws/aws-sdk-java) from 1.12.791 to 1.12.792.
- [Changelog](https://github.com/aws/aws-sdk-java/blob/master/CHANGELOG.md)
- [Commits](https://github.com/aws/aws-sdk-java/compare/1.12.791...1.12.792)

---
updated-dependencies:
- dependency-name: com.amazonaws:aws-java-sdk-s3
  dependency-version: 1.12.792
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-06 20:00:09 +00:00
Tim Donohue
5723ea1297 Merge pull request #11381 from DSpace/dependabot/maven/dspace-7_x/apache-commons-646c41dc72
Bump org.apache.commons:commons-lang3 from 3.18.0 to 3.19.0 in the apache-commons group
2025-10-06 13:33:10 -05:00
Tim Donohue
fa2a639615 Merge pull request #11380 from DSpace/dependabot/maven/dspace-7_x/test-tools-9b413dd7df
Bump the test-tools group with 2 updates
2025-10-06 13:22:57 -05:00
Tim Donohue
a6699f3c43 Merge pull request #11378 from DSpace/dependabot/maven/dspace-7_x/build-tools-4e1d998f9d
Bump the build-tools group with 6 updates
2025-10-06 13:18:24 -05:00
Tim Donohue
5fe6f9f1d0 Merge pull request #11385 from DSpace/dependabot/maven/dspace-7_x/log4j.version-2.25.2
Bump log4j.version from 2.25.1 to 2.25.2
2025-10-06 13:15:27 -05:00
Tim Donohue
97782fae6e Merge pull request #10534 from 4Science/task/dspace-7_x/DURACOM-243
[dspace-7_x] Handles Rotation EXIF Metadata inside JPEGFilter
2025-10-02 14:37:59 -05:00
Tim Donohue
8fc191e9cb Merge pull request #11403 from tdonohue/port_11321_to_7x
[Port dspace-7_x] DS-8943: adds limit to number of typed links to bitstreams in header
2025-10-02 08:48:10 -05:00
nwoodward
b5501981ab added limit to number of typed links to bitstreams to display, falls back to Link Sets if limit is exceeded 2025-10-01 17:15:14 -05:00
Tim Donohue
61f2695b83 Merge pull request #11396 from TexasDigitalLibrary/port_11329_to_7x
[Port dspace-7_x] Fix Hibernate syntax bugs in the CollectionDAO and BitstreamDAO
2025-10-01 16:44:36 -05:00
Tim Donohue
8643888d68 Merge pull request #11399 from tdonohue/port_11139_to_7x
[Port dspace-7_x] fix(#10721): Sanitize non-characters during OAI indexing
2025-10-01 16:39:31 -05:00
JohnnyMendesC
d8fbe16ede fix(#10721): Sanitize non-characters during OAI indexing (#11139)
* fix(#10721): Sanitize non-characters during OAI indexing

* refactor: Use StringEscapeUtils as suggested in review

* fix: Removed whitespace before the import that was causing error

Maven Unit Test failed due to the whitespace before the import
https://github.com/DSpace/DSpace/actions/runs/16891881837/job/47853392956?pr=11139#step:4:1959

* fix: Removed trailing whitespace that was causing error
2025-10-01 15:28:11 -05:00
nwoodward
29e13b77fc checkstyle fix 2025-10-01 13:46:40 -05:00
nwoodward
fc74a7ffdf fix Hibernate bugs 2025-10-01 13:41:41 -05:00
dependabot[bot]
dd6b3b4a55 Bump log4j.version from 2.25.1 to 2.25.2
Bumps `log4j.version` from 2.25.1 to 2.25.2.

Updates `org.apache.logging.log4j:log4j-api` from 2.25.1 to 2.25.2

Updates `org.apache.logging.log4j:log4j-core` from 2.25.1 to 2.25.2

Updates `org.apache.logging.log4j:log4j-1.2-api` from 2.25.1 to 2.25.2

---
updated-dependencies:
- dependency-name: org.apache.logging.log4j:log4j-api
  dependency-version: 2.25.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.apache.logging.log4j:log4j-core
  dependency-version: 2.25.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.apache.logging.log4j:log4j-1.2-api
  dependency-version: 2.25.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-01 02:36:10 +00:00
dependabot[bot]
fa9df3a4ca Bump org.apache.commons:commons-lang3 in the apache-commons group
Bumps the apache-commons group with 1 update: org.apache.commons:commons-lang3.


Updates `org.apache.commons:commons-lang3` from 3.18.0 to 3.19.0

---
updated-dependencies:
- dependency-name: org.apache.commons:commons-lang3
  dependency-version: 3.19.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: apache-commons
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-01 02:34:22 +00:00
dependabot[bot]
5bc41cb8ab Bump the test-tools group with 2 updates
Bumps the test-tools group with 2 updates: [com.h2database:h2](https://github.com/h2database/h2database) and [org.xmlunit:xmlunit-core](https://github.com/xmlunit/xmlunit).


Updates `com.h2database:h2` from 2.3.232 to 2.4.240
- [Release notes](https://github.com/h2database/h2database/releases)
- [Commits](https://github.com/h2database/h2database/compare/version-2.3.232...version-2.4.240)

Updates `org.xmlunit:xmlunit-core` from 2.10.3 to 2.10.4
- [Release notes](https://github.com/xmlunit/xmlunit/releases)
- [Changelog](https://github.com/xmlunit/xmlunit/blob/main/RELEASE_NOTES.md)
- [Commits](https://github.com/xmlunit/xmlunit/compare/v2.10.3...v2.10.4)

---
updated-dependencies:
- dependency-name: com.h2database:h2
  dependency-version: 2.4.240
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: test-tools
- dependency-name: org.xmlunit:xmlunit-core
  dependency-version: 2.10.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: test-tools
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-01 02:34:10 +00:00
dependabot[bot]
ccd3d12ad8 Bump the build-tools group with 6 updates
Bumps the build-tools group with 6 updates:

| Package | From | To |
| --- | --- | --- |
| [com.github.spotbugs:spotbugs](https://github.com/spotbugs/spotbugs) | `4.9.4` | `4.9.6` |
| [org.apache.maven.plugins:maven-compiler-plugin](https://github.com/apache/maven-compiler-plugin) | `3.14.0` | `3.14.1` |
| [com.github.spotbugs:spotbugs-maven-plugin](https://github.com/spotbugs/spotbugs-maven-plugin) | `4.9.4.2` | `4.9.6.0` |
| [org.sonatype.central:central-publishing-maven-plugin](https://github.com/sonatype/central-publishing-maven-plugin) | `0.8.0` | `0.9.0` |
| [org.apache.maven.plugins:maven-javadoc-plugin](https://github.com/apache/maven-javadoc-plugin) | `3.11.3` | `3.12.0` |
| [org.codehaus.mojo:license-maven-plugin](https://github.com/mojohaus/license-maven-plugin) | `2.6.0` | `2.7.0` |


Updates `com.github.spotbugs:spotbugs` from 4.9.4 to 4.9.6
- [Release notes](https://github.com/spotbugs/spotbugs/releases)
- [Changelog](https://github.com/spotbugs/spotbugs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/spotbugs/spotbugs/compare/4.9.4...4.9.6)

Updates `org.apache.maven.plugins:maven-compiler-plugin` from 3.14.0 to 3.14.1
- [Release notes](https://github.com/apache/maven-compiler-plugin/releases)
- [Commits](https://github.com/apache/maven-compiler-plugin/compare/maven-compiler-plugin-3.14.0...maven-compiler-plugin-3.14.1)

Updates `com.github.spotbugs:spotbugs-maven-plugin` from 4.9.4.2 to 4.9.6.0
- [Release notes](https://github.com/spotbugs/spotbugs-maven-plugin/releases)
- [Commits](https://github.com/spotbugs/spotbugs-maven-plugin/compare/spotbugs-maven-plugin-4.9.4.2...spotbugs-maven-plugin-4.9.6.0)

Updates `org.sonatype.central:central-publishing-maven-plugin` from 0.8.0 to 0.9.0
- [Commits](https://github.com/sonatype/central-publishing-maven-plugin/commits)

Updates `org.apache.maven.plugins:maven-javadoc-plugin` from 3.11.3 to 3.12.0
- [Release notes](https://github.com/apache/maven-javadoc-plugin/releases)
- [Commits](https://github.com/apache/maven-javadoc-plugin/compare/maven-javadoc-plugin-3.11.3...maven-javadoc-plugin-3.12.0)

Updates `org.codehaus.mojo:license-maven-plugin` from 2.6.0 to 2.7.0
- [Release notes](https://github.com/mojohaus/license-maven-plugin/releases)
- [Commits](https://github.com/mojohaus/license-maven-plugin/compare/2.6.0...2.7.0)

---
updated-dependencies:
- dependency-name: com.github.spotbugs:spotbugs
  dependency-version: 4.9.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: build-tools
- dependency-name: org.apache.maven.plugins:maven-compiler-plugin
  dependency-version: 3.14.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: build-tools
- dependency-name: com.github.spotbugs:spotbugs-maven-plugin
  dependency-version: 4.9.6.0
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: build-tools
- dependency-name: org.sonatype.central:central-publishing-maven-plugin
  dependency-version: 0.9.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: build-tools
- dependency-name: org.apache.maven.plugins:maven-javadoc-plugin
  dependency-version: 3.12.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: build-tools
- dependency-name: org.codehaus.mojo:license-maven-plugin
  dependency-version: 2.7.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: build-tools
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-01 02:33:13 +00:00
Vincenzo Mecca
82d04061c0 [DURACOM-243] Adds Test for JPEGFilter 2025-03-27 12:48:50 +01:00
Vincenzo Mecca
08e330c1c0 [DURACOM-243] Adds rotation handling inside JPEGFilter
Conflicts:
2025-03-27 12:48:00 +01:00
15 changed files with 650 additions and 93 deletions

View File

@@ -738,7 +738,7 @@
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-s3</artifactId>
<version>1.12.791</version>
<version>1.12.792</version>
</dependency>
<dependency>
@@ -905,7 +905,7 @@
<dependency>
<groupId>org.xmlunit</groupId>
<artifactId>xmlunit-core</artifactId>
<version>2.10.3</version>
<version>2.10.4</version>
<scope>test</scope>
</dependency>
<dependency>

View File

@@ -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 class BrandedPreviewJPEGFilter extends MediaFilter {
@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
);
}
}

View File

@@ -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 @@ import org.dspace.services.factory.DSpaceServicesFactory;
* @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 class JPEGFilter extends MediaFilter implements SelfRegisterInputFormats
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 class JPEGFilter extends MediaFilter implements SelfRegisterInputFormats
@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 class JPEGFilter extends MediaFilter implements SelfRegisterInputFormats
// 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 class JPEGFilter extends MediaFilter implements SelfRegisterInputFormats
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);
}
ByteArrayInputStream bais;
// 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());
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!
}

View File

@@ -81,6 +81,7 @@ public class PDFBoxThumbnail extends MediaFilter {
// Generate thumbnail derivative and return as IO stream.
JPEGFilter jpegFilter = new JPEGFilter();
return jpegFilter.getThumb(currentItem, buf, verbose);
}
}

View File

@@ -152,7 +152,7 @@ public class BitstreamDAOImpl extends AbstractHibernateDSODAO<Bitstream> impleme
@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);

View File

@@ -12,6 +12,7 @@ import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import javax.persistence.Query;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
@@ -19,6 +20,7 @@ import javax.persistence.criteria.Join;
import javax.persistence.criteria.Predicate;
import javax.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 +42,11 @@ import org.dspace.eperson.Group;
* @author kevinvandevelde at atmire.com
*/
public class CollectionDAOImpl extends AbstractHibernateDSODAO<Collection> implements CollectionDAO {
/**
* log4j logger
*/
private static Logger log = org.apache.logging.log4j.LogManager.getLogger(CollectionDAOImpl.class);
protected CollectionDAOImpl() {
super();
}
@@ -172,14 +179,25 @@ public class CollectionDAOImpl extends AbstractHibernateDSODAO<Collection> imple
@SuppressWarnings("unchecked")
public List<Map.Entry<Collection, Long>> 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<Object[]> list = query.getResultList();
List<Map.Entry<Collection, Long>> returnList = new ArrayList<>(list.size());
for (Object[] o : list) {
returnList.add(new AbstractMap.SimpleEntry<>((Collection) o[0], (Long) o[1]));
CriteriaQuery<Collection> criteriaQuery = criteriaBuilder.createQuery(Collection.class);
Root<Collection> collectionRoot = criteriaQuery.from(Collection.class);
criteriaQuery.select(collectionRoot).where(criteriaBuilder.equal(collectionRoot.get("id"), (UUID) o[0]));
Query collectionQuery = createQuery(context, criteriaQuery);
Collection collection = (Collection) collectionQuery.getSingleResult();
if (collection != null) {
returnList.add(new AbstractMap.SimpleEntry<>(collection, (Long) o[1]));
} else {
log.warn("Unable to find Collection with UUID: {}", o[0]);
}
}
return returnList;
}

View File

@@ -464,4 +464,14 @@ public abstract class AbstractHibernateDAO<T> implements GenericDAO<T> {
return executeCriteriaQuery(context, criteria, cacheable, maxResults, offset);
}
/**
* Create a Query object from a CriteriaQuery
* @param context current Context
* @param criteriaQuery CriteriaQuery built via CriteriaBuilder
* @return corresponding Query
* @throws SQLException if error occurs
*/
public Query createQuery(Context context, CriteriaQuery criteriaQuery) throws SQLException {
return this.getHibernateSession(context).createQuery(criteriaQuery);
}
}

View File

@@ -0,0 +1,270 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.mediafilter;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertSame;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import org.dspace.AbstractUnitTest;
import org.dspace.content.Item;
import org.dspace.services.ConfigurationService;
import org.dspace.services.factory.DSpaceServicesFactory;
import org.junit.Test;
import org.mockito.Mock;
public class JPEGFilterTest extends AbstractUnitTest {
@Mock
private ConfigurationService mockConfigurationService;
@Mock
private DSpaceServicesFactory mockDSpaceServicesFactory;
@Mock
private InputStream mockInputStream;
@Mock
private Item mockItem;
/**
* Tests that the convertRotationToDegrees method returns 0 for an input value
* that doesn't match any of the defined rotation cases.
*/
@Test
public void testConvertRotationToDegrees_UnknownValue_ReturnsZero() {
int result = JPEGFilter.convertRotationToDegrees(5);
assertEquals(0, result);
}
/**
* Test getNormalizedInstance method with a null input.
* This tests the edge case of passing a null BufferedImage to the method.
* The method should throw a NullPointerException when given a null input.
*/
@Test(expected = NullPointerException.class)
public void testGetNormalizedInstanceWithNullInput() {
JPEGFilter filter = new JPEGFilter();
filter.getNormalizedInstance(null);
}
/**
* Test getThumbDim method with a null BufferedImage input.
* This tests the edge case where the input image is null, which should result in an exception.
*/
@Test(expected = NullPointerException.class)
public void testGetThumbDimWithNullBufferedImage() throws Exception {
JPEGFilter filter = new JPEGFilter();
Item currentItem = null;
BufferedImage buf = null;
boolean verbose = false;
int xmax = 100;
int ymax = 100;
boolean blurring = false;
boolean hqscaling = false;
int brandHeight = 0;
int brandFontPoint = 0;
int rotation = 0;
String brandFont = null;
filter.getThumbDim(
currentItem, buf, verbose, xmax, ymax, blurring, hqscaling,
brandHeight, brandFontPoint, rotation, brandFont
);
}
/**
* Tests that the rotateImage method returns the original image when the rotation angle is 0.
* This is an edge case explicitly handled in the method implementation.
*/
@Test
public void testRotateImageWithZeroAngle() {
BufferedImage originalImage = new BufferedImage(100, 100, BufferedImage.TYPE_INT_RGB);
BufferedImage rotatedImage = JPEGFilter.rotateImage(originalImage, 0);
assertSame(
"When rotation angle is 0, the original image should be returned",
originalImage, rotatedImage
);
}
/**
* Test case for convertRotationToDegrees method when input is 6.
* Expected to return 90 degrees for the rotation value of 6.
*/
@Test
public void test_convertRotationToDegrees_whenInputIs6_returns90() {
int input = 6;
int expected = 90;
int result = JPEGFilter.convertRotationToDegrees(input);
assertEquals(expected, result);
}
/**
* Tests that getBlurredInstance method applies a blur effect to the input image.
* It verifies that the returned image is not null, has the same dimensions as the input,
* and is different from the original image (indicating that blurring has occurred).
*/
@Test
public void test_getBlurredInstance_appliesBlurEffect() {
JPEGFilter filter = new JPEGFilter();
BufferedImage original = new BufferedImage(100, 100, BufferedImage.TYPE_INT_RGB);
BufferedImage blurred = filter.getBlurredInstance(original);
assertNotNull("Blurred image should not be null", blurred);
assertEquals("Width should be the same", original.getWidth(), blurred.getWidth());
assertEquals("Height should be the same", original.getHeight(), blurred.getHeight());
assertNotEquals("Blurred image should be different from original", original, blurred);
}
/**
* Test case for getBundleName method of JPEGFilter class.
* This test verifies that the getBundleName method returns the expected string "THUMBNAIL".
*/
@Test
public void test_getBundleName_returnsExpectedString() {
JPEGFilter filter = new JPEGFilter();
String result = filter.getBundleName();
assertEquals("THUMBNAIL", result);
}
/**
* Tests that the getDescription method returns the expected string "Generated Thumbnail".
* This verifies that the method correctly provides the description for the JPEG filter.
*/
@Test
public void test_getDescription_1() {
JPEGFilter filter = new JPEGFilter();
String description = filter.getDescription();
assertEquals("Generated Thumbnail", description);
}
/**
* Tests that getFilteredName method appends ".jpg" to the input filename.
*/
@Test
public void test_getFilteredName_appendsJpgExtension() {
JPEGFilter filter = new JPEGFilter();
String oldFilename = "testimage";
String expectedResult = "testimage.jpg";
String actualResult = filter.getFilteredName(oldFilename);
assertEquals(expectedResult, actualResult);
}
/**
* Test case for getFormatString method of JPEGFilter class.
* Verifies that the method returns the expected string "JPEG".
*/
@Test
public void test_getFormatString_returnsJPEG() {
JPEGFilter filter = new JPEGFilter();
String result = filter.getFormatString();
assertEquals("JPEG", result);
}
/**
* Tests the behavior of getImageRotationUsingImageReader when an ImageProcessingException occurs.
* This test verifies that the method handles an ImageProcessingException by logging the error
* and returning 0 degrees rotation.
*/
@Test
public void test_getImageRotationUsingImageReader_imageProcessingException() {
InputStream errorStream = new InputStream() {
@Override
public int read() throws IOException {
throw new IOException("Simulated image processing error");
}
};
int result = JPEGFilter.getImageRotationUsingImageReader(errorStream);
assertEquals(0, result);
}
/**
* Testcase for getImageRotationUsingImageReader when the image doesn't contain orientation metadata.
* This test verifies that the method returns 0 when there's no ExifIFD0Directory
* or when it doesn't contain the TAG_ORIENTATION.
*/
@Test
public void test_getImageRotationUsingImageReader_noOrientationMetadata() throws IOException {
URL resource = this.getClass().getResource("cat.jpg");
int rotationAngle = -1;
try (InputStream inputStream = new FileInputStream(resource.getFile())) {
// Call the method under test
rotationAngle = JPEGFilter.getImageRotationUsingImageReader(inputStream);
}
assertEquals(0, rotationAngle);
}
/**
* Tests the getImageRotationUsingImageReader method when the image contains
* valid EXIF orientation metadata.
*
* This test verifies that the method correctly reads the orientation tag
* from the EXIF metadata and returns the appropriate rotation angle in degrees.
*/
@Test
public void test_getImageRotationUsingImageReader_withValidExifOrientation() throws Exception {
// Create a mock InputStream with EXIF metadata containing orientation information
URL resource = this.getClass().getResource("cat-rotated-90.jpg");
int rotationAngle = -1;
try (InputStream inputStream = new FileInputStream(resource.getFile())) {
// Call the method under test
rotationAngle = JPEGFilter.getImageRotationUsingImageReader(inputStream);
}
// Assert the expected rotation angle
// Note: The expected value should be adjusted based on the mock data
assertEquals(90, rotationAngle);
}
/**
* Tests the getScaledInstance method of JPEGFilter class with higher quality scaling.
* This test verifies that the method correctly scales down an image in multiple passes
* when higherQuality is true and the image dimensions are larger than the target dimensions.
*/
@Test
public void test_getScaledInstance() {
JPEGFilter filter = new JPEGFilter();
BufferedImage originalImage = new BufferedImage(400, 300, BufferedImage.TYPE_INT_RGB);
int targetWidth = 100;
int targetHeight = 75;
Object hint = RenderingHints.VALUE_INTERPOLATION_BILINEAR;
boolean higherQuality = true;
BufferedImage result = filter.getScaledInstance(originalImage, targetWidth, targetHeight, hint, higherQuality);
assertNotNull(result);
assertEquals(targetWidth, result.getWidth());
assertEquals(targetHeight, result.getHeight());
}
/**
* Tests the rotateImage method with a non-zero angle.
* This test verifies that the image is rotated correctly when given a non-zero angle.
*/
@Test
public void test_rotateImage_nonZeroAngle() {
BufferedImage originalImage = new BufferedImage(100, 50, BufferedImage.TYPE_INT_RGB);
int angle = 90;
BufferedImage rotatedImage = JPEGFilter.rotateImage(originalImage, angle);
assertNotNull(rotatedImage);
assertEquals(50, rotatedImage.getWidth());
assertEquals(100, rotatedImage.getHeight());
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

View File

@@ -16,6 +16,7 @@ import java.util.List;
import com.lyncode.xoai.dataprovider.xml.xoai.Element;
import com.lyncode.xoai.dataprovider.xml.xoai.Metadata;
import com.lyncode.xoai.util.Base64Utils;
import org.apache.commons.text.StringEscapeUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dspace.app.util.factory.UtilServiceFactory;
@@ -159,6 +160,19 @@ public class ItemUtils {
return bundles;
}
/**
* Sanitizes a string to remove characters that are invalid
* in XML 1.0 using the Apache Commons Text library.
* @param value The string to sanitize.
* @return A sanitized string, or null if the input was null.
*/
private static String sanitize(String value) {
if (value == null) {
return null;
}
return StringEscapeUtils.escapeXml10(value);
}
private static Element createLicenseElement(Context context, Item item)
throws SQLException, AuthorizeException, IOException {
Element license = create("license");
@@ -232,7 +246,7 @@ public class ItemUtils {
valueElem = language;
}
valueElem.getField().add(createValue("value", val.getValue()));
valueElem.getField().add(createValue("value", sanitize(val.getValue())));
if (val.getAuthority() != null) {
valueElem.getField().add(createValue("authority", val.getAuthority()));
if (val.getConfidence() != Choices.CF_NOVALUE) {

View File

@@ -18,6 +18,7 @@ import org.apache.logging.log4j.Logger;
import org.dspace.app.rest.security.BitstreamMetadataReadPermissionEvaluatorPlugin;
import org.dspace.app.rest.signposting.model.LinksetNode;
import org.dspace.app.rest.signposting.processor.bitstream.BitstreamSignpostingProcessor;
import org.dspace.app.rest.signposting.processor.item.ItemLinksetProcessor;
import org.dspace.app.rest.signposting.processor.item.ItemSignpostingProcessor;
import org.dspace.app.rest.signposting.processor.metadata.MetadataSignpostingProcessor;
import org.dspace.app.rest.signposting.service.LinksetService;
@@ -28,6 +29,7 @@ import org.dspace.content.Item;
import org.dspace.content.service.ItemService;
import org.dspace.core.Constants;
import org.dspace.core.Context;
import org.dspace.services.ConfigurationService;
import org.dspace.utils.DSpace;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@@ -40,12 +42,18 @@ public class LinksetServiceImpl implements LinksetService {
private static final Logger log = LogManager.getLogger(LinksetServiceImpl.class);
@Autowired
private ConfigurationService configurationService;
@Autowired
protected ItemService itemService;
@Autowired
private BitstreamMetadataReadPermissionEvaluatorPlugin bitstreamMetadataReadPermissionEvaluatorPlugin;
@Autowired
ItemLinksetProcessor itemLinksetProcessor;
private final List<BitstreamSignpostingProcessor> bitstreamProcessors = new DSpace().getServiceManager()
.getServicesByType(BitstreamSignpostingProcessor.class);
@@ -74,10 +82,20 @@ public class LinksetServiceImpl implements LinksetService {
Context context,
DSpaceObject object
) {
int itemBitstreamsLimit = configurationService.getIntProperty("signposting.item.bitstreams.limit", 10);
List<LinksetNode> linksetNodes = new ArrayList<>();
if (object.getType() == Constants.ITEM) {
for (ItemSignpostingProcessor processor : itemProcessors) {
processor.addLinkSetNodes(context, request, (Item) object, linksetNodes);
int itemBitstreamsCount = countItemBitstreams((Item) object);
// Do not include individual bitstream typed links if their number exceeds
// the limit in the configuration.
if (itemBitstreamsCount < itemBitstreamsLimit) {
for (ItemSignpostingProcessor processor : itemProcessors) {
processor.addLinkSetNodes(context, request, (Item) object, linksetNodes);
}
} else {
itemLinksetProcessor.addLinkSetNodes(context, request, (Item) object, linksetNodes);
}
} else if (object.getType() == Constants.BITSTREAM) {
for (BitstreamSignpostingProcessor processor : bitstreamProcessors) {
@@ -151,4 +169,17 @@ public class LinksetServiceImpl implements LinksetService {
throw new RuntimeException(e);
}
}
private int countItemBitstreams(Item item) {
try {
int countBitstreams = 0;
List<Bundle> bundles = itemService.getBundles(item, Constants.DEFAULT_BUNDLE_NAME);
for (Bundle bundle: bundles) {
countBitstreams += bundle.getBitstreams().size();
}
return countBitstreams;
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -8,6 +8,7 @@
package org.dspace.app.rest.signposting.controller;
import static org.dspace.content.MetadataSchemaEnum.PERSON;
import static org.junit.Assert.assertTrue;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
@@ -18,7 +19,9 @@ import java.io.InputStream;
import java.text.DateFormat;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.UUID;
import org.apache.commons.codec.CharEncoding;
import org.apache.commons.io.IOUtils;
@@ -691,6 +694,61 @@ public class LinksetRestControllerIT extends AbstractControllerIntegrationTest {
"&& @.type == 'application/linkset+json')]").exists());
}
@Test
public void showTypedLinksMissingForItemWithMoreBitstreamsThanLimit() throws Exception {
String bitstreamContent = "ThisIsSomeDummyText";
String bitstreamMimeType = "text/plain";
int itemBitstreamsLimit = configurationService.getIntProperty("signposting.item.bitstreams.limit", 10);
context.turnOffAuthorisationSystem();
Item item = ItemBuilder.createItem(context, collection)
.withTitle("Item Test")
.withMetadata("dc", "identifier", "doi", doi)
.build();
// Add more bitstreams than the configured limit
ArrayList<UUID> bitstreamIDs = new ArrayList<>();
for (int i = 0; i <= itemBitstreamsLimit; i++) {
Bitstream bitstream = null;
try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) {
bitstream = BitstreamBuilder.createBitstream(context, item, is)
.withName("Bitstream " + i)
.withDescription("description")
.withMimeType(bitstreamMimeType)
.build();
if (bitstream != null) {
bitstreamIDs.add(bitstream.getID());
}
}
}
context.restoreAuthSystemState();
// Make sure the bitstreams were successfully added.
assertTrue("There was a problem ingesting bitstreams.", bitstreamIDs.size() > itemBitstreamsLimit);
String url = configurationService.getProperty("dspace.ui.url");
String signpostingUrl = configurationService.getProperty("signposting.path");
// There should be typed links to the Link Sets but no typed links to the Bitstreams in the response.
// We only need to check for one of the Bitstream UUIDs, since all of them should be absent.
UUID firstBitstreamId = bitstreamIDs.get(0);
getClient().perform(get("/signposting/links/" + item.getID()))
.andExpect(status().isOk())
.andExpect(jsonPath("$[?(@.href == '" + url + "/" + signpostingUrl + "/linksets/" +
item.getID().toString() + "' " +
"&& @.rel == 'linkset' " +
"&& @.type == 'application/linkset')]").exists())
.andExpect(jsonPath("$[?(@.href == '" + url + "/" + signpostingUrl + "/linksets/" +
item.getID().toString() + "/json' " +
"&& @.rel == 'linkset' " +
"&& @.type == 'application/linkset+json')]").exists())
.andExpect(jsonPath("$[?(@.href == '" + url + "/bitstreams/" + firstBitstreamId + "/download' " +
"&& @.rel == 'item' " +
"&& @.type == 'text/plain')]").doesNotExist());;
}
@Test
public void findTypedLinkForBitstream() throws Exception {
String bitstreamContent = "ThisIsSomeDummyText";

View File

@@ -32,4 +32,9 @@ signposting.enabled = true
signposting.describedby.crosswalk-name = DataCite
# Mime-type of response of handling of 'describedby' links.
signposting.describedby.mime-type = application/vnd.datacite.datacite+xml
signposting.describedby.mime-type = application/vnd.datacite.datacite+xml
# Limit to the number of an item's bitstreams to return as typed links.
# If there are more bitstreams than this limit then only the typed links to the Link Sets are added to the header.
# Defaults to 10 if the value is unspecified
# signposting.item.bitstreams.limit = 10

18
pom.xml
View File

@@ -38,7 +38,7 @@
<jcache-version>1.1.1</jcache-version>
<!-- NOTE: Jetty needed for Solr, Handle Server & tests -->
<jetty.version>9.4.58.v20250814</jetty.version>
<log4j.version>2.25.1</log4j.version>
<log4j.version>2.25.2</log4j.version>
<pdfbox-version>2.0.34</pdfbox-version>
<rome.version>1.19.0</rome.version>
<slf4j.version>1.7.36</slf4j.version>
@@ -140,7 +140,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.14.0</version>
<version>3.14.1</version>
<configuration>
<release>11</release>
<!-- Turn on http://errorprone.info (requires fork=true & below compilerArgs)-->
@@ -295,7 +295,7 @@
<plugin>
<groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs-maven-plugin</artifactId>
<version>4.9.4.2</version>
<version>4.9.6.0</version>
<configuration>
<effort>Max</effort>
<threshold>Low</threshold>
@@ -305,7 +305,7 @@
<dependency>
<groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs</artifactId>
<version>4.9.4</version>
<version>4.9.6</version>
</dependency>
</dependencies>
<executions>
@@ -357,13 +357,13 @@
<plugin>
<groupId>org.sonatype.central</groupId>
<artifactId>central-publishing-maven-plugin</artifactId>
<version>0.8.0</version>
<version>0.9.0</version>
</plugin>
<!-- Used to generate JavaDocs for new releases (see release profile). -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.11.3</version>
<version>3.12.0</version>
<configuration>
<!-- Never fail a build based on Javadoc errors -->
<failOnError>false</failOnError>
@@ -680,7 +680,7 @@
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>license-maven-plugin</artifactId>
<version>2.6.0</version>
<version>2.7.0</version>
<!-- This plugin only needs to be run on the Parent POM
as it aggregates results from all child POMs. -->
<inherited>false</inherited>
@@ -1503,7 +1503,7 @@
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.18.0</version>
<version>3.19.0</version>
</dependency>
<!-- NOTE: We don't use commons-logging or commons-compress directly,
but many dependencies rely on them. Only here specified to avoid dependency convergence issues. -->
@@ -1684,7 +1684,7 @@
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>2.3.232</version>
<version>2.4.240</version>
<scope>test</scope>
</dependency>
<!-- Google Analytics -->