CE-881 - support for streamed outputs, implemented for render saved reports process

This commit is contained in:
2024-04-01 12:47:11 -05:00
parent 6f406fc42d
commit 1eeb57f32f
4 changed files with 77 additions and 38 deletions

View File

@ -22,20 +22,19 @@
package com.kingsrook.qqq.api.implementations.savedreports;
import java.io.File;
import java.io.Serializable;
import java.nio.charset.Charset;
import java.util.Map;
import com.kingsrook.qqq.api.model.actions.HttpApiResponse;
import com.kingsrook.qqq.api.model.metadata.processes.ApiProcessOutputInterface;
import com.kingsrook.qqq.api.model.openapi.Content;
import com.kingsrook.qqq.api.model.openapi.Response;
import com.kingsrook.qqq.api.model.openapi.Schema;
import com.kingsrook.qqq.backend.core.actions.tables.StorageAction;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessInput;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessOutput;
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportFormat;
import org.apache.commons.io.FileUtils;
import com.kingsrook.qqq.backend.core.model.actions.tables.storage.StorageInput;
import org.eclipse.jetty.http.HttpStatus;
@ -51,25 +50,10 @@ public class RenderSavedReportProcessApiProcessOutput implements ApiProcessOutpu
@Override
public Serializable getOutputForProcess(RunProcessInput runProcessInput, RunProcessOutput runProcessOutput) throws QException
{
try
{
ReportFormat reportFormat = ReportFormat.fromString(runProcessOutput.getValueString("reportFormat"));
String filePath = runProcessOutput.getValueString("serverFilePath");
File file = new File(filePath);
if(reportFormat.getIsBinary())
{
return FileUtils.readFileToByteArray(file);
}
else
{
return FileUtils.readFileToString(file, Charset.defaultCharset());
}
}
catch(Exception e)
{
throw new QException("Error streaming report contents", e);
}
//////////////////////////////////////////////////////////////////
// we don't use output like this - see customizeHttpApiResponse //
//////////////////////////////////////////////////////////////////
return (null);
}
@ -85,8 +69,18 @@ public class RenderSavedReportProcessApiProcessOutput implements ApiProcessOutpu
/////////////////////////////////////////////////////////////////////////////////////////////
httpApiResponse.setNeedsFormattedAsJson(false);
/////////////////////////////////////////////
// set content type based on report format //
/////////////////////////////////////////////
ReportFormat reportFormat = ReportFormat.fromString(runProcessOutput.getValueString("reportFormat"));
httpApiResponse.setContentType(reportFormat.getMimeType());
////////////////////////////////////////////////////////////////////////////////////////////////////////////
// get an input stream from the backend where the report content is stored - send that down to the caller //
////////////////////////////////////////////////////////////////////////////////////////////////////////////
String storageTableName = runProcessOutput.getValueString("storageTableName");
String storageReference = runProcessOutput.getValueString("storageReference");
httpApiResponse.setInputStream(new StorageAction().getInputStream(new StorageInput(storageTableName).withReference(storageReference)));
}

View File

@ -380,25 +380,36 @@ public class QJavalinApiHandler
context.contentType(response.getContentType());
}
////////////////////////////////////////////////////////////////////////////////////
// else, try to return it raw - as byte[], or String, or as a converted-to-String //
////////////////////////////////////////////////////////////////////////////////////
Serializable result = Objects.requireNonNullElse(response.getResponseBodyObject(), "");
if(result instanceof byte[] ba)
///////////////////////////////////////////////////////////////////////////////////
// if there's an input stream in the response, just send that down to the client //
///////////////////////////////////////////////////////////////////////////////////
if(response.getInputStream() != null)
{
context.result(ba);
storeApiLog(apiLog.withStatusCode(context.statusCode()).withResponseBody("Byte array of length: " + ba.length));
}
else if(result instanceof String s)
{
context.result(s);
storeApiLog(apiLog.withStatusCode(context.statusCode()).withResponseBody(s));
context.result(response.getInputStream());
storeApiLog(apiLog.withStatusCode(context.statusCode()).withResponseBody("Streamed result"));
}
else
{
String resultString = String.valueOf(result);
context.result(resultString);
storeApiLog(apiLog.withStatusCode(context.statusCode()).withResponseBody(resultString));
////////////////////////////////////////////////////////////////////////////////////
// else, try to return it raw - as byte[], or String, or as a converted-to-String //
////////////////////////////////////////////////////////////////////////////////////
Serializable result = Objects.requireNonNullElse(response.getResponseBodyObject(), "");
if(result instanceof byte[] ba)
{
context.result(ba);
storeApiLog(apiLog.withStatusCode(context.statusCode()).withResponseBody("Byte array of length: " + ba.length));
}
else if(result instanceof String s)
{
context.result(s);
storeApiLog(apiLog.withStatusCode(context.statusCode()).withResponseBody(s));
}
else
{
String resultString = String.valueOf(result);
context.result(resultString);
storeApiLog(apiLog.withStatusCode(context.statusCode()).withResponseBody(resultString));
}
}
}
}

View File

@ -22,6 +22,7 @@
package com.kingsrook.qqq.api.model.actions;
import java.io.InputStream;
import java.io.Serializable;
import org.eclipse.jetty.http.HttpStatus;
@ -37,6 +38,8 @@ public class HttpApiResponse
private String contentType;
private InputStream inputStream;
////////////////////////////////////////////////////////////////////////////////////////////////////////
// by default - QJavalinApiHandler will format the responseBodyObject as JSON. //
// set this field to false if you don't want it to do that (e.g., if your response is, say, a byte[]) //
@ -189,4 +192,35 @@ public class HttpApiResponse
return (this);
}
/*******************************************************************************
** Getter for inputStream
*******************************************************************************/
public InputStream getInputStream()
{
return (this.inputStream);
}
/*******************************************************************************
** Setter for inputStream
*******************************************************************************/
public void setInputStream(InputStream inputStream)
{
this.inputStream = inputStream;
}
/*******************************************************************************
** Fluent setter for inputStream
*******************************************************************************/
public HttpApiResponse withInputStream(InputStream inputStream)
{
this.inputStream = inputStream;
return (this);
}
}

View File

@ -175,7 +175,7 @@ public class TestUtils
private static void addSavedReports(QInstance qInstance) throws QException
{
qInstance.add(TablesPossibleValueSourceMetaDataProvider.defineTablesPossibleValueSource(qInstance));
new SavedReportsMetaDataProvider().defineAll(qInstance, MEMORY_BACKEND_NAME, null);
new SavedReportsMetaDataProvider().defineAll(qInstance, MEMORY_BACKEND_NAME, MEMORY_BACKEND_NAME, null);
RenderSavedReportProcessApiMetaDataEnricher.setupProcessForApi(qInstance.getProcess(RenderSavedReportMetaDataProducer.NAME), API_NAME, V2022_Q4);
}