From fdbc7510485fc548fc0f19b06be4c0aeb5c22f05 Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Mon, 5 Jun 2023 08:17:45 -0500 Subject: [PATCH] more support for blobs - fetch heavy flag for process extract - ignore blobs in bulk edit, load - set download urls in child-list render and javalin via shared method in QValueFormatter also, make ETL load step aware of transform step, and more flexible process summary between them --- .../widgets/ChildRecordListRenderer.java | 23 ++-- .../core/actions/values/QValueFormatter.java | 128 ++++++++++++++++++ .../core/instances/QInstanceEnricher.java | 3 +- .../processes/RunBackendStepOutput.java | 22 +++ .../AbstractProcessMetaDataBuilder.java | 11 ++ .../AbstractLoadStep.java | 33 +++++ .../ExtractViaQueryStep.java | 5 + .../StreamedETLExecuteStep.java | 12 +- .../StreamedETLWithFrontendProcess.java | 2 + .../javalin/QJavalinImplementation.java | 128 +----------------- 10 files changed, 227 insertions(+), 140 deletions(-) diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/dashboard/widgets/ChildRecordListRenderer.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/dashboard/widgets/ChildRecordListRenderer.java index fdb7b9df..bf1cea59 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/dashboard/widgets/ChildRecordListRenderer.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/dashboard/widgets/ChildRecordListRenderer.java @@ -33,6 +33,7 @@ import java.util.Set; import com.kingsrook.qqq.backend.core.actions.tables.CountAction; import com.kingsrook.qqq.backend.core.actions.tables.GetAction; import com.kingsrook.qqq.backend.core.actions.tables.QueryAction; +import com.kingsrook.qqq.backend.core.actions.values.QValueFormatter; import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.exceptions.QNotFoundException; import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput; @@ -160,10 +161,12 @@ public class ChildRecordListRenderer extends AbstractWidgetRenderer @Override public RenderWidgetOutput render(RenderWidgetInput input) throws QException { - String widgetLabel = input.getQueryParams().get("widgetLabel"); - String joinName = input.getQueryParams().get("joinName"); - QJoinMetaData join = input.getInstance().getJoin(joinName); - String id = input.getQueryParams().get("id"); + String widgetLabel = input.getQueryParams().get("widgetLabel"); + String joinName = input.getQueryParams().get("joinName"); + QJoinMetaData join = input.getInstance().getJoin(joinName); + String id = input.getQueryParams().get("id"); + QTableMetaData leftTable = input.getInstance().getTable(join.getLeftTable()); + QTableMetaData rightTable = input.getInstance().getTable(join.getRightTable()); Integer maxRows = null; if(StringUtils.hasContent(input.getQueryParams().get("maxRows"))) @@ -187,8 +190,7 @@ public class ChildRecordListRenderer extends AbstractWidgetRenderer if(record == null) { - QTableMetaData table = input.getInstance().getTable(join.getLeftTable()); - throw (new QNotFoundException("Could not find " + (table == null ? "" : table.getLabel()) + " with primary key " + id)); + throw (new QNotFoundException("Could not find " + (leftTable == null ? "" : leftTable.getLabel()) + " with primary key " + id)); } //////////////////////////////////////////////////////////////////// @@ -209,6 +211,8 @@ public class ChildRecordListRenderer extends AbstractWidgetRenderer queryInput.setFilter(filter); QueryOutput queryOutput = new QueryAction().execute(queryInput); + QValueFormatter.setBlobValuesToDownloadUrls(rightTable, queryOutput.getRecords()); + int totalRows = queryOutput.getRecords().size(); if(maxRows != null && (queryOutput.getRecords().size() == maxRows)) { @@ -222,11 +226,10 @@ public class ChildRecordListRenderer extends AbstractWidgetRenderer totalRows = new CountAction().execute(countInput).getCount(); } - QTableMetaData table = input.getInstance().getTable(join.getRightTable()); - String tablePath = input.getInstance().getTablePath(table.getName()); - String viewAllLink = tablePath == null ? null : (tablePath + "?filter=" + URLEncoder.encode(JsonUtils.toJson(filter), Charset.defaultCharset())); + String tablePath = input.getInstance().getTablePath(rightTable.getName()); + String viewAllLink = tablePath == null ? null : (tablePath + "?filter=" + URLEncoder.encode(JsonUtils.toJson(filter), Charset.defaultCharset())); - ChildRecordListData widgetData = new ChildRecordListData(widgetLabel, queryOutput, table, tablePath, viewAllLink, totalRows); + ChildRecordListData widgetData = new ChildRecordListData(widgetLabel, queryOutput, rightTable, tablePath, viewAllLink, totalRows); if(BooleanUtils.isTrue(ValueUtils.getValueAsBoolean(input.getQueryParams().get("canAddChildRecord")))) { diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/values/QValueFormatter.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/values/QValueFormatter.java index 3356fbae..c58d78cc 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/values/QValueFormatter.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/values/QValueFormatter.java @@ -29,13 +29,18 @@ import java.time.LocalTime; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import com.kingsrook.qqq.backend.core.context.QContext; import com.kingsrook.qqq.backend.core.logging.QLogger; import com.kingsrook.qqq.backend.core.model.data.QRecord; +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.tables.ExposedJoin; import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; import com.kingsrook.qqq.backend.core.utils.CollectionUtils; @@ -434,4 +439,127 @@ public class QValueFormatter } } + + + /******************************************************************************* + ** 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. + *******************************************************************************/ + public 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.getAdornment(AdornmentType.FILE_DOWNLOAD); + 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) + { + if(!doesFieldHaveValue(field, record)) + { + continue; + } + + 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); + List values = fileNameFormatFields.stream().map(f -> ValueUtils.getValueAsString(record.getValue(f))).toList(); + fileName = QValueFormatter.formatStringWithValues(fileNameFormat, values); + } + } + + 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 boolean doesFieldHaveValue(QFieldMetaData field, QRecord record) + { + boolean fieldHasValue = false; + + try + { + if(record.getValue(field.getName()) != null) + { + fieldHasValue = true; + } + else if(field.getIsHeavy()) + { + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // heavy fields that weren't fetched - they should have a backend-detail specifying their length (or null if null) // + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + Map heavyFieldLengths = (Map) record.getBackendDetail(QRecord.BACKEND_DETAILS_TYPE_HEAVY_FIELD_LENGTHS); + if(heavyFieldLengths != null) + { + Integer fieldLength = ValueUtils.getValueAsInteger(heavyFieldLengths.get(field.getName())); + if(fieldLength != null && fieldLength > 0) + { + fieldHasValue = true; + } + } + } + } + catch(Exception e) + { + LOG.info("Error checking if field has value", e, logPair("fieldName", field.getName()), logPair("record", record)); + } + + return fieldHasValue; + } + } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceEnricher.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceEnricher.java index 465434bc..816c9257 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceEnricher.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceEnricher.java @@ -716,7 +716,7 @@ public class QInstanceEnricher try { QFieldMetaData field = table.getField(fieldName); - if(field.getIsEditable()) + if(field.getIsEditable() && !field.getType().equals(QFieldType.BLOB)) { editableFields.add(field); } @@ -774,6 +774,7 @@ public class QInstanceEnricher List editableFields = table.getFields().values().stream() .filter(QFieldMetaData::getIsEditable) + .filter(f -> !f.getType().equals(QFieldType.BLOB)) .toList(); QFrontendStepMetaData editScreen = new QFrontendStepMetaData() diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/processes/RunBackendStepOutput.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/processes/RunBackendStepOutput.java index fac4b6c4..9a915141 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/processes/RunBackendStepOutput.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/processes/RunBackendStepOutput.java @@ -29,6 +29,7 @@ import java.util.List; import java.util.Map; import com.kingsrook.qqq.backend.core.model.actions.AbstractActionOutput; import com.kingsrook.qqq.backend.core.model.actions.audits.AuditInput; +import com.kingsrook.qqq.backend.core.model.actions.audits.AuditSingleInput; import com.kingsrook.qqq.backend.core.model.data.QRecord; import com.kingsrook.qqq.backend.core.utils.ValueUtils; @@ -290,4 +291,25 @@ public class RunBackendStepOutput extends AbstractActionOutput implements Serial return (this); } + + + /******************************************************************************* + ** + *******************************************************************************/ + public void addAuditSingleInput(AuditSingleInput auditSingleInput) + { + if(getAuditInputList() == null) + { + setAuditInputList(new ArrayList<>()); + } + + if(getAuditInputList().isEmpty()) + { + getAuditInputList().add(new AuditInput()); + } + + AuditInput auditInput = getAuditInputList().get(0); + auditInput.addAuditSingleInput(auditSingleInput); + } + } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/processes/AbstractProcessMetaDataBuilder.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/processes/AbstractProcessMetaDataBuilder.java index 64d2580e..c1fe43f9 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/processes/AbstractProcessMetaDataBuilder.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/processes/AbstractProcessMetaDataBuilder.java @@ -70,6 +70,17 @@ public class AbstractProcessMetaDataBuilder + /******************************************************************************* + ** + *******************************************************************************/ + public AbstractProcessMetaDataBuilder withInputFieldDefaultValue(String fieldName, Serializable value) + { + setInputFieldDefaultValue(fieldName, value); + return (this); + } + + + /******************************************************************************* ** *******************************************************************************/ diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/etl/streamedwithfrontend/AbstractLoadStep.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/etl/streamedwithfrontend/AbstractLoadStep.java index fa7b086a..835e79d7 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/etl/streamedwithfrontend/AbstractLoadStep.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/etl/streamedwithfrontend/AbstractLoadStep.java @@ -47,6 +47,8 @@ public abstract class AbstractLoadStep implements BackendStep private Optional transaction = Optional.empty(); protected QSession session; + private AbstractTransformStep transformStep; + /******************************************************************************* @@ -122,4 +124,35 @@ public abstract class AbstractLoadStep implements BackendStep return (null); } + + + /******************************************************************************* + ** Getter for transformStep + *******************************************************************************/ + public AbstractTransformStep getTransformStep() + { + return (this.transformStep); + } + + + + /******************************************************************************* + ** Setter for transformStep + *******************************************************************************/ + public void setTransformStep(AbstractTransformStep transformStep) + { + this.transformStep = transformStep; + } + + + + /******************************************************************************* + ** Fluent setter for transformStep + *******************************************************************************/ + public AbstractLoadStep withTransformStep(AbstractTransformStep transformStep) + { + this.transformStep = transformStep; + return (this); + } + } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/etl/streamedwithfrontend/ExtractViaQueryStep.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/etl/streamedwithfrontend/ExtractViaQueryStep.java index 8f38b8fd..c4d7ff89 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/etl/streamedwithfrontend/ExtractViaQueryStep.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/etl/streamedwithfrontend/ExtractViaQueryStep.java @@ -109,6 +109,11 @@ public class ExtractViaQueryStep extends AbstractExtractStep queryInput.setRecordPipe(getRecordPipe()); queryInput.setAsyncJobCallback(runBackendStepInput.getAsyncJobCallback()); + if(runBackendStepInput.getValuePrimitiveBoolean(StreamedETLWithFrontendProcess.FIELD_FETCH_HEAVY_FIELDS)) + { + queryInput.setShouldFetchHeavyFields(true); + } + customizeInputPreQuery(queryInput); new QueryAction().execute(queryInput); diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/etl/streamedwithfrontend/StreamedETLExecuteStep.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/etl/streamedwithfrontend/StreamedETLExecuteStep.java index 2b61ead3..3894ea24 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/etl/streamedwithfrontend/StreamedETLExecuteStep.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/etl/streamedwithfrontend/StreamedETLExecuteStep.java @@ -73,6 +73,8 @@ public class StreamedETLExecuteStep extends BaseStreamedETLStep implements Backe AbstractTransformStep transformStep = getTransformStep(runBackendStepInput); AbstractLoadStep loadStep = getLoadStep(runBackendStepInput); + loadStep.setTransformStep(transformStep); + ///////////////////////////////////////////////////////////////////////////// // let the load step override the capacity for the record pipe. // // this is useful for slower load steps - so that the extract step doesn't // @@ -140,13 +142,17 @@ public class StreamedETLExecuteStep extends BaseStreamedETLStep implements Backe ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // get the process summary from the load step, if it's a summary-provider -- else, use the transform step (which is always a provider) // ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + ArrayList processSummaryLines = null; if(loadStep instanceof ProcessSummaryProviderInterface provider) { - runBackendStepOutput.addValue(StreamedETLWithFrontendProcess.FIELD_PROCESS_SUMMARY, provider.doGetProcessSummary(runBackendStepOutput, true)); + processSummaryLines = provider.doGetProcessSummary(runBackendStepOutput, true); + runBackendStepOutput.addValue(StreamedETLWithFrontendProcess.FIELD_PROCESS_SUMMARY, processSummaryLines); } - else + + if(CollectionUtils.nullSafeIsEmpty(processSummaryLines)) { - runBackendStepOutput.addValue(StreamedETLWithFrontendProcess.FIELD_PROCESS_SUMMARY, transformStep.doGetProcessSummary(runBackendStepOutput, true)); + processSummaryLines = transformStep.doGetProcessSummary(runBackendStepOutput, true); + runBackendStepOutput.addValue(StreamedETLWithFrontendProcess.FIELD_PROCESS_SUMMARY, processSummaryLines); } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/etl/streamedwithfrontend/StreamedETLWithFrontendProcess.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/etl/streamedwithfrontend/StreamedETLWithFrontendProcess.java index 7b31440d..126735ef 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/etl/streamedwithfrontend/StreamedETLWithFrontendProcess.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/etl/streamedwithfrontend/StreamedETLWithFrontendProcess.java @@ -81,6 +81,7 @@ public class StreamedETLWithFrontendProcess public static final String FIELD_DESTINATION_TABLE = "destinationTable"; // String public static final String FIELD_RECORD_COUNT = "recordCount"; // Integer public static final String FIELD_DEFAULT_QUERY_FILTER = "defaultQueryFilter"; // QQueryFilter or String (json, of q QQueryFilter) + public static final String FIELD_FETCH_HEAVY_FIELDS = "fetchHeavyFields"; // Boolean public static final String FIELD_SUPPORTS_FULL_VALIDATION = "supportsFullValidation"; // Boolean public static final String FIELD_DO_FULL_VALIDATION = "doFullValidation"; // Boolean @@ -142,6 +143,7 @@ public class StreamedETLWithFrontendProcess .withCode(new QCodeReference(StreamedETLPreviewStep.class)) .withInputData(new QFunctionInputMetaData() .withField(new QFieldMetaData(FIELD_SOURCE_TABLE, QFieldType.STRING).withDefaultValue(defaultFieldValues.get(FIELD_SOURCE_TABLE))) + .withField(new QFieldMetaData(FIELD_FETCH_HEAVY_FIELDS, QFieldType.BOOLEAN).withDefaultValue(defaultFieldValues.getOrDefault(FIELD_FETCH_HEAVY_FIELDS, false))) .withField(new QFieldMetaData(FIELD_DESTINATION_TABLE, QFieldType.STRING).withDefaultValue(defaultFieldValues.get(FIELD_DESTINATION_TABLE))) .withField(new QFieldMetaData(FIELD_SUPPORTS_FULL_VALIDATION, QFieldType.BOOLEAN).withDefaultValue(defaultFieldValues.getOrDefault(FIELD_SUPPORTS_FULL_VALIDATION, true))) .withField(new QFieldMetaData(FIELD_DO_FULL_VALIDATION, QFieldType.BOOLEAN).withDefaultValue(defaultFieldValues.get(FIELD_DO_FULL_VALIDATION))) 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 9c8174eb..93f0c4c7 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 @@ -32,7 +32,6 @@ 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; @@ -858,7 +857,7 @@ public class QJavalinImplementation + table.getFields().get(table.getPrimaryKeyField()).getLabel() + " of " + primaryKey)); } - setBlobValuesToDownloadUrls(table, List.of(record)); + QValueFormatter.setBlobValuesToDownloadUrls(table, List.of(record)); QJavalinAccessLogger.logEndSuccess(); context.result(JsonUtils.toJson(record)); @@ -872,129 +871,6 @@ public class QJavalinImplementation - /******************************************************************************* - ** 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.getAdornment(AdornmentType.FILE_DOWNLOAD); - 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) - { - if(!doesFieldHaveValue(field, record)) - { - continue; - } - - 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); - List values = fileNameFormatFields.stream().map(f -> ValueUtils.getValueAsString(record.getValue(f))).toList(); - fileName = QValueFormatter.formatStringWithValues(fileNameFormat, values); - } - } - - 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 boolean doesFieldHaveValue(QFieldMetaData field, QRecord record) - { - boolean fieldHasValue = false; - - try - { - if(record.getValue(field.getName()) != null) - { - fieldHasValue = true; - } - else if(field.getIsHeavy()) - { - ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - // heavy fields that weren't fetched - they should have a backend-detail specifying their length (or null if null) // - ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - Map heavyFieldLengths = (Map) record.getBackendDetail(QRecord.BACKEND_DETAILS_TYPE_HEAVY_FIELD_LENGTHS); - if(heavyFieldLengths != null) - { - Integer fieldLength = ValueUtils.getValueAsInteger(heavyFieldLengths.get(field.getName())); - if(fieldLength != null && fieldLength > 0) - { - fieldHasValue = true; - } - } - } - } - catch(Exception e) - { - LOG.info("Error checking if field has value", e, logPair("fieldName", field.getName()), logPair("record", record)); - } - - return fieldHasValue; - } - - - /******************************************************************************* ** *******************************************************************************/ @@ -1199,7 +1075,7 @@ public class QJavalinImplementation QueryAction queryAction = new QueryAction(); QueryOutput queryOutput = queryAction.execute(queryInput); - setBlobValuesToDownloadUrls(QContext.getQInstance().getTable(table), queryOutput.getRecords()); + QValueFormatter.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));