diff --git a/modules/qrest/src/dist/deploy/30_qrest_txnmgr.xml b/modules/qrest/src/dist/deploy/30_qrest_txnmgr.xml
index 136b9a96b2..351982426c 100644
--- a/modules/qrest/src/dist/deploy/30_qrest_txnmgr.xml
+++ b/modules/qrest/src/dist/deploy/30_qrest_txnmgr.xml
@@ -7,6 +7,7 @@
+
@@ -22,6 +23,10 @@
+
+
+
+
diff --git a/modules/qrest/src/main/java/org/jpos/qrest/ExtractFile.java b/modules/qrest/src/main/java/org/jpos/qrest/ExtractFile.java
new file mode 100644
index 0000000000..5175cf12a1
--- /dev/null
+++ b/modules/qrest/src/main/java/org/jpos/qrest/ExtractFile.java
@@ -0,0 +1,106 @@
+/*
+ * jPOS Project [http://jpos.org]
+ * Copyright (C) 2000-2021 jPOS Software SRL
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package org.jpos.qrest;
+
+import io.netty.handler.codec.http.DefaultFullHttpResponse;
+import io.netty.handler.codec.http.FullHttpRequest;
+import io.netty.handler.codec.http.HttpResponseStatus;
+import io.netty.handler.codec.http.multipart.DefaultHttpDataFactory;
+import io.netty.handler.codec.http.multipart.FileUpload;
+import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder;
+import io.netty.handler.codec.http.multipart.InterfaceHttpData;
+import io.netty.handler.codec.http.multipart.InterfaceHttpData.HttpDataType;
+import org.jpos.core.Configurable;
+import org.jpos.core.Configuration;
+import org.jpos.core.ConfigurationException;
+import org.jpos.transaction.Context;
+import org.jpos.transaction.TransactionParticipant;
+
+import java.io.*;
+import java.nio.channels.FileChannel;
+
+import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
+import static org.jpos.qrest.Constants.REQUEST;
+import static org.jpos.qrest.Constants.RESPONSE;
+
+/**
+ * Extracts a file from the request
+ */
+public class ExtractFile implements TransactionParticipant, Configurable {
+
+ private String ctxKey;
+
+ @Override
+ public int prepare(long id, Serializable context) {
+ Context ctx = (Context) context;
+ FullHttpRequest request = ctx.get(REQUEST);
+
+ try {
+ ctx.put(ctxKey, getFileFromRequest(request));
+ } catch (IOException e) {
+ ctx.log(e);
+ ctx.put(RESPONSE, new DefaultFullHttpResponse(HTTP_1_1, HttpResponseStatus.NOT_FOUND));
+ return ABORTED;
+ }
+
+ return PREPARED | READONLY | NO_JOIN;
+ }
+
+ protected File getFileFromRequest(FullHttpRequest httpRequest) throws IOException {
+
+ HttpPostRequestDecoder httpDecoder = new HttpPostRequestDecoder(new DefaultHttpDataFactory(true), httpRequest);
+ httpDecoder.setDiscardThreshold(0);
+
+ httpDecoder.offer(httpRequest);
+ return readChunk(httpDecoder);
+
+ }
+
+ private File readChunk(HttpPostRequestDecoder httpDecoder) throws IOException {
+
+ for (InterfaceHttpData data : httpDecoder.getBodyHttpDatas()) {
+ if (data == null) continue;
+ try {
+ HttpDataType httpDataType = data.getHttpDataType();
+ if (httpDataType == HttpDataType.FileUpload) {
+ final FileUpload fileUpload = (FileUpload) data;
+ final File file = File.createTempFile(fileUpload.getFilename(), ".qrest.extract_file");
+ if (!file.exists() && !file.createNewFile()) {
+ throw new IOException("Can't create file " + file.getAbsolutePath());
+ }
+ try (FileInputStream fis = new FileInputStream(fileUpload.getFile());
+ FileChannel inputChannel = fis.getChannel();
+ FileOutputStream fos = new FileOutputStream(file);
+ FileChannel outputChannel = fos.getChannel()) {
+ outputChannel.transferFrom(inputChannel, 0, inputChannel.size());
+ }
+ return file;
+ }
+ } finally {
+ data.release();
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public void setConfiguration(Configuration cfg) throws ConfigurationException {
+ this.ctxKey = cfg.get("CTX_KEY", "FILE_FROM_REQUEST");
+ }
+}
diff --git a/modules/qrest/src/test/java/org/jpos/qrest/RestTest.java b/modules/qrest/src/test/java/org/jpos/qrest/RestTest.java
index ed2d1c8e98..8cc8a77625 100644
--- a/modules/qrest/src/test/java/org/jpos/qrest/RestTest.java
+++ b/modules/qrest/src/test/java/org/jpos/qrest/RestTest.java
@@ -20,20 +20,23 @@
import io.restassured.RestAssured;
import io.restassured.builder.RequestSpecBuilder;
+import org.apache.http.entity.ContentType;
import org.jpos.q2.Q2;
import org.jpos.util.NameRegistrar;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
-import static io.restassured.RestAssured.given;
-import static io.netty.handler.codec.http.HttpHeaderValues.APPLICATION_JSON;
+import java.io.File;
+import java.nio.file.Files;
-import static org.hamcrest.Matchers.*;
+import static io.netty.handler.codec.http.HttpHeaderValues.APPLICATION_JSON;
+import static io.restassured.RestAssured.given;
+import static org.hamcrest.Matchers.equalTo;
public class RestTest {
private static final String BASE_URL = "http://localhost:8081/";
private static Q2 q2;
-
+
@BeforeAll
public static void setUp() throws NameRegistrar.NotFoundException {
RestAssured.baseURI = BASE_URL;
@@ -100,4 +103,20 @@ public void testMultiplesTMs() {
.body("name", equalTo("txnmgr2")
);
}
+
+ @Test
+ void testUploadFile() throws Exception {
+
+ File tempFile = File.createTempFile("qrest", "test");
+ Files.write(tempFile.toPath(), "hello".getBytes());
+
+ given().log().all()
+ .contentType(ContentType.MULTIPART_FORM_DATA.getMimeType())
+ .multiPart("file", tempFile, "multipart/form-data")
+ .post("/test/load_file")
+ .then()
+ .statusCode(200)
+ .assertThat()
+ .body("content", equalTo("hello"));
+ }
}
diff --git a/modules/qrest/src/test/java/org/jpos/qrest/test/participant/DumpFile.java b/modules/qrest/src/test/java/org/jpos/qrest/test/participant/DumpFile.java
new file mode 100644
index 0000000000..04c8547048
--- /dev/null
+++ b/modules/qrest/src/test/java/org/jpos/qrest/test/participant/DumpFile.java
@@ -0,0 +1,39 @@
+package org.jpos.qrest.test.participant;
+
+import io.netty.handler.codec.http.HttpResponseStatus;
+import org.jpos.qrest.Response;
+import org.jpos.transaction.Context;
+import org.jpos.transaction.TransactionParticipant;
+
+import java.io.File;
+import java.io.Serializable;
+import java.nio.file.Files;
+import java.util.Collections;
+
+import static org.jpos.qrest.Constants.RESPONSE;
+
+/**
+ * @author Arturo Volpe
+ * @since 2022-08-03
+ */
+public class DumpFile implements TransactionParticipant {
+
+
+ @Override
+ public int prepare(long id, Serializable context) {
+
+ Context ctx = (Context) context;
+
+ File file = ctx.get("FILE_FROM_REQUEST");
+ try {
+ String content = String.join("", Files.readAllLines(file.toPath()));
+ ctx.put(RESPONSE, new Response(HttpResponseStatus.OK, Collections.singletonMap("content", content)));
+ return PREPARED | NO_JOIN | READONLY;
+ } catch (Exception e) {
+ ctx.log(e);
+ ctx.put(RESPONSE, new Response(HttpResponseStatus.INTERNAL_SERVER_ERROR, Collections.emptyMap()));
+ return ABORTED;
+ }
+ }
+
+}