Add MultipartFile compatibility shim and swap common imports to it

This commit is contained in:
a
2026-06-12 16:49:43 +01:00
parent 0aad71b127
commit 5eec2b4446
26 changed files with 202 additions and 23 deletions
@@ -16,7 +16,7 @@ import org.slf4j.MDC;
// MultipartFile) and PDFFile.getFileInput()/setFileInput(MultipartFile). Changing the type here
// would ripple into FileStorage and PDFFile public signatures, so the original type is kept until
// those collaborators are migrated.
import org.springframework.web.multipart.MultipartFile;
import stirling.software.common.model.MultipartFile;
import jakarta.annotation.Priority;
import jakarta.enterprise.context.ApplicationScoped;
@@ -0,0 +1,49 @@
package stirling.software.common.model;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
/**
* Migration compatibility shim for Spring's {@code org.springframework.web.multipart.MultipartFile}.
*
* <p>Quarkus/JAX-RS has no drop-in equivalent for the {@code MultipartFile} abstraction that the
* service layer relies on (it exposes {@code org.jboss.resteasy.reactive.multipart.FileUpload} at
* the REST boundary instead). To avoid rewriting the public signatures of dozens of service and
* util methods across every module, this interface mirrors the subset of Spring's API that the
* codebase actually uses. Controllers adapt the inbound {@code FileUpload}/{@code byte[]} to one of
* the implementations ({@link stirling.software.common.model.multipart.ByteArrayMultipartFile},
* {@link stirling.software.common.model.multipart.FileUploadMultipartFile}) and pass it down
* unchanged.
*
* <p>TODO: Migration required - longer term, the REST boundary should standardise on
* {@code FileUpload}/{@code @RestForm} and this shim can be retired.
*/
public interface MultipartFile {
String getName();
String getOriginalFilename();
String getContentType();
boolean isEmpty();
long getSize();
byte[] getBytes() throws IOException;
InputStream getInputStream() throws IOException;
default void transferTo(File dest) throws IOException {
transferTo(dest.toPath());
}
default void transferTo(Path dest) throws IOException {
try (InputStream in = getInputStream()) {
Files.copy(in, dest);
}
}
}
@@ -5,7 +5,7 @@ package stirling.software.common.model.api;
// consumed by controllers/services across the codebase; changing the type (e.g. to
// byte[]/InputStream or jakarta.ws.rs form params) would ripple to every caller.
// Keep the Spring type for now and rebind during the web-layer migration.
import org.springframework.web.multipart.MultipartFile;
import stirling.software.common.model.MultipartFile;
import io.swagger.v3.oas.annotations.media.Schema;
@@ -4,7 +4,7 @@ package stirling.software.common.model.api;
// servlet/JAX-RS drop-in for a DTO field type. Changing this public field type to
// byte[]/InputStream or a JAX-RS multipart type would ripple to all callers and the
// API binding layer, so the type is kept until callers are migrated together.
import org.springframework.web.multipart.MultipartFile;
import stirling.software.common.model.MultipartFile;
import io.swagger.v3.oas.annotations.media.Schema;
@@ -0,0 +1,61 @@
package stirling.software.common.model.multipart;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import stirling.software.common.model.MultipartFile;
/**
* In-memory {@link MultipartFile} backed by a byte array. Useful for tests and for code paths that
* synthesize file content (migration shim - see {@link MultipartFile}).
*/
public class ByteArrayMultipartFile implements MultipartFile {
private final String name;
private final String originalFilename;
private final String contentType;
private final byte[] content;
public ByteArrayMultipartFile(
String name, String originalFilename, String contentType, byte[] content) {
this.name = name;
this.originalFilename = originalFilename;
this.contentType = contentType;
this.content = content != null ? content : new byte[0];
}
@Override
public String getName() {
return name;
}
@Override
public String getOriginalFilename() {
return originalFilename;
}
@Override
public String getContentType() {
return contentType;
}
@Override
public boolean isEmpty() {
return content.length == 0;
}
@Override
public long getSize() {
return content.length;
}
@Override
public byte[] getBytes() {
return content;
}
@Override
public InputStream getInputStream() {
return new ByteArrayInputStream(content);
}
}
@@ -0,0 +1,69 @@
package stirling.software.common.model.multipart;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import org.jboss.resteasy.reactive.multipart.FileUpload;
import stirling.software.common.model.MultipartFile;
/**
* Adapts a Quarkus REST {@link FileUpload} (the inbound multipart representation at the JAX-RS
* boundary) to the {@link MultipartFile} migration shim, so controllers can pass uploads down to
* the existing service layer without changing its method signatures.
*/
public class FileUploadMultipartFile implements MultipartFile {
private final FileUpload delegate;
public FileUploadMultipartFile(FileUpload delegate) {
this.delegate = delegate;
}
/** Null-safe factory: returns null when the upload is absent. */
public static MultipartFile of(FileUpload upload) {
return upload == null ? null : new FileUploadMultipartFile(upload);
}
@Override
public String getName() {
return delegate.name();
}
@Override
public String getOriginalFilename() {
return delegate.fileName();
}
@Override
public String getContentType() {
return delegate.contentType();
}
@Override
public boolean isEmpty() {
return getSize() == 0;
}
@Override
public long getSize() {
return delegate.size();
}
@Override
public byte[] getBytes() throws IOException {
return Files.readAllBytes(delegate.uploadedFile());
}
@Override
public InputStream getInputStream() throws IOException {
return Files.newInputStream(delegate.uploadedFile());
}
@Override
public void transferTo(Path dest) throws IOException {
Files.copy(delegate.uploadedFile(), dest);
}
}
@@ -29,7 +29,7 @@ import org.apache.pdfbox.pdmodel.PDDocument;
// (controllers/other services) span the codebase; changing these public signatures to byte[]/
// InputStream would ripple widely. The original import and type are kept intact pending a
// coordinated migration of all callers.
import org.springframework.web.multipart.MultipartFile;
import stirling.software.common.model.MultipartFile;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
@@ -12,7 +12,7 @@ import org.eclipse.microprofile.config.inject.ConfigProperty;
// JAX-RS/servlet drop-in. It is used as a public method return type and implemented by the
// inner CustomMultipartFile class; converting the type would ripple to all callers. Keeping
// the Spring type for now to preserve the public signature and behavior.
import org.springframework.web.multipart.MultipartFile;
import stirling.software.common.model.MultipartFile;
import lombok.RequiredArgsConstructor;
@@ -18,7 +18,7 @@ import org.springframework.core.io.Resource;
// TODO: Migration required - org.springframework.web.multipart.MultipartFile is used in public
// method signatures (storeFile, retrieveFile). No servlet/JAX-RS drop-in for utility method
// params; changing it would ripple widely into callers. Keeping the type.
import org.springframework.web.multipart.MultipartFile;
import stirling.software.common.model.MultipartFile;
// TODO: Migration required - org.springframework.web.servlet.mvc.method.annotation
// .StreamingResponseBody is used in the public storeFromStreamingBody signature. No Quarkus/JAX-RS
// drop-in; converting would ripple into callers. Keeping the type.
@@ -24,7 +24,7 @@ import org.springframework.http.ResponseEntity;
// TODO: Migration required - org.springframework.web.multipart.MultipartFile has no
// JAX-RS drop-in for these utility method params; changing the type would ripple to
// callers, so it is kept as-is for now.
import org.springframework.web.multipart.MultipartFile;
import stirling.software.common.model.MultipartFile;
// TODO: Migration required - org.springframework.web.servlet.mvc.method.annotation
// .StreamingResponseBody is a Spring MVC servlet type with no JAX-RS equivalent here;
// it is consumed by FileStorage#storeFromStreamingBody and kept as-is for now.
@@ -18,7 +18,7 @@ import io.quarkus.scheduler.Scheduled;
// drop-in. uploadFiles(...) is a public API consumed by callers and relies on isEmpty(),
// getOriginalFilename(), getSize(), getContentType() and transferTo(Path). Changing it to
// byte[]/InputStream would ripple to callers, so the Spring type is kept here intentionally.
import org.springframework.web.multipart.MultipartFile;
import stirling.software.common.model.MultipartFile;
import lombok.extern.slf4j.Slf4j;
@@ -20,7 +20,7 @@ import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
// MultipartFile and rely on isEmpty()/getOriginalFilename()/transferTo(File), which are
// MultipartFile-specific. Converting the signatures (e.g. to byte[]/InputStream) would ripple
// to all callers, so the type is intentionally kept until the calling endpoints are migrated.
import org.springframework.web.multipart.MultipartFile;
import stirling.software.common.model.MultipartFile;
import com.github.junrar.Archive;
import com.github.junrar.exception.CorruptHeaderException;
@@ -24,7 +24,7 @@ import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
// drop-in for these utility method parameters. The type appears in public method signatures
// (convertCbzToPdf, isCbzFile, isComicBookFile) consumed by other modules; switching to
// byte[]/InputStream would ripple across callers. Keep the Spring type until callers migrate.
import org.springframework.web.multipart.MultipartFile;
import stirling.software.common.model.MultipartFile;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
@@ -29,7 +29,7 @@ import org.springframework.core.io.support.ResourcePatternUtils;
// drop-in for these utility method params. The public signatures of convertMultipartFileToFile and
// multipartToFile are used by many callers, so the type is kept to avoid a wide ripple. Revisit to
// accept InputStream/byte[] or a JAX-RS form-data type once callers are migrated.
import org.springframework.web.multipart.MultipartFile;
import stirling.software.common.model.MultipartFile;
import com.fathzer.soft.javaluator.DoubleEvaluator;
@@ -16,7 +16,7 @@ import javax.imageio.stream.ImageInputStream;
// drop-in. loadImageWithExifOrientation(MultipartFile) is a public utility used by callers that
// pass Spring MultipartFile; converting to byte[]/InputStream would change the public signature
// and ripple to those callers. Keep the type until callers are migrated.
import org.springframework.web.multipart.MultipartFile;
import stirling.software.common.model.MultipartFile;
import com.drew.imaging.ImageMetadataReader;
import com.drew.imaging.ImageProcessingException;
@@ -31,7 +31,7 @@ import org.springframework.core.io.Resource;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.multipart.MultipartFile;
import stirling.software.common.model.MultipartFile;
import com.vladsch.flexmark.html2md.converter.FlexmarkHtmlConverter;
import com.vladsch.flexmark.util.data.MutableDataSet;
@@ -47,7 +47,7 @@ import jakarta.ws.rs.core.MediaType;
// MultipartFile implementation with a custom byte[]/InputStream holder and reworking the private
// addAttachmentsToDocumentWithMapping signature. Kept as-is to preserve behavior; revisit to
// introduce a Quarkus-friendly attachment abstraction.
import org.springframework.web.multipart.MultipartFile;
import stirling.software.common.model.MultipartFile;
import lombok.Data;
import lombok.Getter;
@@ -22,7 +22,7 @@ import org.apache.pdfbox.rendering.PDFRenderer;
// (convertPdfToCbr/validatePdfFile/isPdfFile) consumed by callers; changing it to
// byte[]/InputStream would ripple widely, so the type is intentionally kept until callers
// are migrated.
import org.springframework.web.multipart.MultipartFile;
import stirling.software.common.model.MultipartFile;
import lombok.extern.slf4j.Slf4j;
@@ -17,7 +17,7 @@ import org.apache.pdfbox.rendering.PDFRenderer;
// JAX-RS drop-in for these public static utility method params. Converting to byte[]/
// InputStream would change widely-used public signatures (convertPdfToCbz, isPdfFile,
// validatePdfFile) and ripple to callers, so the type is kept until callers are migrated.
import org.springframework.web.multipart.MultipartFile;
import stirling.software.common.model.MultipartFile;
import lombok.extern.slf4j.Slf4j;
@@ -38,7 +38,7 @@ import org.apache.pdfbox.text.PDFTextStripper;
// getOriginalFilename(), getInputStream() and ImageProcessingUtils.loadImageWithExifOrientation(file).
// Changing the public signature would ripple across all callers, so the type is kept for now and
// must be re-migrated together with its callers (e.g. to a JAX-RS multipart form / byte[] / InputStream).
import org.springframework.web.multipart.MultipartFile;
import stirling.software.common.model.MultipartFile;
import io.github.pixee.security.Filenames;
@@ -13,7 +13,7 @@ import jakarta.enterprise.context.ApplicationScoped;
// TODO: Migration required - org.springframework.web.multipart.MultipartFile has no JAX-RS
// drop-in. Kept to preserve the public convertMultipartFileToFile signature used by callers;
// migrate to byte[]/InputStream or a Quarkus multipart type once callers are updated.
import org.springframework.web.multipart.MultipartFile;
import stirling.software.common.model.MultipartFile;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -27,7 +27,7 @@ import org.springframework.http.ResponseEntity;
// drop-in for a utility method parameter. Converting multiPartFileToWebResponse to accept
// byte[]/InputStream would ripple through every caller, so the Spring type and its import are kept
// here intentionally.
import org.springframework.web.multipart.MultipartFile;
import stirling.software.common.model.MultipartFile;
import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.core.MediaType;
@@ -16,7 +16,7 @@ import org.springframework.core.io.InputStreamResource;
// the superclass ctor ReplaceAndInvertColorStrategy(MultipartFile, ReplaceAndInvert) requires it.
// No JAX-RS drop-in exists for this constructor param without rippling changes to the parent and
// every caller, so the type is preserved here.
import org.springframework.web.multipart.MultipartFile;
import stirling.software.common.model.MultipartFile;
import lombok.extern.slf4j.Slf4j;
@@ -28,7 +28,7 @@ import org.springframework.core.io.InputStreamResource;
// the parent ReplaceAndInvertColorStrategy(MultipartFile, ReplaceAndInvert) constructor (not in
// scope for this migration). There is no JAX-RS drop-in for this widely used public signature;
// retained until the parent and its callers are migrated together.
import org.springframework.web.multipart.MultipartFile;
import stirling.software.common.model.MultipartFile;
import lombok.extern.slf4j.Slf4j;
@@ -26,7 +26,7 @@ import org.springframework.core.io.InputStreamResource;
// for these constructor params. The parent class ReplaceAndInvertColorStrategy declares the same
// MultipartFile constructor parameter; changing the public signature here would diverge from the
// parent and ripple to callers, so the type is kept until the parent/callers are migrated.
import org.springframework.web.multipart.MultipartFile;
import stirling.software.common.model.MultipartFile;
import lombok.extern.slf4j.Slf4j;
@@ -11,7 +11,7 @@ import org.springframework.core.io.InputStreamResource;
// the inherited PDFFile.setFileInput(MultipartFile) (PDFFile keeps this type for the same
// reason); changing this constructor parameter would ripple to callers and the API binding
// layer, so the type is kept until PDFFile and its callers are migrated together.
import org.springframework.web.multipart.MultipartFile;
import stirling.software.common.model.MultipartFile;
import lombok.Data;
import lombok.EqualsAndHashCode;