Add Resource shim; convert WebResponseUtils to JAX-RS Response/StreamingOutput
This commit is contained in:
@@ -24,9 +24,9 @@ import java.util.UUID;
|
||||
// custom ConfigSource, and java.nio / ClassLoader#getResourceAsStream for resources).
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.core.env.PropertySource;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.FileSystemResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import stirling.software.common.model.io.ClassPathResource;
|
||||
import stirling.software.common.model.io.FileSystemResource;
|
||||
import stirling.software.common.model.io.Resource;
|
||||
import org.springframework.core.io.support.EncodedResource;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
package stirling.software.common.model.io;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
|
||||
/** Classpath-backed {@link Resource} (migration shim for Spring's {@code ClassPathResource}). */
|
||||
public class ClassPathResource implements Resource {
|
||||
|
||||
private final String path;
|
||||
private final ClassLoader classLoader;
|
||||
|
||||
public ClassPathResource(String path) {
|
||||
this(path, ClassPathResource.class.getClassLoader());
|
||||
}
|
||||
|
||||
public ClassPathResource(String path, ClassLoader classLoader) {
|
||||
this.path = path.startsWith("/") ? path.substring(1) : path;
|
||||
this.classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() throws IOException {
|
||||
InputStream is = classLoader.getResourceAsStream(path);
|
||||
if (is == null) {
|
||||
throw new IOException("class path resource [" + path + "] cannot be opened");
|
||||
}
|
||||
return is;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists() {
|
||||
return classLoader.getResource(path) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFilename() {
|
||||
int sep = path.lastIndexOf('/');
|
||||
return sep != -1 ? path.substring(sep + 1) : path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long contentLength() throws IOException {
|
||||
try (InputStream is = getInputStream()) {
|
||||
long count = 0;
|
||||
byte[] buf = new byte[8192];
|
||||
int read;
|
||||
while ((read = is.read(buf)) != -1) {
|
||||
count += read;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getFile() throws IOException {
|
||||
URL url = classLoader.getResource(path);
|
||||
if (url == null || !"file".equals(url.getProtocol())) {
|
||||
throw new IOException("class path resource [" + path + "] is not a filesystem file");
|
||||
}
|
||||
return new File(url.getFile());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package stirling.software.common.model.io;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
/** File-backed {@link Resource} (migration shim for Spring's {@code FileSystemResource}). */
|
||||
public class FileSystemResource implements Resource {
|
||||
|
||||
private final Path path;
|
||||
|
||||
public FileSystemResource(Path path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
public FileSystemResource(File file) {
|
||||
this.path = file.toPath();
|
||||
}
|
||||
|
||||
public FileSystemResource(String path) {
|
||||
this.path = Path.of(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() throws IOException {
|
||||
return Files.newInputStream(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists() {
|
||||
return Files.exists(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFilename() {
|
||||
Path name = path.getFileName();
|
||||
return name == null ? null : name.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long contentLength() throws IOException {
|
||||
return Files.size(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFile() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getFile() {
|
||||
return path.toFile();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package stirling.software.common.model.io;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* Stream-backed {@link Resource} (migration shim for Spring's {@code InputStreamResource}). As with
|
||||
* Spring, the stream can only be read once.
|
||||
*/
|
||||
public class InputStreamResource implements Resource {
|
||||
|
||||
private final InputStream inputStream;
|
||||
private final String filename;
|
||||
|
||||
public InputStreamResource(InputStream inputStream) {
|
||||
this(inputStream, null);
|
||||
}
|
||||
|
||||
public InputStreamResource(InputStream inputStream, String filename) {
|
||||
this.inputStream = inputStream;
|
||||
this.filename = filename;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() {
|
||||
return inputStream;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFilename() {
|
||||
return filename;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long contentLength() throws IOException {
|
||||
// Spring's InputStreamResource also cannot report length without consuming the stream.
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getFile() throws IOException {
|
||||
throw new IOException("InputStreamResource is not backed by a file");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package stirling.software.common.model.io;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* Migration compatibility shim for Spring's {@code org.springframework.core.io.Resource}.
|
||||
*
|
||||
* <p>Quarkus/Jakarta has no single {@code Resource} abstraction. Rather than rewrite the many
|
||||
* public method signatures across the codebase that accept or return {@code Resource}, this
|
||||
* interface mirrors the subset of Spring's API the codebase actually uses
|
||||
* ({@code getInputStream/exists/getFile/getFilename/contentLength/isFile}) together with the
|
||||
* {@link FileSystemResource}, {@link InputStreamResource} and {@link ClassPathResource}
|
||||
* implementations. Converting a file is then just an import swap.
|
||||
*
|
||||
* <p>TODO: Migration required - longer term, prefer {@code java.nio.file.Path} / {@code InputStream}
|
||||
* directly at the boundaries and retire this shim.
|
||||
*/
|
||||
public interface Resource {
|
||||
|
||||
InputStream getInputStream() throws IOException;
|
||||
|
||||
boolean exists();
|
||||
|
||||
String getFilename();
|
||||
|
||||
long contentLength() throws IOException;
|
||||
|
||||
/** Whether this resource is backed by a real file in the filesystem. */
|
||||
default boolean isFile() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the underlying file.
|
||||
* @throws IOException if the resource is not file-backed.
|
||||
*/
|
||||
File getFile() throws IOException;
|
||||
}
|
||||
@@ -14,7 +14,7 @@ import jakarta.enterprise.context.ApplicationScoped;
|
||||
// TODO: Migration required - org.springframework.core.io.Resource is used in public method
|
||||
// signatures (storeFile, storeFromResource) and for behavior (isFile()/getFile()/getInputStream()).
|
||||
// No drop-in JAX-RS/Jakarta equivalent; converting would ripple into callers. Keeping the type.
|
||||
import org.springframework.core.io.Resource;
|
||||
import stirling.software.common.model.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.
|
||||
|
||||
@@ -16,8 +16,8 @@ import java.util.regex.Pattern;
|
||||
// non-trivial and would ripple to callers (PipelineProcessor, AiWorkflowService) that pass
|
||||
// MultiValueMap bodies and consume ResponseEntity<Resource>. These Spring imports are
|
||||
// intentionally retained until that cross-file migration is scheduled.
|
||||
import org.springframework.core.io.FileSystemResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import stirling.software.common.model.io.FileSystemResource;
|
||||
import stirling.software.common.model.io.Resource;
|
||||
import org.springframework.http.*;
|
||||
import org.springframework.http.client.SimpleClientHttpRequestFactory;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
@@ -17,7 +17,7 @@ import java.util.function.Supplier;
|
||||
// because they are consumed via ResponseEntity#getHeaders()/getBody() and built
|
||||
// via ResponseEntity builders. These imports are intentionally retained until the
|
||||
// callers are migrated as a unit.
|
||||
import org.springframework.core.io.Resource;
|
||||
import stirling.software.common.model.io.Resource;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
|
||||
@@ -21,7 +21,7 @@ import java.util.regex.Pattern;
|
||||
// resource API. Resource/ResourceLoader/ResourcePatternUtils remain in getResourcesFromLocationPattern
|
||||
// because that method's public signature and the Spring resource-pattern resolver are used by
|
||||
// callers; converting it requires a filesystem/classpath glob rewrite that ripples to callers.
|
||||
import org.springframework.core.io.Resource;
|
||||
import stirling.software.common.model.io.Resource;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.core.io.support.ResourcePatternUtils;
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ import org.apache.commons.io.IOUtils;
|
||||
// org.springframework.http.MediaType -> jakarta.ws.rs.core.MediaType,
|
||||
// HttpStatus.BAD_REQUEST -> Response.Status.BAD_REQUEST,
|
||||
// MultipartFile -> byte[]/InputStream or a JAX-RS multipart type.
|
||||
import org.springframework.core.io.Resource;
|
||||
import stirling.software.common.model.io.Resource;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
|
||||
@@ -11,27 +11,12 @@ import java.nio.file.Path;
|
||||
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
|
||||
// TODO: Migration required - org.springframework.core.io.Resource/FileSystemResource have no
|
||||
// JAX-RS drop-in. The file-backed responses below (pdfFileToWebResponse / zipFileToWebResponse /
|
||||
// fileToWebResponse and the ManagedTempFileResource inner class) rely on Spring's
|
||||
// ResourceHttpMessageConverter calling Resource#getInputStream() once and closing it after writing
|
||||
// the body, which is what triggers TempFile deletion. Under Quarkus/JAX-RS the equivalent is to
|
||||
// return a StreamingOutput (or InputStream) and delete the TempFile after the stream is fully
|
||||
// written. The Spring Resource imports are retained until those methods are reworked, because
|
||||
// removing them would require changing the public ResponseEntity<Resource> signatures and the
|
||||
// converter-driven lifecycle they depend on.
|
||||
import org.springframework.core.io.FileSystemResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
// TODO: Migration required - org.springframework.web.multipart.MultipartFile has no servlet/JAX-RS
|
||||
// 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 stirling.software.common.model.MultipartFile;
|
||||
|
||||
import jakarta.ws.rs.core.HttpHeaders;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import jakarta.ws.rs.core.StreamingOutput;
|
||||
|
||||
import io.github.pixee.security.Filenames;
|
||||
|
||||
@@ -91,16 +76,15 @@ public class WebResponseUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a {@link PDDocument} to a managed temp file and return it as a file-backed {@code
|
||||
* ResponseEntity<Resource>}.
|
||||
* Save a {@link PDDocument} to a managed temp file and return it as a streamed web response.
|
||||
*
|
||||
* <p>The returned {@link Resource} owns the supplied {@link TempFile} — the file is deleted
|
||||
* when Spring closes the response {@link InputStream} after writing the body. This is a
|
||||
* synchronous equivalent of the previous {@code StreamingResponseBody} pattern and avoids the
|
||||
* async-dispatch hazards (response-committed races, filter incompatibility, silent write
|
||||
* failures) that {@code StreamingResponseBody} introduced.
|
||||
* <p>MIGRATION (Spring -> JAX-RS): previously returned {@code ResponseEntity<Resource>} backed
|
||||
* by a {@code ManagedTempFileResource}, relying on Spring's {@code ResourceHttpMessageConverter}
|
||||
* to call {@code Resource#getInputStream()} and close it after writing (the hook that deleted
|
||||
* the {@link TempFile}). The JAX-RS equivalent is a {@link StreamingOutput} that copies the file
|
||||
* to the response and deletes the temp file in a {@code finally} block once writing completes.
|
||||
*/
|
||||
public static ResponseEntity<Resource> pdfDocToWebResponse(
|
||||
public static Response pdfDocToWebResponse(
|
||||
PDDocument document, String docName, TempFileManager tempFileManager)
|
||||
throws IOException {
|
||||
TempFile tempFile = tempFileManager.createManagedTempFile(".pdf");
|
||||
@@ -113,71 +97,63 @@ public class WebResponseUtils {
|
||||
return pdfFileToWebResponse(tempFile, docName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a {@link TempFile} holding a PDF into a web response.
|
||||
*
|
||||
* <p>The temp file is deleted when Spring closes the response body stream.
|
||||
*
|
||||
* @param outputTempFile The temporary file to be sent as a response.
|
||||
* @param docName The name of the document.
|
||||
* @return A ResponseEntity whose body streams the file, deleting it on close.
|
||||
*/
|
||||
public static ResponseEntity<Resource> pdfFileToWebResponse(
|
||||
TempFile outputTempFile, String docName) throws IOException {
|
||||
/** Convert a {@link TempFile} holding a PDF into a streamed web response (deletes on close). */
|
||||
public static Response pdfFileToWebResponse(TempFile outputTempFile, String docName)
|
||||
throws IOException {
|
||||
return fileToWebResponse(outputTempFile, docName, APPLICATION_PDF);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a {@link TempFile} holding a ZIP archive into a web response.
|
||||
*
|
||||
* <p>The temp file is deleted when Spring closes the response body stream.
|
||||
*
|
||||
* @param outputTempFile The temporary file to be sent as a response.
|
||||
* @param docName The name of the document.
|
||||
* @return A ResponseEntity whose body streams the file, deleting it on close.
|
||||
*/
|
||||
public static ResponseEntity<Resource> zipFileToWebResponse(
|
||||
TempFile outputTempFile, String docName) throws IOException {
|
||||
/** Convert a {@link TempFile} holding a ZIP into a streamed web response (deletes on close). */
|
||||
public static Response zipFileToWebResponse(TempFile outputTempFile, String docName)
|
||||
throws IOException {
|
||||
return fileToWebResponse(
|
||||
outputTempFile, docName, MediaType.valueOf(MediaType.APPLICATION_OCTET_STREAM));
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a {@link TempFile} into a web response with an explicit media type.
|
||||
* Convert a {@link TempFile} into a streamed web response with an explicit media type.
|
||||
*
|
||||
* <p>The returned {@link ResponseEntity} carries a {@link ManagedTempFileResource} as its body.
|
||||
* Spring's {@code ResourceHttpMessageConverter} calls {@link Resource#getInputStream()} once
|
||||
* and closes the returned stream after writing — at which point the underlying {@link TempFile}
|
||||
* is deleted. Everything runs synchronously on the request thread, so write failures propagate
|
||||
* through normal Spring error handling and are logged, rather than silently truncating the
|
||||
* response.
|
||||
*
|
||||
* @param outputTempFile The temporary file to be sent as a response.
|
||||
* @param docName The name of the document.
|
||||
* @param mediaType The content type to set on the response.
|
||||
* @return A ResponseEntity whose body streams the file, deleting it on close.
|
||||
* <p>The body is a {@link StreamingOutput} that copies the temp file to the client and deletes
|
||||
* the backing {@link TempFile} once the transfer completes (or fails). This replaces the former
|
||||
* Spring {@code ResponseEntity<Resource>} + {@code ResourceHttpMessageConverter} lifecycle.
|
||||
* I/O errors during the copy are logged and propagated.
|
||||
*/
|
||||
// TODO: Migration required - ResponseEntity<Resource> + FileSystemResource depend on Spring's
|
||||
// ResourceHttpMessageConverter to invoke Resource#getInputStream() and close it after writing,
|
||||
// which is the hook that deletes the backing TempFile. There is no faithful JAX-RS drop-in for
|
||||
// that lifecycle; rework to return jakarta.ws.rs.core.Response carrying a StreamingOutput that
|
||||
// deletes the TempFile after writing. Left intact to preserve behaviour and the public
|
||||
// signature until callers can be migrated together.
|
||||
public static ResponseEntity<Resource> fileToWebResponse(
|
||||
public static Response fileToWebResponse(
|
||||
TempFile outputTempFile, String docName, MediaType mediaType) throws IOException {
|
||||
|
||||
try {
|
||||
Path path = outputTempFile.getFile().toPath().normalize();
|
||||
long len = Files.size(path);
|
||||
org.springframework.http.HttpHeaders headers =
|
||||
new org.springframework.http.HttpHeaders();
|
||||
headers.setContentType(
|
||||
org.springframework.http.MediaType.parseMediaType(mediaType.toString()));
|
||||
headers.setContentLength(len);
|
||||
headers.setContentDispositionFormData("attachment", encodeAttachmentName(docName));
|
||||
|
||||
Resource body = new ManagedTempFileResource(outputTempFile);
|
||||
return new ResponseEntity<>(body, headers, org.springframework.http.HttpStatus.OK);
|
||||
StreamingOutput body =
|
||||
output -> {
|
||||
try (InputStream in = Files.newInputStream(path)) {
|
||||
in.transferTo(output);
|
||||
} catch (IOException e) {
|
||||
log.error(
|
||||
"Failed to stream temp response body {} to client",
|
||||
outputTempFile.getAbsolutePath(),
|
||||
e);
|
||||
throw e;
|
||||
} finally {
|
||||
try {
|
||||
outputTempFile.close();
|
||||
} catch (Exception closeEx) {
|
||||
log.warn(
|
||||
"Failed to clean up temp file {} after streaming response",
|
||||
outputTempFile.getAbsolutePath(),
|
||||
closeEx);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return Response.ok(body)
|
||||
.type(mediaType)
|
||||
.header(HttpHeaders.CONTENT_LENGTH, len)
|
||||
.header(
|
||||
"Content-Disposition",
|
||||
"attachment; filename=\"" + encodeAttachmentName(docName) + "\"")
|
||||
.build();
|
||||
} catch (IOException | RuntimeException e) {
|
||||
try {
|
||||
outputTempFile.close();
|
||||
@@ -195,110 +171,8 @@ public class WebResponseUtils {
|
||||
.replaceAll("%20");
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Resource} backed by a {@link TempFile}. The underlying temp file is deleted when the
|
||||
* response {@code InputStream} is closed — i.e. after Spring has finished writing the body. Any
|
||||
* {@link IOException} during the copy is logged via {@link ClosingInputStream} and propagates
|
||||
* through Spring's normal error path. Since response headers are committed before the body
|
||||
* transfer begins, a mid-body failure manifests as a server-side log entry plus an aborted
|
||||
* connection rather than a silently-truncated success — which is the behaviour this class was
|
||||
* added to restore.
|
||||
*
|
||||
* <p><b>Single-use contract:</b> {@link #getInputStream()} is intended to be called once by
|
||||
* Spring's {@code ResourceHttpMessageConverter} on the normal write path. After the returned
|
||||
* stream is closed the backing temp file is deleted, so subsequent {@code getInputStream()}
|
||||
* calls will either see a deleted file (tests that mock {@link TempFile#close()} are an
|
||||
* exception) or fail at read time. Callers that need to re-read the body must copy it first.
|
||||
*/
|
||||
// TODO: Migration required - extends Spring's FileSystemResource and is consumed by Spring's
|
||||
// ResourceHttpMessageConverter; no JAX-RS equivalent. Kept intact pending the Response +
|
||||
// StreamingOutput rework described on fileToWebResponse.
|
||||
public static final class ManagedTempFileResource extends FileSystemResource {
|
||||
|
||||
private final TempFile tempFile;
|
||||
|
||||
public ManagedTempFileResource(TempFile tempFile) {
|
||||
super(tempFile.getFile());
|
||||
this.tempFile = tempFile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() throws IOException {
|
||||
InputStream source;
|
||||
try {
|
||||
source = super.getInputStream();
|
||||
} catch (IOException e) {
|
||||
// Opening the input stream already failed; make sure we don't leak the temp file.
|
||||
try {
|
||||
tempFile.close();
|
||||
} catch (Exception closeEx) {
|
||||
e.addSuppressed(closeEx);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
return new ClosingInputStream(source, tempFile);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stream wrapper that deletes its backing {@link TempFile} on close. Logs — but does not
|
||||
* swallow — any IOException that happens while reading the body, so upstream handlers can
|
||||
* surface the failure to the client.
|
||||
*/
|
||||
private static final class ClosingInputStream extends FilterInputStream {
|
||||
|
||||
private final TempFile tempFile;
|
||||
private boolean closed;
|
||||
|
||||
ClosingInputStream(InputStream delegate, TempFile tempFile) {
|
||||
super(delegate);
|
||||
this.tempFile = tempFile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
try {
|
||||
return super.read();
|
||||
} catch (IOException e) {
|
||||
log.error(
|
||||
"Failed to read temp response body {} while streaming to client",
|
||||
tempFile.getAbsolutePath(),
|
||||
e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] b, int off, int len) throws IOException {
|
||||
try {
|
||||
return super.read(b, off, len);
|
||||
} catch (IOException e) {
|
||||
log.error(
|
||||
"Failed to read temp response body {} while streaming to client",
|
||||
tempFile.getAbsolutePath(),
|
||||
e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
if (closed) {
|
||||
return;
|
||||
}
|
||||
closed = true;
|
||||
try {
|
||||
super.close();
|
||||
} finally {
|
||||
try {
|
||||
tempFile.close();
|
||||
} catch (Exception closeEx) {
|
||||
log.warn(
|
||||
"Failed to clean up temp file {} after streaming response",
|
||||
tempFile.getAbsolutePath(),
|
||||
closeEx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// REMOVED (Spring -> JAX-RS): ManagedTempFileResource (extended Spring FileSystemResource) and
|
||||
// ClosingInputStream. Their job - delete the TempFile after the response body is written - is now
|
||||
// done inline by the StreamingOutput's finally block in fileToWebResponse, which is the idiomatic
|
||||
// JAX-RS lifecycle and removes the dependency on Spring's ResourceHttpMessageConverter.
|
||||
}
|
||||
|
||||
@@ -16,8 +16,8 @@ import java.util.zip.ZipInputStream;
|
||||
// (PipelineProcessor, AiWorkflowService). Replacing them with InputStream/java.nio would ripple
|
||||
// across callers that this single-file migration is not permitted to edit. Keeping the Spring core
|
||||
// io abstraction until callers are migrated together.
|
||||
import org.springframework.core.io.FileSystemResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import stirling.software.common.model.io.FileSystemResource;
|
||||
import stirling.software.common.model.io.Resource;
|
||||
|
||||
import io.github.pixee.security.ZipSecurity;
|
||||
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ import java.util.List;
|
||||
// because the abstract superclass ReplaceAndInvertColorStrategy.replace() declares this return
|
||||
// type. Migrating it (e.g. to java.io.InputStream) would change the public API of the parent
|
||||
// class and all sibling strategies/callers, which is out of scope for this single-file migration.
|
||||
import org.springframework.core.io.InputStreamResource;
|
||||
import stirling.software.common.model.io.InputStreamResource;
|
||||
// TODO: Migration required - org.springframework.web.multipart.MultipartFile is retained because
|
||||
// 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
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ import org.apache.pdfbox.text.TextPosition;
|
||||
// replace() method declared in the parent ReplaceAndInvertColorStrategy (not in scope for
|
||||
// this migration). Changing it here would break the @Override and ripple to all callers, so
|
||||
// the Spring type is retained until the parent and its callers are migrated together.
|
||||
import org.springframework.core.io.InputStreamResource;
|
||||
import stirling.software.common.model.io.InputStreamResource;
|
||||
// TODO: Migration required - MultipartFile is the constructor parameter type that must match
|
||||
// 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;
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ import org.apache.pdfbox.rendering.PDFRenderer;
|
||||
// drop-in here. The abstract parent ReplaceAndInvertColorStrategy.replace() returns
|
||||
// InputStreamResource and editing that signature would ripple to all subclasses and callers; per
|
||||
// task rules, only this file may change, so the type is kept until the parent is migrated.
|
||||
import org.springframework.core.io.InputStreamResource;
|
||||
import stirling.software.common.model.io.InputStreamResource;
|
||||
// TODO: Migration required - org.springframework.web.multipart.MultipartFile has no JAX-RS drop-in
|
||||
// for these constructor params. The parent class ReplaceAndInvertColorStrategy declares the same
|
||||
// MultipartFile constructor parameter; changing the public signature here would diverge from the
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ import java.io.IOException;
|
||||
// type of the public abstract replace() method, implemented by subclasses and consumed by
|
||||
// callers. Replacing it (e.g. with InputStream/StreamingOutput) would ripple across the
|
||||
// strategy subclasses and their callers, so it is kept until they are migrated together.
|
||||
import org.springframework.core.io.InputStreamResource;
|
||||
import stirling.software.common.model.io.InputStreamResource;
|
||||
// TODO: Migration required - org.springframework.web.multipart.MultipartFile is required by
|
||||
// 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
|
||||
|
||||
Reference in New Issue
Block a user