From 36efc4c2d909663584be0a3293ba541f7e0287e5 Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Wed, 24 May 2023 17:07:19 -0500 Subject: [PATCH] More BLOB; FILE_DOWNLOAD adornment; javalin field download endpoint --- .../core/instances/QInstanceValidator.java | 57 +++++- .../model/metadata/fields/AdornmentType.java | 22 +++ .../frontend/QFrontendFieldMetaData.java | 13 ++ .../instances/QInstanceValidatorTest.java | 31 ++++ .../rdbms/actions/AbstractRDBMSAction.java | 4 + .../rdbms/actions/RDBMSInsertAction.java | 3 +- .../javalin/QJavalinImplementation.java | 175 +++++++++++++++++- 7 files changed, 301 insertions(+), 4 deletions(-) diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidator.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidator.java index 64f85dde..111d2dd2 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidator.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidator.java @@ -22,6 +22,7 @@ package com.kingsrook.qqq.backend.core.instances; +import java.io.Serializable; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Modifier; import java.util.ArrayList; @@ -52,6 +53,8 @@ import com.kingsrook.qqq.backend.core.model.metadata.QMiddlewareInstanceMetaData import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference; import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeType; import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeUsage; +import com.kingsrook.qqq.backend.core.model.metadata.fields.AdornmentType; +import com.kingsrook.qqq.backend.core.model.metadata.fields.FieldAdornment; import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData; import com.kingsrook.qqq.backend.core.model.metadata.fields.ValueTooLongBehavior; import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinOn; @@ -82,6 +85,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.cache.CacheOf; import com.kingsrook.qqq.backend.core.model.metadata.tables.cache.CacheUseCase; import com.kingsrook.qqq.backend.core.utils.CollectionUtils; import com.kingsrook.qqq.backend.core.utils.StringUtils; +import com.kingsrook.qqq.backend.core.utils.ValueUtils; /******************************************************************************* @@ -418,7 +422,7 @@ public class QInstanceValidator { table.getFields().forEach((fieldName, field) -> { - validateTableField(qInstance, tableName, fieldName, field); + validateTableField(qInstance, tableName, fieldName, table, field); }); } @@ -659,7 +663,7 @@ public class QInstanceValidator /******************************************************************************* ** *******************************************************************************/ - private void validateTableField(QInstance qInstance, String tableName, String fieldName, QFieldMetaData field) + private void validateTableField(QInstance qInstance, String tableName, String fieldName, QTableMetaData table, QFieldMetaData field) { assertCondition(Objects.equals(fieldName, field.getName()), "Inconsistent naming in table " + tableName + " for field " + fieldName + "/" + field.getName() + "."); @@ -701,6 +705,55 @@ public class QInstanceValidator assertCondition(fieldSecurityLock.getDefaultBehavior() != null, prefix + "has a fieldSecurityLock that is missing a defaultBehavior"); assertCondition(CollectionUtils.nullSafeHasContents(fieldSecurityLock.getOverrideValues()), prefix + "has a fieldSecurityLock that is missing overrideValues"); } + + for(FieldAdornment adornment : CollectionUtils.nonNullList(field.getAdornments())) + { + Map adornmentValues = CollectionUtils.nonNullMap(adornment.getValues()); + if(assertCondition(adornment.getType() != null, prefix + "has an adornment that is missing a type")) + { + String adornmentPrefix = prefix.trim() + ", " + adornment.getType() + " adornment "; + switch(adornment.getType()) + { + case SIZE -> + { + String width = ValueUtils.getValueAsString(adornmentValues.get("width")); + if(assertCondition(StringUtils.hasContent(width), adornmentPrefix + "is missing a width value")) + { + assertNoException(() -> AdornmentType.Size.valueOf(width.toUpperCase()), adornmentPrefix + "has an unrecognized width value [" + width + "]"); + } + } + case FILE_DOWNLOAD -> + { + String fileNameField = ValueUtils.getValueAsString(adornmentValues.get(AdornmentType.FileDownloadValues.FILE_NAME_FIELD)); + if(StringUtils.hasContent(fileNameField)) // file name isn't required - but if given, must be a field on the table. + { + assertNoException(() -> table.getField(fileNameField), adornmentPrefix + "specifies an unrecognized fileNameField [" + fileNameField + "]"); + } + + if(adornmentValues.containsKey(AdornmentType.FileDownloadValues.FILE_NAME_FORMAT_FIELDS)) + { + try + { + @SuppressWarnings("unchecked") + List formatFieldNames = (List) adornmentValues.get(AdornmentType.FileDownloadValues.FILE_NAME_FORMAT_FIELDS); + for(String formatFieldName : CollectionUtils.nonNullList(formatFieldNames)) + { + assertNoException(() -> table.getField(formatFieldName), adornmentPrefix + "specifies an unrecognized field name in fileNameFormatFields [" + formatFieldName + "]"); + } + } + catch(Exception e) + { + errors.add(adornmentPrefix + "fileNameFormatFields could not be accessed (is it a List?)"); + } + } + } + default -> + { + // no validations by default + } + } + } + } } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/fields/AdornmentType.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/fields/AdornmentType.java index e46f4c2c..ba78ceef 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/fields/AdornmentType.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/fields/AdornmentType.java @@ -40,6 +40,7 @@ public enum AdornmentType CODE_EDITOR, RENDER_HTML, REVEAL, + FILE_DOWNLOAD, ERROR; ////////////////////////////////////////////////////////////////////////// // keep these values in sync with AdornmentType.ts in qqq-frontend-core // @@ -58,6 +59,26 @@ public enum AdornmentType + /******************************************************************************* + ** + *******************************************************************************/ + public interface FileDownloadValues + { + String FILE_NAME_FIELD = "fileNameField"; + String DEFAULT_EXTENSION = "defaultExtension"; + String DEFAULT_MIME_TYPE = "defaultMimeType"; + + //////////////////////////////////////////////////// + // use these two together, as in: // + // FILE_NAME_FORMAT = "Order %s Packing Slip.pdf" // + // FILE_NAME_FORMAT_FIELDS = "orderId" // + //////////////////////////////////////////////////// + String FILE_NAME_FORMAT = "fileNameFormat"; + String FILE_NAME_FORMAT_FIELDS = "fileNameFormatFields"; + } + + + /******************************************************************************* ** *******************************************************************************/ @@ -112,6 +133,7 @@ public enum AdornmentType XSMALL, SMALL, MEDIUM, + MEDLARGE, LARGE, XLARGE; diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/frontend/QFrontendFieldMetaData.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/frontend/QFrontendFieldMetaData.java index d4414208..abb79f9c 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/frontend/QFrontendFieldMetaData.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/frontend/QFrontendFieldMetaData.java @@ -43,6 +43,7 @@ public class QFrontendFieldMetaData private QFieldType type; private boolean isRequired; private boolean isEditable; + private boolean isHeavy; private String possibleValueSourceName; private String displayFormat; @@ -64,6 +65,7 @@ public class QFrontendFieldMetaData this.type = fieldMetaData.getType(); this.isRequired = fieldMetaData.getIsRequired(); this.isEditable = fieldMetaData.getIsEditable(); + this.isHeavy = fieldMetaData.getIsHeavy(); this.possibleValueSourceName = fieldMetaData.getPossibleValueSourceName(); this.displayFormat = fieldMetaData.getDisplayFormat(); this.adornments = fieldMetaData.getAdornments(); @@ -126,6 +128,17 @@ public class QFrontendFieldMetaData + /******************************************************************************* + ** Getter for isHeavy + ** + *******************************************************************************/ + public boolean getIsHeavy() + { + return isHeavy; + } + + + /******************************************************************************* ** Getter for displayFormat ** diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidatorTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidatorTest.java index e6e50a68..129aeddb 100644 --- a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidatorTest.java +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidatorTest.java @@ -47,6 +47,8 @@ import com.kingsrook.qqq.backend.core.model.metadata.QInstance; import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference; import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeType; import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeUsage; +import com.kingsrook.qqq.backend.core.model.metadata.fields.AdornmentType; +import com.kingsrook.qqq.backend.core.model.metadata.fields.FieldAdornment; import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData; import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType; import com.kingsrook.qqq.backend.core.model.metadata.fields.ValueTooLongBehavior; @@ -1732,6 +1734,35 @@ class QInstanceValidatorTest extends BaseTest + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testFieldAdornments() + { + Function fieldExtractor = qInstance -> qInstance.getTable(TestUtils.TABLE_NAME_PERSON).getField("firstName"); + + assertValidationFailureReasons((qInstance -> fieldExtractor.apply(qInstance).withFieldAdornment(new FieldAdornment())), "adornment that is missing a type"); + assertValidationSuccess((qInstance -> fieldExtractor.apply(qInstance).withFieldAdornment(new FieldAdornment().withType(AdornmentType.REVEAL)))); + + //////////////////////////////// + // type-specific value checks // + //////////////////////////////// + assertValidationFailureReasons((qInstance -> fieldExtractor.apply(qInstance).withFieldAdornment(new FieldAdornment(AdornmentType.SIZE))), "missing a width value"); + assertValidationFailureReasons((qInstance -> fieldExtractor.apply(qInstance).withFieldAdornment(new FieldAdornment(AdornmentType.SIZE).withValue("width", "foo"))), "unrecognized width value"); + assertValidationSuccess((qInstance -> fieldExtractor.apply(qInstance).withFieldAdornment(new FieldAdornment(AdornmentType.SIZE).withValue("width", AdornmentType.Size.MEDIUM)))); + assertValidationSuccess((qInstance -> fieldExtractor.apply(qInstance).withFieldAdornment(AdornmentType.Size.SMALL.toAdornment()))); + + assertValidationFailureReasons((qInstance -> fieldExtractor.apply(qInstance).withFieldAdornment(new FieldAdornment(AdornmentType.FILE_DOWNLOAD).withValue("fileNameField", "foo"))), "unrecognized fileNameField [foo]"); + assertValidationSuccess((qInstance -> fieldExtractor.apply(qInstance).withFieldAdornment(new FieldAdornment(AdornmentType.FILE_DOWNLOAD).withValue("fileNameField", "lastName")))); + + assertValidationFailureReasons((qInstance -> fieldExtractor.apply(qInstance).withFieldAdornment(new FieldAdornment(AdornmentType.FILE_DOWNLOAD).withValue("fileNameFormatFields", "foo"))), "fileNameFormatFields could not be accessed"); + assertValidationFailureReasons((qInstance -> fieldExtractor.apply(qInstance).withFieldAdornment(new FieldAdornment(AdornmentType.FILE_DOWNLOAD).withValue("fileNameFormatFields", new ArrayList<>(List.of("foo"))))), "unrecognized field name in fileNameFormatFields [foo]"); + assertValidationSuccess((qInstance -> fieldExtractor.apply(qInstance).withFieldAdornment(new FieldAdornment(AdornmentType.FILE_DOWNLOAD).withValue("fileNameFormatFields", new ArrayList<>(List.of("lastName")))))); + } + + + /******************************************************************************* ** *******************************************************************************/ diff --git a/qqq-backend-module-rdbms/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/AbstractRDBMSAction.java b/qqq-backend-module-rdbms/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/AbstractRDBMSAction.java index 6145e8ef..f6386885 100644 --- a/qqq-backend-module-rdbms/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/AbstractRDBMSAction.java +++ b/qqq-backend-module-rdbms/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/AbstractRDBMSAction.java @@ -872,6 +872,10 @@ public abstract class AbstractRDBMSAction implements QActionInterface { return (QueryManager.getBoolean(resultSet, i)); } + case BLOB: + { + return (QueryManager.getByteArray(resultSet, i)); + } default: { throw new IllegalStateException("Unexpected field type: " + type); diff --git a/qqq-backend-module-rdbms/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSInsertAction.java b/qqq-backend-module-rdbms/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSInsertAction.java index 91465e49..dad6d6c0 100644 --- a/qqq-backend-module-rdbms/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSInsertAction.java +++ b/qqq-backend-module-rdbms/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSInsertAction.java @@ -38,6 +38,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData; import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; import com.kingsrook.qqq.backend.core.utils.CollectionUtils; import com.kingsrook.qqq.backend.module.rdbms.jdbc.QueryManager; +import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair; /******************************************************************************* @@ -58,7 +59,7 @@ public class RDBMSInsertAction extends AbstractRDBMSAction implements InsertInte if(CollectionUtils.nullSafeIsEmpty(insertInput.getRecords())) { - LOG.debug("Insert request called with 0 records. Returning with no-op"); + LOG.debug("Insert request called with 0 records. Returning with no-op", logPair("tableName", insertInput.getTableName())); rs.setRecords(new ArrayList<>()); return (rs); } diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinImplementation.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinImplementation.java index be89714c..665047b3 100644 --- a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinImplementation.java +++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinImplementation.java @@ -31,6 +31,7 @@ import java.time.Duration; import java.time.Instant; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Locale; @@ -53,6 +54,7 @@ import com.kingsrook.qqq.backend.core.actions.tables.GetAction; import com.kingsrook.qqq.backend.core.actions.tables.InsertAction; import com.kingsrook.qqq.backend.core.actions.tables.QueryAction; import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction; +import com.kingsrook.qqq.backend.core.actions.values.QValueFormatter; import com.kingsrook.qqq.backend.core.actions.values.SearchPossibleValueSourceAction; import com.kingsrook.qqq.backend.core.adapters.QInstanceAdapter; import com.kingsrook.qqq.backend.core.context.QContext; @@ -95,7 +97,10 @@ import com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetInput; import com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetOutput; import com.kingsrook.qqq.backend.core.model.data.QRecord; import com.kingsrook.qqq.backend.core.model.metadata.QInstance; +import com.kingsrook.qqq.backend.core.model.metadata.fields.AdornmentType; +import com.kingsrook.qqq.backend.core.model.metadata.fields.FieldAdornment; import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData; +import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType; import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData; import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; import com.kingsrook.qqq.backend.core.model.session.QSession; @@ -354,6 +359,9 @@ public class QJavalinImplementation put("", QJavalinImplementation::dataUpdate); // todo - want different semantics?? delete("", QJavalinImplementation::dataDelete); + get("/{fieldName}/{filename}", QJavalinImplementation::dataDownloadRecordField); + post("/{fieldName}/{filename}", QJavalinImplementation::dataDownloadRecordField); + QJavalinScriptsHandler.defineRecordRoutes(); }); }); @@ -733,6 +741,7 @@ public class QJavalinImplementation getInput.setTableName(tableName); getInput.setShouldGenerateDisplayValues(true); getInput.setShouldTranslatePossibleValues(true); + getInput.setShouldFetchHeavyFields(true); PermissionsHelper.checkTablePermissionThrowing(getInput, TablePermissionSubType.READ); @@ -744,6 +753,149 @@ public class QJavalinImplementation GetAction getAction = new GetAction(); GetOutput getOutput = getAction.execute(getInput); + /////////////////////////////////////////////////////// + // throw a not found error if the record isn't found // + /////////////////////////////////////////////////////// + QRecord record = getOutput.getRecord(); + if(record == null) + { + throw (new QNotFoundException("Could not find " + table.getLabel() + " with " + + table.getFields().get(table.getPrimaryKeyField()).getLabel() + " of " + primaryKey)); + } + + setBlobValuesToDownloadUrls(table, List.of(record)); + + QJavalinAccessLogger.logEndSuccess(); + context.result(JsonUtils.toJson(record)); + } + catch(Exception e) + { + QJavalinAccessLogger.logEndFail(e); + handleException(context, e); + } + } + + + + /******************************************************************************* + ** For any BLOB type fields in the list of records, change their value to + ** the URL where they can be downloaded, and set their display value to a file name. + *******************************************************************************/ + private static void setBlobValuesToDownloadUrls(QTableMetaData table, List records) + { + for(QFieldMetaData field : table.getFields().values()) + { + if(field.getType().equals(QFieldType.BLOB)) + { + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // file name comes from: // + // if there's a FILE_DOWNLOAD adornment, with a FILE_NAME_FIELD value, then the full filename comes from that field // + // - unless it was empty - then we do the "default thing": // + // else - the "default thing" is: // + // - tableLabel primaryKey fieldLabel // + // - and - if the FILE_DOWNLOAD adornment had a DEFAULT_EXTENSION, then it gets added (preceded by a dot) // + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + Optional fileDownloadAdornment = field.getAdornments().stream().filter(a -> a.getType().equals(AdornmentType.FILE_DOWNLOAD)).findFirst(); + Map adornmentValues = Collections.emptyMap(); + + if(fileDownloadAdornment.isPresent()) + { + adornmentValues = fileDownloadAdornment.get().getValues(); + } + + String fileNameField = ValueUtils.getValueAsString(adornmentValues.get(AdornmentType.FileDownloadValues.FILE_NAME_FIELD)); + String fileNameFormat = ValueUtils.getValueAsString(adornmentValues.get(AdornmentType.FileDownloadValues.FILE_NAME_FORMAT)); + String defaultExtension = ValueUtils.getValueAsString(adornmentValues.get(AdornmentType.FileDownloadValues.DEFAULT_EXTENSION)); + + for(QRecord record : records) + { + Serializable primaryKey = record.getValue(table.getPrimaryKeyField()); + String fileName = null; + + ////////////////////////////////////////////////// + // try to make file name from the fileNameField // + ////////////////////////////////////////////////// + if(StringUtils.hasContent(fileNameField)) + { + fileName = record.getValueString(fileNameField); + } + + if(!StringUtils.hasContent(fileName)) + { + if(StringUtils.hasContent(fileNameFormat)) + { + @SuppressWarnings("unchecked") // instance validation should make this safe! + List fileNameFormatFields = (List) adornmentValues.get(AdornmentType.FileDownloadValues.FILE_NAME_FORMAT_FIELDS); + fileName = QValueFormatter.formatStringWithValues(fileNameFormat, fileNameFormatFields); + } + } + + if(!StringUtils.hasContent(fileName)) + { + ////////////////////////////////// + // make default name if missing // + ////////////////////////////////// + fileName = table.getLabel() + " " + primaryKey + " " + field.getLabel(); + + if(StringUtils.hasContent(defaultExtension)) + { + ////////////////////////////////////////// + // add default extension if we have one // + ////////////////////////////////////////// + fileName += "." + defaultExtension; + } + } + + record.setValue(field.getName(), "/data/" + table.getName() + "/" + primaryKey + "/" + field.getName() + "/" + fileName); + record.setDisplayValue(field.getName(), fileName); + } + } + } + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + private static void dataDownloadRecordField(Context context) + { + String tableName = context.pathParam("table"); + String primaryKey = context.pathParam("primaryKey"); + String fieldName = context.pathParam("fieldName"); + String filename = context.pathParam("filename"); + + try + { + QTableMetaData table = qInstance.getTable(tableName); + GetInput getInput = new GetInput(); + + setupSession(context, getInput); + QJavalinAccessLogger.logStart("downloadRecordField", logPair("table", tableName), logPair("primaryKey", primaryKey), logPair("fieldName", fieldName)); + + //////////////////////////////////////////// + // validate field name - 404 if not found // + //////////////////////////////////////////// + QFieldMetaData fieldMetaData; + try + { + fieldMetaData = table.getField(fieldName); + } + catch(Exception e) + { + throw (new QNotFoundException("Could not find field named " + fieldName + " on table " + tableName)); + } + + getInput.setTableName(tableName); + getInput.setShouldFetchHeavyFields(true); + + PermissionsHelper.checkTablePermissionThrowing(getInput, TablePermissionSubType.READ); + + getInput.setPrimaryKey(primaryKey); + + GetAction getAction = new GetAction(); + GetOutput getOutput = getAction.execute(getInput); + /////////////////////////////////////////////////////// // throw a not found error if the record isn't found // /////////////////////////////////////////////////////// @@ -753,8 +905,27 @@ public class QJavalinImplementation + table.getFields().get(table.getPrimaryKeyField()).getLabel() + " of " + primaryKey)); } + String mimeType = null; + Optional fileDownloadAdornment = fieldMetaData.getAdornments().stream().filter(a -> a.getType().equals(AdornmentType.FILE_DOWNLOAD)).findFirst(); + if(fileDownloadAdornment.isPresent()) + { + Map values = fileDownloadAdornment.get().getValues(); + mimeType = ValueUtils.getValueAsString(values.get(AdornmentType.FileDownloadValues.DEFAULT_MIME_TYPE)); + } + + if(mimeType != null) + { + context.contentType(mimeType); + } + + if(context.queryParamMap().containsKey("download") || context.formParamMap().containsKey("download")) + { + context.header("Content-Disposition", "attachment; filename=" + filename); + } + + context.result(getOutput.getRecord().getValueByteArray(fieldName)); + QJavalinAccessLogger.logEndSuccess(); - context.result(JsonUtils.toJson(getOutput.getRecord())); } catch(Exception e) { @@ -888,6 +1059,8 @@ public class QJavalinImplementation QueryAction queryAction = new QueryAction(); QueryOutput queryOutput = queryAction.execute(queryInput); + setBlobValuesToDownloadUrls(QContext.getQInstance().getTable(table), queryOutput.getRecords()); + QJavalinAccessLogger.logEndSuccess(logPair("recordCount", queryOutput.getRecords().size()), logPairIfSlow("filter", filter, SLOW_LOG_THRESHOLD_MS), logPairIfSlow("joins", queryJoins, SLOW_LOG_THRESHOLD_MS)); context.result(JsonUtils.toJson(queryOutput)); }