mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-17 20:50:44 +00:00
Merged dev into feature/meta-data-loaders
This commit is contained in:
@ -100,7 +100,12 @@
|
||||
<dependency>
|
||||
<groupId>org.dhatim</groupId>
|
||||
<artifactId>fastexcel</artifactId>
|
||||
<version>0.12.15</version>
|
||||
<version>0.18.4</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.dhatim</groupId>
|
||||
<artifactId>fastexcel-reader</artifactId>
|
||||
<version>0.18.4</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.poi</groupId>
|
||||
@ -112,6 +117,14 @@
|
||||
<artifactId>poi-ooxml</artifactId>
|
||||
<version>5.2.5</version>
|
||||
</dependency>
|
||||
|
||||
<!-- adding to help FastExcel -->
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
<version>2.16.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.auth0</groupId>
|
||||
<artifactId>auth0</artifactId>
|
||||
|
@ -27,6 +27,7 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
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.actions.tables.update.UpdateInput;
|
||||
@ -160,4 +161,18 @@ public interface RecordCustomizerUtilityInterface
|
||||
return (oldRecordMap);
|
||||
}
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
static <T extends Serializable> T getValueFromRecordOrOldRecord(String fieldName, QRecord record, Serializable primaryKey, Optional<Map<Serializable, QRecord>> oldRecordMap)
|
||||
{
|
||||
T value = (T) record.getValue(fieldName);
|
||||
if(value == null && primaryKey != null && oldRecordMap.isPresent() && oldRecordMap.get().containsKey(primaryKey))
|
||||
{
|
||||
value = (T) oldRecordMap.get().get(primaryKey).getValue(fieldName);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -22,15 +22,19 @@
|
||||
package com.kingsrook.qqq.backend.core.actions.customizers;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.QueryOrGetInputInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
@ -47,7 +51,6 @@ public interface TableCustomizerInterface
|
||||
{
|
||||
QLogger LOG = QLogger.getLogger(TableCustomizerInterface.class);
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** custom actions to run after a query (or get!) takes place.
|
||||
**
|
||||
@ -77,8 +80,15 @@ public interface TableCustomizerInterface
|
||||
*******************************************************************************/
|
||||
default List<QRecord> preInsert(InsertInput insertInput, List<QRecord> records, boolean isPreview) throws QException
|
||||
{
|
||||
LOG.info("A default implementation of preInsert is running... Probably not expected!", logPair("tableName", insertInput.getTableName()));
|
||||
return (records);
|
||||
try
|
||||
{
|
||||
return (preInsertOrUpdate(insertInput, records, isPreview, Optional.empty()));
|
||||
}
|
||||
catch(NotImplementedHereException e)
|
||||
{
|
||||
LOG.info("A default implementation of preInsert is running... Probably not expected!", logPair("tableName", insertInput.getTableName()));
|
||||
return (records);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -104,8 +114,15 @@ public interface TableCustomizerInterface
|
||||
*******************************************************************************/
|
||||
default List<QRecord> postInsert(InsertInput insertInput, List<QRecord> records) throws QException
|
||||
{
|
||||
LOG.info("A default implementation of postInsert is running... Probably not expected!", logPair("tableName", insertInput.getTableName()));
|
||||
return (records);
|
||||
try
|
||||
{
|
||||
return (postInsertOrUpdate(insertInput, records, Optional.empty()));
|
||||
}
|
||||
catch(NotImplementedHereException e)
|
||||
{
|
||||
LOG.info("A default implementation of postInsert is running... Probably not expected!", logPair("tableName", insertInput.getTableName()));
|
||||
return (records);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -130,8 +147,15 @@ public interface TableCustomizerInterface
|
||||
*******************************************************************************/
|
||||
default List<QRecord> preUpdate(UpdateInput updateInput, List<QRecord> records, boolean isPreview, Optional<List<QRecord>> oldRecordList) throws QException
|
||||
{
|
||||
LOG.info("A default implementation of preUpdate is running... Probably not expected!", logPair("tableName", updateInput.getTableName()));
|
||||
return (records);
|
||||
try
|
||||
{
|
||||
return (preInsertOrUpdate(updateInput, records, isPreview, oldRecordList));
|
||||
}
|
||||
catch(NotImplementedHereException e)
|
||||
{
|
||||
LOG.info("A default implementation of preUpdate is running... Probably not expected!", logPair("tableName", updateInput.getTableName()));
|
||||
return (records);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -151,8 +175,15 @@ public interface TableCustomizerInterface
|
||||
*******************************************************************************/
|
||||
default List<QRecord> postUpdate(UpdateInput updateInput, List<QRecord> records, Optional<List<QRecord>> oldRecordList) throws QException
|
||||
{
|
||||
LOG.info("A default implementation of postUpdate is running... Probably not expected!", logPair("tableName", updateInput.getTableName()));
|
||||
return (records);
|
||||
try
|
||||
{
|
||||
return (postInsertOrUpdate(updateInput, records, oldRecordList));
|
||||
}
|
||||
catch(NotImplementedHereException e)
|
||||
{
|
||||
LOG.info("A default implementation of postUpdate is running... Probably not expected!", logPair("tableName", updateInput.getTableName()));
|
||||
return (records);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -199,4 +230,59 @@ public interface TableCustomizerInterface
|
||||
return (records);
|
||||
}
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
** Optional method to override in a customizer that does the same thing for
|
||||
** both preInsert & preUpdate.
|
||||
***************************************************************************/
|
||||
default List<QRecord> preInsertOrUpdate(AbstractActionInput input, List<QRecord> records, boolean isPreview, Optional<List<QRecord>> oldRecordList) throws QException
|
||||
{
|
||||
throw NotImplementedHereException.instance;
|
||||
}
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
** Optional method to override in a customizer that does the same thing for
|
||||
** both postInsert & postUpdate.
|
||||
***************************************************************************/
|
||||
default List<QRecord> postInsertOrUpdate(AbstractActionInput input, List<QRecord> records, Optional<List<QRecord>> oldRecordList) throws QException
|
||||
{
|
||||
throw NotImplementedHereException.instance;
|
||||
}
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
default Optional<Map<Serializable, QRecord>> oldRecordListToMap(String primaryKeyField, Optional<List<QRecord>> oldRecordList)
|
||||
{
|
||||
if(oldRecordList.isPresent())
|
||||
{
|
||||
return (Optional.of(CollectionUtils.listToMap(oldRecordList.get(), r -> r.getValue(primaryKeyField))));
|
||||
}
|
||||
else
|
||||
{
|
||||
return (Optional.empty());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
class NotImplementedHereException extends QException
|
||||
{
|
||||
private static NotImplementedHereException instance = new NotImplementedHereException();
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
private NotImplementedHereException()
|
||||
{
|
||||
super("Not implemented here");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -37,6 +37,8 @@ import com.kingsrook.qqq.backend.core.actions.values.QValueFormatter;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QNotFoundException;
|
||||
import com.kingsrook.qqq.backend.core.instances.QInstanceValidator;
|
||||
import com.kingsrook.qqq.backend.core.instances.validation.plugins.QInstanceValidatorPluginInterface;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetInput;
|
||||
@ -51,12 +53,15 @@ import com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.ChildRecordListData;
|
||||
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.WidgetType;
|
||||
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.code.QCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.AbstractWidgetMetaDataBuilder;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaDataInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinOn;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
@ -83,7 +88,9 @@ public class ChildRecordListRenderer extends AbstractWidgetRenderer
|
||||
.withIsCard(true)
|
||||
.withCodeReference(new QCodeReference(ChildRecordListRenderer.class))
|
||||
.withType(WidgetType.CHILD_RECORD_LIST.getType())
|
||||
.withDefaultValue("joinName", join.getName())));
|
||||
.withDefaultValue("joinName", join.getName())
|
||||
.withValidatorPlugin(new ChildRecordListWidgetValidator())
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@ -168,6 +175,7 @@ public class ChildRecordListRenderer extends AbstractWidgetRenderer
|
||||
widgetMetaData.withDefaultValue("manageAssociationName", manageAssociationName);
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -194,7 +202,7 @@ public class ChildRecordListRenderer extends AbstractWidgetRenderer
|
||||
}
|
||||
else if(input.getWidgetMetaData().getDefaultValues().containsKey("maxRows"))
|
||||
{
|
||||
maxRows = ValueUtils.getValueAsInteger(input.getWidgetMetaData().getDefaultValues().containsKey("maxRows"));
|
||||
maxRows = ValueUtils.getValueAsInteger(input.getWidgetMetaData().getDefaultValues().get("maxRows"));
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -299,6 +307,13 @@ public class ChildRecordListRenderer extends AbstractWidgetRenderer
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(widgetValues.containsKey("defaultValuesForNewChildRecordsFromParentFields"))
|
||||
{
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, String> defaultValuesForNewChildRecordsFromParentFields = (Map<String, String>) widgetValues.get("defaultValuesForNewChildRecordsFromParentFields");
|
||||
widgetData.setDefaultValuesForNewChildRecordsFromParentFields(defaultValuesForNewChildRecordsFromParentFields);
|
||||
}
|
||||
}
|
||||
|
||||
widgetData.setAllowRecordEdit(BooleanUtils.isTrue(ValueUtils.getValueAsBoolean(input.getQueryParams().get("allowRecordEdit"))));
|
||||
@ -313,4 +328,68 @@ public class ChildRecordListRenderer extends AbstractWidgetRenderer
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
private static class ChildRecordListWidgetValidator implements QInstanceValidatorPluginInterface<QWidgetMetaDataInterface>
|
||||
{
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public void validate(QWidgetMetaDataInterface widgetMetaData, QInstance qInstance, QInstanceValidator qInstanceValidator)
|
||||
{
|
||||
String prefix = "Widget " + widgetMetaData.getName() + ": ";
|
||||
|
||||
//////////////////////////////////
|
||||
// make sure join name is given //
|
||||
//////////////////////////////////
|
||||
String joinName = ValueUtils.getValueAsString(CollectionUtils.nonNullMap(widgetMetaData.getDefaultValues()).get("joinName"));
|
||||
if(qInstanceValidator.assertCondition(StringUtils.hasContent(joinName), prefix + "defaultValue for joinName must be given"))
|
||||
{
|
||||
///////////////////////////
|
||||
// make sure join exists //
|
||||
///////////////////////////
|
||||
QJoinMetaData join = qInstance.getJoin(joinName);
|
||||
if(qInstanceValidator.assertCondition(join != null, prefix + "No join named " + joinName + " exists in the instance"))
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////////////////////
|
||||
// if there's a manageAssociationName, make sure the table has that association //
|
||||
//////////////////////////////////////////////////////////////////////////////////
|
||||
String manageAssociationName = ValueUtils.getValueAsString(widgetMetaData.getDefaultValues().get("manageAssociationName"));
|
||||
if(StringUtils.hasContent(manageAssociationName))
|
||||
{
|
||||
validateAssociationName(prefix, manageAssociationName, join, qInstance, qInstanceValidator);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
private void validateAssociationName(String prefix, String manageAssociationName, QJoinMetaData join, QInstance qInstance, QInstanceValidator qInstanceValidator)
|
||||
{
|
||||
///////////////////////////////////
|
||||
// make sure join's table exists //
|
||||
///////////////////////////////////
|
||||
QTableMetaData table = qInstance.getTable(join.getLeftTable());
|
||||
if(table == null)
|
||||
{
|
||||
qInstanceValidator.getErrors().add(prefix + "Unable to validate manageAssociationName, as table [" + join.getLeftTable() + "] on left-side table of join [" + join.getName() + "] does not exist.");
|
||||
}
|
||||
else
|
||||
{
|
||||
if(CollectionUtils.nonNullList(table.getAssociations()).stream().noneMatch(a -> manageAssociationName.equals(a.getName())))
|
||||
{
|
||||
qInstanceValidator.getErrors().add(prefix + "an association named [" + manageAssociationName + "] does not exist on table [" + join.getLeftTable() + "]");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,251 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.actions.dashboard.widgets;
|
||||
|
||||
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.HashMap;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.CountAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.values.QValueFormatter;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.instances.QInstanceValidator;
|
||||
import com.kingsrook.qqq.backend.core.instances.validation.plugins.QInstanceValidatorPluginInterface;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.FilterUseCase;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
||||
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.dashboard.widgets.ChildRecordListData;
|
||||
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.WidgetType;
|
||||
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.dashboard.AbstractWidgetMetaDataBuilder;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaDataInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Generic widget to display a list of records.
|
||||
**
|
||||
** Note, closely related to (and copied from ChildRecordListRenderer.
|
||||
** opportunity to share more code with that in the future??
|
||||
*******************************************************************************/
|
||||
public class RecordListWidgetRenderer extends AbstractWidgetRenderer
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(RecordListWidgetRenderer.class);
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static Builder widgetMetaDataBuilder(String widgetName)
|
||||
{
|
||||
return (new Builder(new QWidgetMetaData()
|
||||
.withName(widgetName)
|
||||
.withIsCard(true)
|
||||
.withCodeReference(new QCodeReference(RecordListWidgetRenderer.class))
|
||||
.withType(WidgetType.CHILD_RECORD_LIST.getType())
|
||||
.withValidatorPlugin(new RecordListWidgetValidator())
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static class Builder extends AbstractWidgetMetaDataBuilder
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Builder(QWidgetMetaData widgetMetaData)
|
||||
{
|
||||
super(widgetMetaData);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Builder withLabel(String label)
|
||||
{
|
||||
widgetMetaData.setLabel(label);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Builder withMaxRows(Integer maxRows)
|
||||
{
|
||||
widgetMetaData.withDefaultValue("maxRows", maxRows);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Builder withTableName(String tableName)
|
||||
{
|
||||
widgetMetaData.withDefaultValue("tableName", tableName);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Builder withFilter(QQueryFilter filter)
|
||||
{
|
||||
widgetMetaData.withDefaultValue("filter", filter);
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public RenderWidgetOutput render(RenderWidgetInput input) throws QException
|
||||
{
|
||||
try
|
||||
{
|
||||
Integer maxRows = null;
|
||||
if(StringUtils.hasContent(input.getQueryParams().get("maxRows")))
|
||||
{
|
||||
maxRows = ValueUtils.getValueAsInteger(input.getQueryParams().get("maxRows"));
|
||||
}
|
||||
else if(input.getWidgetMetaData().getDefaultValues().containsKey("maxRows"))
|
||||
{
|
||||
maxRows = ValueUtils.getValueAsInteger(input.getWidgetMetaData().getDefaultValues().get("maxRows"));
|
||||
}
|
||||
|
||||
QQueryFilter filter = ((QQueryFilter) input.getWidgetMetaData().getDefaultValues().get("filter")).clone();
|
||||
filter.interpretValues(new HashMap<>(input.getQueryParams()), FilterUseCase.DEFAULT);
|
||||
filter.setLimit(maxRows);
|
||||
|
||||
String tableName = ValueUtils.getValueAsString(input.getWidgetMetaData().getDefaultValues().get("tableName"));
|
||||
QTableMetaData table = QContext.getQInstance().getTable(tableName);
|
||||
|
||||
QueryInput queryInput = new QueryInput();
|
||||
queryInput.setTableName(tableName);
|
||||
queryInput.setShouldTranslatePossibleValues(true);
|
||||
queryInput.setShouldGenerateDisplayValues(true);
|
||||
queryInput.setFilter(filter);
|
||||
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||
|
||||
QValueFormatter.setBlobValuesToDownloadUrls(table, queryOutput.getRecords());
|
||||
|
||||
int totalRows = queryOutput.getRecords().size();
|
||||
if(maxRows != null && (queryOutput.getRecords().size() == maxRows))
|
||||
{
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
// if the input said to only do some max, and the # of results we got is that max, //
|
||||
// then do a count query, for displaying 1-n of <count> //
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
CountInput countInput = new CountInput();
|
||||
countInput.setTableName(tableName);
|
||||
countInput.setFilter(filter);
|
||||
totalRows = new CountAction().execute(countInput).getCount();
|
||||
}
|
||||
|
||||
String tablePath = QContext.getQInstance().getTablePath(tableName);
|
||||
String viewAllLink = tablePath == null ? null : (tablePath + "?filter=" + URLEncoder.encode(JsonUtils.toJson(filter), Charset.defaultCharset()));
|
||||
|
||||
ChildRecordListData widgetData = new ChildRecordListData(input.getQueryParams().get("widgetLabel"), queryOutput, table, tablePath, viewAllLink, totalRows);
|
||||
|
||||
return (new RenderWidgetOutput(widgetData));
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.warn("Error rendering record list widget", e, logPair("widgetName", () -> input.getWidgetMetaData().getName()));
|
||||
throw (e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
private static class RecordListWidgetValidator implements QInstanceValidatorPluginInterface<QWidgetMetaDataInterface>
|
||||
{
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public void validate(QWidgetMetaDataInterface widgetMetaData, QInstance qInstance, QInstanceValidator qInstanceValidator)
|
||||
{
|
||||
String prefix = "Widget " + widgetMetaData.getName() + ": ";
|
||||
|
||||
//////////////////////////////////////////////
|
||||
// make sure table name is given and exists //
|
||||
//////////////////////////////////////////////
|
||||
QTableMetaData table = null;
|
||||
String tableName = ValueUtils.getValueAsString(CollectionUtils.nonNullMap(widgetMetaData.getDefaultValues()).get("tableName"));
|
||||
if(qInstanceValidator.assertCondition(StringUtils.hasContent(tableName), prefix + "defaultValue for tableName must be given"))
|
||||
{
|
||||
////////////////////////////
|
||||
// make sure table exists //
|
||||
////////////////////////////
|
||||
table = qInstance.getTable(tableName);
|
||||
qInstanceValidator.assertCondition(table != null, prefix + "No table named " + tableName + " exists in the instance");
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
// make sure filter is given and is valid (only check that if table is given too) //
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
QQueryFilter filter = ((QQueryFilter) widgetMetaData.getDefaultValues().get("filter"));
|
||||
if(qInstanceValidator.assertCondition(filter != null, prefix + "defaultValue for filter must be given") && table != null)
|
||||
{
|
||||
qInstanceValidator.validateQueryFilter(qInstance, prefix, table, filter, null);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -36,6 +36,8 @@ import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
||||
@ -46,6 +48,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaD
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFunctionInputMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QStepMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
|
||||
|
||||
@ -188,21 +191,40 @@ public class RunBackendStepAction
|
||||
{
|
||||
if(CollectionUtils.nullSafeIsEmpty(runBackendStepInput.getRecords()))
|
||||
{
|
||||
QueryInput queryInput = new QueryInput();
|
||||
queryInput.setTableName(inputMetaData.getRecordListMetaData().getTableName());
|
||||
QTableMetaData table = QContext.getQInstance().getTable(inputMetaData.getRecordListMetaData().getTableName());
|
||||
QueryInput queryInput = new QueryInput();
|
||||
queryInput.setTableName(table.getName());
|
||||
|
||||
// todo - handle this being async (e.g., http)
|
||||
// seems like it just needs to throw, breaking this flow, and to send a response to the frontend, directing it to prompt the user for the needed data
|
||||
// then this step can re-run, hopefully with the needed data.
|
||||
|
||||
QProcessCallback callback = runBackendStepInput.getCallback();
|
||||
if(callback == null)
|
||||
//////////////////////////////////////////////////
|
||||
// look for record ids in the input data values //
|
||||
//////////////////////////////////////////////////
|
||||
String recordIds = (String) runBackendStepInput.getValue("recordIds");
|
||||
if(recordIds == null)
|
||||
{
|
||||
throw (new QUserFacingException("Missing input records.",
|
||||
new QException("Function is missing input records, but no callback was present to request fields from a user")));
|
||||
recordIds = (String) runBackendStepInput.getValue("recordId");
|
||||
}
|
||||
|
||||
queryInput.setFilter(callback.getQueryFilter());
|
||||
///////////////////////////////////////////////////////////
|
||||
// if records were found, add as criteria to query input //
|
||||
///////////////////////////////////////////////////////////
|
||||
if(recordIds != null)
|
||||
{
|
||||
queryInput.setFilter(new QQueryFilter(new QFilterCriteria(table.getPrimaryKeyField(), QCriteriaOperator.IN, recordIds.split(","))));
|
||||
}
|
||||
else
|
||||
{
|
||||
// todo - handle this being async (e.g., http)
|
||||
// seems like it just needs to throw, breaking this flow, and to send a response to the frontend, directing it to prompt the user for the needed data
|
||||
// then this step can re-run, hopefully with the needed data.
|
||||
QProcessCallback callback = runBackendStepInput.getCallback();
|
||||
if(callback == null)
|
||||
{
|
||||
throw (new QUserFacingException("Missing input records.",
|
||||
new QException("Function is missing input records, but no callback was present to request fields from a user")));
|
||||
}
|
||||
|
||||
queryInput.setFilter(callback.getQueryFilter());
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if process has a max-no of records, set a limit on the process of that number plus 1 //
|
||||
@ -210,7 +232,7 @@ public class RunBackendStepAction
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
if(process.getMaxInputRecords() != null)
|
||||
{
|
||||
if(callback.getQueryFilter() == null)
|
||||
if(queryInput.getFilter() == null)
|
||||
{
|
||||
queryInput.setFilter(new QQueryFilter());
|
||||
}
|
||||
|
@ -32,6 +32,7 @@ import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
|
||||
import com.kingsrook.qqq.backend.core.actions.dashboard.widgets.NoCodeWidgetRenderer;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||
@ -53,6 +54,7 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.NoCodeWidgetFrontendComponentMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaData;
|
||||
@ -63,6 +65,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.processes.QStateMachineStep
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QStepMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.basepull.BasepullConfiguration;
|
||||
import com.kingsrook.qqq.backend.core.processes.tracing.ProcessTracerInterface;
|
||||
import com.kingsrook.qqq.backend.core.state.InMemoryStateProvider;
|
||||
import com.kingsrook.qqq.backend.core.state.StateProviderInterface;
|
||||
import com.kingsrook.qqq.backend.core.state.StateType;
|
||||
@ -71,6 +74,7 @@ import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
import org.apache.commons.lang.BooleanUtils;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -87,12 +91,16 @@ public class RunProcessAction
|
||||
public static final String BASEPULL_TIMESTAMP_FIELD = "basepullTimestampField";
|
||||
public static final String BASEPULL_CONFIGURATION = "basepullConfiguration";
|
||||
|
||||
public static final String PROCESS_TRACER_CODE_REFERENCE_FIELD = "processTracerCodeReference";
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// indicator that the timestamp field should be updated - e.g., the execute step is finished. //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
public static final String BASEPULL_READY_TO_UPDATE_TIMESTAMP_FIELD = "basepullReadyToUpdateTimestamp";
|
||||
public static final String BASEPULL_DID_QUERY_USING_TIMESTAMP_FIELD = "basepullDidQueryUsingTimestamp";
|
||||
|
||||
private ProcessTracerInterface processTracer;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -119,9 +127,17 @@ public class RunProcessAction
|
||||
}
|
||||
runProcessOutput.setProcessUUID(runProcessInput.getProcessUUID());
|
||||
|
||||
traceStartOrResume(runProcessInput, process);
|
||||
|
||||
UUIDAndTypeStateKey stateKey = new UUIDAndTypeStateKey(UUID.fromString(runProcessInput.getProcessUUID()), StateType.PROCESS_STATUS);
|
||||
ProcessState processState = primeProcessState(runProcessInput, stateKey, process);
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// these should always be clear when we're starting a run - so make sure they haven't leaked from previous //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
processState.clearNextStepName();
|
||||
processState.clearBackStepName();
|
||||
|
||||
/////////////////////////////////////////////////////////
|
||||
// if process is 'basepull' style, keep track of 'now' //
|
||||
/////////////////////////////////////////////////////////
|
||||
@ -160,6 +176,7 @@ public class RunProcessAction
|
||||
////////////////////////////////////////////////////////////
|
||||
// upon exception (e.g., one thrown by a step), throw it. //
|
||||
////////////////////////////////////////////////////////////
|
||||
traceBreakOrFinish(runProcessInput, runProcessOutput, qe);
|
||||
throw (qe);
|
||||
}
|
||||
catch(Exception e)
|
||||
@ -167,6 +184,7 @@ public class RunProcessAction
|
||||
////////////////////////////////////////////////////////////
|
||||
// upon exception (e.g., one thrown by a step), throw it. //
|
||||
////////////////////////////////////////////////////////////
|
||||
traceBreakOrFinish(runProcessInput, runProcessOutput, e);
|
||||
throw (new QException("Error running process", e));
|
||||
}
|
||||
finally
|
||||
@ -177,6 +195,8 @@ public class RunProcessAction
|
||||
runProcessOutput.setProcessState(processState);
|
||||
}
|
||||
|
||||
traceBreakOrFinish(runProcessInput, runProcessOutput, null);
|
||||
|
||||
return (runProcessOutput);
|
||||
}
|
||||
|
||||
@ -188,14 +208,35 @@ public class RunProcessAction
|
||||
private void runLinearStepLoop(QProcessMetaData process, ProcessState processState, UUIDAndTypeStateKey stateKey, RunProcessInput runProcessInput, RunProcessOutput runProcessOutput) throws Exception
|
||||
{
|
||||
String lastStepName = runProcessInput.getStartAfterStep();
|
||||
String startAtStep = runProcessInput.getStartAtStep();
|
||||
|
||||
while(true)
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// always refresh the step list - as any step that runs can modify it (in the process state). //
|
||||
// this is why we don't do a loop over the step list - as we'd get ConcurrentModificationExceptions. //
|
||||
// deal with if we were told, from the input, to start After a step, or start At a step. //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
List<QStepMetaData> stepList = getAvailableStepList(processState, process, lastStepName);
|
||||
List<QStepMetaData> stepList;
|
||||
if(startAtStep == null)
|
||||
{
|
||||
stepList = getAvailableStepList(processState, process, lastStepName, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
stepList = getAvailableStepList(processState, process, startAtStep, true);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// clear this field - so after we run a step, we'll then loop in last-step mode. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
startAtStep = null;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// if we're going to run a backend step now, let it see that this is a step-back //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
processState.setIsStepBack(true);
|
||||
}
|
||||
|
||||
if(stepList.isEmpty())
|
||||
{
|
||||
break;
|
||||
@ -232,7 +273,18 @@ public class RunProcessAction
|
||||
//////////////////////////////////////////////////
|
||||
throw (new QException("Unsure how to run a step of type: " + step.getClass().getName()));
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
// only let this value be set for the original back step - don't let it stick around. //
|
||||
// if a process wants to keep track of this itself, it can, but in a different slot. //
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
processState.setIsStepBack(false);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// in case we broke from the loop above (e.g., by going directly into a frontend step), once again make sure to lower this flag. //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
processState.setIsStepBack(false);
|
||||
}
|
||||
|
||||
|
||||
@ -264,6 +316,12 @@ public class RunProcessAction
|
||||
processFrontendStepFieldDefaultValues(processState, step);
|
||||
processFrontendComponents(processState, step);
|
||||
processState.setNextStepName(step.getName());
|
||||
|
||||
if(StringUtils.hasContent(step.getBackStepName()) && processState.getBackStepName().isEmpty())
|
||||
{
|
||||
processState.setBackStepName(step.getBackStepName());
|
||||
}
|
||||
|
||||
return LoopTodo.BREAK;
|
||||
}
|
||||
case SKIP ->
|
||||
@ -317,6 +375,7 @@ public class RunProcessAction
|
||||
// else run the given lastStepName //
|
||||
/////////////////////////////////////
|
||||
processState.clearNextStepName();
|
||||
processState.clearBackStepName();
|
||||
step = process.getStep(lastStepName);
|
||||
if(step == null)
|
||||
{
|
||||
@ -398,6 +457,7 @@ public class RunProcessAction
|
||||
// its sub-steps, or, to fall out of the loop and end the process. //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
processState.clearNextStepName();
|
||||
processState.clearBackStepName();
|
||||
runStateMachineStep(nextStepName.get(), process, processState, stateKey, runProcessInput, runProcessOutput, stackDepth + 1);
|
||||
return;
|
||||
}
|
||||
@ -584,6 +644,7 @@ public class RunProcessAction
|
||||
runBackendStepInput.setCallback(runProcessInput.getCallback());
|
||||
runBackendStepInput.setFrontendStepBehavior(runProcessInput.getFrontendStepBehavior());
|
||||
runBackendStepInput.setAsyncJobCallback(runProcessInput.getAsyncJobCallback());
|
||||
runBackendStepInput.setProcessTracer(processTracer);
|
||||
|
||||
runBackendStepInput.setTableName(process.getTableName());
|
||||
if(!StringUtils.hasContent(runBackendStepInput.getTableName()))
|
||||
@ -605,9 +666,13 @@ public class RunProcessAction
|
||||
runBackendStepInput.setBasepullLastRunTime((Instant) runProcessInput.getValues().get(BASEPULL_LAST_RUNTIME_KEY));
|
||||
}
|
||||
|
||||
traceStepStart(runBackendStepInput);
|
||||
|
||||
RunBackendStepOutput runBackendStepOutput = new RunBackendStepAction().execute(runBackendStepInput);
|
||||
storeState(stateKey, runBackendStepOutput.getProcessState());
|
||||
|
||||
traceStepFinish(runBackendStepInput, runBackendStepOutput);
|
||||
|
||||
if(runBackendStepOutput.getException() != null)
|
||||
{
|
||||
runProcessOutput.setException(runBackendStepOutput.getException());
|
||||
@ -621,8 +686,10 @@ public class RunProcessAction
|
||||
|
||||
/*******************************************************************************
|
||||
** Get the list of steps which are eligible to run.
|
||||
**
|
||||
** lastStep will be included in the list, or not, based on includeLastStep.
|
||||
*******************************************************************************/
|
||||
private List<QStepMetaData> getAvailableStepList(ProcessState processState, QProcessMetaData process, String lastStep) throws QException
|
||||
static List<QStepMetaData> getAvailableStepList(ProcessState processState, QProcessMetaData process, String lastStep, boolean includeLastStep) throws QException
|
||||
{
|
||||
if(lastStep == null)
|
||||
{
|
||||
@ -649,6 +716,10 @@ public class RunProcessAction
|
||||
if(stepName.equals(lastStep))
|
||||
{
|
||||
foundLastStep = true;
|
||||
if(includeLastStep)
|
||||
{
|
||||
validStepNames.add(stepName);
|
||||
}
|
||||
}
|
||||
}
|
||||
return (stepNamesToSteps(process, validStepNames));
|
||||
@ -660,7 +731,7 @@ public class RunProcessAction
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private List<QStepMetaData> stepNamesToSteps(QProcessMetaData process, List<String> stepNames) throws QException
|
||||
private static List<QStepMetaData> stepNamesToSteps(QProcessMetaData process, List<String> stepNames) throws QException
|
||||
{
|
||||
List<QStepMetaData> result = new ArrayList<>();
|
||||
|
||||
@ -744,13 +815,14 @@ public class RunProcessAction
|
||||
{
|
||||
QSession session = QContext.getQSession();
|
||||
QBackendMetaData backendMetaData = QContext.getQInstance().getBackend(process.getVariantBackend());
|
||||
if(session.getBackendVariants() == null || !session.getBackendVariants().containsKey(backendMetaData.getVariantOptionsTableTypeValue()))
|
||||
String variantTypeKey = backendMetaData.getBackendVariantsConfig().getVariantTypeKey();
|
||||
if(session.getBackendVariants() == null || !session.getBackendVariants().containsKey(variantTypeKey))
|
||||
{
|
||||
LOG.warn("Could not find Backend Variant information for Backend '" + backendMetaData.getName() + "'");
|
||||
}
|
||||
else
|
||||
{
|
||||
basepullKeyValue += "-" + session.getBackendVariants().get(backendMetaData.getVariantOptionsTableTypeValue());
|
||||
basepullKeyValue += "-" + session.getBackendVariants().get(variantTypeKey);
|
||||
}
|
||||
}
|
||||
|
||||
@ -879,4 +951,153 @@ public class RunProcessAction
|
||||
runProcessInput.getValues().put(BASEPULL_TIMESTAMP_FIELD, basepullConfiguration.getTimestampField());
|
||||
runProcessInput.getValues().put(BASEPULL_CONFIGURATION, basepullConfiguration);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
private void setupProcessTracer(RunProcessInput runProcessInput, QProcessMetaData process)
|
||||
{
|
||||
try
|
||||
{
|
||||
if(process.getProcessTracerCodeReference() != null)
|
||||
{
|
||||
processTracer = QCodeLoader.getAdHoc(ProcessTracerInterface.class, process.getProcessTracerCodeReference());
|
||||
}
|
||||
|
||||
Serializable processTracerCodeReference = runProcessInput.getValue(PROCESS_TRACER_CODE_REFERENCE_FIELD);
|
||||
if(processTracerCodeReference != null)
|
||||
{
|
||||
if(processTracerCodeReference instanceof QCodeReference codeReference)
|
||||
{
|
||||
processTracer = QCodeLoader.getAdHoc(ProcessTracerInterface.class, codeReference);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.warn("Error setting up processTracer", e, logPair("processName", runProcessInput.getProcessName()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
private void traceStartOrResume(RunProcessInput runProcessInput, QProcessMetaData process)
|
||||
{
|
||||
setupProcessTracer(runProcessInput, process);
|
||||
|
||||
try
|
||||
{
|
||||
if(processTracer != null)
|
||||
{
|
||||
if(StringUtils.hasContent(runProcessInput.getStartAfterStep()) || StringUtils.hasContent(runProcessInput.getStartAtStep()))
|
||||
{
|
||||
processTracer.handleProcessResume(runProcessInput);
|
||||
}
|
||||
else
|
||||
{
|
||||
processTracer.handleProcessStart(runProcessInput);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.info("Error in traceStart", e, logPair("processName", runProcessInput.getProcessName()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
private void traceBreakOrFinish(RunProcessInput runProcessInput, RunProcessOutput runProcessOutput, Exception processException)
|
||||
{
|
||||
try
|
||||
{
|
||||
if(processTracer != null)
|
||||
{
|
||||
ProcessState processState = runProcessOutput.getProcessState();
|
||||
boolean isBreak = true;
|
||||
|
||||
/////////////////////////////////////////////////////////////
|
||||
// if there's no next step, that means the process is done //
|
||||
/////////////////////////////////////////////////////////////
|
||||
if(processState.getNextStepName().isEmpty())
|
||||
{
|
||||
isBreak = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// or if the next step is the last index, then we're also done //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
String nextStepName = processState.getNextStepName().get();
|
||||
int nextStepIndex = processState.getStepList().indexOf(nextStepName);
|
||||
if(nextStepIndex == processState.getStepList().size() - 1)
|
||||
{
|
||||
isBreak = false;
|
||||
}
|
||||
}
|
||||
|
||||
if(isBreak)
|
||||
{
|
||||
processTracer.handleProcessBreak(runProcessInput, runProcessOutput, processException);
|
||||
}
|
||||
else
|
||||
{
|
||||
processTracer.handleProcessFinish(runProcessInput, runProcessOutput, processException);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.info("Error in traceProcessFinish", e, logPair("processName", runProcessInput.getProcessName()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
private void traceStepStart(RunBackendStepInput runBackendStepInput)
|
||||
{
|
||||
try
|
||||
{
|
||||
if(processTracer != null)
|
||||
{
|
||||
processTracer.handleStepStart(runBackendStepInput);
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.info("Error in traceStepFinish", e, logPair("processName", runBackendStepInput.getProcessName()), logPair("stepName", runBackendStepInput.getStepName()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
private void traceStepFinish(RunBackendStepInput runBackendStepInput, RunBackendStepOutput runBackendStepOutput)
|
||||
{
|
||||
try
|
||||
{
|
||||
if(processTracer != null)
|
||||
{
|
||||
processTracer.handleStepFinish(runBackendStepInput, runBackendStepOutput);
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.info("Error in traceStepFinish", e, logPair("processName", runBackendStepInput.getProcessName()), logPair("stepName", runBackendStepInput.getStepName()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -108,7 +108,7 @@ public class CsvExportStreamer implements ExportStreamerInterface
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw (new QReportingException("Error starting CSV report"));
|
||||
throw (new QReportingException("Error starting CSV report", e));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -71,6 +71,7 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.FieldAndJoinTable;
|
||||
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.reporting.QReportDataSource;
|
||||
@ -567,7 +568,7 @@ public class GenerateReportAction extends AbstractQActionFunction<ReportInput, R
|
||||
// all pivotFields that are possible value sources are implicitly translated //
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
QTableMetaData mainTable = QContext.getQInstance().getTable(dataSource.getSourceTable());
|
||||
FieldAndJoinTable fieldAndJoinTable = getFieldAndJoinTable(mainTable, summaryFieldName);
|
||||
FieldAndJoinTable fieldAndJoinTable = FieldAndJoinTable.get(mainTable, summaryFieldName);
|
||||
if(fieldAndJoinTable.field().getPossibleValueSourceName() != null)
|
||||
{
|
||||
fieldsToTranslatePossibleValues.add(summaryFieldName);
|
||||
@ -580,32 +581,6 @@ public class GenerateReportAction extends AbstractQActionFunction<ReportInput, R
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static FieldAndJoinTable getFieldAndJoinTable(QTableMetaData mainTable, String fieldName) throws QException
|
||||
{
|
||||
if(fieldName.indexOf('.') > -1)
|
||||
{
|
||||
String joinTableName = fieldName.replaceAll("\\..*", "");
|
||||
String joinFieldName = fieldName.replaceAll(".*\\.", "");
|
||||
|
||||
QTableMetaData joinTable = QContext.getQInstance().getTable(joinTableName);
|
||||
if(joinTable == null)
|
||||
{
|
||||
throw (new QException("Unrecognized join table name: " + joinTableName));
|
||||
}
|
||||
|
||||
return new FieldAndJoinTable(joinTable.getField(joinFieldName), joinTable);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new FieldAndJoinTable(mainTable.getField(fieldName), mainTable);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -756,7 +731,7 @@ public class GenerateReportAction extends AbstractQActionFunction<ReportInput, R
|
||||
SummaryKey key = new SummaryKey();
|
||||
for(String summaryFieldName : view.getSummaryFields())
|
||||
{
|
||||
FieldAndJoinTable fieldAndJoinTable = getFieldAndJoinTable(table, summaryFieldName);
|
||||
FieldAndJoinTable fieldAndJoinTable = FieldAndJoinTable.get(table, summaryFieldName);
|
||||
Serializable summaryValue = record.getValue(summaryFieldName);
|
||||
if(fieldAndJoinTable.field().getPossibleValueSourceName() != null)
|
||||
{
|
||||
@ -811,7 +786,7 @@ public class GenerateReportAction extends AbstractQActionFunction<ReportInput, R
|
||||
//////////////////////////////////////////////////////
|
||||
// todo - memoize this, if we ever need to optimize //
|
||||
//////////////////////////////////////////////////////
|
||||
FieldAndJoinTable fieldAndJoinTable = getFieldAndJoinTable(table, fieldName);
|
||||
FieldAndJoinTable fieldAndJoinTable = FieldAndJoinTable.get(table, fieldName);
|
||||
field = fieldAndJoinTable.field();
|
||||
}
|
||||
catch(Exception e)
|
||||
@ -956,7 +931,7 @@ public class GenerateReportAction extends AbstractQActionFunction<ReportInput, R
|
||||
List<QFieldMetaData> fields = new ArrayList<>();
|
||||
for(String summaryFieldName : view.getSummaryFields())
|
||||
{
|
||||
FieldAndJoinTable fieldAndJoinTable = getFieldAndJoinTable(table, summaryFieldName);
|
||||
FieldAndJoinTable fieldAndJoinTable = FieldAndJoinTable.get(table, summaryFieldName);
|
||||
fields.add(new QFieldMetaData(summaryFieldName, fieldAndJoinTable.field().getType()).withLabel(fieldAndJoinTable.field().getLabel())); // todo do we need the type? if so need table as input here
|
||||
}
|
||||
for(QReportField column : view.getColumns())
|
||||
@ -1208,27 +1183,4 @@ public class GenerateReportAction extends AbstractQActionFunction<ReportInput, R
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public record FieldAndJoinTable(QFieldMetaData field, QTableMetaData joinTable)
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getLabel(QTableMetaData mainTable)
|
||||
{
|
||||
if(mainTable.getName().equals(joinTable.getName()))
|
||||
{
|
||||
return (field.getLabel());
|
||||
}
|
||||
else
|
||||
{
|
||||
return (joinTable.getLabel() + ": " + field.getLabel());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -32,6 +32,7 @@ import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.querystats.QueryStat;
|
||||
@ -82,6 +83,22 @@ public class CountAction
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** shorthand way to call for the most common use-case, when you just want the
|
||||
** count to be returned, and you just want to pass in a table name and filter.
|
||||
*******************************************************************************/
|
||||
public static Integer execute(String tableName, QQueryFilter filter) throws QException
|
||||
{
|
||||
CountAction countAction = new CountAction();
|
||||
CountInput countInput = new CountInput();
|
||||
countInput.setTableName(tableName);
|
||||
countInput.setFilter(filter);
|
||||
CountOutput countOutput = countAction.execute(countInput);
|
||||
return (countOutput.getCount());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
|
@ -82,6 +82,11 @@ public class DeleteAction
|
||||
{
|
||||
ActionHelper.validateSession(deleteInput);
|
||||
|
||||
if(deleteInput.getTableName() == null)
|
||||
{
|
||||
throw (new QException("Table name was not specified in delete input"));
|
||||
}
|
||||
|
||||
QTableMetaData table = deleteInput.getTable();
|
||||
String primaryKeyFieldName = table.getPrimaryKeyField();
|
||||
QFieldMetaData primaryKeyField = table.getField(primaryKeyFieldName);
|
||||
@ -320,7 +325,7 @@ public class DeleteAction
|
||||
QTableMetaData table = deleteInput.getTable();
|
||||
List<QRecord> primaryKeysNotFound = validateRecordsExistAndCanBeAccessed(deleteInput, oldRecordList.get());
|
||||
|
||||
ValidateRecordSecurityLockHelper.validateSecurityFields(table, oldRecordList.get(), ValidateRecordSecurityLockHelper.Action.DELETE);
|
||||
ValidateRecordSecurityLockHelper.validateSecurityFields(table, oldRecordList.get(), ValidateRecordSecurityLockHelper.Action.DELETE, deleteInput.getTransaction());
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// after all validations, run the pre-delete customizer, if there is one //
|
||||
|
@ -238,6 +238,11 @@ public class GetAction
|
||||
*******************************************************************************/
|
||||
public static QRecord execute(String tableName, Serializable primaryKey) throws QException
|
||||
{
|
||||
if(primaryKey instanceof QQueryFilter)
|
||||
{
|
||||
LOG.warn("Unexpected use of QQueryFilter instead of primary key in GetAction call");
|
||||
}
|
||||
|
||||
GetAction getAction = new GetAction();
|
||||
GetInput getInput = new GetInput(tableName).withPrimaryKey(primaryKey);
|
||||
return getAction.executeForRecord(getInput);
|
||||
|
@ -67,6 +67,7 @@ import com.kingsrook.qqq.backend.core.model.statusmessages.QWarningMessage;
|
||||
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleDispatcher;
|
||||
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
@ -110,6 +111,12 @@ public class InsertAction extends AbstractQActionFunction<InsertInput, InsertOut
|
||||
public InsertOutput execute(InsertInput insertInput) throws QException
|
||||
{
|
||||
ActionHelper.validateSession(insertInput);
|
||||
|
||||
if(!StringUtils.hasContent(insertInput.getTableName()))
|
||||
{
|
||||
throw (new QException("Table name was not specified in insert input"));
|
||||
}
|
||||
|
||||
QTableMetaData table = insertInput.getTable();
|
||||
|
||||
if(table == null)
|
||||
@ -122,7 +129,7 @@ public class InsertAction extends AbstractQActionFunction<InsertInput, InsertOut
|
||||
/////////////////////////////
|
||||
// run standard validators //
|
||||
/////////////////////////////
|
||||
performValidations(insertInput, false);
|
||||
performValidations(insertInput, false, false);
|
||||
|
||||
//////////////////////////////////////////////////////
|
||||
// use the backend module to actually do the insert //
|
||||
@ -225,7 +232,7 @@ public class InsertAction extends AbstractQActionFunction<InsertInput, InsertOut
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void performValidations(InsertInput insertInput, boolean isPreview) throws QException
|
||||
public void performValidations(InsertInput insertInput, boolean isPreview, boolean didAlreadyRunCustomizer) throws QException
|
||||
{
|
||||
if(CollectionUtils.nullSafeIsEmpty(insertInput.getRecords()))
|
||||
{
|
||||
@ -237,12 +244,10 @@ public class InsertAction extends AbstractQActionFunction<InsertInput, InsertOut
|
||||
///////////////////////////////////////////////////////////////////
|
||||
// load the pre-insert customizer and set it up, if there is one //
|
||||
// then we'll run it based on its WhenToRun value //
|
||||
// note - if we already ran it, then don't re-run it! //
|
||||
///////////////////////////////////////////////////////////////////
|
||||
Optional<TableCustomizerInterface> preInsertCustomizer = QCodeLoader.getTableCustomizer(table, TableCustomizers.PRE_INSERT_RECORD.getRole());
|
||||
if(preInsertCustomizer.isPresent())
|
||||
{
|
||||
runPreInsertCustomizerIfItIsTime(insertInput, isPreview, preInsertCustomizer, AbstractPreInsertCustomizer.WhenToRun.BEFORE_ALL_VALIDATIONS);
|
||||
}
|
||||
Optional<TableCustomizerInterface> preInsertCustomizer = didAlreadyRunCustomizer ? Optional.empty() : QCodeLoader.getTableCustomizer(table, TableCustomizers.PRE_INSERT_RECORD.getRole());
|
||||
runPreInsertCustomizerIfItIsTime(insertInput, isPreview, preInsertCustomizer, AbstractPreInsertCustomizer.WhenToRun.BEFORE_ALL_VALIDATIONS);
|
||||
|
||||
setDefaultValuesInRecords(table, insertInput.getRecords());
|
||||
|
||||
@ -258,7 +263,7 @@ public class InsertAction extends AbstractQActionFunction<InsertInput, InsertOut
|
||||
}
|
||||
|
||||
runPreInsertCustomizerIfItIsTime(insertInput, isPreview, preInsertCustomizer, AbstractPreInsertCustomizer.WhenToRun.BEFORE_SECURITY_CHECKS);
|
||||
ValidateRecordSecurityLockHelper.validateSecurityFields(insertInput.getTable(), insertInput.getRecords(), ValidateRecordSecurityLockHelper.Action.INSERT);
|
||||
ValidateRecordSecurityLockHelper.validateSecurityFields(insertInput.getTable(), insertInput.getRecords(), ValidateRecordSecurityLockHelper.Action.INSERT, insertInput.getTransaction());
|
||||
|
||||
runPreInsertCustomizerIfItIsTime(insertInput, isPreview, preInsertCustomizer, AbstractPreInsertCustomizer.WhenToRun.AFTER_ALL_VALIDATIONS);
|
||||
}
|
||||
|
@ -47,7 +47,8 @@ public class StorageAction
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
** create an output stream in the storage backend - that can be written to,
|
||||
** for the purpose of inserting or writing a file into storage.
|
||||
*******************************************************************************/
|
||||
public OutputStream createOutputStream(StorageInput storageInput) throws QException
|
||||
{
|
||||
@ -59,7 +60,8 @@ public class StorageAction
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
** create an input stream in the storage backend - that can be read from,
|
||||
** for the purpose of getting or reading a file from storage.
|
||||
*******************************************************************************/
|
||||
public InputStream getInputStream(StorageInput storageInput) throws QException
|
||||
{
|
||||
|
@ -74,6 +74,7 @@ import com.kingsrook.qqq.backend.core.model.statusmessages.QWarningMessage;
|
||||
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleDispatcher;
|
||||
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
import org.apache.commons.lang.BooleanUtils;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
@ -118,6 +119,11 @@ public class UpdateAction
|
||||
{
|
||||
ActionHelper.validateSession(updateInput);
|
||||
|
||||
if(!StringUtils.hasContent(updateInput.getTableName()))
|
||||
{
|
||||
throw (new QException("Table name was not specified in update input"));
|
||||
}
|
||||
|
||||
QTableMetaData table = updateInput.getTable();
|
||||
|
||||
//////////////////////////////////////////////////////
|
||||
@ -261,7 +267,7 @@ public class UpdateAction
|
||||
}
|
||||
else
|
||||
{
|
||||
ValidateRecordSecurityLockHelper.validateSecurityFields(table, updateInput.getRecords(), ValidateRecordSecurityLockHelper.Action.UPDATE);
|
||||
ValidateRecordSecurityLockHelper.validateSecurityFields(table, updateInput.getRecords(), ValidateRecordSecurityLockHelper.Action.UPDATE, updateInput.getTransaction());
|
||||
}
|
||||
|
||||
if(updateInput.getInputSource().shouldValidateRequiredFields())
|
||||
@ -374,7 +380,7 @@ public class UpdateAction
|
||||
}
|
||||
}
|
||||
|
||||
ValidateRecordSecurityLockHelper.validateSecurityFields(table, updateInput.getRecords(), ValidateRecordSecurityLockHelper.Action.UPDATE);
|
||||
ValidateRecordSecurityLockHelper.validateSecurityFields(table, updateInput.getRecords(), ValidateRecordSecurityLockHelper.Action.UPDATE, updateInput.getTransaction());
|
||||
|
||||
for(QRecord record : page)
|
||||
{
|
||||
|
@ -31,16 +31,12 @@ import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Supplier;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.instances.QMetaDataVariableInterpreter;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
@ -54,12 +50,10 @@ import com.kingsrook.qqq.backend.core.model.querystats.QueryStatCriteriaField;
|
||||
import com.kingsrook.qqq.backend.core.model.querystats.QueryStatJoinTable;
|
||||
import com.kingsrook.qqq.backend.core.model.querystats.QueryStatOrderByField;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import com.kingsrook.qqq.backend.core.model.tables.QQQTable;
|
||||
import com.kingsrook.qqq.backend.core.model.tables.QQQTablesMetaDataProvider;
|
||||
import com.kingsrook.qqq.backend.core.model.tables.QQQTableTableManager;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.PrefixedDefaultThreadFactory;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.collections.MapBuilder;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
@ -371,7 +365,7 @@ public class QueryStatManager
|
||||
//////////////////////
|
||||
// set the table id //
|
||||
//////////////////////
|
||||
Integer qqqTableId = getQQQTableId(queryStat.getTableName());
|
||||
Integer qqqTableId = QQQTableTableManager.getQQQTableId(getInstance().qInstance, queryStat.getTableName());
|
||||
queryStat.setQqqTableId(qqqTableId);
|
||||
|
||||
//////////////////////////////
|
||||
@ -382,7 +376,7 @@ public class QueryStatManager
|
||||
List<QueryStatJoinTable> queryStatJoinTableList = new ArrayList<>();
|
||||
for(String joinTableName : queryStat.getJoinTableNames())
|
||||
{
|
||||
queryStatJoinTableList.add(new QueryStatJoinTable().withQqqTableId(getQQQTableId(joinTableName)));
|
||||
queryStatJoinTableList.add(new QueryStatJoinTable().withQqqTableId(QQQTableTableManager.getQQQTableId(getInstance().qInstance, joinTableName)));
|
||||
}
|
||||
queryStat.setQueryStatJoinTableList(queryStatJoinTableList);
|
||||
}
|
||||
@ -460,7 +454,7 @@ public class QueryStatManager
|
||||
String[] parts = fieldName.split("\\.");
|
||||
if(parts.length > 1)
|
||||
{
|
||||
queryStatCriteriaField.setQqqTableId(getQQQTableId(parts[0]));
|
||||
queryStatCriteriaField.setQqqTableId(QQQTableTableManager.getQQQTableId(getInstance().qInstance, parts[0]));
|
||||
queryStatCriteriaField.setName(parts[1]);
|
||||
}
|
||||
}
|
||||
@ -498,7 +492,7 @@ public class QueryStatManager
|
||||
String[] parts = fieldName.split("\\.");
|
||||
if(parts.length > 1)
|
||||
{
|
||||
queryStatOrderByField.setQqqTableId(getQQQTableId(parts[0]));
|
||||
queryStatOrderByField.setQqqTableId(QQQTableTableManager.getQQQTableId(getInstance().qInstance, parts[0]));
|
||||
queryStatOrderByField.setName(parts[1]);
|
||||
}
|
||||
}
|
||||
@ -512,44 +506,6 @@ public class QueryStatManager
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static Integer getQQQTableId(String tableName) throws QException
|
||||
{
|
||||
/////////////////////////////
|
||||
// look in the cache table //
|
||||
/////////////////////////////
|
||||
GetInput getInput = new GetInput();
|
||||
getInput.setTableName(QQQTablesMetaDataProvider.QQQ_TABLE_CACHE_TABLE_NAME);
|
||||
getInput.setUniqueKey(MapBuilder.of("name", tableName));
|
||||
GetOutput getOutput = new GetAction().execute(getInput);
|
||||
|
||||
////////////////////////
|
||||
// upon cache miss... //
|
||||
////////////////////////
|
||||
if(getOutput.getRecord() == null)
|
||||
{
|
||||
///////////////////////////////////////////////////////
|
||||
// insert the record (into the table, not the cache) //
|
||||
///////////////////////////////////////////////////////
|
||||
QTableMetaData tableMetaData = getInstance().qInstance.getTable(tableName);
|
||||
InsertInput insertInput = new InsertInput();
|
||||
insertInput.setTableName(QQQTable.TABLE_NAME);
|
||||
insertInput.setRecords(List.of(new QRecord().withValue("name", tableName).withValue("label", tableMetaData.getLabel())));
|
||||
InsertOutput insertOutput = new InsertAction().execute(insertInput);
|
||||
|
||||
///////////////////////////////////
|
||||
// repeat the get from the cache //
|
||||
///////////////////////////////////
|
||||
getOutput = new GetAction().execute(getInput);
|
||||
}
|
||||
|
||||
return getOutput.getRecord().getValueInteger("id");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -28,6 +28,7 @@ import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
@ -83,7 +84,7 @@ public class ValidateRecordSecurityLockHelper
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static void validateSecurityFields(QTableMetaData table, List<QRecord> records, Action action) throws QException
|
||||
public static void validateSecurityFields(QTableMetaData table, List<QRecord> records, Action action, QBackendTransaction transaction) throws QException
|
||||
{
|
||||
MultiRecordSecurityLock locksToCheck = getRecordSecurityLocks(table, action);
|
||||
if(locksToCheck == null || CollectionUtils.nullSafeIsEmpty(locksToCheck.getLocks()))
|
||||
@ -101,7 +102,7 @@ public class ValidateRecordSecurityLockHelper
|
||||
// actually check lock values //
|
||||
////////////////////////////////
|
||||
Map<Serializable, RecordWithErrors> errorRecords = new HashMap<>();
|
||||
evaluateRecordLocks(table, records, action, locksToCheck, errorRecords, new ArrayList<>(), madeUpPrimaryKeys);
|
||||
evaluateRecordLocks(table, records, action, locksToCheck, errorRecords, new ArrayList<>(), madeUpPrimaryKeys, transaction);
|
||||
|
||||
/////////////////////////////////
|
||||
// propagate errors to records //
|
||||
@ -141,7 +142,7 @@ public class ValidateRecordSecurityLockHelper
|
||||
** BUT - WRITE locks - in their case, we read the record no matter what, and in
|
||||
** here we need to verify we have a key that allows us to WRITE the record.
|
||||
*******************************************************************************/
|
||||
private static void evaluateRecordLocks(QTableMetaData table, List<QRecord> records, Action action, RecordSecurityLock recordSecurityLock, Map<Serializable, RecordWithErrors> errorRecords, List<Integer> treePosition, Map<Serializable, QRecord> madeUpPrimaryKeys) throws QException
|
||||
private static void evaluateRecordLocks(QTableMetaData table, List<QRecord> records, Action action, RecordSecurityLock recordSecurityLock, Map<Serializable, RecordWithErrors> errorRecords, List<Integer> treePosition, Map<Serializable, QRecord> madeUpPrimaryKeys, QBackendTransaction transaction) throws QException
|
||||
{
|
||||
if(recordSecurityLock instanceof MultiRecordSecurityLock multiRecordSecurityLock)
|
||||
{
|
||||
@ -152,7 +153,7 @@ public class ValidateRecordSecurityLockHelper
|
||||
for(RecordSecurityLock childLock : CollectionUtils.nonNullList(multiRecordSecurityLock.getLocks()))
|
||||
{
|
||||
treePosition.add(i);
|
||||
evaluateRecordLocks(table, records, action, childLock, errorRecords, treePosition, madeUpPrimaryKeys);
|
||||
evaluateRecordLocks(table, records, action, childLock, errorRecords, treePosition, madeUpPrimaryKeys, transaction);
|
||||
treePosition.remove(treePosition.size() - 1);
|
||||
i++;
|
||||
}
|
||||
@ -225,6 +226,7 @@ public class ValidateRecordSecurityLockHelper
|
||||
// query will be like (fkey1=? and fkey2=?) OR (fkey1=? and fkey2=?) OR (fkey1=? and fkey2=?) //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
QueryInput queryInput = new QueryInput();
|
||||
queryInput.setTransaction(transaction);
|
||||
queryInput.setTableName(leftMostJoin.getLeftTable());
|
||||
QQueryFilter filter = new QQueryFilter().withBooleanOperator(QQueryFilter.BooleanOperator.OR);
|
||||
queryInput.setFilter(filter);
|
||||
|
@ -104,10 +104,21 @@ public class RenderTemplateAction extends AbstractQActionFunction<RenderTemplate
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Static wrapper to render a Velocity template.
|
||||
*******************************************************************************/
|
||||
@Deprecated(since = "Call the version that doesn't take an ActionInput")
|
||||
public static String renderVelocity(AbstractActionInput parentActionInput, Map<String, Object> context, String code) throws QException
|
||||
{
|
||||
return (renderVelocity(context, code));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Most convenient static wrapper to render a Velocity template.
|
||||
*******************************************************************************/
|
||||
public static String renderVelocity(AbstractActionInput parentActionInput, Map<String, Object> context, String code) throws QException
|
||||
public static String renderVelocity(Map<String, Object> context, String code) throws QException
|
||||
{
|
||||
return (render(TemplateType.VELOCITY, context, code));
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ package com.kingsrook.qqq.backend.core.actions.values;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.values.SearchPossibleValueSourceInput;
|
||||
@ -74,6 +75,31 @@ public interface QCustomPossibleValueProvider<T extends Serializable>
|
||||
}
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
** meant to be protected (but interface...) - for a custom PVS implementation
|
||||
** to complete its search (e.g., after it generates the list of PVS objects,
|
||||
** let this method do the filtering).
|
||||
***************************************************************************/
|
||||
default List<QPossibleValue<T>> completeCustomPVSSearch(SearchPossibleValueSourceInput input, List<QPossibleValue<T>> possibleValues)
|
||||
{
|
||||
SearchPossibleValueSourceAction.PreparedSearchPossibleValueSourceInput preparedInput = SearchPossibleValueSourceAction.prepareSearchPossibleValueSourceInput(input);
|
||||
|
||||
List<QPossibleValue<T>> rs = new ArrayList<>();
|
||||
|
||||
for(QPossibleValue<T> possibleValue : possibleValues)
|
||||
{
|
||||
if(possibleValue != null && SearchPossibleValueSourceAction.doesPossibleValueMatchSearchInput(possibleValue, preparedInput))
|
||||
{
|
||||
rs.add(possibleValue);
|
||||
}
|
||||
}
|
||||
|
||||
rs.sort(Comparator.nullsLast(Comparator.comparing((QPossibleValue<T> pv) -> pv.getLabel())));
|
||||
|
||||
return (rs);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
|
@ -28,6 +28,7 @@ import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@ -44,6 +45,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
@ -484,6 +486,8 @@ public class QValueFormatter
|
||||
String fileNameFormat = ValueUtils.getValueAsString(adornmentValues.get(AdornmentType.FileDownloadValues.FILE_NAME_FORMAT));
|
||||
String defaultExtension = ValueUtils.getValueAsString(adornmentValues.get(AdornmentType.FileDownloadValues.DEFAULT_EXTENSION));
|
||||
|
||||
Boolean downloadUrlDynamic = ValueUtils.getValueAsBoolean(adornmentValues.get(AdornmentType.FileDownloadValues.DOWNLOAD_URL_DYNAMIC));
|
||||
|
||||
for(QRecord record : records)
|
||||
{
|
||||
if(!doesFieldHaveValue(field, record))
|
||||
@ -491,6 +495,11 @@ public class QValueFormatter
|
||||
continue;
|
||||
}
|
||||
|
||||
if(BooleanUtils.isTrue(downloadUrlDynamic))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Serializable primaryKey = record.getValue(table.getPrimaryKeyField());
|
||||
String fileName = null;
|
||||
|
||||
@ -508,7 +517,7 @@ public class QValueFormatter
|
||||
{
|
||||
@SuppressWarnings("unchecked") // instance validation should make this safe!
|
||||
List<String> fileNameFormatFields = (List<String>) adornmentValues.get(AdornmentType.FileDownloadValues.FILE_NAME_FORMAT_FIELDS);
|
||||
List<String> values = fileNameFormatFields.stream().map(f -> ValueUtils.getValueAsString(record.getValue(f))).toList();
|
||||
List<String> values = CollectionUtils.nullSafeHasContents(fileNameFormatFields) ? fileNameFormatFields.stream().map(f -> ValueUtils.getValueAsString(record.getValue(f))).toList() : Collections.emptyList();
|
||||
fileName = QValueFormatter.formatStringWithValues(fileNameFormat, values);
|
||||
}
|
||||
}
|
||||
@ -531,7 +540,7 @@ public class QValueFormatter
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if field type is blob OR if there's a supplemental process or code-ref that needs to run - //
|
||||
// then update its value to be a callback-url that'll give access to the bytes to download //
|
||||
// then update its value to be a callback-url that'll give access to the bytes to download. //
|
||||
// implied here is that a String value (w/o supplemental code/proc) has its value stay as a //
|
||||
// URL, which is where the file is directly downloaded from. And in the case of a String //
|
||||
// with code-to-run, then the code should run, followed by a redirect to the value URL. //
|
||||
@ -540,7 +549,7 @@ public class QValueFormatter
|
||||
|| adornmentValues.containsKey(AdornmentType.FileDownloadValues.SUPPLEMENTAL_CODE_REFERENCE)
|
||||
|| adornmentValues.containsKey(AdornmentType.FileDownloadValues.SUPPLEMENTAL_PROCESS_NAME))
|
||||
{
|
||||
record.setValue(field.getName(), "/data/" + table.getName() + "/" + primaryKey + "/" + field.getName() + "/" + fileName);
|
||||
record.setValue(field.getName(), AdornmentType.FileDownloadValues.makeFieldDownloadUrl(table.getName(), primaryKey, field.getName(), fileName));
|
||||
}
|
||||
record.setDisplayValue(field.getName(), fileName);
|
||||
}
|
||||
|
@ -26,8 +26,13 @@ import java.io.Serializable;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
@ -47,10 +52,9 @@ import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleVal
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSourceType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
import org.apache.commons.lang.NotImplementedException;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -61,6 +65,9 @@ public class SearchPossibleValueSourceAction
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(SearchPossibleValueSourceAction.class);
|
||||
|
||||
private static final Set<String> warnedAboutUnexpectedValueField = Collections.synchronizedSet(new HashSet<>());
|
||||
private static final Set<String> warnedAboutUnexpectedNoOfFieldsToSearchByLabel = Collections.synchronizedSet(new HashSet<>());
|
||||
|
||||
private QPossibleValueTranslator possibleValueTranslator;
|
||||
|
||||
|
||||
@ -101,47 +108,54 @@ public class SearchPossibleValueSourceAction
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
** record to store "computed" values as part of a possible-value search -
|
||||
** e.g., ids type-convered, and lower-cased labels.
|
||||
***************************************************************************/
|
||||
public record PreparedSearchPossibleValueSourceInput(Collection<?> inputIdsAsCorrectType, Collection<String> lowerCaseLabels, String searchTerm) {}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public static PreparedSearchPossibleValueSourceInput prepareSearchPossibleValueSourceInput(SearchPossibleValueSourceInput input)
|
||||
{
|
||||
QPossibleValueSource possibleValueSource = QContext.getQInstance().getPossibleValueSource(input.getPossibleValueSourceName());
|
||||
List<?> inputIdsAsCorrectType = convertInputIdsToPossibleValueSourceIdType(possibleValueSource, input.getIdList());
|
||||
|
||||
Set<String> lowerCaseLabels = null;
|
||||
if(input.getLabelList() != null)
|
||||
{
|
||||
lowerCaseLabels = input.getLabelList().stream()
|
||||
.filter(Objects::nonNull)
|
||||
.map(l -> l.toLowerCase())
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
return (new PreparedSearchPossibleValueSourceInput(inputIdsAsCorrectType, lowerCaseLabels, input.getSearchTerm()));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private SearchPossibleValueSourceOutput searchPossibleValueEnum(SearchPossibleValueSourceInput input, QPossibleValueSource possibleValueSource)
|
||||
{
|
||||
PreparedSearchPossibleValueSourceInput preparedSearchPossibleValueSourceInput = prepareSearchPossibleValueSourceInput(input);
|
||||
|
||||
SearchPossibleValueSourceOutput output = new SearchPossibleValueSourceOutput();
|
||||
List<Serializable> matchingIds = new ArrayList<>();
|
||||
|
||||
List<?> inputIdsAsCorrectType = convertInputIdsToEnumIdType(possibleValueSource, input.getIdList());
|
||||
|
||||
for(QPossibleValue<?> possibleValue : possibleValueSource.getEnumValues())
|
||||
{
|
||||
boolean match = false;
|
||||
|
||||
if(input.getIdList() != null)
|
||||
{
|
||||
if(inputIdsAsCorrectType.contains(possibleValue.getId()))
|
||||
{
|
||||
match = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(StringUtils.hasContent(input.getSearchTerm()))
|
||||
{
|
||||
match = (Objects.equals(ValueUtils.getValueAsString(possibleValue.getId()).toLowerCase(), input.getSearchTerm().toLowerCase())
|
||||
|| possibleValue.getLabel().toLowerCase().startsWith(input.getSearchTerm().toLowerCase()));
|
||||
}
|
||||
else
|
||||
{
|
||||
match = true;
|
||||
}
|
||||
}
|
||||
boolean match = doesPossibleValueMatchSearchInput(possibleValue, preparedSearchPossibleValueSourceInput);
|
||||
|
||||
if(match)
|
||||
{
|
||||
matchingIds.add((Serializable) possibleValue.getId());
|
||||
matchingIds.add(possibleValue.getId());
|
||||
}
|
||||
|
||||
// todo - skip & limit?
|
||||
// todo - default filter
|
||||
}
|
||||
|
||||
List<QPossibleValue<?>> qPossibleValues = possibleValueTranslator.buildTranslatedPossibleValueList(possibleValueSource, matchingIds);
|
||||
@ -152,37 +166,95 @@ public class SearchPossibleValueSourceAction
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public static boolean doesPossibleValueMatchSearchInput(QPossibleValue<?> possibleValue, PreparedSearchPossibleValueSourceInput input)
|
||||
{
|
||||
boolean match = false;
|
||||
|
||||
if(input.inputIdsAsCorrectType() != null)
|
||||
{
|
||||
if(input.inputIdsAsCorrectType().contains(possibleValue.getId()))
|
||||
{
|
||||
match = true;
|
||||
}
|
||||
}
|
||||
else if(input.lowerCaseLabels() != null)
|
||||
{
|
||||
if(input.lowerCaseLabels().contains(possibleValue.getLabel().toLowerCase()))
|
||||
{
|
||||
match = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(StringUtils.hasContent(input.searchTerm()))
|
||||
{
|
||||
match = (Objects.equals(ValueUtils.getValueAsString(possibleValue.getId()).toLowerCase(), input.searchTerm().toLowerCase())
|
||||
|| possibleValue.getLabel().toLowerCase().startsWith(input.searchTerm().toLowerCase()));
|
||||
}
|
||||
else
|
||||
{
|
||||
match = true;
|
||||
}
|
||||
}
|
||||
return match;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** The input list of ids might come through as a type that isn't the same as
|
||||
** the type of the ids in the enum (e.g., strings from a frontend, integers
|
||||
** in an enum). So, this method looks at the first id in the enum, and then
|
||||
** maps all the inputIds to be of the same type.
|
||||
** in an enum). So, this method type-converts them.
|
||||
*******************************************************************************/
|
||||
private List<Object> convertInputIdsToEnumIdType(QPossibleValueSource possibleValueSource, List<Serializable> inputIdList)
|
||||
private static List<Object> convertInputIdsToPossibleValueSourceIdType(QPossibleValueSource possibleValueSource, List<Serializable> inputIdList)
|
||||
{
|
||||
List<Object> rs = new ArrayList<>();
|
||||
if(CollectionUtils.nullSafeIsEmpty(inputIdList))
|
||||
|
||||
if(inputIdList == null)
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
else if(inputIdList.isEmpty())
|
||||
{
|
||||
return (rs);
|
||||
}
|
||||
|
||||
Object anIdFromTheEnum = possibleValueSource.getEnumValues().get(0).getId();
|
||||
QFieldType type = possibleValueSource.getIdType();
|
||||
|
||||
if(anIdFromTheEnum instanceof Integer)
|
||||
for(Serializable inputId : inputIdList)
|
||||
{
|
||||
inputIdList.forEach(id -> rs.add(ValueUtils.getValueAsInteger(id)));
|
||||
}
|
||||
else if(anIdFromTheEnum instanceof String)
|
||||
{
|
||||
inputIdList.forEach(id -> rs.add(ValueUtils.getValueAsString(id)));
|
||||
}
|
||||
else if(anIdFromTheEnum instanceof Boolean)
|
||||
{
|
||||
inputIdList.forEach(id -> rs.add(ValueUtils.getValueAsBoolean(id)));
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG.warn("Unexpected type [" + anIdFromTheEnum.getClass().getSimpleName() + "] for ids in enum: " + possibleValueSource.getName());
|
||||
Object properlyTypedId = null;
|
||||
try
|
||||
{
|
||||
if(type.equals(QFieldType.INTEGER))
|
||||
{
|
||||
properlyTypedId = ValueUtils.getValueAsInteger(inputId);
|
||||
}
|
||||
else if(type.isStringLike())
|
||||
{
|
||||
properlyTypedId = ValueUtils.getValueAsString(inputId);
|
||||
}
|
||||
else if(type.equals(QFieldType.BOOLEAN))
|
||||
{
|
||||
properlyTypedId = ValueUtils.getValueAsBoolean(inputId);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG.warn("Unexpected type [" + type + "] for ids in enum: " + possibleValueSource.getName());
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.debug("Error converting possible value id to expected id type", e, logPair("value", inputId));
|
||||
}
|
||||
|
||||
if(properlyTypedId != null)
|
||||
{
|
||||
rs.add(properlyTypedId);
|
||||
}
|
||||
}
|
||||
|
||||
return (rs);
|
||||
@ -209,6 +281,53 @@ public class SearchPossibleValueSourceAction
|
||||
{
|
||||
queryFilter.addCriteria(new QFilterCriteria(table.getPrimaryKeyField(), QCriteriaOperator.IN, input.getIdList()));
|
||||
}
|
||||
else if(input.getLabelList() != null)
|
||||
{
|
||||
List<String> fieldNames = new ArrayList<>();
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// the 'value fields' will either be 'id' or 'label' (which means, use the fields from the tableMetaData's label fields) //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
for(String valueField : possibleValueSource.getValueFields())
|
||||
{
|
||||
if("id".equals(valueField))
|
||||
{
|
||||
fieldNames.add(table.getPrimaryKeyField());
|
||||
}
|
||||
else if("label".equals(valueField))
|
||||
{
|
||||
if(table.getRecordLabelFields() != null)
|
||||
{
|
||||
fieldNames.addAll(table.getRecordLabelFields());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
String message = "Unexpected valueField defined in possibleValueSource when searching possibleValueSource by label (required: 'id' or 'label')";
|
||||
if(!warnedAboutUnexpectedValueField.contains(possibleValueSource.getName()))
|
||||
{
|
||||
LOG.warn(message, logPair("valueField", valueField), logPair("possibleValueSource", possibleValueSource.getName()));
|
||||
warnedAboutUnexpectedValueField.add(possibleValueSource.getName());
|
||||
}
|
||||
output.setWarning(message);
|
||||
}
|
||||
}
|
||||
|
||||
if(fieldNames.size() == 1)
|
||||
{
|
||||
queryFilter.addCriteria(new QFilterCriteria(fieldNames.get(0), QCriteriaOperator.IN, input.getLabelList()));
|
||||
}
|
||||
else
|
||||
{
|
||||
String message = "Unexpected number of fields found for searching possibleValueSource by label (required: 1, found: " + fieldNames.size() + ")";
|
||||
if(!warnedAboutUnexpectedNoOfFieldsToSearchByLabel.contains(possibleValueSource.getName()))
|
||||
{
|
||||
LOG.warn(message);
|
||||
warnedAboutUnexpectedNoOfFieldsToSearchByLabel.add(possibleValueSource.getName());
|
||||
}
|
||||
output.setWarning(message);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
String searchTerm = input.getSearchTerm();
|
||||
@ -269,8 +388,8 @@ public class SearchPossibleValueSourceAction
|
||||
queryFilter = input.getDefaultQueryFilter();
|
||||
}
|
||||
|
||||
// todo - skip & limit as params
|
||||
queryFilter.setLimit(250);
|
||||
queryFilter.setLimit(input.getLimit());
|
||||
queryFilter.setSkip(input.getSkip());
|
||||
|
||||
queryFilter.setOrderBys(possibleValueSource.getOrderByFields());
|
||||
|
||||
@ -288,7 +407,7 @@ public class SearchPossibleValueSourceAction
|
||||
fieldName = table.getPrimaryKeyField();
|
||||
}
|
||||
|
||||
List<Serializable> ids = queryOutput.getRecords().stream().map(r -> r.getValue(fieldName)).toList();
|
||||
List<Serializable> ids = queryOutput.getRecords().stream().map(r -> r.getValue(fieldName)).toList();
|
||||
List<QPossibleValue<?>> qPossibleValues = possibleValueTranslator.buildTranslatedPossibleValueList(possibleValueSource, ids);
|
||||
output.setResults(qPossibleValues);
|
||||
|
||||
@ -301,7 +420,7 @@ public class SearchPossibleValueSourceAction
|
||||
**
|
||||
*******************************************************************************/
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
private SearchPossibleValueSourceOutput searchPossibleValueCustom(SearchPossibleValueSourceInput input, QPossibleValueSource possibleValueSource)
|
||||
private SearchPossibleValueSourceOutput searchPossibleValueCustom(SearchPossibleValueSourceInput input, QPossibleValueSource possibleValueSource) throws QException
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -314,11 +433,10 @@ public class SearchPossibleValueSourceAction
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
// LOG.warn("Error sending [" + value + "] for field [" + field + "] through custom code for PVS [" + field.getPossibleValueSourceName() + "]", e);
|
||||
String message = "Error searching custom possible value source [" + input.getPossibleValueSourceName() + "]";
|
||||
LOG.warn(message, e);
|
||||
throw (new QException(message, e));
|
||||
}
|
||||
|
||||
throw new NotImplementedException("Not impleemnted");
|
||||
// return (null);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -23,7 +23,11 @@ package com.kingsrook.qqq.backend.core.instances;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
@ -31,21 +35,27 @@ import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
|
||||
import com.kingsrook.qqq.backend.core.actions.metadata.JoinGraph;
|
||||
import com.kingsrook.qqq.backend.core.actions.permissions.BulkTableActionProcessPermissionChecker;
|
||||
import com.kingsrook.qqq.backend.core.actions.values.QCustomPossibleValueProvider;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.instances.enrichment.plugins.QInstanceEnricherPluginInterface;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
|
||||
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.dashboard.QWidgetMetaDataInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.AdornmentType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.AdornmentType.FileUploadAdornment;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.DynamicDefaultValueBehavior;
|
||||
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.joins.QJoinMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppChildMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppSection;
|
||||
@ -54,6 +64,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.permissions.MetaDataWithPer
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.permissions.QPermissionRules;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSourceType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QComponentType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendComponentMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendStepMetaData;
|
||||
@ -75,13 +86,20 @@ import com.kingsrook.qqq.backend.core.processes.implementations.bulk.edit.BulkEd
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.bulk.edit.BulkEditTransformStep;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.bulk.insert.BulkInsertExtractStep;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.bulk.insert.BulkInsertLoadStep;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.bulk.insert.BulkInsertPrepareFileMappingStep;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.bulk.insert.BulkInsertPrepareFileUploadStep;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.bulk.insert.BulkInsertPrepareValueMappingStep;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.bulk.insert.BulkInsertReceiveFileMappingStep;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.bulk.insert.BulkInsertReceiveValueMappingStep;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.bulk.insert.BulkInsertTransformStep;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.ExtractViaQueryStep;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.StreamedETLWithFrontendProcess;
|
||||
import com.kingsrook.qqq.backend.core.scheduler.QScheduleManager;
|
||||
import com.kingsrook.qqq.backend.core.utils.ClassPathUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ListingHash;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -107,6 +125,8 @@ public class QInstanceEnricher
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
private static final Map<String, String> labelMappings = new LinkedHashMap<>();
|
||||
|
||||
private static ListingHash<Class<?>, QInstanceEnricherPluginInterface<?>> enricherPlugins = new ListingHash<>();
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -168,6 +188,7 @@ public class QInstanceEnricher
|
||||
}
|
||||
|
||||
enrichJoins();
|
||||
enrichInstance();
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// if the instance DOES have 1 or more scheduler, but no schedulable types, //
|
||||
@ -184,6 +205,16 @@ public class QInstanceEnricher
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
private void enrichInstance()
|
||||
{
|
||||
runPlugins(QInstance.class, qInstance, qInstance);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -248,6 +279,14 @@ public class QInstanceEnricher
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////
|
||||
// run plugins on joins if there are any //
|
||||
///////////////////////////////////////////
|
||||
for(QJoinMetaData join : qInstance.getJoins().values())
|
||||
{
|
||||
runPlugins(QJoinMetaData.class, join, qInstance);
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
@ -263,6 +302,7 @@ public class QInstanceEnricher
|
||||
private void enrichWidget(QWidgetMetaDataInterface widgetMetaData)
|
||||
{
|
||||
enrichPermissionRules(widgetMetaData);
|
||||
runPlugins(QWidgetMetaDataInterface.class, widgetMetaData, qInstance);
|
||||
}
|
||||
|
||||
|
||||
@ -273,6 +313,7 @@ public class QInstanceEnricher
|
||||
private void enrichBackend(QBackendMetaData qBackendMetaData)
|
||||
{
|
||||
qBackendMetaData.enrich();
|
||||
runPlugins(QBackendMetaData.class, qBackendMetaData, qInstance);
|
||||
}
|
||||
|
||||
|
||||
@ -327,6 +368,7 @@ public class QInstanceEnricher
|
||||
|
||||
enrichPermissionRules(table);
|
||||
enrichAuditRules(table);
|
||||
runPlugins(QTableMetaData.class, table, qInstance);
|
||||
}
|
||||
|
||||
|
||||
@ -417,6 +459,7 @@ public class QInstanceEnricher
|
||||
}
|
||||
|
||||
enrichPermissionRules(process);
|
||||
runPlugins(QProcessMetaData.class, process, qInstance);
|
||||
}
|
||||
|
||||
|
||||
@ -538,6 +581,8 @@ public class QInstanceEnricher
|
||||
field.withBehavior(DynamicDefaultValueBehavior.MODIFY_DATE);
|
||||
}
|
||||
}
|
||||
|
||||
runPlugins(QFieldMetaData.class, field, qInstance);
|
||||
}
|
||||
|
||||
|
||||
@ -609,6 +654,7 @@ public class QInstanceEnricher
|
||||
ensureAppSectionMembersAreAppChildren(app);
|
||||
|
||||
enrichPermissionRules(app);
|
||||
runPlugins(QAppMetaData.class, app, qInstance);
|
||||
}
|
||||
|
||||
|
||||
@ -756,6 +802,7 @@ public class QInstanceEnricher
|
||||
}
|
||||
|
||||
enrichPermissionRules(report);
|
||||
runPlugins(QReportMetaData.class, report, qInstance);
|
||||
}
|
||||
|
||||
|
||||
@ -847,7 +894,7 @@ public class QInstanceEnricher
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void defineTableBulkInsert(QInstance qInstance, QTableMetaData table, String processName)
|
||||
public void defineTableBulkInsert(QInstance qInstance, QTableMetaData table, String processName)
|
||||
{
|
||||
Map<String, Serializable> values = new HashMap<>();
|
||||
values.put(StreamedETLWithFrontendProcess.FIELD_DESTINATION_TABLE, table.getName());
|
||||
@ -859,6 +906,7 @@ public class QInstanceEnricher
|
||||
values
|
||||
)
|
||||
.withName(processName)
|
||||
.withIcon(new QIcon().withName("library_add"))
|
||||
.withLabel(table.getLabel() + " Bulk Insert")
|
||||
.withTableName(table.getName())
|
||||
.withIsHidden(true)
|
||||
@ -889,18 +937,76 @@ public class QInstanceEnricher
|
||||
.map(QFieldMetaData::getLabel)
|
||||
.collect(Collectors.joining(", "));
|
||||
|
||||
QBackendStepMetaData prepareFileUploadStep = new QBackendStepMetaData()
|
||||
.withName("prepareFileUpload")
|
||||
.withCode(new QCodeReference(BulkInsertPrepareFileUploadStep.class));
|
||||
|
||||
QFrontendStepMetaData uploadScreen = new QFrontendStepMetaData()
|
||||
.withName("upload")
|
||||
.withLabel("Upload File")
|
||||
.withFormField(new QFieldMetaData("theFile", QFieldType.BLOB).withLabel(table.getLabel() + " File").withIsRequired(true))
|
||||
.withComponent(new QFrontendComponentMetaData()
|
||||
.withType(QComponentType.HELP_TEXT)
|
||||
.withValue("previewText", "file upload instructions")
|
||||
.withValue("text", "Upload a CSV file with the following columns:\n" + fieldsForHelpText))
|
||||
.withFormField(new QFieldMetaData("theFile", QFieldType.BLOB)
|
||||
.withFieldAdornment(FileUploadAdornment.newFieldAdornment()
|
||||
.withValue(FileUploadAdornment.formatDragAndDrop())
|
||||
.withValue(FileUploadAdornment.widthFull()))
|
||||
.withLabel(table.getLabel() + " File")
|
||||
.withIsRequired(true))
|
||||
.withComponent(new QFrontendComponentMetaData().withType(QComponentType.HTML))
|
||||
.withComponent(new QFrontendComponentMetaData().withType(QComponentType.EDIT_FORM));
|
||||
|
||||
process.addStep(0, uploadScreen);
|
||||
process.getFrontendStep("review").setRecordListFields(editableFields);
|
||||
QBackendStepMetaData prepareFileMappingStep = new QBackendStepMetaData()
|
||||
.withName("prepareFileMapping")
|
||||
.withCode(new QCodeReference(BulkInsertPrepareFileMappingStep.class));
|
||||
|
||||
QFrontendStepMetaData fileMappingScreen = new QFrontendStepMetaData()
|
||||
.withName("fileMapping")
|
||||
.withLabel("File Mapping")
|
||||
.withBackStepName("prepareFileUpload")
|
||||
.withComponent(new QFrontendComponentMetaData().withType(QComponentType.BULK_LOAD_FILE_MAPPING_FORM))
|
||||
.withFormField(new QFieldMetaData("hasHeaderRow", QFieldType.BOOLEAN))
|
||||
.withFormField(new QFieldMetaData("layout", QFieldType.STRING)); // is actually PVS, but, this field is only added to help support helpContent, so :shrug:
|
||||
|
||||
QBackendStepMetaData receiveFileMappingStep = new QBackendStepMetaData()
|
||||
.withName("receiveFileMapping")
|
||||
.withCode(new QCodeReference(BulkInsertReceiveFileMappingStep.class));
|
||||
|
||||
QBackendStepMetaData prepareValueMappingStep = new QBackendStepMetaData()
|
||||
.withName("prepareValueMapping")
|
||||
.withCode(new QCodeReference(BulkInsertPrepareValueMappingStep.class));
|
||||
|
||||
QFrontendStepMetaData valueMappingScreen = new QFrontendStepMetaData()
|
||||
.withName("valueMapping")
|
||||
.withLabel("Value Mapping")
|
||||
.withBackStepName("prepareFileMapping")
|
||||
.withComponent(new QFrontendComponentMetaData().withType(QComponentType.BULK_LOAD_VALUE_MAPPING_FORM));
|
||||
|
||||
QBackendStepMetaData receiveValueMappingStep = new QBackendStepMetaData()
|
||||
.withName("receiveValueMapping")
|
||||
.withCode(new QCodeReference(BulkInsertReceiveValueMappingStep.class));
|
||||
|
||||
int i = 0;
|
||||
process.addStep(i++, prepareFileUploadStep);
|
||||
process.addStep(i++, uploadScreen);
|
||||
|
||||
process.addStep(i++, prepareFileMappingStep);
|
||||
process.addStep(i++, fileMappingScreen);
|
||||
process.addStep(i++, receiveFileMappingStep);
|
||||
|
||||
process.addStep(i++, prepareValueMappingStep);
|
||||
process.addStep(i++, valueMappingScreen);
|
||||
process.addStep(i++, receiveValueMappingStep);
|
||||
|
||||
process.getFrontendStep(StreamedETLWithFrontendProcess.STEP_NAME_REVIEW).setRecordListFields(editableFields);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
// put the bulk-load profile form (e.g., for saving it) on the review & result screens) //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
process.getFrontendStep(StreamedETLWithFrontendProcess.STEP_NAME_REVIEW)
|
||||
.withBackStepName("prepareFileMapping")
|
||||
.getComponents().add(0, new QFrontendComponentMetaData().withType(QComponentType.BULK_LOAD_PROFILE_FORM));
|
||||
|
||||
process.getFrontendStep(StreamedETLWithFrontendProcess.STEP_NAME_RESULT)
|
||||
.getComponents().add(0, new QFrontendComponentMetaData().withType(QComponentType.BULK_LOAD_PROFILE_FORM));
|
||||
|
||||
qInstance.addProcess(process);
|
||||
}
|
||||
|
||||
@ -1295,6 +1401,137 @@ public class QInstanceEnricher
|
||||
}
|
||||
}
|
||||
|
||||
if(possibleValueSource.getIdType() == null)
|
||||
{
|
||||
QTableMetaData table = qInstance.getTable(possibleValueSource.getTableName());
|
||||
if(table != null)
|
||||
{
|
||||
String primaryKeyField = table.getPrimaryKeyField();
|
||||
QFieldMetaData primaryKeyFieldMetaData = table.getFields().get(primaryKeyField);
|
||||
if(primaryKeyFieldMetaData != null)
|
||||
{
|
||||
possibleValueSource.setIdType(primaryKeyFieldMetaData.getType());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(QPossibleValueSourceType.ENUM.equals(possibleValueSource.getType()))
|
||||
{
|
||||
if(possibleValueSource.getIdType() == null)
|
||||
{
|
||||
if(CollectionUtils.nullSafeHasContents(possibleValueSource.getEnumValues()))
|
||||
{
|
||||
Object id = possibleValueSource.getEnumValues().get(0).getId();
|
||||
try
|
||||
{
|
||||
possibleValueSource.setIdType(QFieldType.fromClass(id.getClass()));
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.warn("Error enriching possible value source with idType based on first enum value", e, logPair("possibleValueSource", possibleValueSource.getName()), logPair("id", id));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(QPossibleValueSourceType.CUSTOM.equals(possibleValueSource.getType()))
|
||||
{
|
||||
if(possibleValueSource.getIdType() == null)
|
||||
{
|
||||
try
|
||||
{
|
||||
QCustomPossibleValueProvider<?> customPossibleValueProvider = QCodeLoader.getCustomPossibleValueProvider(possibleValueSource);
|
||||
|
||||
Method getPossibleValueMethod = customPossibleValueProvider.getClass().getDeclaredMethod("getPossibleValue", Serializable.class);
|
||||
Type returnType = getPossibleValueMethod.getGenericReturnType();
|
||||
Type idType = ((ParameterizedType) returnType).getActualTypeArguments()[0];
|
||||
|
||||
if(idType instanceof Class<?> c)
|
||||
{
|
||||
possibleValueSource.setIdType(QFieldType.fromClass(c));
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.warn("Error enriching possible value source with idType based on first custom value", e, logPair("possibleValueSource", possibleValueSource.getName()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
runPlugins(QPossibleValueSource.class, possibleValueSource, qInstance);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static void addEnricherPlugin(QInstanceEnricherPluginInterface<?> plugin)
|
||||
{
|
||||
Optional<Method> enrichMethod = Arrays.stream(plugin.getClass().getDeclaredMethods())
|
||||
.filter(m -> m.getName().equals("enrich")
|
||||
&& m.getParameterCount() == 2
|
||||
&& !m.getParameterTypes()[0].equals(Object.class)
|
||||
&& m.getParameterTypes()[1].equals(QInstance.class)
|
||||
).findFirst();
|
||||
|
||||
if(enrichMethod.isPresent())
|
||||
{
|
||||
Class<?> parameterType = enrichMethod.get().getParameterTypes()[0];
|
||||
enricherPlugins.add(parameterType, plugin);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG.warn("Could not find enrich method on enricher plugin [" + plugin.getClass().getName() + "] (to infer type being enriched) - this plugin will not be used.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static void removeAllEnricherPlugins()
|
||||
{
|
||||
enricherPlugins.clear();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
** scan the classpath for classes in the specified package name which
|
||||
** implement the QInstanceEnricherPluginInterface - any found get added
|
||||
***************************************************************************/
|
||||
public static void discoverAndAddPluginsInPackage(String packageName) throws QException
|
||||
{
|
||||
try
|
||||
{
|
||||
for(Class<?> aClass : ClassPathUtils.getClassesInPackage(packageName))
|
||||
{
|
||||
if(QInstanceEnricherPluginInterface.class.isAssignableFrom(aClass))
|
||||
{
|
||||
QInstanceEnricherPluginInterface<?> plugin = (QInstanceEnricherPluginInterface<?>) aClass.getConstructor().newInstance();
|
||||
addEnricherPlugin(plugin);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw (new QException("Error discovering and adding enricher plugins in package [" + packageName + "]", e));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private <T> void runPlugins(Class<T> c, T t, QInstance qInstance)
|
||||
{
|
||||
for(QInstanceEnricherPluginInterface<?> plugin : CollectionUtils.nonNullList(enricherPlugins.get(c)))
|
||||
{
|
||||
@SuppressWarnings("unchecked")
|
||||
QInstanceEnricherPluginInterface<T> castedPlugin = (QInstanceEnricherPluginInterface<T>) plugin;
|
||||
castedPlugin.enrich(t, qInstance);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -30,7 +30,6 @@ import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
||||
@ -111,7 +110,7 @@ public class QInstanceHelpContentManager
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG.info("Discarding help content with key that does not contain name:value format", logPair("key", key), logPair("id", record.getValue("id")));
|
||||
LOG.info("Discarding help content with key-part that does not contain name:value format", logPair("key", key), logPair("part", part), logPair("id", record.getValue("id")));
|
||||
}
|
||||
}
|
||||
|
||||
@ -150,19 +149,19 @@ public class QInstanceHelpContentManager
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
if(StringUtils.hasContent(tableName))
|
||||
{
|
||||
processHelpContentForTable(key, tableName, sectionName, fieldName, slotName, roles, helpContent);
|
||||
processHelpContentForTable(qInstance, key, tableName, sectionName, fieldName, slotName, roles, helpContent);
|
||||
}
|
||||
else if(StringUtils.hasContent(processName))
|
||||
{
|
||||
processHelpContentForProcess(key, processName, fieldName, stepName, roles, helpContent);
|
||||
processHelpContentForProcess(qInstance, key, processName, fieldName, stepName, roles, helpContent);
|
||||
}
|
||||
else if(StringUtils.hasContent(widgetName))
|
||||
{
|
||||
processHelpContentForWidget(key, widgetName, slotName, roles, helpContent);
|
||||
processHelpContentForWidget(qInstance, key, widgetName, slotName, roles, helpContent);
|
||||
}
|
||||
else if(nameValuePairs.containsKey("instanceLevel"))
|
||||
{
|
||||
processHelpContentForInstance(key, slotName, roles, helpContent);
|
||||
processHelpContentForInstance(qInstance, key, slotName, roles, helpContent);
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
@ -176,9 +175,9 @@ public class QInstanceHelpContentManager
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static void processHelpContentForTable(String key, String tableName, String sectionName, String fieldName, String slotName, Set<HelpRole> roles, QHelpContent helpContent)
|
||||
private static void processHelpContentForTable(QInstance qInstance, String key, String tableName, String sectionName, String fieldName, String slotName, Set<HelpRole> roles, QHelpContent helpContent)
|
||||
{
|
||||
QTableMetaData table = QContext.getQInstance().getTable(tableName);
|
||||
QTableMetaData table = qInstance.getTable(tableName);
|
||||
if(table == null)
|
||||
{
|
||||
LOG.info("Unrecognized table in help content", logPair("key", key));
|
||||
@ -246,9 +245,30 @@ public class QInstanceHelpContentManager
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static void processHelpContentForProcess(String key, String processName, String fieldName, String stepName, Set<HelpRole> roles, QHelpContent helpContent)
|
||||
private static void processHelpContentForProcess(QInstance qInstance, String key, String processName, String fieldName, String stepName, Set<HelpRole> roles, QHelpContent helpContent)
|
||||
{
|
||||
QProcessMetaData process = QContext.getQInstance().getProcess(processName);
|
||||
if(processName.startsWith("*") && processName.length() > 1)
|
||||
{
|
||||
boolean anyMatched = false;
|
||||
String subName = processName.substring(1);
|
||||
for(QProcessMetaData process : qInstance.getProcesses().values())
|
||||
{
|
||||
if(process.getName().endsWith(subName))
|
||||
{
|
||||
anyMatched = true;
|
||||
processHelpContentForProcess(qInstance, key, process.getName(), fieldName, stepName, roles, helpContent);
|
||||
}
|
||||
}
|
||||
|
||||
if(!anyMatched)
|
||||
{
|
||||
LOG.info("Wildcard process name did not match any processes in help content", logPair("key", key));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
QProcessMetaData process = qInstance.getProcess(processName);
|
||||
if(process == null)
|
||||
{
|
||||
LOG.info("Unrecognized process in help content", logPair("key", key));
|
||||
@ -306,9 +326,9 @@ public class QInstanceHelpContentManager
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static void processHelpContentForWidget(String key, String widgetName, String slotName, Set<HelpRole> roles, QHelpContent helpContent)
|
||||
private static void processHelpContentForWidget(QInstance qInstance, String key, String widgetName, String slotName, Set<HelpRole> roles, QHelpContent helpContent)
|
||||
{
|
||||
QWidgetMetaDataInterface widget = QContext.getQInstance().getWidget(widgetName);
|
||||
QWidgetMetaDataInterface widget = qInstance.getWidget(widgetName);
|
||||
if(!StringUtils.hasContent(slotName))
|
||||
{
|
||||
LOG.info("Missing slot name in help content", logPair("key", key));
|
||||
@ -335,7 +355,7 @@ public class QInstanceHelpContentManager
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static void processHelpContentForInstance(String key, String slotName, Set<HelpRole> roles, QHelpContent helpContent)
|
||||
private static void processHelpContentForInstance(QInstance qInstance, String key, String slotName, Set<HelpRole> roles, QHelpContent helpContent)
|
||||
{
|
||||
if(!StringUtils.hasContent(slotName))
|
||||
{
|
||||
@ -345,11 +365,11 @@ public class QInstanceHelpContentManager
|
||||
{
|
||||
if(helpContent != null)
|
||||
{
|
||||
QContext.getQInstance().withHelpContent(slotName, helpContent);
|
||||
qInstance.withHelpContent(slotName, helpContent);
|
||||
}
|
||||
else
|
||||
{
|
||||
QContext.getQInstance().removeHelpContent(slotName, roles);
|
||||
qInstance.removeHelpContent(slotName, roles);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -37,7 +37,9 @@ import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.TimeZone;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import com.kingsrook.qqq.backend.core.actions.automation.RecordAutomationHandler;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
|
||||
@ -108,12 +110,16 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.Automatio
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.QTableAutomationDetails;
|
||||
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.model.metadata.variants.BackendVariantSetting;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.variants.BackendVariantsConfig;
|
||||
import com.kingsrook.qqq.backend.core.modules.authentication.QAuthenticationModuleCustomizerInterface;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ListingHash;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.lambdas.UnsafeFunction;
|
||||
import com.kingsrook.qqq.backend.core.utils.lambdas.UnsafeLambda;
|
||||
import org.apache.commons.lang.BooleanUtils;
|
||||
import org.quartz.CronExpression;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
@ -136,6 +142,8 @@ public class QInstanceValidator
|
||||
|
||||
private static ListingHash<Class<?>, QInstanceValidatorPluginInterface<?>> validatorPlugins = new ListingHash<>();
|
||||
|
||||
private JoinGraph joinGraph = null;
|
||||
|
||||
private List<String> errors = new ArrayList<>();
|
||||
|
||||
|
||||
@ -163,8 +171,7 @@ public class QInstanceValidator
|
||||
// the enricher will build a join graph (if there are any joins). we'd like to only do that //
|
||||
// once, during the enrichment/validation work, so, capture it, and store it back in the instance. //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
JoinGraph joinGraph = null;
|
||||
long start = System.currentTimeMillis();
|
||||
long start = System.currentTimeMillis();
|
||||
try
|
||||
{
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -173,7 +180,7 @@ public class QInstanceValidator
|
||||
// TODO - possible point of customization (use a different enricher, or none, or pass it options).
|
||||
QInstanceEnricher qInstanceEnricher = new QInstanceEnricher(qInstance);
|
||||
qInstanceEnricher.enrich();
|
||||
joinGraph = qInstanceEnricher.getJoinGraph();
|
||||
this.joinGraph = qInstanceEnricher.getJoinGraph();
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
@ -373,8 +380,8 @@ public class QInstanceValidator
|
||||
assertCondition(join.getType() != null, "Missing type for join: " + joinName);
|
||||
assertCondition(CollectionUtils.nullSafeHasContents(join.getJoinOns()), "Missing joinOns for join: " + joinName);
|
||||
|
||||
boolean leftTableExists = assertCondition(qInstance.getTable(join.getLeftTable()) != null, "Left-table name " + join.getLeftTable() + " join " + joinName + " is not a defined table in this instance.");
|
||||
boolean rightTableExists = assertCondition(qInstance.getTable(join.getRightTable()) != null, "Right-table name " + join.getRightTable() + " join " + joinName + " is not a defined table in this instance.");
|
||||
boolean leftTableExists = assertCondition(qInstance.getTable(join.getLeftTable()) != null, "Left-table name " + join.getLeftTable() + " in join " + joinName + " is not a defined table in this instance.");
|
||||
boolean rightTableExists = assertCondition(qInstance.getTable(join.getRightTable()) != null, "Right-table name " + join.getRightTable() + " in join " + joinName + " is not a defined table in this instance.");
|
||||
|
||||
for(JoinOn joinOn : CollectionUtils.nonNullList(join.getJoinOns()))
|
||||
{
|
||||
@ -543,6 +550,60 @@ public class QInstanceValidator
|
||||
{
|
||||
assertCondition(Objects.equals(backendName, backend.getName()), "Inconsistent naming for backend: " + backendName + "/" + backend.getName() + ".");
|
||||
|
||||
///////////////////////
|
||||
// validate variants //
|
||||
///////////////////////
|
||||
BackendVariantsConfig backendVariantsConfig = backend.getBackendVariantsConfig();
|
||||
if(BooleanUtils.isTrue(backend.getUsesVariants()))
|
||||
{
|
||||
if(assertCondition(backendVariantsConfig != null, "Missing backendVariantsConfig in backend [" + backendName + "] which is marked as usesVariants"))
|
||||
{
|
||||
assertCondition(StringUtils.hasContent(backendVariantsConfig.getVariantTypeKey()), "Missing variantTypeKey in backendVariantsConfig in [" + backendName + "]");
|
||||
|
||||
String optionsTableName = backendVariantsConfig.getOptionsTableName();
|
||||
QTableMetaData optionsTable = qInstance.getTable(optionsTableName);
|
||||
if(assertCondition(StringUtils.hasContent(optionsTableName), "Missing optionsTableName in backendVariantsConfig in [" + backendName + "]"))
|
||||
{
|
||||
if(assertCondition(optionsTable != null, "Unrecognized optionsTableName [" + optionsTableName + "] in backendVariantsConfig in [" + backendName + "]"))
|
||||
{
|
||||
QQueryFilter optionsFilter = backendVariantsConfig.getOptionsFilter();
|
||||
if(optionsFilter != null)
|
||||
{
|
||||
validateQueryFilter(qInstance, "optionsFilter in backendVariantsConfig in backend [" + backendName + "]: ", optionsTable, optionsFilter, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Map<BackendVariantSetting, String> backendSettingSourceFieldNameMap = backendVariantsConfig.getBackendSettingSourceFieldNameMap();
|
||||
if(assertCondition(CollectionUtils.nullSafeHasContents(backendSettingSourceFieldNameMap), "Missing or empty backendSettingSourceFieldNameMap in backendVariantsConfig in [" + backendName + "]"))
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// only validate field names in the backendSettingSourceFieldNameMap if there is NOT a variantRecordSupplier //
|
||||
// (the idea being, that the supplier might be building a record with fieldNames that aren't in the table... //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
if(optionsTable != null && backendVariantsConfig.getVariantRecordLookupFunction() == null)
|
||||
{
|
||||
for(Map.Entry<BackendVariantSetting, String> entry : backendSettingSourceFieldNameMap.entrySet())
|
||||
{
|
||||
assertCondition(optionsTable.getFields().containsKey(entry.getValue()), "Unrecognized fieldName [" + entry.getValue() + "] in backendSettingSourceFieldNameMap in backendVariantsConfig in [" + backendName + "]");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(backendVariantsConfig.getVariantRecordLookupFunction() != null)
|
||||
{
|
||||
validateSimpleCodeReference("VariantRecordSupplier in backendVariantsConfig in backend [" + backendName + "]: ", backendVariantsConfig.getVariantRecordLookupFunction(), UnsafeFunction.class, Function.class);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
assertCondition(backendVariantsConfig == null, "Should not have a backendVariantsConfig in backend [" + backendName + "] which is not marked as usesVariants");
|
||||
}
|
||||
|
||||
///////////////////////////////////////////
|
||||
// let the backend do its own validation //
|
||||
///////////////////////////////////////////
|
||||
backend.performValidation(this);
|
||||
|
||||
runPlugins(QBackendMetaData.class, backend, qInstance);
|
||||
@ -577,7 +638,7 @@ public class QInstanceValidator
|
||||
private void validateAuthentication(QInstance qInstance)
|
||||
{
|
||||
QAuthenticationMetaData authentication = qInstance.getAuthentication();
|
||||
if(authentication != null)
|
||||
if(assertCondition(authentication != null, "Authentication MetaData must be defined."))
|
||||
{
|
||||
if(authentication.getCustomizer() != null)
|
||||
{
|
||||
@ -780,7 +841,7 @@ public class QInstanceValidator
|
||||
{
|
||||
if(assertCondition(StringUtils.hasContent(association.getName()), "missing a name for an Association on table " + table.getName()))
|
||||
{
|
||||
String messageSuffix = " for Association " + association.getName() + " on table " + table.getName();
|
||||
String messageSuffix = " for Association " + association.getName() + " on table " + table.getName();
|
||||
boolean recognizedTable = false;
|
||||
if(assertCondition(StringUtils.hasContent(association.getAssociatedTableName()), "missing associatedTableName" + messageSuffix))
|
||||
{
|
||||
@ -988,7 +1049,15 @@ public class QInstanceValidator
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<FieldBehavior<?>> behaviorClass = (Class<FieldBehavior<?>>) fieldBehavior.getClass();
|
||||
|
||||
errors.addAll(fieldBehavior.validateBehaviorConfiguration(table, field));
|
||||
List<String> behaviorErrors = fieldBehavior.validateBehaviorConfiguration(table, field);
|
||||
if(behaviorErrors != null)
|
||||
{
|
||||
String prefixMinusTrailingSpace = prefix.replaceFirst(" *$", "");
|
||||
for(String behaviorError : behaviorErrors)
|
||||
{
|
||||
errors.add(prefixMinusTrailingSpace + ": " + behaviorClass.getSimpleName() + ": " + behaviorError);
|
||||
}
|
||||
}
|
||||
|
||||
if(!fieldBehavior.allowMultipleBehaviorsOfThisType())
|
||||
{
|
||||
@ -1348,7 +1417,7 @@ public class QInstanceValidator
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
if(customizerInstance != null && tableCustomizer.getExpectedType() != null)
|
||||
{
|
||||
assertObjectCanBeCasted(prefix, tableCustomizer.getExpectedType(), customizerInstance);
|
||||
assertObjectCanBeCasted(prefix, customizerInstance, tableCustomizer.getExpectedType());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1360,18 +1429,31 @@ public class QInstanceValidator
|
||||
/*******************************************************************************
|
||||
** Make sure that a given object can be casted to an expected type.
|
||||
*******************************************************************************/
|
||||
private <T> T assertObjectCanBeCasted(String errorPrefix, Class<T> expectedType, Object object)
|
||||
private void assertObjectCanBeCasted(String errorPrefix, Object object, Class<?>... anyOfExpectedClasses)
|
||||
{
|
||||
T castedObject = null;
|
||||
try
|
||||
for(Class<?> expectedClass : anyOfExpectedClasses)
|
||||
{
|
||||
castedObject = expectedType.cast(object);
|
||||
try
|
||||
{
|
||||
expectedClass.cast(object);
|
||||
return;
|
||||
}
|
||||
catch(ClassCastException e)
|
||||
{
|
||||
/////////////////////////////////////
|
||||
// try next type (if there is one) //
|
||||
/////////////////////////////////////
|
||||
}
|
||||
}
|
||||
catch(ClassCastException e)
|
||||
|
||||
if(anyOfExpectedClasses.length == 1)
|
||||
{
|
||||
errors.add(errorPrefix + "CodeReference is not of the expected type: " + expectedType);
|
||||
errors.add(errorPrefix + "CodeReference is not of the expected type: " + anyOfExpectedClasses[0]);
|
||||
}
|
||||
else
|
||||
{
|
||||
errors.add(errorPrefix + "CodeReference is not any of the expected types: " + Arrays.stream(anyOfExpectedClasses).map(c -> c.getName()).collect(Collectors.joining(", ")));
|
||||
}
|
||||
return castedObject;
|
||||
}
|
||||
|
||||
|
||||
@ -1608,12 +1690,12 @@ public class QInstanceValidator
|
||||
|
||||
for(QFieldMetaData field : process.getInputFields())
|
||||
{
|
||||
validateFieldPossibleValueSourceAttributes(qInstance, field, "Process " + processName + ", input field " + field.getName());
|
||||
validateFieldPossibleValueSourceAttributes(qInstance, field, "Process " + processName + ", input field " + field.getName() + " ");
|
||||
}
|
||||
|
||||
for(QFieldMetaData field : process.getOutputFields())
|
||||
{
|
||||
validateFieldPossibleValueSourceAttributes(qInstance, field, "Process " + processName + ", output field " + field.getName());
|
||||
validateFieldPossibleValueSourceAttributes(qInstance, field, "Process " + processName + ", output field " + field.getName() + " ");
|
||||
}
|
||||
|
||||
if(process.getCancelStep() != null)
|
||||
@ -1824,7 +1906,7 @@ public class QInstanceValidator
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void validateQueryFilter(QInstance qInstance, String context, QTableMetaData table, QQueryFilter queryFilter, List<QueryJoin> queryJoins)
|
||||
public void validateQueryFilter(QInstance qInstance, String context, QTableMetaData table, QQueryFilter queryFilter, List<QueryJoin> queryJoins)
|
||||
{
|
||||
for(QFilterCriteria criterion : CollectionUtils.nonNullList(queryFilter.getCriteria()))
|
||||
{
|
||||
@ -1868,7 +1950,8 @@ public class QInstanceValidator
|
||||
{
|
||||
if(fieldName.contains("."))
|
||||
{
|
||||
String fieldNameAfterDot = fieldName.substring(fieldName.lastIndexOf(".") + 1);
|
||||
String fieldNameAfterDot = fieldName.substring(fieldName.lastIndexOf(".") + 1);
|
||||
String tableNameBeforeDot = fieldName.substring(0, fieldName.lastIndexOf("."));
|
||||
|
||||
if(CollectionUtils.nullSafeHasContents(queryJoins))
|
||||
{
|
||||
@ -1892,11 +1975,32 @@ public class QInstanceValidator
|
||||
}
|
||||
else
|
||||
{
|
||||
errors.add("QInstanceValidator does not yet support finding a field that looks like a join field, but isn't associated with a query.");
|
||||
return (true);
|
||||
// todo! for(QJoinMetaData join : CollectionUtils.nonNullMap(qInstance.getJoins()).values())
|
||||
// {
|
||||
// }
|
||||
if(this.joinGraph != null)
|
||||
{
|
||||
Set<JoinGraph.JoinConnectionList> joinConnections = joinGraph.getJoinConnections(table.getName());
|
||||
for(JoinGraph.JoinConnectionList joinConnectionList : joinConnections)
|
||||
{
|
||||
JoinGraph.JoinConnection joinConnection = joinConnectionList.list().get(joinConnectionList.list().size() - 1);
|
||||
if(tableNameBeforeDot.equals(joinConnection.joinTable()))
|
||||
{
|
||||
QTableMetaData joinTable = qInstance.getTable(tableNameBeforeDot);
|
||||
if(joinTable.getFields().containsKey(fieldNameAfterDot))
|
||||
{
|
||||
/////////////////////////
|
||||
// mmm, looks valid... //
|
||||
/////////////////////////
|
||||
return (true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// todo - not sure how vulnerable we are to ongoing issues here... //
|
||||
// idea: let a filter (or any object?) be opted out of validation, some version of //
|
||||
// a static map of objects we can check at the top of various validate methods... //
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
errors.add("Failed to find field named: " + fieldName);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2000,6 +2104,11 @@ public class QInstanceValidator
|
||||
}
|
||||
}
|
||||
|
||||
if(widget.getValidatorPlugin() != null)
|
||||
{
|
||||
widget.getValidatorPlugin().validate(widget, qInstance, this);
|
||||
}
|
||||
|
||||
runPlugins(QWidgetMetaDataInterface.class, widget, qInstance);
|
||||
}
|
||||
);
|
||||
@ -2099,6 +2208,8 @@ public class QInstanceValidator
|
||||
default -> errors.add("Unexpected possibleValueSource type: " + possibleValueSource.getType());
|
||||
}
|
||||
|
||||
assertCondition(possibleValueSource.getIdType() != null, "possibleValueSource " + name + " is missing its idType.");
|
||||
|
||||
runPlugins(QPossibleValueSource.class, possibleValueSource, qInstance);
|
||||
}
|
||||
}
|
||||
@ -2108,7 +2219,8 @@ public class QInstanceValidator
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void validateSimpleCodeReference(String prefix, QCodeReference codeReference, Class<?> expectedClass)
|
||||
@SafeVarargs
|
||||
private void validateSimpleCodeReference(String prefix, QCodeReference codeReference, Class<?>... anyOfExpectedClasses)
|
||||
{
|
||||
if(!preAssertionsForCodeReference(codeReference, prefix))
|
||||
{
|
||||
@ -2136,7 +2248,7 @@ public class QInstanceValidator
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
if(classInstance != null)
|
||||
{
|
||||
assertObjectCanBeCasted(prefix, expectedClass, classInstance);
|
||||
assertObjectCanBeCasted(prefix, classInstance, anyOfExpectedClasses);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.instances.enrichment.plugins;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Interface for additional / optional enrichment to be done on q instance members.
|
||||
** Some may be provided by QQQ - others can be defined by applications.
|
||||
*******************************************************************************/
|
||||
public interface QInstanceEnricherPluginInterface<T>
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
void enrich(T object, QInstance qInstance);
|
||||
|
||||
}
|
@ -326,6 +326,20 @@ public class AuditSingleInput implements Serializable
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for details
|
||||
*******************************************************************************/
|
||||
public AuditSingleInput withDetailMessages(List<String> details)
|
||||
{
|
||||
for(String detail : details)
|
||||
{
|
||||
addDetail(message);
|
||||
}
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
|
@ -40,6 +40,8 @@ public class ProcessState implements Serializable
|
||||
private Map<String, Serializable> values = new HashMap<>();
|
||||
private List<String> stepList = new ArrayList<>();
|
||||
private Optional<String> nextStepName = Optional.empty();
|
||||
private Optional<String> backStepName = Optional.empty();
|
||||
private boolean isStepBack = false;
|
||||
|
||||
private ProcessMetaDataAdjustment processMetaDataAdjustment = null;
|
||||
|
||||
@ -122,6 +124,39 @@ public class ProcessState implements Serializable
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for backStepName
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Optional<String> getBackStepName()
|
||||
{
|
||||
return backStepName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for backStepName
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setBackStepName(String backStepName)
|
||||
{
|
||||
this.backStepName = Optional.of(backStepName);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** clear out the value of backStepName (set the Optional to empty)
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void clearBackStepName()
|
||||
{
|
||||
this.backStepName = Optional.empty();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for stepList
|
||||
**
|
||||
@ -176,4 +211,35 @@ public class ProcessState implements Serializable
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for isStepBack
|
||||
*******************************************************************************/
|
||||
public boolean getIsStepBack()
|
||||
{
|
||||
return (this.isStepBack);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for isStepBack
|
||||
*******************************************************************************/
|
||||
public void setIsStepBack(boolean isStepBack)
|
||||
{
|
||||
this.isStepBack = isStepBack;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for isStepBack
|
||||
*******************************************************************************/
|
||||
public ProcessState withIsStepBack(boolean isStepBack)
|
||||
{
|
||||
this.isStepBack = isStepBack;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -53,6 +53,7 @@ public class ProcessSummaryLine implements ProcessSummaryLineInterface
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
private ArrayList<Serializable> primaryKeys;
|
||||
|
||||
private ArrayList<String> bulletsOfText;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -497,4 +498,35 @@ public class ProcessSummaryLine implements ProcessSummaryLineInterface
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for bulletsOfText
|
||||
*******************************************************************************/
|
||||
public ArrayList<String> getBulletsOfText()
|
||||
{
|
||||
return (this.bulletsOfText);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for bulletsOfText
|
||||
*******************************************************************************/
|
||||
public void setBulletsOfText(ArrayList<String> bulletsOfText)
|
||||
{
|
||||
this.bulletsOfText = bulletsOfText;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for bulletsOfText
|
||||
*******************************************************************************/
|
||||
public ProcessSummaryLine withBulletsOfText(ArrayList<String> bulletsOfText)
|
||||
{
|
||||
this.bulletsOfText = bulletsOfText;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -25,19 +25,28 @@ package com.kingsrook.qqq.backend.core.model.actions.processes;
|
||||
import java.io.Serializable;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import com.kingsrook.qqq.backend.core.actions.async.AsyncJobCallback;
|
||||
import com.kingsrook.qqq.backend.core.actions.async.AsyncJobStatus;
|
||||
import com.kingsrook.qqq.backend.core.actions.async.NonPersistedAsyncJobCallback;
|
||||
import com.kingsrook.qqq.backend.core.actions.processes.QProcessCallback;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QStepMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.processes.tracing.ProcessTracerInterface;
|
||||
import com.kingsrook.qqq.backend.core.processes.tracing.ProcessTracerMessage;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -46,6 +55,8 @@ import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
*******************************************************************************/
|
||||
public class RunBackendStepInput extends AbstractActionInput
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(RunBackendStepInput.class);
|
||||
|
||||
private ProcessState processState;
|
||||
private String processName;
|
||||
private String tableName;
|
||||
@ -55,12 +66,13 @@ public class RunBackendStepInput extends AbstractActionInput
|
||||
private RunProcessInput.FrontendStepBehavior frontendStepBehavior;
|
||||
private Instant basepullLastRunTime;
|
||||
|
||||
private ProcessTracerInterface processTracer;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// note - new fields should generally be added in method: cloneFieldsInto //
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -96,6 +108,7 @@ public class RunBackendStepInput extends AbstractActionInput
|
||||
target.setAsyncJobCallback(getAsyncJobCallback());
|
||||
target.setFrontendStepBehavior(getFrontendStepBehavior());
|
||||
target.setValues(getValues());
|
||||
target.setProcessTracer(getProcessTracer().orElse(null));
|
||||
}
|
||||
|
||||
|
||||
@ -238,6 +251,26 @@ public class RunBackendStepInput extends AbstractActionInput
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for records converted to entities of a given type.
|
||||
**
|
||||
*******************************************************************************/
|
||||
public <E extends QRecordEntity> List<E> getRecordsAsEntities(Class<E> entityClass) throws QException
|
||||
{
|
||||
List<E> rs = new ArrayList<>();
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// note - important to call getRecords here, which is overwritten in subclasses! //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
for(QRecord record : getRecords())
|
||||
{
|
||||
rs.add(QRecordEntity.fromQRecord(entityClass, record));
|
||||
}
|
||||
return (rs);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for records
|
||||
**
|
||||
@ -419,6 +452,17 @@ public class RunBackendStepInput extends AbstractActionInput
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Accessor for processState's isStepBack attribute
|
||||
**
|
||||
*******************************************************************************/
|
||||
public boolean getIsStepBack()
|
||||
{
|
||||
return processState.getIsStepBack();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Accessor for processState - protected, because we generally want to access
|
||||
** its members through wrapper methods, we think
|
||||
@ -524,4 +568,64 @@ public class RunBackendStepInput extends AbstractActionInput
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for processTracer
|
||||
*******************************************************************************/
|
||||
public void setProcessTracer(ProcessTracerInterface processTracer)
|
||||
{
|
||||
this.processTracer = processTracer;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for processTracer
|
||||
*******************************************************************************/
|
||||
public RunBackendStepInput withProcessTracer(ProcessTracerInterface processTracer)
|
||||
{
|
||||
this.processTracer = processTracer;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public Optional<ProcessTracerInterface> getProcessTracer()
|
||||
{
|
||||
return Optional.ofNullable(processTracer);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public void traceMessage(ProcessTracerMessage message)
|
||||
{
|
||||
if(processTracer != null && message != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
processTracer.handleMessage(this, message);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.warn("Error tracing message", e, logPair("message", message));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public QProcessMetaData getProcess()
|
||||
{
|
||||
return (QContext.getQInstance().getProcess(getProcessName()));
|
||||
}
|
||||
}
|
||||
|
@ -33,6 +33,7 @@ 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.model.data.QRecordEntity;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendStepMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
@ -258,7 +259,7 @@ public class RunBackendStepOutput extends AbstractActionOutput implements Serial
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
** add a record to the step output, e.g., for going through to the next step.
|
||||
*******************************************************************************/
|
||||
public void addRecord(QRecord record)
|
||||
{
|
||||
@ -271,6 +272,16 @@ public class RunBackendStepOutput extends AbstractActionOutput implements Serial
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
** add a RecordEntity to the step output, e.g., for going through to the next step.
|
||||
***************************************************************************/
|
||||
public void addRecordEntity(QRecordEntity recordEntity)
|
||||
{
|
||||
addRecord(recordEntity.toQRecord());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for auditInputList
|
||||
*******************************************************************************/
|
||||
|
@ -49,6 +49,7 @@ public class RunProcessInput extends AbstractActionInput
|
||||
private ProcessState processState;
|
||||
private FrontendStepBehavior frontendStepBehavior = FrontendStepBehavior.BREAK;
|
||||
private String startAfterStep;
|
||||
private String startAtStep;
|
||||
private String processUUID;
|
||||
private AsyncJobCallback asyncJobCallback;
|
||||
|
||||
@ -451,4 +452,35 @@ public class RunProcessInput extends AbstractActionInput
|
||||
{
|
||||
return asyncJobCallback;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for startAtStep
|
||||
*******************************************************************************/
|
||||
public String getStartAtStep()
|
||||
{
|
||||
return (this.startAtStep);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for startAtStep
|
||||
*******************************************************************************/
|
||||
public void setStartAtStep(String startAtStep)
|
||||
{
|
||||
this.startAtStep = startAtStep;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for startAtStep
|
||||
*******************************************************************************/
|
||||
public RunProcessInput withStartAtStep(String startAtStep)
|
||||
{
|
||||
this.startAtStep = startAtStep;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.actions.tables.query;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public enum CriteriaOption implements CriteriaOptionInterface
|
||||
{
|
||||
CASE_INSENSITIVE;
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.actions.tables.query;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public interface CriteriaOptionInterface
|
||||
{
|
||||
}
|
@ -26,8 +26,10 @@ import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.serialization.QFilterCriteriaDeserializer;
|
||||
@ -45,7 +47,7 @@ public class QFilterCriteria implements Serializable, Cloneable, QMetaDataObject
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(QFilterCriteria.class);
|
||||
|
||||
private String fieldName;
|
||||
private String fieldName;
|
||||
private QCriteriaOperator operator;
|
||||
private List<Serializable> values;
|
||||
|
||||
@ -54,6 +56,8 @@ public class QFilterCriteria implements Serializable, Cloneable, QMetaDataObject
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
private String otherFieldName;
|
||||
|
||||
private Set<CriteriaOptionInterface> options = null;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -70,6 +74,13 @@ public class QFilterCriteria implements Serializable, Cloneable, QMetaDataObject
|
||||
clone.values = new ArrayList<>();
|
||||
clone.values.addAll(values);
|
||||
}
|
||||
|
||||
if(options != null)
|
||||
{
|
||||
clone.options = new HashSet<>();
|
||||
clone.options.addAll(options);
|
||||
}
|
||||
|
||||
return clone;
|
||||
}
|
||||
catch(CloneNotSupportedException e)
|
||||
@ -386,4 +397,78 @@ public class QFilterCriteria implements Serializable, Cloneable, QMetaDataObject
|
||||
return Objects.hash(fieldName, operator, values, otherFieldName);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for options
|
||||
*******************************************************************************/
|
||||
public Set<CriteriaOptionInterface> getOptions()
|
||||
{
|
||||
return (this.options);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for options
|
||||
*******************************************************************************/
|
||||
public void setOptions(Set<CriteriaOptionInterface> options)
|
||||
{
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for options
|
||||
*******************************************************************************/
|
||||
public QFilterCriteria withOptions(Set<CriteriaOptionInterface> options)
|
||||
{
|
||||
this.options = options;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public QFilterCriteria withOption(CriteriaOptionInterface option)
|
||||
{
|
||||
if(options == null)
|
||||
{
|
||||
options = new HashSet<>();
|
||||
}
|
||||
options.add(option);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public QFilterCriteria withoutOption(CriteriaOptionInterface option)
|
||||
{
|
||||
if(options != null)
|
||||
{
|
||||
options.remove(option);
|
||||
}
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public boolean hasOption(CriteriaOptionInterface option)
|
||||
{
|
||||
if(options == null)
|
||||
{
|
||||
return (false);
|
||||
}
|
||||
|
||||
return (options.contains(option));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -854,4 +854,20 @@ public class QQueryFilter implements Serializable, Cloneable, QMetaDataObject
|
||||
}
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public void applyCriteriaOptionToAllCriteria(CriteriaOptionInterface criteriaOption)
|
||||
{
|
||||
for(QFilterCriteria criteria : CollectionUtils.nonNullList(this.criteria))
|
||||
{
|
||||
criteria.withOption(criteriaOption);
|
||||
}
|
||||
|
||||
for(QQueryFilter subFilter : CollectionUtils.nonNullList(subFilters))
|
||||
{
|
||||
subFilter.applyCriteriaOptionToAllCriteria(criteriaOption);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -22,13 +22,14 @@
|
||||
package com.kingsrook.qqq.backend.core.model.actions.tables.storage;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Input for Storage actions.
|
||||
*******************************************************************************/
|
||||
public class StorageInput extends AbstractTableActionInput
|
||||
public class StorageInput extends AbstractTableActionInput implements Serializable
|
||||
{
|
||||
private String reference;
|
||||
private String contentType;
|
||||
|
@ -38,9 +38,10 @@ public class SearchPossibleValueSourceInput extends AbstractActionInput implemen
|
||||
private QQueryFilter defaultQueryFilter;
|
||||
private String searchTerm;
|
||||
private List<Serializable> idList;
|
||||
private List<String> labelList;
|
||||
|
||||
private Integer skip = 0;
|
||||
private Integer limit = 100;
|
||||
private Integer limit = 250;
|
||||
|
||||
|
||||
|
||||
@ -281,4 +282,35 @@ public class SearchPossibleValueSourceInput extends AbstractActionInput implemen
|
||||
this.limit = limit;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for labelList
|
||||
*******************************************************************************/
|
||||
public List<String> getLabelList()
|
||||
{
|
||||
return (this.labelList);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for labelList
|
||||
*******************************************************************************/
|
||||
public void setLabelList(List<String> labelList)
|
||||
{
|
||||
this.labelList = labelList;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for labelList
|
||||
*******************************************************************************/
|
||||
public SearchPossibleValueSourceInput withLabelList(List<String> labelList)
|
||||
{
|
||||
this.labelList = labelList;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -35,6 +35,7 @@ public class SearchPossibleValueSourceOutput extends AbstractActionOutput
|
||||
{
|
||||
private List<QPossibleValue<?>> results = new ArrayList<>();
|
||||
|
||||
private String warning;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -88,4 +89,35 @@ public class SearchPossibleValueSourceOutput extends AbstractActionOutput
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for warning
|
||||
*******************************************************************************/
|
||||
public String getWarning()
|
||||
{
|
||||
return (this.warning);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for warning
|
||||
*******************************************************************************/
|
||||
public void setWarning(String warning)
|
||||
{
|
||||
this.warning = warning;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for warning
|
||||
*******************************************************************************/
|
||||
public SearchPossibleValueSourceOutput withWarning(String warning)
|
||||
{
|
||||
this.warning = warning;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -51,6 +51,7 @@ public class ChildRecordListData extends QWidgetData
|
||||
private boolean canAddChildRecord = false;
|
||||
private Map<String, Serializable> defaultValuesForNewChildRecords;
|
||||
private Set<String> disabledFieldsForNewChildRecords;
|
||||
private Map<String, String> defaultValuesForNewChildRecordsFromParentFields;
|
||||
|
||||
|
||||
|
||||
@ -523,6 +524,37 @@ public class ChildRecordListData extends QWidgetData
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for defaultValuesForNewChildRecordsFromParentFields
|
||||
*******************************************************************************/
|
||||
public Map<String, String> getDefaultValuesForNewChildRecordsFromParentFields()
|
||||
{
|
||||
return (this.defaultValuesForNewChildRecordsFromParentFields);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for defaultValuesForNewChildRecordsFromParentFields
|
||||
*******************************************************************************/
|
||||
public void setDefaultValuesForNewChildRecordsFromParentFields(Map<String, String> defaultValuesForNewChildRecordsFromParentFields)
|
||||
{
|
||||
this.defaultValuesForNewChildRecordsFromParentFields = defaultValuesForNewChildRecordsFromParentFields;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for defaultValuesForNewChildRecordsFromParentFields
|
||||
*******************************************************************************/
|
||||
public ChildRecordListData withDefaultValuesForNewChildRecordsFromParentFields(Map<String, String> defaultValuesForNewChildRecordsFromParentFields)
|
||||
{
|
||||
this.defaultValuesForNewChildRecordsFromParentFields = defaultValuesForNewChildRecordsFromParentFields;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -34,8 +34,12 @@ public class FilterAndColumnsSetupData extends QWidgetData
|
||||
private String tableName;
|
||||
private Boolean allowVariables = false;
|
||||
private Boolean hideColumns = false;
|
||||
private Boolean hidePreview = false;
|
||||
private List<String> filterDefaultFieldNames;
|
||||
|
||||
private String filterFieldName = "queryFilterJson";
|
||||
private String columnFieldName = "columnsJson";
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -193,4 +197,97 @@ public class FilterAndColumnsSetupData extends QWidgetData
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for hidePreview
|
||||
*******************************************************************************/
|
||||
public Boolean getHidePreview()
|
||||
{
|
||||
return (this.hidePreview);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for hidePreview
|
||||
*******************************************************************************/
|
||||
public void setHidePreview(Boolean hidePreview)
|
||||
{
|
||||
this.hidePreview = hidePreview;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for hidePreview
|
||||
*******************************************************************************/
|
||||
public FilterAndColumnsSetupData withHidePreview(Boolean hidePreview)
|
||||
{
|
||||
this.hidePreview = hidePreview;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for filterFieldName
|
||||
*******************************************************************************/
|
||||
public String getFilterFieldName()
|
||||
{
|
||||
return (this.filterFieldName);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for filterFieldName
|
||||
*******************************************************************************/
|
||||
public void setFilterFieldName(String filterFieldName)
|
||||
{
|
||||
this.filterFieldName = filterFieldName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for filterFieldName
|
||||
*******************************************************************************/
|
||||
public FilterAndColumnsSetupData withFilterFieldName(String filterFieldName)
|
||||
{
|
||||
this.filterFieldName = filterFieldName;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for columnFieldName
|
||||
*******************************************************************************/
|
||||
public String getColumnFieldName()
|
||||
{
|
||||
return (this.columnFieldName);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for columnFieldName
|
||||
*******************************************************************************/
|
||||
public void setColumnFieldName(String columnFieldName)
|
||||
{
|
||||
this.columnFieldName = columnFieldName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for columnFieldName
|
||||
*******************************************************************************/
|
||||
public FilterAndColumnsSetupData withColumnFieldName(String columnFieldName)
|
||||
{
|
||||
this.columnFieldName = columnFieldName;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -89,6 +89,11 @@ public @interface QField
|
||||
*******************************************************************************/
|
||||
int maxLength() default Integer.MAX_VALUE;
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
int gridColumns() default -1;
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
|
@ -154,7 +154,7 @@ public class QRecord implements Serializable
|
||||
return (null);
|
||||
}
|
||||
|
||||
Map<String, V> clone = new LinkedHashMap<>();
|
||||
Map<String, V> clone = new LinkedHashMap<>(map.size());
|
||||
for(Map.Entry<String, V> entry : map.entrySet())
|
||||
{
|
||||
Serializable value = entry.getValue();
|
||||
@ -246,6 +246,24 @@ public class QRecord implements Serializable
|
||||
}
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
** copy all values from 'joinedRecord' into this record's values map,
|
||||
** prefixing field names with joinTableNam + "."
|
||||
***************************************************************************/
|
||||
public void addJoinedRecordValues(String joinTableName, QRecord joinedRecord)
|
||||
{
|
||||
if(joinedRecord == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for(Map.Entry<String, Serializable> entry : joinedRecord.getValues().entrySet())
|
||||
{
|
||||
setValue(joinTableName + "." + entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
|
@ -41,11 +41,14 @@ import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QRuntimeException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ListingHash;
|
||||
import com.kingsrook.qqq.backend.core.utils.ObjectUtils;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
@ -61,6 +64,11 @@ public abstract class QRecordEntity
|
||||
|
||||
private Map<String, Serializable> originalRecordValues;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// map of entity class names to QTableMetaData objects that they helped build //
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
private static Map<String, QTableMetaData> tableReferences = new HashMap<>();
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -95,6 +103,19 @@ public abstract class QRecordEntity
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
** register a mapping between an entity class and a table that it is associated with.
|
||||
***************************************************************************/
|
||||
public static void registerTable(Class<? extends QRecordEntity> entityClass, QTableMetaData table)
|
||||
{
|
||||
if(entityClass != null && table != null)
|
||||
{
|
||||
tableReferences.put(entityClass.getName(), table);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Build an entity of this QRecord type from a QRecord
|
||||
**
|
||||
@ -176,7 +197,10 @@ public abstract class QRecordEntity
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Convert this entity to a QRecord.
|
||||
** Convert this entity to a QRecord. ALL fields in the entity will be set
|
||||
** in the QRecord. Note that, if you're using this for an input to the UpdateAction,
|
||||
** that this could cause values to be set to null, e.g., if you constructed
|
||||
** a entity from scratch, and didn't set all values in it!!
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QRecord toQRecord() throws QRuntimeException
|
||||
@ -190,25 +214,7 @@ public abstract class QRecordEntity
|
||||
qRecord.setValue(qRecordEntityField.getFieldName(), (Serializable) qRecordEntityField.getGetter().invoke(this));
|
||||
}
|
||||
|
||||
for(QRecordEntityAssociation qRecordEntityAssociation : getAssociationList(this.getClass()))
|
||||
{
|
||||
@SuppressWarnings("unchecked")
|
||||
List<? extends QRecordEntity> associatedEntities = (List<? extends QRecordEntity>) qRecordEntityAssociation.getGetter().invoke(this);
|
||||
String associationName = qRecordEntityAssociation.getAssociationAnnotation().name();
|
||||
|
||||
if(associatedEntities != null)
|
||||
{
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
// do this so an empty list in the entity becomes an empty list in the QRecord //
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
qRecord.withAssociatedRecords(associationName, new ArrayList<>());
|
||||
}
|
||||
|
||||
for(QRecordEntity associatedEntity : CollectionUtils.nonNullList(associatedEntities))
|
||||
{
|
||||
qRecord.withAssociatedRecord(associationName, associatedEntity.toQRecord());
|
||||
}
|
||||
}
|
||||
toQRecordProcessAssociations(qRecord, (entity) -> entity.toQRecord());
|
||||
|
||||
return (qRecord);
|
||||
}
|
||||
@ -220,15 +226,65 @@ public abstract class QRecordEntity
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
*
|
||||
***************************************************************************/
|
||||
private void toQRecordProcessAssociations(QRecord outputRecord, Function<QRecordEntity, QRecord> toRecordFunction) throws Exception
|
||||
{
|
||||
for(QRecordEntityAssociation qRecordEntityAssociation : getAssociationList(this.getClass()))
|
||||
{
|
||||
@SuppressWarnings("unchecked")
|
||||
List<? extends QRecordEntity> associatedEntities = (List<? extends QRecordEntity>) qRecordEntityAssociation.getGetter().invoke(this);
|
||||
String associationName = qRecordEntityAssociation.getAssociationAnnotation().name();
|
||||
|
||||
if(associatedEntities != null)
|
||||
{
|
||||
outputRecord.withAssociatedRecords(associationName, new ArrayList<>());
|
||||
for(QRecordEntity associatedEntity : associatedEntities)
|
||||
{
|
||||
outputRecord.withAssociatedRecord(associationName, toRecordFunction.apply(associatedEntity));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
** Overload of toQRecordOnlyChangedFields that preserves original behavior of
|
||||
** that method, which is, to NOT includePrimaryKey
|
||||
*******************************************************************************/
|
||||
@Deprecated(since = "includePrimaryKey param was added")
|
||||
public QRecord toQRecordOnlyChangedFields()
|
||||
{
|
||||
return toQRecordOnlyChangedFields(false);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Useful for the use-case of:
|
||||
** - fetch a QRecord (e.g., QueryAction or GetAction)
|
||||
** - build a QRecordEntity out of it
|
||||
** - change a field (or two) in it
|
||||
** - want to pass it into an UpdateAction, and want to see only the fields that
|
||||
** you know you changed get passed in to UpdateAction (e.g., PATCH semantics).
|
||||
**
|
||||
** But also - per the includePrimaryKey param, include the primaryKey in the
|
||||
** records (e.g., to tell the Update which records to update).
|
||||
**
|
||||
** Also, useful for:
|
||||
** - construct new entity, calling setters to populate some fields
|
||||
** - pass that entity into
|
||||
*******************************************************************************/
|
||||
public QRecord toQRecordOnlyChangedFields(boolean includePrimaryKey)
|
||||
{
|
||||
try
|
||||
{
|
||||
QRecord qRecord = new QRecord();
|
||||
|
||||
String primaryKeyFieldName = ObjectUtils.tryElse(() -> tableReferences.get(getClass().getName()).getPrimaryKeyField(), null);
|
||||
|
||||
for(QRecordEntityField qRecordEntityField : getFieldList(this.getClass()))
|
||||
{
|
||||
Serializable thisValue = (Serializable) qRecordEntityField.getGetter().invoke(this);
|
||||
@ -238,31 +294,16 @@ public abstract class QRecordEntity
|
||||
originalValue = originalRecordValues.get(qRecordEntityField.getFieldName());
|
||||
}
|
||||
|
||||
if(!Objects.equals(thisValue, originalValue))
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if this value and the original value don't match - OR - this is the table's primary key field - then put the value in the record. //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
if(!Objects.equals(thisValue, originalValue) || (includePrimaryKey && Objects.equals(primaryKeyFieldName, qRecordEntityField.getFieldName())))
|
||||
{
|
||||
qRecord.setValue(qRecordEntityField.getFieldName(), thisValue);
|
||||
}
|
||||
}
|
||||
|
||||
for(QRecordEntityAssociation qRecordEntityAssociation : getAssociationList(this.getClass()))
|
||||
{
|
||||
@SuppressWarnings("unchecked")
|
||||
List<? extends QRecordEntity> associatedEntities = (List<? extends QRecordEntity>) qRecordEntityAssociation.getGetter().invoke(this);
|
||||
String associationName = qRecordEntityAssociation.getAssociationAnnotation().name();
|
||||
|
||||
if(associatedEntities != null)
|
||||
{
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
// do this so an empty list in the entity becomes an empty list in the QRecord //
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
qRecord.withAssociatedRecords(associationName, new ArrayList<>());
|
||||
}
|
||||
|
||||
for(QRecordEntity associatedEntity : CollectionUtils.nonNullList(associatedEntities))
|
||||
{
|
||||
qRecord.withAssociatedRecord(associationName, associatedEntity.toQRecord());
|
||||
}
|
||||
}
|
||||
toQRecordProcessAssociations(qRecord, (entity) -> entity.toQRecordOnlyChangedFields(includePrimaryKey));
|
||||
|
||||
return (qRecord);
|
||||
}
|
||||
@ -488,15 +529,16 @@ public abstract class QRecordEntity
|
||||
{
|
||||
// todo - more types!!
|
||||
return (returnType.equals(String.class)
|
||||
|| returnType.equals(Integer.class)
|
||||
|| returnType.equals(int.class)
|
||||
|| returnType.equals(Boolean.class)
|
||||
|| returnType.equals(boolean.class)
|
||||
|| returnType.equals(BigDecimal.class)
|
||||
|| returnType.equals(Instant.class)
|
||||
|| returnType.equals(LocalDate.class)
|
||||
|| returnType.equals(LocalTime.class)
|
||||
|| returnType.equals(byte[].class));
|
||||
|| returnType.equals(Integer.class)
|
||||
|| returnType.equals(Long.class)
|
||||
|| returnType.equals(int.class)
|
||||
|| returnType.equals(Boolean.class)
|
||||
|| returnType.equals(boolean.class)
|
||||
|| returnType.equals(BigDecimal.class)
|
||||
|| returnType.equals(Instant.class)
|
||||
|| returnType.equals(LocalDate.class)
|
||||
|| returnType.equals(LocalTime.class)
|
||||
|| returnType.equals(byte[].class));
|
||||
/////////////////////////////////////////////
|
||||
// note - this list has implications upon: //
|
||||
// - QFieldType.fromClass //
|
||||
|
@ -0,0 +1,201 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.data;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Extension on QRecord, intended to be used where you've got records from
|
||||
** multiple tables, and you want to combine them into a single "wide" joined
|
||||
** record - but to do so without copying or modifying any of the individual
|
||||
** records.
|
||||
**
|
||||
** e.g., given:
|
||||
** - Order (id, orderNo, orderDate) (main table)
|
||||
** - LineItem (id, sku, quantity)
|
||||
** - Extrinsic (id, key, value)
|
||||
**
|
||||
** If set up in here as:
|
||||
** - new QRecordWithJoinedRecords(order)
|
||||
** .withJoinedRecordValues(lineItem)
|
||||
** .withJoinedRecordValues(extrinsic)
|
||||
**
|
||||
** Then we'd have the appearance of values in the object like:
|
||||
** - id, orderNo, orderDate, lineItem.id, lineItem.sku, lineItem.quantity, extrinsic.id, extrinsic.key, extrinsic.value
|
||||
**
|
||||
** Which, by the by, is how a query that returns joined records looks, and, is
|
||||
** what BackendQueryFilterUtils can use to do filter.
|
||||
**
|
||||
** This is done without copying or mutating any of the records (which, if you just use
|
||||
** QRecord.withJoinedRecordValues, then those values are copied into the main record)
|
||||
** - because this object is just storing references to the input records.
|
||||
**
|
||||
** Note that this implies that, values changed in this record (e.g, calls to setValue)
|
||||
** WILL impact the underlying records!
|
||||
*******************************************************************************/
|
||||
public class QRecordWithJoinedRecords extends QRecord
|
||||
{
|
||||
private QRecord mainRecord;
|
||||
private Map<String, QRecord> components = new LinkedHashMap<>();
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public QRecordWithJoinedRecords(QRecord mainRecord)
|
||||
{
|
||||
this.mainRecord = mainRecord;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public void addJoinedRecordValues(String joinTableName, QRecord joinedRecord)
|
||||
{
|
||||
components.put(joinTableName, joinedRecord);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public QRecordWithJoinedRecords withJoinedRecordValues(QRecord record, String joinTableName)
|
||||
{
|
||||
addJoinedRecordValues(joinTableName, record);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public Serializable getValue(String fieldName)
|
||||
{
|
||||
return performFunctionOnRecordBasedOnFieldName(fieldName, ((record, f) -> record.getValue(f)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
*
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public void setValue(String fieldName, Object value)
|
||||
{
|
||||
performFunctionOnRecordBasedOnFieldName(fieldName, ((record, f) ->
|
||||
{
|
||||
record.setValue(f, value);
|
||||
return (null);
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
*
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public void setValue(String fieldName, Serializable value)
|
||||
{
|
||||
performFunctionOnRecordBasedOnFieldName(fieldName, ((record, f) ->
|
||||
{
|
||||
record.setValue(f, value);
|
||||
return (null);
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public void removeValue(String fieldName)
|
||||
{
|
||||
performFunctionOnRecordBasedOnFieldName(fieldName, ((record, f) ->
|
||||
{
|
||||
record.removeValue(f);
|
||||
return (null);
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
** avoid having this same block in all the functions that call it...
|
||||
** given a fieldName, which may be a joinTable.fieldName, apply the function
|
||||
** to the right entity.
|
||||
***************************************************************************/
|
||||
private Serializable performFunctionOnRecordBasedOnFieldName(String fieldName, BiFunction<QRecord, String, Serializable> functionToPerform)
|
||||
{
|
||||
if(fieldName.contains("."))
|
||||
{
|
||||
String[] parts = fieldName.split("\\.");
|
||||
QRecord component = components.get(parts[0]);
|
||||
if(component != null)
|
||||
{
|
||||
return functionToPerform.apply(component, parts[1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return functionToPerform.apply(mainRecord, fieldName);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public Map<String, Serializable> getValues()
|
||||
{
|
||||
Map<String, Serializable> rs = new LinkedHashMap<>(mainRecord.getValues());
|
||||
for(Map.Entry<String, QRecord> componentEntry : components.entrySet())
|
||||
{
|
||||
String joinTableName = componentEntry.getKey();
|
||||
QRecord componentRecord = componentEntry.getValue();
|
||||
for(Map.Entry<String, Serializable> entry : componentRecord.getValues().entrySet())
|
||||
{
|
||||
rs.put(joinTableName + "." + entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
return (rs);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.metadata;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** for use-cases where a metaDataProducer directly adds its objects to the
|
||||
** qInstance, then this empty object can be returned.
|
||||
*******************************************************************************/
|
||||
public class EmptyMetaDataProducerOutput implements MetaDataProducerOutput
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(EmptyMetaDataProducerOutput.class);
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public void addSelfToInstance(QInstance instance)
|
||||
{
|
||||
/////////////////////////////////
|
||||
// noop - this output is empty //
|
||||
/////////////////////////////////
|
||||
LOG.trace("empty meta data producer has nothing to add.");
|
||||
}
|
||||
}
|
@ -29,5 +29,40 @@ package com.kingsrook.qqq.backend.core.model.metadata;
|
||||
*******************************************************************************/
|
||||
public abstract class MetaDataProducer<T extends MetaDataProducerOutput> implements MetaDataProducerInterface<T>
|
||||
{
|
||||
private Class<?> sourceClass;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for sourceClass
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public Class<?> getSourceClass()
|
||||
{
|
||||
return sourceClass;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for sourceClass
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void setSourceClass(Class<?> sourceClass)
|
||||
{
|
||||
this.sourceClass = sourceClass;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for sourceClass
|
||||
**
|
||||
*******************************************************************************/
|
||||
public MetaDataProducer<T> withSourceClass(Class<?> sourceClass)
|
||||
{
|
||||
this.sourceClass = sourceClass;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -22,6 +22,7 @@
|
||||
package com.kingsrook.qqq.backend.core.model.metadata;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
@ -40,12 +41,15 @@ import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.PossibleValueEnum;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.producers.ChildJoinFromRecordEntityGenericMetaDataProducer;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.producers.ChildRecordListWidgetFromRecordEntityGenericMetaDataProducer;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.producers.MetaDataCustomizerInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.producers.PossibleValueSourceOfEnumGenericMetaDataProducer;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.producers.PossibleValueSourceOfTableGenericMetaDataProducer;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.producers.RecordEntityToTableGenericMetaDataProducer;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.producers.annotations.ChildRecordListWidget;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.producers.annotations.ChildTable;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.producers.annotations.QMetaDataProducingEntity;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.producers.annotations.QMetaDataProducingPossibleValueEnum;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.utils.ClassPathUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
@ -82,13 +86,30 @@ public class MetaDataProducerHelper
|
||||
comparatorValuesByType.put(QAppMetaData.class, 23);
|
||||
}
|
||||
|
||||
private static MetaDataCustomizerInterface<QTableMetaData> tableMetaDataCustomizer = null;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Recursively find all classes in the given package, that implement MetaDataProducerInterface
|
||||
** run them, and add their output to the given qInstance.
|
||||
** run them, and add their output to the given qInstance - using the provided
|
||||
** tableMetaDataCustomizer to help with all RecordEntity's that
|
||||
** are configured to make tables.
|
||||
**
|
||||
** Note - they'll be sorted by the sortOrder they provide.
|
||||
*******************************************************************************/
|
||||
public static void processAllMetaDataProducersInPackage(QInstance instance, String packageName) throws QException
|
||||
public static void processAllMetaDataProducersInPackage(QInstance instance, String packageName, MetaDataCustomizerInterface<QTableMetaData> tableMetaDataCustomizer) throws QException
|
||||
{
|
||||
MetaDataProducerHelper.tableMetaDataCustomizer = tableMetaDataCustomizer;
|
||||
processAllMetaDataProducersInPackage(instance, packageName);
|
||||
MetaDataProducerHelper.tableMetaDataCustomizer = null;
|
||||
}
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public static List<MetaDataProducerInterface<?>> findProducers(String packageName) throws QException
|
||||
{
|
||||
List<Class<?>> classesInPackage;
|
||||
try
|
||||
@ -116,20 +137,27 @@ public class MetaDataProducerHelper
|
||||
continue;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// handle classes which are themselves MetaDataProducerInterface's //
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
if(MetaDataProducerInterface.class.isAssignableFrom(aClass))
|
||||
{
|
||||
CollectionUtils.addIfNotNull(producers, processMetaDataProducer(aClass));
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
// handle classes that have the @QMetaDataProducingEntity annotation - //
|
||||
// record entities that should produce meta-data //
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
if(aClass.isAnnotationPresent(QMetaDataProducingEntity.class))
|
||||
{
|
||||
QMetaDataProducingEntity qMetaDataProducingEntity = aClass.getAnnotation(QMetaDataProducingEntity.class);
|
||||
if(qMetaDataProducingEntity.producePossibleValueSource())
|
||||
{
|
||||
producers.addAll(processMetaDataProducingEntity(aClass));
|
||||
}
|
||||
producers.addAll(processMetaDataProducingEntity(aClass));
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
// handle classes with the @QMetaDataProducingPossibleValueEnum //
|
||||
// enums that are PVS's //
|
||||
//////////////////////////////////////////////////////////////////
|
||||
if(aClass.isAnnotationPresent(QMetaDataProducingPossibleValueEnum.class))
|
||||
{
|
||||
QMetaDataProducingPossibleValueEnum qMetaDataProducingPossibleValueEnum = aClass.getAnnotation(QMetaDataProducingPossibleValueEnum.class);
|
||||
@ -164,6 +192,20 @@ public class MetaDataProducerHelper
|
||||
}
|
||||
}));
|
||||
|
||||
return (producers);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Recursively find all classes in the given package, that implement MetaDataProducerInterface
|
||||
** run them, and add their output to the given qInstance.
|
||||
**
|
||||
** Note - they'll be sorted by the sortOrder they provide.
|
||||
*******************************************************************************/
|
||||
public static void processAllMetaDataProducersInPackage(QInstance instance, String packageName) throws QException
|
||||
{
|
||||
List<MetaDataProducerInterface<?>> producers = findProducers(packageName);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// execute each one (if enabled), adding their meta data to the instance //
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
@ -197,17 +239,19 @@ public class MetaDataProducerHelper
|
||||
**
|
||||
***************************************************************************/
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <T extends PossibleValueEnum<T>> MetaDataProducerInterface<?> processMetaDataProducingPossibleValueEnum(Class<?> aClass)
|
||||
private static <T extends Serializable & PossibleValueEnum<T>> MetaDataProducerInterface<?> processMetaDataProducingPossibleValueEnum(Class<?> sourceClass)
|
||||
{
|
||||
String warningPrefix = "Found a class annotated as @" + QMetaDataProducingPossibleValueEnum.class.getSimpleName();
|
||||
if(!PossibleValueEnum.class.isAssignableFrom(aClass))
|
||||
if(!PossibleValueEnum.class.isAssignableFrom(sourceClass))
|
||||
{
|
||||
LOG.warn(warningPrefix + ", but which is not a " + PossibleValueEnum.class.getSimpleName() + ", so it will not be used.", logPair("class", aClass.getSimpleName()));
|
||||
LOG.warn(warningPrefix + ", but which is not a " + PossibleValueEnum.class.getSimpleName() + ", so it will not be used.", logPair("class", sourceClass.getSimpleName()));
|
||||
return null;
|
||||
}
|
||||
|
||||
PossibleValueEnum<?>[] values = (PossibleValueEnum<?>[]) aClass.getEnumConstants();
|
||||
return (new PossibleValueSourceOfEnumGenericMetaDataProducer<T>(aClass.getSimpleName(), (PossibleValueEnum<T>[]) values));
|
||||
PossibleValueEnum<?>[] values = (PossibleValueEnum<?>[]) sourceClass.getEnumConstants();
|
||||
PossibleValueSourceOfEnumGenericMetaDataProducer<T> producer = new PossibleValueSourceOfEnumGenericMetaDataProducer<>(sourceClass.getSimpleName(), (PossibleValueEnum<T>[]) values);
|
||||
producer.setSourceClass(sourceClass);
|
||||
return producer;
|
||||
}
|
||||
|
||||
|
||||
@ -215,41 +259,90 @@ public class MetaDataProducerHelper
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
private static List<MetaDataProducerInterface<?>> processMetaDataProducingEntity(Class<?> aClass) throws Exception
|
||||
private static List<MetaDataProducerInterface<?>> processMetaDataProducingEntity(Class<?> sourceClass) throws Exception
|
||||
{
|
||||
List<MetaDataProducerInterface<?>> rs = new ArrayList<>();
|
||||
|
||||
String warningPrefix = "Found a class annotated as @" + QMetaDataProducingEntity.class.getSimpleName();
|
||||
if(!QRecordEntity.class.isAssignableFrom(aClass))
|
||||
QMetaDataProducingEntity qMetaDataProducingEntity = sourceClass.getAnnotation(QMetaDataProducingEntity.class);
|
||||
String warningPrefix = "Found a class annotated as @" + QMetaDataProducingEntity.class.getSimpleName();
|
||||
|
||||
///////////////////////////////////////////////////////////
|
||||
// make sures class is QRecordEntity and cast it as such //
|
||||
///////////////////////////////////////////////////////////
|
||||
if(!QRecordEntity.class.isAssignableFrom(sourceClass))
|
||||
{
|
||||
LOG.warn(warningPrefix + ", but which is not a " + QRecordEntity.class.getSimpleName() + ", so it will not be used.", logPair("class", aClass.getSimpleName()));
|
||||
LOG.warn(warningPrefix + ", but which is not a " + QRecordEntity.class.getSimpleName() + ", so it will not be used.", logPair("class", sourceClass.getSimpleName()));
|
||||
return (rs);
|
||||
}
|
||||
|
||||
Field tableNameField = aClass.getDeclaredField("TABLE_NAME");
|
||||
@SuppressWarnings("unchecked") // safe per the check above.
|
||||
Class<? extends QRecordEntity> recordEntityClass = (Class<? extends QRecordEntity>) sourceClass;
|
||||
|
||||
////////////////////////////////////////////////
|
||||
// get TABLE_NAME static field from the class //
|
||||
////////////////////////////////////////////////
|
||||
Field tableNameField = recordEntityClass.getDeclaredField("TABLE_NAME");
|
||||
if(!tableNameField.getType().equals(String.class))
|
||||
{
|
||||
LOG.warn(warningPrefix + ", but whose TABLE_NAME field is not a String, so it will not be used.", logPair("class", aClass.getSimpleName()));
|
||||
LOG.warn(warningPrefix + ", but whose TABLE_NAME field is not a String, so it will not be used.", logPair("class", recordEntityClass.getSimpleName()));
|
||||
return (rs);
|
||||
}
|
||||
|
||||
String tableNameValue = (String) tableNameField.get(null);
|
||||
rs.add(new PossibleValueSourceOfTableGenericMetaDataProducer(tableNameValue));
|
||||
|
||||
//////////////////////////////////////////
|
||||
// add table producer, if so configured //
|
||||
//////////////////////////////////////////
|
||||
if(qMetaDataProducingEntity.produceTableMetaData())
|
||||
{
|
||||
try
|
||||
{
|
||||
Class<? extends MetaDataCustomizerInterface<?>> genericMetaProductionCustomizer = (Class<? extends MetaDataCustomizerInterface<?>>) qMetaDataProducingEntity.tableMetaDataCustomizer();
|
||||
Class<? extends MetaDataCustomizerInterface<QTableMetaData>> tableMetaDataProductionCustomizer = null;
|
||||
if(!genericMetaProductionCustomizer.equals(MetaDataCustomizerInterface.NoopMetaDataCustomizer.class))
|
||||
{
|
||||
tableMetaDataProductionCustomizer = (Class<? extends MetaDataCustomizerInterface<QTableMetaData>>) genericMetaProductionCustomizer;
|
||||
}
|
||||
|
||||
RecordEntityToTableGenericMetaDataProducer producer = new RecordEntityToTableGenericMetaDataProducer(tableNameValue, recordEntityClass, tableMetaDataProductionCustomizer);
|
||||
producer.setSourceClass(recordEntityClass);
|
||||
|
||||
if(tableMetaDataCustomizer != null)
|
||||
{
|
||||
producer.addRecordEntityTableMetaDataProductionCustomizer(tableMetaDataCustomizer);
|
||||
}
|
||||
|
||||
rs.add(producer);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw new QException("Error processing table meta data producer for entity class: " + recordEntityClass.getName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////
|
||||
// add PVS producer, if so configured //
|
||||
////////////////////////////////////////
|
||||
if(qMetaDataProducingEntity.producePossibleValueSource())
|
||||
{
|
||||
PossibleValueSourceOfTableGenericMetaDataProducer producer = new PossibleValueSourceOfTableGenericMetaDataProducer(tableNameValue);
|
||||
producer.setSourceClass(recordEntityClass);
|
||||
rs.add(producer);
|
||||
}
|
||||
|
||||
//////////////////////////
|
||||
// process child tables //
|
||||
//////////////////////////
|
||||
QMetaDataProducingEntity qMetaDataProducingEntity = aClass.getAnnotation(QMetaDataProducingEntity.class);
|
||||
for(ChildTable childTable : qMetaDataProducingEntity.childTables())
|
||||
{
|
||||
Class<? extends QRecordEntity> childEntityClass = childTable.childTableEntityClass();
|
||||
if(childTable.childJoin().enabled())
|
||||
{
|
||||
CollectionUtils.addIfNotNull(rs, processChildJoin(aClass, childTable));
|
||||
CollectionUtils.addIfNotNull(rs, processChildJoin(recordEntityClass, childTable));
|
||||
|
||||
if(childTable.childRecordListWidget().enabled())
|
||||
{
|
||||
CollectionUtils.addIfNotNull(rs, processChildRecordListWidget(aClass, childTable));
|
||||
CollectionUtils.addIfNotNull(rs, processChildRecordListWidget(recordEntityClass, childTable));
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -259,7 +352,7 @@ public class MetaDataProducerHelper
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// if not doing the join, can't do the child-widget, so warn about that //
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
LOG.warn(warningPrefix + " requested to produce a ChildRecordListWidget, but not produce a Join - which is not allowed (must do join to do widget). ", logPair("class", aClass.getSimpleName()), logPair("childEntityClass", childEntityClass.getSimpleName()));
|
||||
LOG.warn(warningPrefix + " requested to produce a ChildRecordListWidget, but not produce a Join - which is not allowed (must do join to do widget). ", logPair("class", recordEntityClass.getSimpleName()), logPair("childEntityClass", childEntityClass.getSimpleName()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -272,14 +365,16 @@ public class MetaDataProducerHelper
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
private static MetaDataProducerInterface<?> processChildRecordListWidget(Class<?> aClass, ChildTable childTable) throws Exception
|
||||
private static MetaDataProducerInterface<?> processChildRecordListWidget(Class<? extends QRecordEntity> sourceClass, ChildTable childTable) throws Exception
|
||||
{
|
||||
Class<? extends QRecordEntity> childEntityClass = childTable.childTableEntityClass();
|
||||
String parentTableName = getTableNameStaticFieldValue(aClass);
|
||||
String parentTableName = getTableNameStaticFieldValue(sourceClass);
|
||||
String childTableName = getTableNameStaticFieldValue(childEntityClass);
|
||||
|
||||
ChildRecordListWidget childRecordListWidget = childTable.childRecordListWidget();
|
||||
return (new ChildRecordListWidgetFromRecordEntityGenericMetaDataProducer(childTableName, parentTableName, childRecordListWidget));
|
||||
ChildRecordListWidgetFromRecordEntityGenericMetaDataProducer producer = new ChildRecordListWidgetFromRecordEntityGenericMetaDataProducer(childTableName, parentTableName, childRecordListWidget);
|
||||
producer.setSourceClass(sourceClass);
|
||||
return producer;
|
||||
}
|
||||
|
||||
|
||||
@ -309,20 +404,22 @@ public class MetaDataProducerHelper
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
private static MetaDataProducerInterface<?> processChildJoin(Class<?> aClass, ChildTable childTable) throws Exception
|
||||
private static MetaDataProducerInterface<?> processChildJoin(Class<? extends QRecordEntity> entityClass, ChildTable childTable) throws Exception
|
||||
{
|
||||
Class<? extends QRecordEntity> childEntityClass = childTable.childTableEntityClass();
|
||||
|
||||
String parentTableName = getTableNameStaticFieldValue(aClass);
|
||||
String parentTableName = getTableNameStaticFieldValue(entityClass);
|
||||
String childTableName = getTableNameStaticFieldValue(childEntityClass);
|
||||
String possibleValueFieldName = findPossibleValueField(childEntityClass, parentTableName);
|
||||
if(!StringUtils.hasContent(possibleValueFieldName))
|
||||
{
|
||||
LOG.warn("Could not find field in [" + childEntityClass.getSimpleName() + "] with possibleValueSource referencing table [" + aClass.getSimpleName() + "]");
|
||||
LOG.warn("Could not find field in [" + childEntityClass.getSimpleName() + "] with possibleValueSource referencing table [" + entityClass.getSimpleName() + "]");
|
||||
return (null);
|
||||
}
|
||||
|
||||
return (new ChildJoinFromRecordEntityGenericMetaDataProducer(childTableName, parentTableName, possibleValueFieldName));
|
||||
ChildJoinFromRecordEntityGenericMetaDataProducer producer = new ChildJoinFromRecordEntityGenericMetaDataProducer(childTableName, parentTableName, possibleValueFieldName);
|
||||
producer.setSourceClass(entityClass);
|
||||
return producer;
|
||||
}
|
||||
|
||||
|
||||
@ -330,18 +427,20 @@ public class MetaDataProducerHelper
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
private static MetaDataProducerInterface<?> processMetaDataProducer(Class<?> aClass) throws Exception
|
||||
private static MetaDataProducerInterface<?> processMetaDataProducer(Class<?> sourceCClass) throws Exception
|
||||
{
|
||||
for(Constructor<?> constructor : aClass.getConstructors())
|
||||
for(Constructor<?> constructor : sourceCClass.getConstructors())
|
||||
{
|
||||
if(constructor.getParameterCount() == 0)
|
||||
{
|
||||
Object o = constructor.newInstance();
|
||||
return (MetaDataProducerInterface<?>) o;
|
||||
MetaDataProducerInterface<?> producer = (MetaDataProducerInterface<?>) o;
|
||||
producer.setSourceClass(sourceCClass);
|
||||
return producer;
|
||||
}
|
||||
}
|
||||
|
||||
LOG.warn("Found a class which implements MetaDataProducerInterface, but it does not have a no-arg constructor, so it cannot be used.", logPair("class", aClass.getSimpleName()));
|
||||
LOG.warn("Found a class which implements MetaDataProducerInterface, but it does not have a no-arg constructor, so it cannot be used.", logPair("class", sourceCClass.getSimpleName()));
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -361,4 +460,35 @@ public class MetaDataProducerHelper
|
||||
String tableNameValue = (String) tableNameField.get(null);
|
||||
return (tableNameValue);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for tableMetaDataCustomizer
|
||||
*******************************************************************************/
|
||||
public MetaDataCustomizerInterface<QTableMetaData> getTableMetaDataCustomizer()
|
||||
{
|
||||
return (MetaDataProducerHelper.tableMetaDataCustomizer);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for tableMetaDataCustomizer
|
||||
*******************************************************************************/
|
||||
public void setTableMetaDataCustomizer(MetaDataCustomizerInterface<QTableMetaData> tableMetaDataCustomizer)
|
||||
{
|
||||
MetaDataProducerHelper.tableMetaDataCustomizer = tableMetaDataCustomizer;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for tableMetaDataCustomizer
|
||||
*******************************************************************************/
|
||||
public void withTableMetaDataCustomizer(MetaDataCustomizerInterface<QTableMetaData> tableMetaDataCustomizer)
|
||||
{
|
||||
MetaDataProducerHelper.tableMetaDataCustomizer = tableMetaDataCustomizer;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -73,4 +73,23 @@ public interface MetaDataProducerInterface<T extends MetaDataProducerOutput>
|
||||
return (true);
|
||||
}
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
*
|
||||
***************************************************************************/
|
||||
default void setSourceClass(Class<?> sourceClass)
|
||||
{
|
||||
//////////
|
||||
// noop //
|
||||
//////////
|
||||
}
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
default Class<?> getSourceClass()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ package com.kingsrook.qqq.backend.core.model.metadata;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.qbits.SourceQBitAware;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
|
||||
|
||||
@ -31,10 +32,12 @@ import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
** Output object for a MetaDataProducer, which contains multiple meta-data
|
||||
** objects.
|
||||
*******************************************************************************/
|
||||
public class MetaDataProducerMultiOutput implements MetaDataProducerOutput
|
||||
public class MetaDataProducerMultiOutput implements MetaDataProducerOutput, SourceQBitAware
|
||||
{
|
||||
private List<MetaDataProducerOutput> contents;
|
||||
|
||||
private String sourceQBitName;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -98,4 +101,48 @@ public class MetaDataProducerMultiOutput implements MetaDataProducerOutput
|
||||
return (rs);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public String getSourceQBitName()
|
||||
{
|
||||
return (this.sourceQBitName);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public void setSourceQBitName(String sourceQBitName)
|
||||
{
|
||||
this.sourceQBitName = sourceQBitName;
|
||||
|
||||
/////////////////////////////////////////////
|
||||
// propagate the name down to the children //
|
||||
/////////////////////////////////////////////
|
||||
for(MetaDataProducerOutput content : contents)
|
||||
{
|
||||
if(content instanceof SourceQBitAware aware)
|
||||
{
|
||||
aware.setSourceQBitName(sourceQBitName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public MetaDataProducerMultiOutput withSourceQBitName(String sourceQBitName)
|
||||
{
|
||||
setSourceQBitName(sourceQBitName);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
@ -26,8 +26,13 @@ import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import com.kingsrook.qqq.backend.core.instances.QInstanceValidator;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.serialization.QBackendMetaDataDeserializer;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.Capability;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.variants.BackendVariantsConfig;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.variants.LegacyBackendVariantSetting;
|
||||
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface;
|
||||
|
||||
|
||||
@ -45,21 +50,18 @@ public class QBackendMetaData implements TopLevelMetaDataInterface
|
||||
private Set<Capability> enabledCapabilities = new HashSet<>();
|
||||
private Set<Capability> disabledCapabilities = new HashSet<>();
|
||||
|
||||
private Boolean usesVariants = false;
|
||||
private String variantOptionsTableIdField;
|
||||
private String variantOptionsTableNameField;
|
||||
private String variantOptionsTableTypeField;
|
||||
private String variantOptionsTableTypeValue;
|
||||
private String variantOptionsTableUsernameField;
|
||||
private String variantOptionsTablePasswordField;
|
||||
private String variantOptionsTableApiKeyField;
|
||||
private String variantOptionsTableClientIdField;
|
||||
private String variantOptionsTableClientSecretField;
|
||||
private String variantOptionsTableName;
|
||||
private Boolean usesVariants = false;
|
||||
private BackendVariantsConfig backendVariantsConfig;
|
||||
|
||||
// todo - at some point, we may want to apply this to secret properties on subclasses?
|
||||
// @JsonFilter("secretsFilter")
|
||||
|
||||
@Deprecated(since = "Replaced by filter in backendVariantsConfig - but leaving as field to pair with ...TypeValue for building filter")
|
||||
private String variantOptionsTableTypeField; // a field on which to filter the variant-options table, to limit which records in it are available as variants
|
||||
|
||||
@Deprecated(since = "Replaced by variantTypeKey and value in filter in backendVariantsConfig - but leaving as field to pair with ...TypeField for building filter")
|
||||
private String variantOptionsTableTypeValue; // value for the type-field, to limit which records in it are available as variants; but also, the key in the session.backendVariants map!
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -394,22 +396,15 @@ public class QBackendMetaData implements TopLevelMetaDataInterface
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for variantOptionsTableIdField
|
||||
*******************************************************************************/
|
||||
public String getVariantOptionsTableIdField()
|
||||
{
|
||||
return (this.variantOptionsTableIdField);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for variantOptionsTableIdField
|
||||
*******************************************************************************/
|
||||
@Deprecated(since = "backendVariantsConfig will infer this from the variant options table's primary key")
|
||||
public void setVariantOptionsTableIdField(String variantOptionsTableIdField)
|
||||
{
|
||||
this.variantOptionsTableIdField = variantOptionsTableIdField;
|
||||
/////////////////////////////////////////////////
|
||||
// noop as we migrate to backendVariantsConfig //
|
||||
/////////////////////////////////////////////////
|
||||
}
|
||||
|
||||
|
||||
@ -417,30 +412,24 @@ public class QBackendMetaData implements TopLevelMetaDataInterface
|
||||
/*******************************************************************************
|
||||
** Fluent setter for variantOptionsTableIdField
|
||||
*******************************************************************************/
|
||||
@Deprecated(since = "backendVariantsConfig will infer this from the variant options table's primary key")
|
||||
public QBackendMetaData withVariantOptionsTableIdField(String variantOptionsTableIdField)
|
||||
{
|
||||
this.variantOptionsTableIdField = variantOptionsTableIdField;
|
||||
this.setVariantOptionsTableIdField(variantOptionsTableIdField);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for variantOptionsTableNameField
|
||||
*******************************************************************************/
|
||||
public String getVariantOptionsTableNameField()
|
||||
{
|
||||
return (this.variantOptionsTableNameField);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for variantOptionsTableNameField
|
||||
*******************************************************************************/
|
||||
@Deprecated(since = "backendVariantsConfig will infer this from the variant options table's recordLabel")
|
||||
public void setVariantOptionsTableNameField(String variantOptionsTableNameField)
|
||||
{
|
||||
this.variantOptionsTableNameField = variantOptionsTableNameField;
|
||||
/////////////////////////////////////////////////
|
||||
// noop as we migrate to backendVariantsConfig //
|
||||
/////////////////////////////////////////////////
|
||||
}
|
||||
|
||||
|
||||
@ -448,30 +437,26 @@ public class QBackendMetaData implements TopLevelMetaDataInterface
|
||||
/*******************************************************************************
|
||||
** Fluent setter for variantOptionsTableNameField
|
||||
*******************************************************************************/
|
||||
@Deprecated(since = "backendVariantsConfig will infer this from the variant options table's recordLabel")
|
||||
public QBackendMetaData withVariantOptionsTableNameField(String variantOptionsTableNameField)
|
||||
{
|
||||
this.variantOptionsTableNameField = variantOptionsTableNameField;
|
||||
this.setVariantOptionsTableNameField(variantOptionsTableNameField);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for variantOptionsTableTypeField
|
||||
*******************************************************************************/
|
||||
public String getVariantOptionsTableTypeField()
|
||||
{
|
||||
return (this.variantOptionsTableTypeField);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for variantOptionsTableTypeField
|
||||
*******************************************************************************/
|
||||
@Deprecated(since = "Replaced by fieldName in filter in backendVariantsConfig - but leaving as field to pair with ...TypeValue for building filter")
|
||||
public void setVariantOptionsTableTypeField(String variantOptionsTableTypeField)
|
||||
{
|
||||
this.variantOptionsTableTypeField = variantOptionsTableTypeField;
|
||||
if(this.variantOptionsTableTypeValue != null)
|
||||
{
|
||||
this.getOrWithNewBackendVariantsConfig().setOptionsFilter(new QQueryFilter(new QFilterCriteria(variantOptionsTableTypeField, QCriteriaOperator.EQUALS, variantOptionsTableTypeValue)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -479,30 +464,28 @@ public class QBackendMetaData implements TopLevelMetaDataInterface
|
||||
/*******************************************************************************
|
||||
** Fluent setter for variantOptionsTableTypeField
|
||||
*******************************************************************************/
|
||||
@Deprecated(since = "Replaced by fieldName in filter in backendVariantsConfig - but leaving as field to pair with ...TypeValue for building filter")
|
||||
public QBackendMetaData withVariantOptionsTableTypeField(String variantOptionsTableTypeField)
|
||||
{
|
||||
this.variantOptionsTableTypeField = variantOptionsTableTypeField;
|
||||
this.setVariantOptionsTableTypeField(variantOptionsTableTypeField);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for variantOptionsTableTypeValue
|
||||
*******************************************************************************/
|
||||
public String getVariantOptionsTableTypeValue()
|
||||
{
|
||||
return (this.variantOptionsTableTypeValue);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for variantOptionsTableTypeValue
|
||||
*******************************************************************************/
|
||||
@Deprecated(since = "Replaced by variantTypeKey and value in filter in backendVariantsConfig - but leaving as field to pair with ...TypeField for building filter")
|
||||
public void setVariantOptionsTableTypeValue(String variantOptionsTableTypeValue)
|
||||
{
|
||||
this.getOrWithNewBackendVariantsConfig().setVariantTypeKey(variantOptionsTableTypeValue);
|
||||
|
||||
this.variantOptionsTableTypeValue = variantOptionsTableTypeValue;
|
||||
if(this.variantOptionsTableTypeField != null)
|
||||
{
|
||||
this.getOrWithNewBackendVariantsConfig().setOptionsFilter(new QQueryFilter(new QFilterCriteria(variantOptionsTableTypeField, QCriteriaOperator.EQUALS, variantOptionsTableTypeValue)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -510,30 +493,22 @@ public class QBackendMetaData implements TopLevelMetaDataInterface
|
||||
/*******************************************************************************
|
||||
** Fluent setter for variantOptionsTableTypeValue
|
||||
*******************************************************************************/
|
||||
@Deprecated(since = "Replaced by variantTypeKey and value in filter in backendVariantsConfig - but leaving as field to pair with ...TypeField for building filter")
|
||||
public QBackendMetaData withVariantOptionsTableTypeValue(String variantOptionsTableTypeValue)
|
||||
{
|
||||
this.variantOptionsTableTypeValue = variantOptionsTableTypeValue;
|
||||
this.setVariantOptionsTableTypeValue(variantOptionsTableTypeValue);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for variantOptionsTableUsernameField
|
||||
*******************************************************************************/
|
||||
public String getVariantOptionsTableUsernameField()
|
||||
{
|
||||
return (this.variantOptionsTableUsernameField);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for variantOptionsTableUsernameField
|
||||
*******************************************************************************/
|
||||
@Deprecated(since = "Replaced by backendVariantsConfig.backendSettingSourceFieldNameMap")
|
||||
public void setVariantOptionsTableUsernameField(String variantOptionsTableUsernameField)
|
||||
{
|
||||
this.variantOptionsTableUsernameField = variantOptionsTableUsernameField;
|
||||
this.getOrWithNewBackendVariantsConfig().withBackendSettingSourceFieldName(LegacyBackendVariantSetting.USERNAME, variantOptionsTableUsernameField);
|
||||
}
|
||||
|
||||
|
||||
@ -541,30 +516,22 @@ public class QBackendMetaData implements TopLevelMetaDataInterface
|
||||
/*******************************************************************************
|
||||
** Fluent setter for variantOptionsTableUsernameField
|
||||
*******************************************************************************/
|
||||
@Deprecated(since = "Replaced by backendVariantsConfig.backendSettingSourceFieldNameMap")
|
||||
public QBackendMetaData withVariantOptionsTableUsernameField(String variantOptionsTableUsernameField)
|
||||
{
|
||||
this.variantOptionsTableUsernameField = variantOptionsTableUsernameField;
|
||||
this.setVariantOptionsTableUsernameField(variantOptionsTableUsernameField);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for variantOptionsTablePasswordField
|
||||
*******************************************************************************/
|
||||
public String getVariantOptionsTablePasswordField()
|
||||
{
|
||||
return (this.variantOptionsTablePasswordField);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for variantOptionsTablePasswordField
|
||||
*******************************************************************************/
|
||||
@Deprecated(since = "Replaced by backendVariantsConfig.backendSettingSourceFieldNameMap")
|
||||
public void setVariantOptionsTablePasswordField(String variantOptionsTablePasswordField)
|
||||
{
|
||||
this.variantOptionsTablePasswordField = variantOptionsTablePasswordField;
|
||||
this.getOrWithNewBackendVariantsConfig().withBackendSettingSourceFieldName(LegacyBackendVariantSetting.PASSWORD, variantOptionsTablePasswordField);
|
||||
}
|
||||
|
||||
|
||||
@ -572,30 +539,22 @@ public class QBackendMetaData implements TopLevelMetaDataInterface
|
||||
/*******************************************************************************
|
||||
** Fluent setter for variantOptionsTablePasswordField
|
||||
*******************************************************************************/
|
||||
@Deprecated(since = "Replaced by backendVariantsConfig.backendSettingSourceFieldNameMap")
|
||||
public QBackendMetaData withVariantOptionsTablePasswordField(String variantOptionsTablePasswordField)
|
||||
{
|
||||
this.variantOptionsTablePasswordField = variantOptionsTablePasswordField;
|
||||
this.setVariantOptionsTablePasswordField(variantOptionsTablePasswordField);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for variantOptionsTableApiKeyField
|
||||
*******************************************************************************/
|
||||
public String getVariantOptionsTableApiKeyField()
|
||||
{
|
||||
return (this.variantOptionsTableApiKeyField);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for variantOptionsTableApiKeyField
|
||||
*******************************************************************************/
|
||||
@Deprecated(since = "Replaced by backendVariantsConfig.backendSettingSourceFieldNameMap")
|
||||
public void setVariantOptionsTableApiKeyField(String variantOptionsTableApiKeyField)
|
||||
{
|
||||
this.variantOptionsTableApiKeyField = variantOptionsTableApiKeyField;
|
||||
this.getOrWithNewBackendVariantsConfig().withBackendSettingSourceFieldName(LegacyBackendVariantSetting.API_KEY, variantOptionsTableApiKeyField);
|
||||
}
|
||||
|
||||
|
||||
@ -603,30 +562,22 @@ public class QBackendMetaData implements TopLevelMetaDataInterface
|
||||
/*******************************************************************************
|
||||
** Fluent setter for variantOptionsTableApiKeyField
|
||||
*******************************************************************************/
|
||||
@Deprecated(since = "Replaced by backendVariantsConfig.backendSettingSourceFieldNameMap")
|
||||
public QBackendMetaData withVariantOptionsTableApiKeyField(String variantOptionsTableApiKeyField)
|
||||
{
|
||||
this.variantOptionsTableApiKeyField = variantOptionsTableApiKeyField;
|
||||
this.setVariantOptionsTableApiKeyField(variantOptionsTableApiKeyField);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for variantOptionsTableName
|
||||
*******************************************************************************/
|
||||
public String getVariantOptionsTableName()
|
||||
{
|
||||
return (this.variantOptionsTableName);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for variantOptionsTableName
|
||||
*******************************************************************************/
|
||||
@Deprecated(since = "Replaced by backendVariantsConfig.tableName")
|
||||
public void setVariantOptionsTableName(String variantOptionsTableName)
|
||||
{
|
||||
this.variantOptionsTableName = variantOptionsTableName;
|
||||
this.getOrWithNewBackendVariantsConfig().withOptionsTableName(variantOptionsTableName);
|
||||
}
|
||||
|
||||
|
||||
@ -634,9 +585,10 @@ public class QBackendMetaData implements TopLevelMetaDataInterface
|
||||
/*******************************************************************************
|
||||
** Fluent setter for variantOptionsTableName
|
||||
*******************************************************************************/
|
||||
@Deprecated(since = "Replaced by backendVariantsConfig.tableName")
|
||||
public QBackendMetaData withVariantOptionsTableName(String variantOptionsTableName)
|
||||
{
|
||||
this.variantOptionsTableName = variantOptionsTableName;
|
||||
this.setVariantOptionsTableName(variantOptionsTableName);
|
||||
return (this);
|
||||
}
|
||||
|
||||
@ -651,22 +603,15 @@ public class QBackendMetaData implements TopLevelMetaDataInterface
|
||||
qInstance.addBackend(this);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for variantOptionsTableClientIdField
|
||||
*******************************************************************************/
|
||||
public String getVariantOptionsTableClientIdField()
|
||||
{
|
||||
return (this.variantOptionsTableClientIdField);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for variantOptionsTableClientIdField
|
||||
*******************************************************************************/
|
||||
@Deprecated(since = "Replaced by backendVariantsConfig.backendSettingSourceFieldNameMap")
|
||||
public void setVariantOptionsTableClientIdField(String variantOptionsTableClientIdField)
|
||||
{
|
||||
this.variantOptionsTableClientIdField = variantOptionsTableClientIdField;
|
||||
this.getOrWithNewBackendVariantsConfig().withBackendSettingSourceFieldName(LegacyBackendVariantSetting.CLIENT_ID, variantOptionsTableClientIdField);
|
||||
}
|
||||
|
||||
|
||||
@ -674,30 +619,22 @@ public class QBackendMetaData implements TopLevelMetaDataInterface
|
||||
/*******************************************************************************
|
||||
** Fluent setter for variantOptionsTableClientIdField
|
||||
*******************************************************************************/
|
||||
@Deprecated(since = "Replaced by backendVariantsConfig.backendSettingSourceFieldNameMap")
|
||||
public QBackendMetaData withVariantOptionsTableClientIdField(String variantOptionsTableClientIdField)
|
||||
{
|
||||
this.variantOptionsTableClientIdField = variantOptionsTableClientIdField;
|
||||
this.setVariantOptionsTableClientIdField(variantOptionsTableClientIdField);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for variantOptionsTableClientSecretField
|
||||
*******************************************************************************/
|
||||
public String getVariantOptionsTableClientSecretField()
|
||||
{
|
||||
return (this.variantOptionsTableClientSecretField);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for variantOptionsTableClientSecretField
|
||||
*******************************************************************************/
|
||||
@Deprecated(since = "Replaced by backendVariantsConfig.backendSettingSourceFieldNameMap")
|
||||
public void setVariantOptionsTableClientSecretField(String variantOptionsTableClientSecretField)
|
||||
{
|
||||
this.variantOptionsTableClientSecretField = variantOptionsTableClientSecretField;
|
||||
this.getOrWithNewBackendVariantsConfig().withBackendSettingSourceFieldName(LegacyBackendVariantSetting.CLIENT_SECRET, variantOptionsTableClientSecretField);
|
||||
}
|
||||
|
||||
|
||||
@ -705,11 +642,55 @@ public class QBackendMetaData implements TopLevelMetaDataInterface
|
||||
/*******************************************************************************
|
||||
** Fluent setter for variantOptionsTableClientSecretField
|
||||
*******************************************************************************/
|
||||
@Deprecated(since = "Replaced by backendVariantsConfig.backendSettingSourceFieldNameMap")
|
||||
public QBackendMetaData withVariantOptionsTableClientSecretField(String variantOptionsTableClientSecretField)
|
||||
{
|
||||
this.variantOptionsTableClientSecretField = variantOptionsTableClientSecretField;
|
||||
this.setVariantOptionsTableClientSecretField(variantOptionsTableClientSecretField);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for backendVariantsConfig
|
||||
*******************************************************************************/
|
||||
public BackendVariantsConfig getBackendVariantsConfig()
|
||||
{
|
||||
return (this.backendVariantsConfig);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for backendVariantsConfig
|
||||
*******************************************************************************/
|
||||
public void setBackendVariantsConfig(BackendVariantsConfig backendVariantsConfig)
|
||||
{
|
||||
this.backendVariantsConfig = backendVariantsConfig;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for backendVariantsConfig
|
||||
*******************************************************************************/
|
||||
public QBackendMetaData withBackendVariantsConfig(BackendVariantsConfig backendVariantsConfig)
|
||||
{
|
||||
this.backendVariantsConfig = backendVariantsConfig;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
private BackendVariantsConfig getOrWithNewBackendVariantsConfig()
|
||||
{
|
||||
if(backendVariantsConfig == null)
|
||||
{
|
||||
setBackendVariantsConfig(new BackendVariantsConfig());
|
||||
}
|
||||
return backendVariantsConfig;
|
||||
}
|
||||
}
|
||||
|
@ -56,6 +56,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.permissions.QPermissionRule
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QStepMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.qbits.QBitMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.queues.QQueueMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.queues.QQueueProviderMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData;
|
||||
@ -89,6 +90,7 @@ public class QInstance
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Important to use LinkedHashmap here, to preserve the order in which entries are added. //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
private Map<String, QBitMetaData> qBits = new LinkedHashMap<>();
|
||||
private Map<String, QTableMetaData> tables = new LinkedHashMap<>();
|
||||
private Map<String, QJoinMetaData> joins = new LinkedHashMap<>();
|
||||
private Map<String, QPossibleValueSource> possibleValueSources = new LinkedHashMap<>();
|
||||
@ -1489,6 +1491,7 @@ public class QInstance
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for metaDataFilter
|
||||
*******************************************************************************/
|
||||
@ -1519,4 +1522,68 @@ public class QInstance
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void addQBit(QBitMetaData qBitMetaData)
|
||||
{
|
||||
List<String> missingParts = new ArrayList<>();
|
||||
if(!StringUtils.hasContent(qBitMetaData.getGroupId()))
|
||||
{
|
||||
missingParts.add("groupId");
|
||||
}
|
||||
if(!StringUtils.hasContent(qBitMetaData.getArtifactId()))
|
||||
{
|
||||
missingParts.add("artifactId");
|
||||
}
|
||||
if(!StringUtils.hasContent(qBitMetaData.getVersion()))
|
||||
{
|
||||
missingParts.add("version");
|
||||
|
||||
}
|
||||
if(!missingParts.isEmpty())
|
||||
{
|
||||
throw (new IllegalArgumentException("Attempted to add a qBit without a " + StringUtils.joinWithCommasAndAnd(missingParts)));
|
||||
}
|
||||
|
||||
String name = qBitMetaData.getName();
|
||||
if(this.qBits.containsKey(name))
|
||||
{
|
||||
throw (new IllegalArgumentException("Attempted to add a second qBit with name (formed from 'groupId:artifactId:version[:namespace]'): " + name));
|
||||
}
|
||||
this.qBits.put(name, qBitMetaData);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for qBits
|
||||
*******************************************************************************/
|
||||
public Map<String, QBitMetaData> getQBits()
|
||||
{
|
||||
return (this.qBits);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for qBits
|
||||
*******************************************************************************/
|
||||
public void setQBits(Map<String, QBitMetaData> qBits)
|
||||
{
|
||||
this.qBits = qBits;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for qBits
|
||||
*******************************************************************************/
|
||||
public QInstance withQBits(Map<String, QBitMetaData> qBits)
|
||||
{
|
||||
this.qBits = qBits;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -30,4 +30,5 @@ public enum AuditLevel
|
||||
NONE,
|
||||
RECORD,
|
||||
FIELD
|
||||
// idea: only audit changes to fields, e.g., on edit. though, is that a different dimension than this?
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import com.kingsrook.qqq.backend.core.instances.QInstanceHelpContentManager;
|
||||
import com.kingsrook.qqq.backend.core.instances.validation.plugins.QInstanceValidatorPluginInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.WidgetType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.help.HelpRole;
|
||||
@ -69,6 +70,7 @@ public class QWidgetMetaData implements QWidgetMetaDataInterface
|
||||
|
||||
protected Map<String, Serializable> defaultValues = new LinkedHashMap<>();
|
||||
|
||||
protected QInstanceValidatorPluginInterface<QWidgetMetaDataInterface> validatorPlugin;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -764,4 +766,35 @@ public class QWidgetMetaData implements QWidgetMetaDataInterface
|
||||
QInstanceHelpContentManager.removeHelpContentByRoleSetFromList(roles, listForSlot);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for validatorPlugin
|
||||
*******************************************************************************/
|
||||
public QInstanceValidatorPluginInterface<QWidgetMetaDataInterface> getValidatorPlugin()
|
||||
{
|
||||
return (this.validatorPlugin);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for validatorPlugin
|
||||
*******************************************************************************/
|
||||
public void setValidatorPlugin(QInstanceValidatorPluginInterface<QWidgetMetaDataInterface> validatorPlugin)
|
||||
{
|
||||
this.validatorPlugin = validatorPlugin;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for validatorPlugin
|
||||
*******************************************************************************/
|
||||
public QWidgetMetaData withValidatorPlugin(QInstanceValidatorPluginInterface<QWidgetMetaDataInterface> validatorPlugin)
|
||||
{
|
||||
this.validatorPlugin = validatorPlugin;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import com.kingsrook.qqq.backend.core.instances.validation.plugins.QInstanceValidatorPluginInterface;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.TopLevelMetaDataInterface;
|
||||
@ -277,5 +278,13 @@ public interface QWidgetMetaDataInterface extends MetaDataWithPermissionRules, T
|
||||
qInstance.addWidget(this);
|
||||
}
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
** let the widget include an instance validator plugin
|
||||
***************************************************************************/
|
||||
default QInstanceValidatorPluginInterface<QWidgetMetaDataInterface> getValidatorPlugin()
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -23,8 +23,12 @@ package com.kingsrook.qqq.backend.core.model.metadata.fields;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Objects;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.PossibleValueEnum;
|
||||
import com.kingsrook.qqq.backend.core.utils.Pair;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.collections.MapBuilder;
|
||||
|
||||
|
||||
@ -41,20 +45,22 @@ public enum AdornmentType
|
||||
RENDER_HTML,
|
||||
REVEAL,
|
||||
FILE_DOWNLOAD,
|
||||
FILE_UPLOAD,
|
||||
TOOLTIP,
|
||||
ERROR;
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// keep these values in sync with AdornmentType.ts in qqq-frontend-core //
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public interface LinkValues
|
||||
{
|
||||
String TARGET = "target";
|
||||
String TO_RECORD_FROM_TABLE = "toRecordFromTable";
|
||||
String TARGET = "target";
|
||||
String TO_RECORD_FROM_TABLE = "toRecordFromTable";
|
||||
String TO_RECORD_FROM_TABLE_DYNAMIC = "toRecordFromTableDynamic";
|
||||
}
|
||||
|
||||
|
||||
@ -71,6 +77,8 @@ public enum AdornmentType
|
||||
String SUPPLEMENTAL_PROCESS_NAME = "supplementalProcessName";
|
||||
String SUPPLEMENTAL_CODE_REFERENCE = "supplementalCodeReference";
|
||||
|
||||
String DOWNLOAD_URL_DYNAMIC = "downloadUrlDynamic";
|
||||
|
||||
////////////////////////////////////////////////////
|
||||
// use these two together, as in: //
|
||||
// FILE_NAME_FORMAT = "Order %s Packing Slip.pdf" //
|
||||
@ -78,6 +86,17 @@ public enum AdornmentType
|
||||
////////////////////////////////////////////////////
|
||||
String FILE_NAME_FORMAT = "fileNameFormat";
|
||||
String FILE_NAME_FORMAT_FIELDS = "fileNameFormatFields";
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
static String makeFieldDownloadUrl(String tableName, Serializable primaryKey, String fieldName, String fileName)
|
||||
{
|
||||
return ("/data/" + tableName + "/"
|
||||
+ URLEncoder.encode(Objects.requireNonNullElse(ValueUtils.getValueAsString(primaryKey), ""), StandardCharsets.UTF_8).replace("+", "%20") + "/"
|
||||
+ fieldName + "/"
|
||||
+ URLEncoder.encode(Objects.requireNonNullElse(fileName, ""), StandardCharsets.UTF_8).replace("+", "%20"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -167,4 +186,76 @@ public enum AdornmentType
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static class FileUploadAdornment
|
||||
{
|
||||
public static String FORMAT = "format";
|
||||
public static String WIDTH = "width";
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public static FieldAdornment newFieldAdornment()
|
||||
{
|
||||
return (new FieldAdornment(AdornmentType.FILE_UPLOAD));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public static Pair<String, String> formatDragAndDrop()
|
||||
{
|
||||
return (Pair.of(FORMAT, "dragAndDrop"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public static Pair<String, String> formatButton()
|
||||
{
|
||||
return (Pair.of(FORMAT, "button"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public static Pair<String, String> widthFull()
|
||||
{
|
||||
return (Pair.of(WIDTH, "full"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public static Pair<String, String> widthHalf()
|
||||
{
|
||||
return (Pair.of(WIDTH, "half"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public interface TooltipValues
|
||||
{
|
||||
String STATIC_TEXT = "staticText";
|
||||
String TOOLTIP_DYNAMIC = "tooltipDynamic";
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -177,7 +177,7 @@ public class FieldAdornment
|
||||
** Fluent setter for values
|
||||
**
|
||||
*******************************************************************************/
|
||||
public FieldAdornment withValue(Pair<String, Serializable> value)
|
||||
public FieldAdornment withValue(Pair<String, ? extends Serializable> value)
|
||||
{
|
||||
return (withValue(value.getA(), value.getB()));
|
||||
}
|
||||
|
@ -0,0 +1,86 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.fields;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Wrapper (record) that holds a QFieldMetaData and a QTableMetaData -
|
||||
**
|
||||
** With a factory method (`get()`) to go from the use-case of, a String that's
|
||||
** "joinTable.fieldName" or "fieldName" to the pair.
|
||||
**
|
||||
** Note that the "joinTable" member here - could be the "mainTable" passed in
|
||||
** to that `get()` method.
|
||||
**
|
||||
*******************************************************************************/
|
||||
public record FieldAndJoinTable(QFieldMetaData field, QTableMetaData joinTable)
|
||||
{
|
||||
|
||||
/***************************************************************************
|
||||
** given a table, and a field-name string (which should either be the name
|
||||
** of a field on that table, or another tableName + "." + fieldName (from
|
||||
** that table) - get back the pair of table & field metaData that the
|
||||
** input string is talking about.
|
||||
***************************************************************************/
|
||||
public static FieldAndJoinTable get(QTableMetaData mainTable, String fieldName) throws QException
|
||||
{
|
||||
if(fieldName.indexOf('.') > -1)
|
||||
{
|
||||
String joinTableName = fieldName.replaceAll("\\..*", "");
|
||||
String joinFieldName = fieldName.replaceAll(".*\\.", "");
|
||||
|
||||
QTableMetaData joinTable = QContext.getQInstance().getTable(joinTableName);
|
||||
if(joinTable == null)
|
||||
{
|
||||
throw (new QException("Unrecognized join table name: " + joinTableName));
|
||||
}
|
||||
|
||||
return new FieldAndJoinTable(joinTable.getField(joinFieldName), joinTable);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new FieldAndJoinTable(mainTable.getField(fieldName), mainTable);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getLabel(QTableMetaData mainTable)
|
||||
{
|
||||
if(mainTable.getName().equals(joinTable.getName()))
|
||||
{
|
||||
return (field.getLabel());
|
||||
}
|
||||
else
|
||||
{
|
||||
return (joinTable.getLabel() + ": " + field.getLabel());
|
||||
}
|
||||
}
|
||||
}
|
@ -22,11 +22,45 @@
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.fields;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.actions.values.ValueBehaviorApplier;
|
||||
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.tables.QTableMetaData;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Interface to mark a field behavior as one to be used during generating
|
||||
** display values.
|
||||
*******************************************************************************/
|
||||
public interface FieldDisplayBehavior<T extends FieldDisplayBehavior<T>> extends FieldBehavior<T>
|
||||
{
|
||||
NoopFieldDisplayBehavior NOOP = new NoopFieldDisplayBehavior();
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
default T getDefault()
|
||||
{
|
||||
return (T) NOOP;
|
||||
}
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
** a default implementation for this behavior type, which does nothing.
|
||||
***************************************************************************/
|
||||
class NoopFieldDisplayBehavior implements FieldDisplayBehavior<NoopFieldDisplayBehavior>
|
||||
{
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public void apply(ValueBehaviorApplier.Action action, List<QRecord> recordList, QInstance instance, QTableMetaData table, QFieldMetaData field)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,73 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.fields;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.kingsrook.qqq.backend.core.actions.values.ValueBehaviorApplier;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
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.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Display value formatter for fields which store a QQueryFilter as JSON.
|
||||
*******************************************************************************/
|
||||
public class FilterJsonFieldDisplayValueFormatter implements FieldDisplayBehavior<FilterJsonFieldDisplayValueFormatter>
|
||||
{
|
||||
private static Consumer<ObjectMapper> jsonMapperCustomizer = om -> om.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void apply(ValueBehaviorApplier.Action action, List<QRecord> recordList, QInstance instance, QTableMetaData table, QFieldMetaData field)
|
||||
{
|
||||
for(QRecord record : CollectionUtils.nonNullList(recordList))
|
||||
{
|
||||
String queryFilterJson = record.getValueString(field.getName());
|
||||
if(StringUtils.hasContent(queryFilterJson))
|
||||
{
|
||||
try
|
||||
{
|
||||
QQueryFilter qQueryFilter = JsonUtils.toObject(queryFilterJson, QQueryFilter.class, jsonMapperCustomizer);
|
||||
int criteriaCount = CollectionUtils.nonNullList(qQueryFilter.getCriteria()).size();
|
||||
record.setDisplayValue(field.getName(), criteriaCount + " Filter" + StringUtils.plural(criteriaCount));
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
record.setDisplayValue(field.getName(), "Invalid Filter...");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -82,6 +82,7 @@ public class QFieldMetaData implements Cloneable, QMetaDataObject
|
||||
private QQueryFilter possibleValueSourceFilter;
|
||||
private QPossibleValueSource inlinePossibleValueSource;
|
||||
|
||||
private Integer gridColumns;
|
||||
private Integer maxLength;
|
||||
private Set<FieldBehavior<?>> behaviors;
|
||||
|
||||
@ -199,6 +200,7 @@ public class QFieldMetaData implements Cloneable, QMetaDataObject
|
||||
setIsRequired(fieldAnnotation.isRequired());
|
||||
setIsEditable(fieldAnnotation.isEditable());
|
||||
setIsHidden(fieldAnnotation.isHidden());
|
||||
setGridColumns(fieldAnnotation.gridColumns());
|
||||
|
||||
if(StringUtils.hasContent(fieldAnnotation.label()))
|
||||
{
|
||||
@ -1063,6 +1065,7 @@ public class QFieldMetaData implements Cloneable, QMetaDataObject
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for inlinePossibleValueSource
|
||||
*******************************************************************************/
|
||||
@ -1093,4 +1096,34 @@ public class QFieldMetaData implements Cloneable, QMetaDataObject
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for gridColumns
|
||||
*******************************************************************************/
|
||||
public Integer getGridColumns()
|
||||
{
|
||||
return (this.gridColumns);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for gridColumns
|
||||
*******************************************************************************/
|
||||
public void setGridColumns(Integer gridColumns)
|
||||
{
|
||||
this.gridColumns = gridColumns;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for gridColumns
|
||||
*******************************************************************************/
|
||||
public QFieldMetaData withGridColumns(Integer gridColumns)
|
||||
{
|
||||
this.gridColumns = gridColumns;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -27,6 +27,7 @@ import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalTime;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -100,6 +101,16 @@ public enum QFieldType
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public String getMixedCaseLabel()
|
||||
{
|
||||
return StringUtils.allCapsToMixedCase(name());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
|
@ -0,0 +1,477 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.fields;
|
||||
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.actions.values.ValueBehaviorApplier;
|
||||
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.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.statusmessages.BadInputStatusMessage;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Validate the min & max value for numeric fields.
|
||||
**
|
||||
** For each min & max, there are 4 possible settings:
|
||||
** - value - the number that is compared.
|
||||
** - allowEqualTo - defaults to true. controls if < (>) or ≤ (≥)
|
||||
** - behavior - defaults to ERROR. optionally can be "CLIP" instead.
|
||||
** - clipAmount - if clipping, and not allowing equalTo, how much off the limit
|
||||
** value should be added or subtracted. Defaults to 1.
|
||||
**
|
||||
** Convenient `withMin()` and `withMax()` methods exist for setting all 4
|
||||
** properties for each of min or max. Else, fluent-setters are recommended.
|
||||
*******************************************************************************/
|
||||
public class ValueRangeBehavior implements FieldBehavior<ValueRangeBehavior>
|
||||
{
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public enum Behavior
|
||||
{
|
||||
ERROR,
|
||||
CLIP
|
||||
}
|
||||
|
||||
|
||||
|
||||
private Number minValue;
|
||||
private boolean minAllowEqualTo = true;
|
||||
private Behavior minBehavior = Behavior.ERROR;
|
||||
private BigDecimal minClipAmount = BigDecimal.ONE;
|
||||
|
||||
private Number maxValue;
|
||||
private boolean maxAllowEqualTo = true;
|
||||
private Behavior maxBehavior = Behavior.ERROR;
|
||||
private BigDecimal maxClipAmount = BigDecimal.ONE;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public ValueRangeBehavior()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public ValueRangeBehavior getDefault()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public void apply(ValueBehaviorApplier.Action action, List<QRecord> recordList, QInstance instance, QTableMetaData table, QFieldMetaData field)
|
||||
{
|
||||
BigDecimal minLimitBigDecimal = minValue == null ? null : new BigDecimal(minValue.toString());
|
||||
String minLimitString = minValue == null ? null : minValue.toString();
|
||||
|
||||
BigDecimal maxLimitBigDecimal = maxValue == null ? null : new BigDecimal(maxValue.toString());
|
||||
String maxLimitString = maxValue == null ? null : maxValue.toString();
|
||||
|
||||
for(QRecord record : recordList)
|
||||
{
|
||||
BigDecimal recordValue = record.getValueBigDecimal(field.getName());
|
||||
if(recordValue != null)
|
||||
{
|
||||
if(minLimitBigDecimal != null)
|
||||
{
|
||||
int compare = recordValue.compareTo(minLimitBigDecimal);
|
||||
if(compare < 0 || (compare == 0 && !minAllowEqualTo))
|
||||
{
|
||||
if(this.minBehavior == Behavior.ERROR)
|
||||
{
|
||||
String operator = minAllowEqualTo ? "" : "greater than ";
|
||||
record.addError(new BadInputStatusMessage("The value for " + field.getLabel() + " is too small (minimum allowed value is " + operator + minLimitString + ")"));
|
||||
}
|
||||
else if(this.minBehavior == Behavior.CLIP)
|
||||
{
|
||||
if(minAllowEqualTo)
|
||||
{
|
||||
record.setValue(field.getName(), minLimitBigDecimal);
|
||||
}
|
||||
else
|
||||
{
|
||||
record.setValue(field.getName(), minLimitBigDecimal.add(minClipAmount));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(maxLimitBigDecimal != null)
|
||||
{
|
||||
int compare = recordValue.compareTo(maxLimitBigDecimal);
|
||||
if(compare > 0 || (compare == 0 && !maxAllowEqualTo))
|
||||
{
|
||||
if(this.maxBehavior == Behavior.ERROR)
|
||||
{
|
||||
String operator = maxAllowEqualTo ? "" : "less than ";
|
||||
record.addError(new BadInputStatusMessage("The value for " + field.getLabel() + " is too large (maximum allowed value is " + operator + maxLimitString + ")"));
|
||||
}
|
||||
else if(this.maxBehavior == Behavior.CLIP)
|
||||
{
|
||||
if(maxAllowEqualTo)
|
||||
{
|
||||
record.setValue(field.getName(), maxLimitBigDecimal);
|
||||
}
|
||||
else
|
||||
{
|
||||
record.setValue(field.getName(), maxLimitBigDecimal.subtract(maxClipAmount));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public boolean allowMultipleBehaviorsOfThisType()
|
||||
{
|
||||
return (false);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public List<String> validateBehaviorConfiguration(QTableMetaData tableMetaData, QFieldMetaData fieldMetaData)
|
||||
{
|
||||
List<String> errors = new ArrayList<>();
|
||||
|
||||
if(minValue == null && maxValue == null)
|
||||
{
|
||||
errors.add("Either minValue or maxValue (or both) must be set.");
|
||||
}
|
||||
|
||||
if(minValue != null && maxValue != null && new BigDecimal(minValue.toString()).compareTo(new BigDecimal(maxValue.toString())) > 0)
|
||||
{
|
||||
errors.add("minValue must be >= maxValue.");
|
||||
}
|
||||
|
||||
if(fieldMetaData != null && fieldMetaData.getType() != null && !fieldMetaData.getType().isNumeric())
|
||||
{
|
||||
errors.add("can only be applied to a numeric type field.");
|
||||
}
|
||||
|
||||
return (errors);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public ValueRangeBehavior withMin(Number value, boolean allowEqualTo, Behavior behavior, BigDecimal clipAmount)
|
||||
{
|
||||
setMinValue(value);
|
||||
setMinAllowEqualTo(allowEqualTo);
|
||||
setMinBehavior(behavior);
|
||||
setMinClipAmount(clipAmount);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public ValueRangeBehavior withMax(Number value, boolean allowEqualTo, Behavior behavior, BigDecimal clipAmount)
|
||||
{
|
||||
setMaxValue(value);
|
||||
setMaxAllowEqualTo(allowEqualTo);
|
||||
setMaxBehavior(behavior);
|
||||
setMaxClipAmount(clipAmount);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for minValue
|
||||
*******************************************************************************/
|
||||
public Number getMinValue()
|
||||
{
|
||||
return (this.minValue);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for minValue
|
||||
*******************************************************************************/
|
||||
public void setMinValue(Number minValue)
|
||||
{
|
||||
this.minValue = minValue;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for minValue
|
||||
*******************************************************************************/
|
||||
public ValueRangeBehavior withMinValue(Number minValue)
|
||||
{
|
||||
this.minValue = minValue;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for maxValue
|
||||
*******************************************************************************/
|
||||
public Number getMaxValue()
|
||||
{
|
||||
return (this.maxValue);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for maxValue
|
||||
*******************************************************************************/
|
||||
public void setMaxValue(Number maxValue)
|
||||
{
|
||||
this.maxValue = maxValue;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for maxValue
|
||||
*******************************************************************************/
|
||||
public ValueRangeBehavior withMaxValue(Number maxValue)
|
||||
{
|
||||
this.maxValue = maxValue;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for minAllowEqualTo
|
||||
*******************************************************************************/
|
||||
public boolean getMinAllowEqualTo()
|
||||
{
|
||||
return (this.minAllowEqualTo);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for minAllowEqualTo
|
||||
*******************************************************************************/
|
||||
public void setMinAllowEqualTo(boolean minAllowEqualTo)
|
||||
{
|
||||
this.minAllowEqualTo = minAllowEqualTo;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for minAllowEqualTo
|
||||
*******************************************************************************/
|
||||
public ValueRangeBehavior withMinAllowEqualTo(boolean minAllowEqualTo)
|
||||
{
|
||||
this.minAllowEqualTo = minAllowEqualTo;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for maxAllowEqualTo
|
||||
*******************************************************************************/
|
||||
public boolean getMaxAllowEqualTo()
|
||||
{
|
||||
return (this.maxAllowEqualTo);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for maxAllowEqualTo
|
||||
*******************************************************************************/
|
||||
public void setMaxAllowEqualTo(boolean maxAllowEqualTo)
|
||||
{
|
||||
this.maxAllowEqualTo = maxAllowEqualTo;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for maxAllowEqualTo
|
||||
*******************************************************************************/
|
||||
public ValueRangeBehavior withMaxAllowEqualTo(boolean maxAllowEqualTo)
|
||||
{
|
||||
this.maxAllowEqualTo = maxAllowEqualTo;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for minBehavior
|
||||
*******************************************************************************/
|
||||
public Behavior getMinBehavior()
|
||||
{
|
||||
return (this.minBehavior);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for minBehavior
|
||||
*******************************************************************************/
|
||||
public void setMinBehavior(Behavior minBehavior)
|
||||
{
|
||||
this.minBehavior = minBehavior;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for minBehavior
|
||||
*******************************************************************************/
|
||||
public ValueRangeBehavior withMinBehavior(Behavior minBehavior)
|
||||
{
|
||||
this.minBehavior = minBehavior;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for maxBehavior
|
||||
*******************************************************************************/
|
||||
public Behavior getMaxBehavior()
|
||||
{
|
||||
return (this.maxBehavior);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for maxBehavior
|
||||
*******************************************************************************/
|
||||
public void setMaxBehavior(Behavior maxBehavior)
|
||||
{
|
||||
this.maxBehavior = maxBehavior;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for maxBehavior
|
||||
*******************************************************************************/
|
||||
public ValueRangeBehavior withMaxBehavior(Behavior maxBehavior)
|
||||
{
|
||||
this.maxBehavior = maxBehavior;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for minClipAmount
|
||||
*******************************************************************************/
|
||||
public BigDecimal getMinClipAmount()
|
||||
{
|
||||
return (this.minClipAmount);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for minClipAmount
|
||||
*******************************************************************************/
|
||||
public void setMinClipAmount(BigDecimal minClipAmount)
|
||||
{
|
||||
this.minClipAmount = minClipAmount;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for minClipAmount
|
||||
*******************************************************************************/
|
||||
public ValueRangeBehavior withMinClipAmount(BigDecimal minClipAmount)
|
||||
{
|
||||
this.minClipAmount = minClipAmount;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for maxClipAmount
|
||||
*******************************************************************************/
|
||||
public BigDecimal getMaxClipAmount()
|
||||
{
|
||||
return (this.maxClipAmount);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for maxClipAmount
|
||||
*******************************************************************************/
|
||||
public void setMaxClipAmount(BigDecimal maxClipAmount)
|
||||
{
|
||||
this.maxClipAmount = maxClipAmount;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for maxClipAmount
|
||||
*******************************************************************************/
|
||||
public ValueRangeBehavior withMaxClipAmount(BigDecimal maxClipAmount)
|
||||
{
|
||||
this.maxClipAmount = maxClipAmount;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
@ -43,7 +43,7 @@ import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
*
|
||||
*******************************************************************************/
|
||||
@JsonInclude(Include.NON_NULL)
|
||||
public class QFrontendFieldMetaData
|
||||
public class QFrontendFieldMetaData implements Serializable
|
||||
{
|
||||
private String name;
|
||||
private String label;
|
||||
@ -51,6 +51,7 @@ public class QFrontendFieldMetaData
|
||||
private boolean isRequired;
|
||||
private boolean isEditable;
|
||||
private boolean isHeavy;
|
||||
private Integer gridColumns;
|
||||
private String possibleValueSourceName;
|
||||
private String displayFormat;
|
||||
private Serializable defaultValue;
|
||||
@ -66,7 +67,6 @@ public class QFrontendFieldMetaData
|
||||
//////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
*******************************************************************************/
|
||||
@ -78,6 +78,7 @@ public class QFrontendFieldMetaData
|
||||
this.isRequired = fieldMetaData.getIsRequired();
|
||||
this.isEditable = fieldMetaData.getIsEditable();
|
||||
this.isHeavy = fieldMetaData.getIsHeavy();
|
||||
this.gridColumns = fieldMetaData.getGridColumns();
|
||||
this.possibleValueSourceName = fieldMetaData.getPossibleValueSourceName();
|
||||
this.displayFormat = fieldMetaData.getDisplayFormat();
|
||||
this.adornments = fieldMetaData.getAdornments();
|
||||
@ -166,6 +167,17 @@ public class QFrontendFieldMetaData
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for gridColumns
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Integer getGridColumns()
|
||||
{
|
||||
return gridColumns;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for displayFormat
|
||||
**
|
||||
|
@ -86,7 +86,6 @@ public class QFrontendTableMetaData
|
||||
//////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -170,7 +169,7 @@ public class QFrontendTableMetaData
|
||||
if(backend != null && backend.getUsesVariants())
|
||||
{
|
||||
usesVariants = true;
|
||||
variantTableLabel = QContext.getQInstance().getTable(backend.getVariantOptionsTableName()).getLabel();
|
||||
variantTableLabel = QContext.getQInstance().getTable(backend.getBackendVariantsConfig().getOptionsTableName()).getLabel();
|
||||
}
|
||||
|
||||
this.helpContents = tableMetaData.getHelpContent();
|
||||
|
@ -216,11 +216,16 @@ public class SendSESAction
|
||||
{
|
||||
LOG.warn("More than one FROM value was found, will send using the first one found [" + partyList.get(0).getAddress() + "].");
|
||||
}
|
||||
Party fromParty = partyList.get(0);
|
||||
if(fromParty.getAddress() == null)
|
||||
{
|
||||
throw (new QException("Cannot send SES message because a FROM address was not provided."));
|
||||
}
|
||||
|
||||
/////////////////////////////
|
||||
// return the from address //
|
||||
/////////////////////////////
|
||||
return (partyList.get(0).getAddress());
|
||||
return (getFullEmailAddress(fromParty));
|
||||
}
|
||||
|
||||
|
||||
@ -267,15 +272,15 @@ public class SendSESAction
|
||||
{
|
||||
if(EmailPartyRole.CC.equals(party.getRole()))
|
||||
{
|
||||
ccList.add(party.getAddress());
|
||||
ccList.add(getFullEmailAddress(party));
|
||||
}
|
||||
else if(EmailPartyRole.BCC.equals(party.getRole()))
|
||||
{
|
||||
bccList.add(party.getAddress());
|
||||
bccList.add(getFullEmailAddress(party));
|
||||
}
|
||||
else if(party.getRole() == null || PartyRole.Default.DEFAULT.equals(party.getRole()) || EmailPartyRole.TO.equals(party.getRole()))
|
||||
{
|
||||
toList.add(party.getAddress());
|
||||
toList.add(getFullEmailAddress(party));
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -332,4 +337,22 @@ public class SendSESAction
|
||||
|
||||
return amazonSES;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private String getFullEmailAddress(Party party)
|
||||
{
|
||||
if(party.getLabel() != null)
|
||||
{
|
||||
return (party.getLabel() + " <" + party.getAddress() + ">");
|
||||
}
|
||||
|
||||
/////////////////////////////
|
||||
// return the from address //
|
||||
/////////////////////////////
|
||||
return (party.getAddress());
|
||||
}
|
||||
}
|
||||
|
@ -22,11 +22,14 @@
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.possiblevalues;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Interface to be implemented by enums which can be used as a PossibleValueSource.
|
||||
**
|
||||
*******************************************************************************/
|
||||
public interface PossibleValueEnum<T>
|
||||
public interface PossibleValueEnum<T extends Serializable>
|
||||
{
|
||||
/*******************************************************************************
|
||||
**
|
||||
|
@ -22,6 +22,7 @@
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.possiblevalues;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Objects;
|
||||
|
||||
|
||||
@ -30,7 +31,7 @@ import java.util.Objects;
|
||||
**
|
||||
** Type parameter `T` is the type of the id (often Integer, maybe String)
|
||||
*******************************************************************************/
|
||||
public class QPossibleValue<T>
|
||||
public class QPossibleValue<T extends Serializable>
|
||||
{
|
||||
private final T id;
|
||||
private final String label;
|
||||
|
@ -22,6 +22,7 @@
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.possiblevalues;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
@ -31,6 +32,8 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.TopLevelMetaDataInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
||||
import net.sf.saxon.trans.SaxonErrorCode;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -45,6 +48,8 @@ public class QPossibleValueSource implements TopLevelMetaDataInterface
|
||||
private String label;
|
||||
private QPossibleValueSourceType type;
|
||||
|
||||
private QFieldType idType;
|
||||
|
||||
private String valueFormat = PVSValueFormatAndFields.LABEL_ONLY.getFormat();
|
||||
private List<String> valueFields = PVSValueFormatAndFields.LABEL_ONLY.getFields();
|
||||
private String valueFormatIfNotFound = null;
|
||||
@ -100,7 +105,7 @@ public class QPossibleValueSource implements TopLevelMetaDataInterface
|
||||
** Create a new possible value source, for an enum, with default settings.
|
||||
** e.g., type=ENUM; name from param values from the param; LABEL_ONLY format
|
||||
*******************************************************************************/
|
||||
public static <I, T extends PossibleValueEnum<I>> QPossibleValueSource newForEnum(String name, T[] values)
|
||||
public static <I extends Serializable, T extends PossibleValueEnum<I>> QPossibleValueSource newForEnum(String name, T[] values)
|
||||
{
|
||||
return new QPossibleValueSource()
|
||||
.withName(name)
|
||||
@ -556,7 +561,7 @@ public class QPossibleValueSource implements TopLevelMetaDataInterface
|
||||
** myPossibleValueSource.withValuesFromEnum(MyEnum.values()));
|
||||
**
|
||||
*******************************************************************************/
|
||||
public <I, T extends PossibleValueEnum<I>> QPossibleValueSource withValuesFromEnum(T[] values)
|
||||
public <I extends Serializable, T extends PossibleValueEnum<I>> QPossibleValueSource withValuesFromEnum(T[] values)
|
||||
{
|
||||
Set<I> usedIds = new HashSet<>();
|
||||
List<I> duplicatedIds = new ArrayList<>();
|
||||
@ -679,4 +684,35 @@ public class QPossibleValueSource implements TopLevelMetaDataInterface
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for idType
|
||||
*******************************************************************************/
|
||||
public QFieldType getIdType()
|
||||
{
|
||||
return (this.idType);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for idType
|
||||
*******************************************************************************/
|
||||
public void setIdType(QFieldType idType)
|
||||
{
|
||||
this.idType = idType;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for idType
|
||||
*******************************************************************************/
|
||||
public QPossibleValueSource withIdType(QFieldType idType)
|
||||
{
|
||||
this.idType = idType;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -29,6 +29,9 @@ public enum QComponentType
|
||||
{
|
||||
HELP_TEXT,
|
||||
BULK_EDIT_FORM,
|
||||
BULK_LOAD_FILE_MAPPING_FORM,
|
||||
BULK_LOAD_VALUE_MAPPING_FORM,
|
||||
BULK_LOAD_PROFILE_FORM,
|
||||
VALIDATION_REVIEW_SCREEN,
|
||||
EDIT_FORM,
|
||||
VIEW_FORM,
|
||||
|
@ -48,6 +48,7 @@ public class QFrontendStepMetaData extends QStepMetaData
|
||||
private Map<String, QFieldMetaData> formFieldMap;
|
||||
|
||||
private String format;
|
||||
private String backStepName;
|
||||
|
||||
private List<QHelpContent> helpContents;
|
||||
|
||||
@ -436,4 +437,35 @@ public class QFrontendStepMetaData extends QStepMetaData
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for backStepName
|
||||
*******************************************************************************/
|
||||
public String getBackStepName()
|
||||
{
|
||||
return (this.backStepName);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for backStepName
|
||||
*******************************************************************************/
|
||||
public void setBackStepName(String backStepName)
|
||||
{
|
||||
this.backStepName = backStepName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for backStepName
|
||||
*******************************************************************************/
|
||||
public QFrontendStepMetaData withBackStepName(String backStepName)
|
||||
{
|
||||
this.backStepName = backStepName;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -31,11 +31,13 @@ import java.util.Set;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.TopLevelMetaDataInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppChildMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.permissions.MetaDataWithPermissionRules;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.permissions.QPermissionRules;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.qbits.SourceQBitAware;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.scheduleing.QScheduleMetaData;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.basepull.BasepullConfiguration;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
@ -45,11 +47,14 @@ import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
** Meta-Data to define a process in a QQQ instance.
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class QProcessMetaData implements QAppChildMetaData, MetaDataWithPermissionRules, TopLevelMetaDataInterface
|
||||
public class QProcessMetaData implements QAppChildMetaData, MetaDataWithPermissionRules, TopLevelMetaDataInterface, SourceQBitAware
|
||||
{
|
||||
private String name;
|
||||
private String label;
|
||||
private String tableName;
|
||||
private String name;
|
||||
private String label;
|
||||
private String tableName;
|
||||
|
||||
private String sourceQBitName;
|
||||
|
||||
private boolean isHidden = false;
|
||||
private BasepullConfiguration basepullConfiguration;
|
||||
private QPermissionRules permissionRules;
|
||||
@ -70,6 +75,8 @@ public class QProcessMetaData implements QAppChildMetaData, MetaDataWithPermissi
|
||||
private VariantRunStrategy variantRunStrategy;
|
||||
private String variantBackend;
|
||||
|
||||
private QCodeReference processTracerCodeReference;
|
||||
|
||||
private Map<String, QSupplementalProcessMetaData> supplementalMetaData;
|
||||
|
||||
|
||||
@ -877,4 +884,69 @@ public class QProcessMetaData implements QAppChildMetaData, MetaDataWithPermissi
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for processTracerCodeReference
|
||||
*******************************************************************************/
|
||||
public QCodeReference getProcessTracerCodeReference()
|
||||
{
|
||||
return (this.processTracerCodeReference);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for processTracerCodeReference
|
||||
*******************************************************************************/
|
||||
public void setProcessTracerCodeReference(QCodeReference processTracerCodeReference)
|
||||
{
|
||||
this.processTracerCodeReference = processTracerCodeReference;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for processTracerCodeReference
|
||||
*******************************************************************************/
|
||||
public QProcessMetaData withProcessTracerCodeReference(QCodeReference processTracerCodeReference)
|
||||
{
|
||||
this.processTracerCodeReference = processTracerCodeReference;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for sourceQBitName
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public String getSourceQBitName()
|
||||
{
|
||||
return (this.sourceQBitName);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for sourceQBitName
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void setSourceQBitName(String sourceQBitName)
|
||||
{
|
||||
this.sourceQBitName = sourceQBitName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for sourceQBitName
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public QProcessMetaData withSourceQBitName(String sourceQBitName)
|
||||
{
|
||||
this.sourceQBitName = sourceQBitName;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -62,6 +62,7 @@ public class ChildJoinFromRecordEntityGenericMetaDataProducer implements MetaDat
|
||||
private String parentTableName; // e.g., order
|
||||
private String foreignKeyFieldName; // e.g., orderId
|
||||
|
||||
private Class<?> sourceClass;
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
@ -102,4 +103,37 @@ public class ChildJoinFromRecordEntityGenericMetaDataProducer implements MetaDat
|
||||
return (join);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for sourceClass
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Class<?> getSourceClass()
|
||||
{
|
||||
return sourceClass;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for sourceClass
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setSourceClass(Class<?> sourceClass)
|
||||
{
|
||||
this.sourceClass = sourceClass;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for sourceClass
|
||||
**
|
||||
*******************************************************************************/
|
||||
public ChildJoinFromRecordEntityGenericMetaDataProducer withSourceClass(Class<?> sourceClass)
|
||||
{
|
||||
this.sourceClass = sourceClass;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -38,14 +38,14 @@ import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
** produce a QJoinMetaData, based on a QRecordEntity and a ChildTable sub-annotation.
|
||||
**
|
||||
** e.g., Orders & LineItems - on the Order entity
|
||||
** <code>
|
||||
<code>
|
||||
@QMetaDataProducingEntity( childTables = { @ChildTable(
|
||||
childTableEntityClass = LineItem.class,
|
||||
childJoin = @ChildJoin(enabled = true),
|
||||
childRecordListWidget = @ChildRecordListWidget(enabled = true, label = "Order Lines"))
|
||||
childTableEntityClass = LineItem.class,
|
||||
childJoin = @ChildJoin(enabled = true),
|
||||
childRecordListWidget = @ChildRecordListWidget(enabled = true, label = "Order Lines"))
|
||||
})
|
||||
public class Order extends QRecordEntity
|
||||
** </code>
|
||||
</code>
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class ChildRecordListWidgetFromRecordEntityGenericMetaDataProducer implements MetaDataProducerInterface<QWidgetMetaData>
|
||||
@ -53,18 +53,29 @@ public class ChildRecordListWidgetFromRecordEntityGenericMetaDataProducer implem
|
||||
private String childTableName; // e.g., lineItem
|
||||
private String parentTableName; // e.g., order
|
||||
|
||||
private MetaDataCustomizerInterface<QWidgetMetaData> widgetMetaDataProductionCustomizer = null;
|
||||
|
||||
private ChildRecordListWidget childRecordListWidget;
|
||||
|
||||
private Class<?> sourceClass;
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public ChildRecordListWidgetFromRecordEntityGenericMetaDataProducer(String childTableName, String parentTableName, ChildRecordListWidget childRecordListWidget)
|
||||
public ChildRecordListWidgetFromRecordEntityGenericMetaDataProducer(String childTableName, String parentTableName, ChildRecordListWidget childRecordListWidget) throws Exception
|
||||
{
|
||||
this.childTableName = childTableName;
|
||||
this.parentTableName = parentTableName;
|
||||
this.childRecordListWidget = childRecordListWidget;
|
||||
|
||||
Class<? extends MetaDataCustomizerInterface<?>> genericMetaProductionCustomizer = (Class<? extends MetaDataCustomizerInterface<?>>) childRecordListWidget.widgetMetaDataCustomizer();
|
||||
if(!genericMetaProductionCustomizer.equals(MetaDataCustomizerInterface.NoopMetaDataCustomizer.class))
|
||||
{
|
||||
Class<? extends MetaDataCustomizerInterface<QWidgetMetaData>> widgetMetaProductionCustomizerClass = (Class<? extends MetaDataCustomizerInterface<QWidgetMetaData>>) genericMetaProductionCustomizer;
|
||||
this.widgetMetaDataProductionCustomizer = widgetMetaProductionCustomizerClass.getConstructor().newInstance();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -94,7 +105,44 @@ public class ChildRecordListWidgetFromRecordEntityGenericMetaDataProducer implem
|
||||
widget.withDefaultValue("maxRows", childRecordListWidget.maxRows());
|
||||
}
|
||||
|
||||
if(this.widgetMetaDataProductionCustomizer != null)
|
||||
{
|
||||
widget = this.widgetMetaDataProductionCustomizer.customizeMetaData(qInstance, widget);
|
||||
}
|
||||
|
||||
return (widget);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for sourceClass
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Class<?> getSourceClass()
|
||||
{
|
||||
return sourceClass;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for sourceClass
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setSourceClass(Class<?> sourceClass)
|
||||
{
|
||||
this.sourceClass = sourceClass;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for sourceClass
|
||||
**
|
||||
*******************************************************************************/
|
||||
public ChildRecordListWidgetFromRecordEntityGenericMetaDataProducer withSourceClass(Class<?> sourceClass)
|
||||
{
|
||||
this.sourceClass = sourceClass;
|
||||
return (this);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.producers;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.TopLevelMetaDataInterface;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Interface to be implemented by classes that are designed to help customize
|
||||
** meta-data objects as they're being produced, e.g., such as a table produced
|
||||
** via the QMetaDataProducingEntity, or maybe tables loaded by a qbit??
|
||||
*******************************************************************************/
|
||||
public interface MetaDataCustomizerInterface<T extends TopLevelMetaDataInterface>
|
||||
{
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
T customizeMetaData(QInstance qInstance, T metaData) throws QException;
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
** noop version of this interface - used as default value in annotation
|
||||
**
|
||||
***************************************************************************/
|
||||
class NoopMetaDataCustomizer<T extends TopLevelMetaDataInterface> implements MetaDataCustomizerInterface<T>
|
||||
{
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public T customizeMetaData(QInstance qInstance, T metaData) throws QException
|
||||
{
|
||||
return (metaData);
|
||||
}
|
||||
}
|
||||
}
|
@ -22,6 +22,7 @@
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.producers;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.PossibleValueEnum;
|
||||
@ -34,11 +35,15 @@ import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleVal
|
||||
** based on a PossibleValueEnum
|
||||
**
|
||||
***************************************************************************/
|
||||
public class PossibleValueSourceOfEnumGenericMetaDataProducer<T extends PossibleValueEnum<T>> implements MetaDataProducerInterface<QPossibleValueSource>
|
||||
public class PossibleValueSourceOfEnumGenericMetaDataProducer<T extends Serializable & PossibleValueEnum<T>> implements MetaDataProducerInterface<QPossibleValueSource>
|
||||
{
|
||||
private final String name;
|
||||
private final PossibleValueEnum<T>[] values;
|
||||
|
||||
private Class<?> sourceClass;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -61,4 +66,37 @@ public class PossibleValueSourceOfEnumGenericMetaDataProducer<T extends Possible
|
||||
{
|
||||
return (QPossibleValueSource.newForEnum(name, values));
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for sourceClass
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Class<?> getSourceClass()
|
||||
{
|
||||
return sourceClass;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for sourceClass
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setSourceClass(Class<?> sourceClass)
|
||||
{
|
||||
this.sourceClass = sourceClass;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for sourceClass
|
||||
**
|
||||
*******************************************************************************/
|
||||
public PossibleValueSourceOfEnumGenericMetaDataProducer<T> withSourceClass(Class<?> sourceClass)
|
||||
{
|
||||
this.sourceClass = sourceClass;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -37,6 +37,7 @@ public class PossibleValueSourceOfTableGenericMetaDataProducer implements MetaDa
|
||||
{
|
||||
private final String tableName;
|
||||
|
||||
private Class<?> sourceClass;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -58,4 +59,38 @@ public class PossibleValueSourceOfTableGenericMetaDataProducer implements MetaDa
|
||||
{
|
||||
return (QPossibleValueSource.newForTable(tableName));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for sourceClass
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Class<?> getSourceClass()
|
||||
{
|
||||
return sourceClass;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for sourceClass
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setSourceClass(Class<?> sourceClass)
|
||||
{
|
||||
this.sourceClass = sourceClass;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for sourceClass
|
||||
**
|
||||
*******************************************************************************/
|
||||
public PossibleValueSourceOfTableGenericMetaDataProducer withSourceClass(Class<?> sourceClass)
|
||||
{
|
||||
this.sourceClass = sourceClass;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,191 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.producers;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
** Generic meta-data-producer, which should be instantiated (e.g., by
|
||||
** MetaDataProducerHelper), to produce a QPossibleValueSource meta-data
|
||||
** based on a QRecordEntity class (which has corresponding QTableMetaData).
|
||||
**
|
||||
***************************************************************************/
|
||||
public class RecordEntityToTableGenericMetaDataProducer implements MetaDataProducerInterface<QTableMetaData>
|
||||
{
|
||||
private final String tableName;
|
||||
private final Class<? extends QRecordEntity> entityClass;
|
||||
|
||||
private final List<MetaDataCustomizerInterface<QTableMetaData>> metaDataCustomizers = new ArrayList<>();
|
||||
|
||||
private static MetaDataCustomizerInterface<QTableMetaData> defaultMetaDataCustomizer = null;
|
||||
|
||||
private Class<?> sourceClass;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public RecordEntityToTableGenericMetaDataProducer(String tableName, Class<? extends QRecordEntity> entityClass, Class<? extends MetaDataCustomizerInterface<QTableMetaData>> metaDataProductionCustomizerClass) throws QException
|
||||
{
|
||||
this.tableName = tableName;
|
||||
this.entityClass = entityClass;
|
||||
|
||||
if(metaDataProductionCustomizerClass != null)
|
||||
{
|
||||
metaDataCustomizers.add(getMetaDataProductionCustomizer(metaDataProductionCustomizerClass));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public QTableMetaData produce(QInstance qInstance) throws QException
|
||||
{
|
||||
QTableMetaData qTableMetaData = new QTableMetaData();
|
||||
qTableMetaData.setName(tableName);
|
||||
qTableMetaData.setRecordLabelFormat("%s");
|
||||
qTableMetaData.withFieldsFromEntity(entityClass);
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// use the productionCustomizers to fill in more of the meta data //
|
||||
////////////////////////////////////////////////////////////////////
|
||||
for(MetaDataCustomizerInterface<QTableMetaData> metaDataMetaDataCustomizer : metaDataCustomizers)
|
||||
{
|
||||
qTableMetaData = metaDataMetaDataCustomizer.customizeMetaData(qInstance, qTableMetaData);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// now if there's a default customizer, call it too - for generic, common things //
|
||||
// you might want on all of your tables, or defaults if not set otherwise //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
if(defaultMetaDataCustomizer != null)
|
||||
{
|
||||
qTableMetaData = defaultMetaDataCustomizer.customizeMetaData(qInstance, qTableMetaData);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
// use primary key as record label field, if it hasn't been set so far //
|
||||
// todo - does this belong in the enricher?? //
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
if(CollectionUtils.nullSafeIsEmpty(qTableMetaData.getRecordLabelFields()) && StringUtils.hasContent(qTableMetaData.getPrimaryKeyField()))
|
||||
{
|
||||
qTableMetaData.setRecordLabelFields(List.of(qTableMetaData.getPrimaryKeyField()));
|
||||
}
|
||||
|
||||
return qTableMetaData;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
private MetaDataCustomizerInterface<QTableMetaData> getMetaDataProductionCustomizer(Class<? extends MetaDataCustomizerInterface<QTableMetaData>> metaDataCustomizerClass) throws QException
|
||||
{
|
||||
try
|
||||
{
|
||||
return metaDataCustomizerClass.getConstructor().newInstance();
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw (new QException("Error constructing table metadata production customizer class [" + metaDataCustomizerClass + "]: ", e));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public void addRecordEntityTableMetaDataProductionCustomizer(MetaDataCustomizerInterface<QTableMetaData> metaDataMetaDataCustomizer)
|
||||
{
|
||||
metaDataCustomizers.add(metaDataMetaDataCustomizer);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for defaultMetaDataCustomizer
|
||||
*******************************************************************************/
|
||||
public static MetaDataCustomizerInterface<QTableMetaData> getDefaultMetaDataCustomizer()
|
||||
{
|
||||
return (RecordEntityToTableGenericMetaDataProducer.defaultMetaDataCustomizer);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for defaultMetaDataCustomizer
|
||||
*******************************************************************************/
|
||||
public static void setDefaultMetaDataCustomizer(MetaDataCustomizerInterface<QTableMetaData> defaultMetaDataCustomizer)
|
||||
{
|
||||
RecordEntityToTableGenericMetaDataProducer.defaultMetaDataCustomizer = defaultMetaDataCustomizer;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for sourceClass
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Class<?> getSourceClass()
|
||||
{
|
||||
return sourceClass;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for sourceClass
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setSourceClass(Class<?> sourceClass)
|
||||
{
|
||||
this.sourceClass = sourceClass;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for sourceClass
|
||||
**
|
||||
*******************************************************************************/
|
||||
public RecordEntityToTableGenericMetaDataProducer withSourceClass(Class<?> sourceClass)
|
||||
{
|
||||
this.sourceClass = sourceClass;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
@ -24,6 +24,7 @@ package com.kingsrook.qqq.backend.core.model.metadata.producers.annotations;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.producers.MetaDataCustomizerInterface;
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
@ -43,4 +44,6 @@ public @interface ChildRecordListWidget
|
||||
boolean canAddChildRecords() default false;
|
||||
|
||||
String manageAssociationName() default "";
|
||||
|
||||
Class<? extends MetaDataCustomizerInterface> widgetMetaDataCustomizer() default MetaDataCustomizerInterface.NoopMetaDataCustomizer.class;
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.producers.MetaDataCustomizerInterface;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -41,8 +42,10 @@ import java.lang.annotation.Target;
|
||||
@SuppressWarnings("checkstyle:MissingJavadocMethod")
|
||||
public @interface QMetaDataProducingEntity
|
||||
{
|
||||
boolean producePossibleValueSource() default true;
|
||||
boolean produceTableMetaData() default false;
|
||||
Class<? extends MetaDataCustomizerInterface> tableMetaDataCustomizer() default MetaDataCustomizerInterface.NoopMetaDataCustomizer.class;
|
||||
|
||||
boolean producePossibleValueSource() default false;
|
||||
ChildTable[] childTables() default { };
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,122 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.qbits;
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
** Common (maybe)? qbit config pattern, where the qbit may be able to provide
|
||||
** a particular table, or, the application may supply it itself.
|
||||
**
|
||||
** If the qbit provides it, then we need to be told (by the application)
|
||||
** what backendName to use for the table.
|
||||
**
|
||||
** Else if the application supplies it, it needs to tell the qBit what the
|
||||
** tableName is.
|
||||
***************************************************************************/
|
||||
public class ProvidedOrSuppliedTableConfig
|
||||
{
|
||||
private boolean doProvideTable;
|
||||
private String backendName;
|
||||
private String tableName;
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public ProvidedOrSuppliedTableConfig(boolean doProvideTable, String backendName, String tableName)
|
||||
{
|
||||
this.doProvideTable = doProvideTable;
|
||||
this.backendName = backendName;
|
||||
this.tableName = tableName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public static ProvidedOrSuppliedTableConfig provideTableUsingBackendNamed(String backendName)
|
||||
{
|
||||
return (new ProvidedOrSuppliedTableConfig(true, backendName, null));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public static ProvidedOrSuppliedTableConfig useSuppliedTaleNamed(String tableName)
|
||||
{
|
||||
return (new ProvidedOrSuppliedTableConfig(false, null, tableName));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public String getEffectiveTableName(String tableNameIfProviding)
|
||||
{
|
||||
if (getDoProvideTable())
|
||||
{
|
||||
return tableNameIfProviding;
|
||||
}
|
||||
else
|
||||
{
|
||||
return getTableName();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for tableName
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getTableName()
|
||||
{
|
||||
return tableName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for doProvideTable
|
||||
**
|
||||
*******************************************************************************/
|
||||
public boolean getDoProvideTable()
|
||||
{
|
||||
return doProvideTable;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for backendName
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getBackendName()
|
||||
{
|
||||
return backendName;
|
||||
}
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.qbits;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerOutput;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** extension of MetaDataProducerInterface, designed for producing meta data
|
||||
** within a (java-defined, at this time) QBit.
|
||||
**
|
||||
** Specifically exists to accept the QBitConfig as a type parameter and a value,
|
||||
** easily accessed in the producer's methods as getQBitConfig()
|
||||
*******************************************************************************/
|
||||
public abstract class QBitComponentMetaDataProducer<T extends MetaDataProducerOutput, C extends QBitConfig> implements MetaDataProducerInterface<T>
|
||||
{
|
||||
private C qBitConfig = null;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for qBitConfig
|
||||
*******************************************************************************/
|
||||
public C getQBitConfig()
|
||||
{
|
||||
return (this.qBitConfig);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for qBitConfig
|
||||
*******************************************************************************/
|
||||
public void setQBitConfig(C qBitConfig)
|
||||
{
|
||||
this.qBitConfig = qBitConfig;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for qBitConfig
|
||||
*******************************************************************************/
|
||||
public QBitComponentMetaDataProducer<T, C> withQBitConfig(C qBitConfig)
|
||||
{
|
||||
this.qBitConfig = qBitConfig;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,110 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.qbits;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.producers.MetaDataCustomizerInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Interface for configuration settings used both in the production of meta-data
|
||||
** for a QBit, but also at runtime, e.g., to be aware of exactly how the qbit
|
||||
** has been incorporated into an application.
|
||||
**
|
||||
** For example:
|
||||
** - should the QBit define certain tables, or will they be supplied by the application?
|
||||
** - what other meta-data names should the qbit reference (backends, schedulers)
|
||||
** - what meta-data-customizer(s) should be used?
|
||||
**
|
||||
** When implementing a QBit, you'll implement this interface - adding whatever
|
||||
** (if any) properties you need, and if you have any rules, then overriding
|
||||
** the validate method (ideally the one that takes the List-of-String errors)
|
||||
**
|
||||
** When using a QBit, you'll create an instance of the QBit's config object,
|
||||
** and pass it through to the QBit producer.
|
||||
*******************************************************************************/
|
||||
public interface QBitConfig extends Serializable
|
||||
{
|
||||
QLogger LOG = QLogger.getLogger(QBitConfig.class);
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
default void validate(QInstance qInstance) throws QBitConfigValidationException
|
||||
{
|
||||
List<String> errors = new ArrayList<>();
|
||||
|
||||
try
|
||||
{
|
||||
validate(qInstance, errors);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.warn("Error validating QBitConfig: " + this.getClass().getName(), e);
|
||||
}
|
||||
|
||||
if(!errors.isEmpty())
|
||||
{
|
||||
throw (new QBitConfigValidationException(this, errors));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
default void validate(QInstance qInstance, List<String> errors)
|
||||
{
|
||||
/////////////////////////////////////
|
||||
// nothing to validate by default! //
|
||||
/////////////////////////////////////
|
||||
}
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
default boolean assertCondition(boolean condition, String message, List<String> errors)
|
||||
{
|
||||
if(!condition)
|
||||
{
|
||||
errors.add(message);
|
||||
}
|
||||
return (condition);
|
||||
}
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
default MetaDataCustomizerInterface<QTableMetaData> getTableMetaDataCustomizer()
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.qbits;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** thrown by QBitConfig.validate() if there's an issue.
|
||||
*******************************************************************************/
|
||||
public class QBitConfigValidationException extends QException
|
||||
{
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public QBitConfigValidationException(QBitConfig qBitConfig, List<String> errors)
|
||||
{
|
||||
super("Validation failed for QBitConfig: " + qBitConfig.getClass().getName() + ":\n" + StringUtils.join("\n", errors));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,237 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.qbits;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.TopLevelMetaDataInterface;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Meta-data to define an active QBit in a QQQ Instance.
|
||||
**
|
||||
** The unique "name" for the QBit is composed of its groupId and artifactId
|
||||
** (maven style). There is also a version - but it is not part of the unique
|
||||
** name. But - there is also a namespace attribute, which IS part of the
|
||||
** unique name. This will (eventually?) allow us to have multiple instances
|
||||
** of the same qbit in a qInstance at the same time (e.g., 2 versions of some
|
||||
** table, which should be namespace-prefixed);
|
||||
**
|
||||
** QBitMetaData also retains the QBitConfig that was used to produce the QBit.
|
||||
**
|
||||
** Some meta-data objects are aware of the fact that they may have come from a
|
||||
** QBit - see SourceQBitAware interface. These objects can get their source
|
||||
** QBitMetaData (this object) and its config,via that interface.
|
||||
*******************************************************************************/
|
||||
public class QBitMetaData implements TopLevelMetaDataInterface
|
||||
{
|
||||
private String groupId;
|
||||
private String artifactId;
|
||||
private String version;
|
||||
private String namespace;
|
||||
|
||||
private QBitConfig config;
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public String getName()
|
||||
{
|
||||
String name = groupId + ":" + artifactId;
|
||||
if(StringUtils.hasContent(namespace))
|
||||
{
|
||||
name += ":" + namespace;
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public void addSelfToInstance(QInstance qInstance)
|
||||
{
|
||||
qInstance.addQBit(this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for config
|
||||
*******************************************************************************/
|
||||
public QBitConfig getConfig()
|
||||
{
|
||||
return (this.config);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for config
|
||||
*******************************************************************************/
|
||||
public void setConfig(QBitConfig config)
|
||||
{
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for config
|
||||
*******************************************************************************/
|
||||
public QBitMetaData withConfig(QBitConfig config)
|
||||
{
|
||||
this.config = config;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for groupId
|
||||
*******************************************************************************/
|
||||
public String getGroupId()
|
||||
{
|
||||
return (this.groupId);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for groupId
|
||||
*******************************************************************************/
|
||||
public void setGroupId(String groupId)
|
||||
{
|
||||
this.groupId = groupId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for groupId
|
||||
*******************************************************************************/
|
||||
public QBitMetaData withGroupId(String groupId)
|
||||
{
|
||||
this.groupId = groupId;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for artifactId
|
||||
*******************************************************************************/
|
||||
public String getArtifactId()
|
||||
{
|
||||
return (this.artifactId);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for artifactId
|
||||
*******************************************************************************/
|
||||
public void setArtifactId(String artifactId)
|
||||
{
|
||||
this.artifactId = artifactId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for artifactId
|
||||
*******************************************************************************/
|
||||
public QBitMetaData withArtifactId(String artifactId)
|
||||
{
|
||||
this.artifactId = artifactId;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for version
|
||||
*******************************************************************************/
|
||||
public String getVersion()
|
||||
{
|
||||
return (this.version);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for version
|
||||
*******************************************************************************/
|
||||
public void setVersion(String version)
|
||||
{
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for version
|
||||
*******************************************************************************/
|
||||
public QBitMetaData withVersion(String version)
|
||||
{
|
||||
this.version = version;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for namespace
|
||||
*******************************************************************************/
|
||||
public String getNamespace()
|
||||
{
|
||||
return (this.namespace);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for namespace
|
||||
*******************************************************************************/
|
||||
public void setNamespace(String namespace)
|
||||
{
|
||||
this.namespace = namespace;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for namespace
|
||||
*******************************************************************************/
|
||||
public QBitMetaData withNamespace(String namespace)
|
||||
{
|
||||
this.namespace = namespace;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,117 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.qbits;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** interface for how a QBit's meta-data gets produced and added to a QInstance.
|
||||
**
|
||||
** When implementing a QBit, you'll implement this interface:
|
||||
** - adding a QBitConfig subclass as a property
|
||||
** - overriding the produce(qInstance, namespace) method - where you'll:
|
||||
** -- create and add your QBitMetaData
|
||||
** -- call MetaDataProducerHelper.findProducers
|
||||
** -- hand off to finishProducing() in this interface
|
||||
**
|
||||
** When using a QBit, you'll create an instance of the QBit's config object,
|
||||
** pass it in to the producer, then call produce, ala:
|
||||
**
|
||||
** new SomeQBitProducer()
|
||||
** .withQBitConfig(someQBitConfig)
|
||||
** .produce(qInstance);
|
||||
**
|
||||
*******************************************************************************/
|
||||
public interface QBitProducer
|
||||
{
|
||||
QLogger LOG = QLogger.getLogger(QBitProducer.class);
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
default void produce(QInstance qInstance) throws QException
|
||||
{
|
||||
produce(qInstance, null);
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
void produce(QInstance qInstance, String namespace) throws QException;
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
*
|
||||
***************************************************************************/
|
||||
default <C extends QBitConfig> void finishProducing(QInstance qInstance, QBitMetaData qBitMetaData, C qBitConfig, List<MetaDataProducerInterface<?>> producers) throws QException
|
||||
{
|
||||
qBitConfig.validate(qInstance);
|
||||
|
||||
///////////////////////////////
|
||||
// todo - move to base class //
|
||||
///////////////////////////////
|
||||
for(MetaDataProducerInterface<?> producer : producers)
|
||||
{
|
||||
if(producer instanceof QBitComponentMetaDataProducer<?, ?>)
|
||||
{
|
||||
QBitComponentMetaDataProducer<?, C> qBitComponentMetaDataProducer = (QBitComponentMetaDataProducer<?, C>) producer;
|
||||
qBitComponentMetaDataProducer.setQBitConfig(qBitConfig);
|
||||
}
|
||||
|
||||
if(!producer.isEnabled())
|
||||
{
|
||||
LOG.debug("Not using producer which is not enabled", logPair("producer", producer.getClass().getSimpleName()));
|
||||
continue;
|
||||
}
|
||||
|
||||
MetaDataProducerOutput output = producer.produce(qInstance);
|
||||
|
||||
/////////////////////////////////////////
|
||||
// apply table customizer, if provided //
|
||||
/////////////////////////////////////////
|
||||
if(qBitConfig.getTableMetaDataCustomizer() != null && output instanceof QTableMetaData table)
|
||||
{
|
||||
output = qBitConfig.getTableMetaDataCustomizer().customizeMetaData(qInstance, table);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////
|
||||
// set source qbit, if output is aware of such //
|
||||
/////////////////////////////////////////////////
|
||||
if(output instanceof SourceQBitAware sourceQBitAware)
|
||||
{
|
||||
sourceQBitAware.setSourceQBitName(qBitMetaData.getName());
|
||||
}
|
||||
|
||||
output.addSelfToInstance(qInstance);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.qbits;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** interface for meta data objects that may have come from a qbit, and where we
|
||||
** might want to get data about that qbit (e.g., config or meta-data).
|
||||
*******************************************************************************/
|
||||
public interface SourceQBitAware
|
||||
{
|
||||
/*******************************************************************************
|
||||
** Getter for sourceQBitName
|
||||
*******************************************************************************/
|
||||
String getSourceQBitName();
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for sourceQBitName
|
||||
*******************************************************************************/
|
||||
void setSourceQBitName(String sourceQBitName);
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for sourceQBitName
|
||||
*******************************************************************************/
|
||||
Object withSourceQBitName(String sourceQBitName);
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
default QBitMetaData getSourceQBit()
|
||||
{
|
||||
String qbitName = getSourceQBitName();
|
||||
return (QContext.getQInstance().getQBits().get(qbitName));
|
||||
}
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
default QBitConfig getSourceQBitConfig()
|
||||
{
|
||||
QBitMetaData sourceQBit = getSourceQBit();
|
||||
if(sourceQBit == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
return sourceQBit.getConfig();
|
||||
}
|
||||
}
|
||||
}
|
@ -50,10 +50,12 @@ import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppChildMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.permissions.MetaDataWithPermissionRules;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.permissions.QPermissionRules;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.qbits.SourceQBitAware;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.security.RecordSecurityLock;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.sharing.ShareableTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.QTableAutomationDetails;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.cache.CacheOf;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
@ -61,25 +63,19 @@ import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
** Meta-Data to define a table in a QQQ instance.
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class QTableMetaData implements QAppChildMetaData, Serializable, MetaDataWithPermissionRules, TopLevelMetaDataInterface
|
||||
public class QTableMetaData implements QAppChildMetaData, Serializable, MetaDataWithPermissionRules, TopLevelMetaDataInterface, SourceQBitAware
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(QTableMetaData.class);
|
||||
|
||||
private String name;
|
||||
private String label;
|
||||
|
||||
// TODO: resolve confusion over:
|
||||
// Is this name of what backend the table is stored in (yes)
|
||||
// Or the "name" of the table WITHIN the backend (no)
|
||||
// although that's how "backendName" is used in QFieldMetaData.
|
||||
// Idea:
|
||||
// rename "backendName" here to "backend"
|
||||
// add "nameInBackend" (or similar) for the table name in the backend
|
||||
// OR - add a whole "backendDetails" object, with different details per backend-type
|
||||
private String backendName;
|
||||
private String primaryKeyField;
|
||||
private boolean isHidden = false;
|
||||
|
||||
private String sourceQBitName;
|
||||
|
||||
private Map<String, QFieldMetaData> fields;
|
||||
private List<UniqueKey> uniqueKeys;
|
||||
private List<Association> associations;
|
||||
@ -184,6 +180,12 @@ public class QTableMetaData implements QAppChildMetaData, Serializable, MetaData
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// stash a reference from this entityClass to this table in the QRecordEntity class //
|
||||
// (used within that class later, if it wants to know about a table that an Entity helped build) //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
QRecordEntity.registerTable(entityClass, this);
|
||||
|
||||
return (this);
|
||||
}
|
||||
|
||||
@ -714,6 +716,25 @@ public class QTableMetaData implements QAppChildMetaData, Serializable, MetaData
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for sections
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QFieldSection getSection(String name)
|
||||
{
|
||||
for(QFieldSection qFieldSection : CollectionUtils.nonNullList(sections))
|
||||
{
|
||||
if(qFieldSection.getName().equals(name))
|
||||
{
|
||||
return (qFieldSection);
|
||||
}
|
||||
}
|
||||
|
||||
return (null);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for sections
|
||||
**
|
||||
@ -1038,7 +1059,7 @@ public class QTableMetaData implements QAppChildMetaData, Serializable, MetaData
|
||||
{
|
||||
for(Capability disabledCapability : disabledCapabilities)
|
||||
{
|
||||
withCapability(disabledCapability);
|
||||
withoutCapability(disabledCapability);
|
||||
}
|
||||
return (this);
|
||||
}
|
||||
@ -1536,4 +1557,38 @@ public class QTableMetaData implements QAppChildMetaData, Serializable, MetaData
|
||||
QInstanceHelpContentManager.removeHelpContentByRoleSetFromList(roles, listForSlot);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for sourceQBitName
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public String getSourceQBitName()
|
||||
{
|
||||
return (this.sourceQBitName);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for sourceQBitName
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void setSourceQBitName(String sourceQBitName)
|
||||
{
|
||||
this.sourceQBitName = sourceQBitName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for sourceQBitName
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public QTableMetaData withSourceQBitName(String sourceQBitName)
|
||||
{
|
||||
this.sourceQBitName = sourceQBitName;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,221 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.tables;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Factory class for creating "standard" qfield sections. e.g., if you want
|
||||
** the same t1, t2, and t3 section on all your tables, use this class to
|
||||
** produce them.
|
||||
**
|
||||
** You can change the default name & iconNames for those sections, but note,
|
||||
** this is a static/utility style class, so those settings are static fields.
|
||||
**
|
||||
** The method customT2 is provided as not much of a shortcut over "doing it yourself",
|
||||
** but to allow all sections for a table to be produced through calls to this factory,
|
||||
** so they look more similar.
|
||||
*******************************************************************************/
|
||||
public class SectionFactory
|
||||
{
|
||||
private static String defaultT1name = "identity";
|
||||
private static String defaultT1iconName = "badge";
|
||||
private static String defaultT2name = "data";
|
||||
private static String defaultT2iconName = "text_snippet";
|
||||
private static String defaultT3name = "dates";
|
||||
private static String defaultT3iconName = "calendar_month";
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** private constructor, to enforce static usage, e.g., to make clear the fields
|
||||
** are static fields.
|
||||
**
|
||||
*******************************************************************************/
|
||||
private SectionFactory()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public static QFieldSection defaultT1(String... fieldNames)
|
||||
{
|
||||
return new QFieldSection(defaultT1name, new QIcon().withName(defaultT1iconName), Tier.T1, List.of(fieldNames));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public static QFieldSection defaultT2(String... fieldNames)
|
||||
{
|
||||
return new QFieldSection(defaultT2name, new QIcon().withName(defaultT2iconName), Tier.T2, List.of(fieldNames));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public static QFieldSection customT2(String name, QIcon icon, String... fieldNames)
|
||||
{
|
||||
return new QFieldSection(name, icon, Tier.T2, List.of(fieldNames));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public static QFieldSection defaultT3(String... fieldNames)
|
||||
{
|
||||
return new QFieldSection(defaultT3name, new QIcon().withName(defaultT3iconName), Tier.T3, List.of(fieldNames));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for defaultT1name
|
||||
*******************************************************************************/
|
||||
public static String getDefaultT1name()
|
||||
{
|
||||
return (SectionFactory.defaultT1name);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for defaultT1name
|
||||
*******************************************************************************/
|
||||
public static void setDefaultT1name(String defaultT1name)
|
||||
{
|
||||
SectionFactory.defaultT1name = defaultT1name;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for defaultT1iconName
|
||||
*******************************************************************************/
|
||||
public static String getDefaultT1iconName()
|
||||
{
|
||||
return (SectionFactory.defaultT1iconName);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for defaultT1iconName
|
||||
*******************************************************************************/
|
||||
public static void setDefaultT1iconName(String defaultT1iconName)
|
||||
{
|
||||
SectionFactory.defaultT1iconName = defaultT1iconName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for defaultT2name
|
||||
*******************************************************************************/
|
||||
public static String getDefaultT2name()
|
||||
{
|
||||
return (SectionFactory.defaultT2name);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for defaultT2name
|
||||
*******************************************************************************/
|
||||
public static void setDefaultT2name(String defaultT2name)
|
||||
{
|
||||
SectionFactory.defaultT2name = defaultT2name;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for defaultT2iconName
|
||||
*******************************************************************************/
|
||||
public static String getDefaultT2iconName()
|
||||
{
|
||||
return (SectionFactory.defaultT2iconName);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for defaultT2iconName
|
||||
*******************************************************************************/
|
||||
public static void setDefaultT2iconName(String defaultT2iconName)
|
||||
{
|
||||
SectionFactory.defaultT2iconName = defaultT2iconName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for defaultT3name
|
||||
*******************************************************************************/
|
||||
public static String getDefaultT3name()
|
||||
{
|
||||
return (SectionFactory.defaultT3name);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for defaultT3name
|
||||
*******************************************************************************/
|
||||
public static void setDefaultT3name(String defaultT3name)
|
||||
{
|
||||
SectionFactory.defaultT3name = defaultT3name;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for defaultT3iconName
|
||||
*******************************************************************************/
|
||||
public static String getDefaultT3iconName()
|
||||
{
|
||||
return (SectionFactory.defaultT3iconName);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for defaultT3iconName
|
||||
*******************************************************************************/
|
||||
public static void setDefaultT3iconName(String defaultT3iconName)
|
||||
{
|
||||
SectionFactory.defaultT3iconName = defaultT3iconName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,89 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.tables;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.actions.permissions.PermissionCheckResult;
|
||||
import com.kingsrook.qqq.backend.core.actions.permissions.PermissionsHelper;
|
||||
import com.kingsrook.qqq.backend.core.actions.values.QCustomPossibleValueProvider;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.values.SearchPossibleValueSourceInput;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValue;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** possible-value source provider for the `Tables` PVS - a list of all tables
|
||||
** in an application/qInstance.
|
||||
*******************************************************************************/
|
||||
public class TablesCustomPossibleValueProvider implements QCustomPossibleValueProvider<String>
|
||||
{
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public QPossibleValue<String> getPossibleValue(Serializable idValue)
|
||||
{
|
||||
QTableMetaData table = QContext.getQInstance().getTable(ValueUtils.getValueAsString(idValue));
|
||||
if(table != null && !table.getIsHidden())
|
||||
{
|
||||
PermissionCheckResult permissionCheckResult = PermissionsHelper.getPermissionCheckResult(new QueryInput(table.getName()), table);
|
||||
if(PermissionCheckResult.ALLOW.equals(permissionCheckResult))
|
||||
{
|
||||
return (new QPossibleValue<>(table.getName(), table.getLabel()));
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public List<QPossibleValue<String>> search(SearchPossibleValueSourceInput input) throws QException
|
||||
{
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
// build all of the possible values (note, will be filtered by user's permissions) //
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
List<QPossibleValue<String>> allPossibleValues = new ArrayList<>();
|
||||
for(QTableMetaData table : QContext.getQInstance().getTables().values())
|
||||
{
|
||||
QPossibleValue<String> possibleValue = getPossibleValue(table.getName());
|
||||
if(possibleValue != null)
|
||||
{
|
||||
allPossibleValues.add(possibleValue);
|
||||
}
|
||||
}
|
||||
|
||||
return completeCustomPVSSearch(input, allPossibleValues);
|
||||
}
|
||||
|
||||
}
|
@ -22,17 +22,11 @@
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.tables;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.instances.QInstanceEnricher;
|
||||
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.possiblevalues.PVSValueFormatAndFields;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValue;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSourceType;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import org.apache.commons.lang.BooleanUtils;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -51,22 +45,10 @@ public class TablesPossibleValueSourceMetaDataProvider
|
||||
{
|
||||
QPossibleValueSource possibleValueSource = new QPossibleValueSource()
|
||||
.withName(NAME)
|
||||
.withType(QPossibleValueSourceType.ENUM)
|
||||
.withType(QPossibleValueSourceType.CUSTOM)
|
||||
.withCustomCodeReference(new QCodeReference(TablesCustomPossibleValueProvider.class))
|
||||
.withValueFormatAndFields(PVSValueFormatAndFields.LABEL_ONLY);
|
||||
|
||||
List<QPossibleValue<?>> enumValues = new ArrayList<>();
|
||||
for(QTableMetaData table : qInstance.getTables().values())
|
||||
{
|
||||
if(BooleanUtils.isNotTrue(table.getIsHidden()))
|
||||
{
|
||||
String label = StringUtils.hasContent(table.getLabel()) ? table.getLabel() : QInstanceEnricher.nameToLabel(table.getName());
|
||||
enumValues.add(new QPossibleValue<>(table.getName(), label));
|
||||
}
|
||||
}
|
||||
|
||||
enumValues.sort(Comparator.comparing(QPossibleValue::getLabel));
|
||||
|
||||
possibleValueSource.withEnumValues(enumValues);
|
||||
return (possibleValueSource);
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.variants;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** interface to be implemented by enums (presumably) that define the possible
|
||||
** settings a particular backend type can get from a variant record.
|
||||
*******************************************************************************/
|
||||
public interface BackendVariantSetting
|
||||
{
|
||||
}
|
@ -0,0 +1,225 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.variants;
|
||||
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Configs for how a backend that uses variants works. Specifically:
|
||||
**
|
||||
** - the variant "type key" - e.g., key for variants map in session.
|
||||
** - what table supplies the variant options (optionsTableName
|
||||
** - an optional filter to apply to that options table
|
||||
** - a map of the settings that a backend gets from its variant table to the
|
||||
** field names in that table that they come from. e.g., a backend may have a
|
||||
** username attribute, whose value comes from a field named "theUser" in the
|
||||
** variant options table.
|
||||
** - an optional code reference to a variantRecordLookupFunction - to customize
|
||||
** how the variant record is looked up (such as, adding joined or other custom
|
||||
** fields).
|
||||
*******************************************************************************/
|
||||
public class BackendVariantsConfig
|
||||
{
|
||||
private String variantTypeKey;
|
||||
|
||||
private String optionsTableName;
|
||||
private QQueryFilter optionsFilter;
|
||||
private QCodeReference variantRecordLookupFunction;
|
||||
|
||||
private Map<BackendVariantSetting, String> backendSettingSourceFieldNameMap;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for tableName
|
||||
*******************************************************************************/
|
||||
public String getOptionsTableName()
|
||||
{
|
||||
return (this.optionsTableName);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for tableName
|
||||
*******************************************************************************/
|
||||
public void setOptionsTableName(String optionsTableName)
|
||||
{
|
||||
this.optionsTableName = optionsTableName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for filter
|
||||
*******************************************************************************/
|
||||
public QQueryFilter getOptionsFilter()
|
||||
{
|
||||
return (this.optionsFilter);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for filter
|
||||
*******************************************************************************/
|
||||
public void setOptionsFilter(QQueryFilter optionsFilter)
|
||||
{
|
||||
this.optionsFilter = optionsFilter;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for backendSettingSourceFieldNameMap
|
||||
*******************************************************************************/
|
||||
public Map<BackendVariantSetting, String> getBackendSettingSourceFieldNameMap()
|
||||
{
|
||||
return (this.backendSettingSourceFieldNameMap);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for backendSettingSourceFieldNameMap
|
||||
*******************************************************************************/
|
||||
public void setBackendSettingSourceFieldNameMap(Map<BackendVariantSetting, String> backendSettingSourceFieldNameMap)
|
||||
{
|
||||
this.backendSettingSourceFieldNameMap = backendSettingSourceFieldNameMap;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for backendSettingSourceFieldNameMap
|
||||
*******************************************************************************/
|
||||
public BackendVariantsConfig withBackendSettingSourceFieldName(BackendVariantSetting backendVariantSetting, String sourceFieldName)
|
||||
{
|
||||
if(this.backendSettingSourceFieldNameMap == null)
|
||||
{
|
||||
this.backendSettingSourceFieldNameMap = new HashMap<>();
|
||||
}
|
||||
this.backendSettingSourceFieldNameMap.put(backendVariantSetting, sourceFieldName);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for backendSettingSourceFieldNameMap
|
||||
*******************************************************************************/
|
||||
public BackendVariantsConfig withBackendSettingSourceFieldNameMap(Map<BackendVariantSetting, String> backendSettingSourceFieldNameMap)
|
||||
{
|
||||
this.backendSettingSourceFieldNameMap = backendSettingSourceFieldNameMap;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for variantTypeKey
|
||||
*******************************************************************************/
|
||||
public String getVariantTypeKey()
|
||||
{
|
||||
return (this.variantTypeKey);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for variantTypeKey
|
||||
*******************************************************************************/
|
||||
public void setVariantTypeKey(String variantTypeKey)
|
||||
{
|
||||
this.variantTypeKey = variantTypeKey;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for variantTypeKey
|
||||
*******************************************************************************/
|
||||
public BackendVariantsConfig withVariantTypeKey(String variantTypeKey)
|
||||
{
|
||||
this.variantTypeKey = variantTypeKey;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for optionsTableName
|
||||
*******************************************************************************/
|
||||
public BackendVariantsConfig withOptionsTableName(String optionsTableName)
|
||||
{
|
||||
this.optionsTableName = optionsTableName;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for optionsFilter
|
||||
*******************************************************************************/
|
||||
public BackendVariantsConfig withOptionsFilter(QQueryFilter optionsFilter)
|
||||
{
|
||||
this.optionsFilter = optionsFilter;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for variantRecordLookupFunction
|
||||
*******************************************************************************/
|
||||
public QCodeReference getVariantRecordLookupFunction()
|
||||
{
|
||||
return (this.variantRecordLookupFunction);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for variantRecordLookupFunction
|
||||
*******************************************************************************/
|
||||
public void setVariantRecordLookupFunction(QCodeReference variantRecordLookupFunction)
|
||||
{
|
||||
this.variantRecordLookupFunction = variantRecordLookupFunction;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for variantRecordLookupFunction
|
||||
*******************************************************************************/
|
||||
public BackendVariantsConfig withVariantRecordLookupFunction(QCodeReference variantRecordLookupFunction)
|
||||
{
|
||||
this.variantRecordLookupFunction = variantRecordLookupFunction;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,106 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.variants;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.function.Function;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import com.kingsrook.qqq.backend.core.utils.lambdas.UnsafeFunction;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Utility methods for backends working with Variants.
|
||||
*******************************************************************************/
|
||||
public class BackendVariantsUtil
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
** Get the variant id from the session for the backend.
|
||||
*******************************************************************************/
|
||||
public static Serializable getVariantId(QBackendMetaData backendMetaData) throws QException
|
||||
{
|
||||
QSession session = QContext.getQSession();
|
||||
String variantTypeKey = backendMetaData.getBackendVariantsConfig().getVariantTypeKey();
|
||||
if(session.getBackendVariants() == null || !session.getBackendVariants().containsKey(variantTypeKey))
|
||||
{
|
||||
throw (new QException("Could not find Backend Variant information in session under key '" + variantTypeKey + "' for Backend '" + backendMetaData.getName() + "'"));
|
||||
}
|
||||
Serializable variantId = session.getBackendVariants().get(variantTypeKey);
|
||||
return variantId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** For backends that use variants, look up the variant record (in theory, based
|
||||
** on an id in the session's backend variants map, then fetched from the backend's
|
||||
** variant options table.
|
||||
*******************************************************************************/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static QRecord getVariantRecord(QBackendMetaData backendMetaData) throws QException
|
||||
{
|
||||
Serializable variantId = getVariantId(backendMetaData);
|
||||
|
||||
QRecord record;
|
||||
if(backendMetaData.getBackendVariantsConfig().getVariantRecordLookupFunction() != null)
|
||||
{
|
||||
Object o = QCodeLoader.getAdHoc(Object.class, backendMetaData.getBackendVariantsConfig().getVariantRecordLookupFunction());
|
||||
if(o instanceof UnsafeFunction<?,?,?> unsafeFunction)
|
||||
{
|
||||
record = ((UnsafeFunction<Serializable, QRecord, QException>) unsafeFunction).apply(variantId);
|
||||
}
|
||||
else if(o instanceof Function<?,?> function)
|
||||
{
|
||||
record = ((Function<Serializable, QRecord>) function).apply(variantId);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw (new QException("Backend Variant's recordLookupFunction is not of any expected type (should have been caught by instance validation??)"));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GetInput getInput = new GetInput();
|
||||
getInput.setShouldMaskPasswords(false);
|
||||
getInput.setTableName(backendMetaData.getBackendVariantsConfig().getOptionsTableName());
|
||||
getInput.setPrimaryKey(variantId);
|
||||
GetOutput getOutput = new GetAction().execute(getInput);
|
||||
|
||||
record = getOutput.getRecord();
|
||||
}
|
||||
|
||||
if(record == null)
|
||||
{
|
||||
throw (new QException("Could not find Backend Variant in table " + backendMetaData.getBackendVariantsConfig().getOptionsTableName() + " with id '" + variantId + "'"));
|
||||
}
|
||||
return record;
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.variants;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** temporary class, while we migrate from original way that variants were set up
|
||||
** e.g., by calling 'variantOptionsTableUsernameField', to the new way, using
|
||||
** the BackendVariantsConfig which uses a map of enum constants.
|
||||
**
|
||||
** so when those deprecated setters are removed, this enum can be too.
|
||||
*****************************************************************************/
|
||||
public enum LegacyBackendVariantSetting implements BackendVariantSetting
|
||||
{
|
||||
USERNAME,
|
||||
PASSWORD,
|
||||
API_KEY,
|
||||
CLIENT_ID,
|
||||
CLIENT_SECRET
|
||||
}
|
@ -0,0 +1,230 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.processes;
|
||||
|
||||
|
||||
import java.time.Instant;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QField;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.ValueTooLongBehavior;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** QRecord Entity for QQQProcess table - e.g., table that stores an id, name
|
||||
** and the label for all processes in the QQQ application. Useful as a foreign
|
||||
** key from other logging type tables.
|
||||
*******************************************************************************/
|
||||
public class QQQProcess extends QRecordEntity
|
||||
{
|
||||
public static final String TABLE_NAME = "qqqProcess";
|
||||
|
||||
@QField(isEditable = false)
|
||||
private Integer id;
|
||||
|
||||
@QField(isEditable = false)
|
||||
private Instant createDate;
|
||||
|
||||
@QField(isEditable = false)
|
||||
private Instant modifyDate;
|
||||
|
||||
@QField(maxLength = 250, valueTooLongBehavior = ValueTooLongBehavior.ERROR)
|
||||
private String name;
|
||||
|
||||
@QField(maxLength = 250, valueTooLongBehavior = ValueTooLongBehavior.TRUNCATE_ELLIPSIS)
|
||||
private String label;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Default constructor
|
||||
*******************************************************************************/
|
||||
public QQQProcess()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor that takes a QRecord
|
||||
*******************************************************************************/
|
||||
public QQQProcess(QRecord record)
|
||||
{
|
||||
populateFromQRecord(record);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for id
|
||||
*******************************************************************************/
|
||||
public Integer getId()
|
||||
{
|
||||
return (this.id);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for id
|
||||
*******************************************************************************/
|
||||
public void setId(Integer id)
|
||||
{
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for id
|
||||
*******************************************************************************/
|
||||
public QQQProcess withId(Integer id)
|
||||
{
|
||||
this.id = id;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for createDate
|
||||
*******************************************************************************/
|
||||
public Instant getCreateDate()
|
||||
{
|
||||
return (this.createDate);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for createDate
|
||||
*******************************************************************************/
|
||||
public void setCreateDate(Instant createDate)
|
||||
{
|
||||
this.createDate = createDate;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for createDate
|
||||
*******************************************************************************/
|
||||
public QQQProcess withCreateDate(Instant createDate)
|
||||
{
|
||||
this.createDate = createDate;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for modifyDate
|
||||
*******************************************************************************/
|
||||
public Instant getModifyDate()
|
||||
{
|
||||
return (this.modifyDate);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for modifyDate
|
||||
*******************************************************************************/
|
||||
public void setModifyDate(Instant modifyDate)
|
||||
{
|
||||
this.modifyDate = modifyDate;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for modifyDate
|
||||
*******************************************************************************/
|
||||
public QQQProcess withModifyDate(Instant modifyDate)
|
||||
{
|
||||
this.modifyDate = modifyDate;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for name
|
||||
*******************************************************************************/
|
||||
public String getName()
|
||||
{
|
||||
return (this.name);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for name
|
||||
*******************************************************************************/
|
||||
public void setName(String name)
|
||||
{
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for name
|
||||
*******************************************************************************/
|
||||
public QQQProcess withName(String name)
|
||||
{
|
||||
this.name = name;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for label
|
||||
*******************************************************************************/
|
||||
public String getLabel()
|
||||
{
|
||||
return (this.label);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for label
|
||||
*******************************************************************************/
|
||||
public void setLabel(String label)
|
||||
{
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for label
|
||||
*******************************************************************************/
|
||||
public QQQProcess withLabel(String label)
|
||||
{
|
||||
this.label = label;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.processes;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertOutput;
|
||||
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.processes.QProcessMetaData;
|
||||
import com.kingsrook.qqq.backend.core.utils.collections.MapBuilder;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Utility class for accessing QQQProcess records (well, just their ids at this time)
|
||||
** Takes care of inserting upon a miss, and dealing with the cache table.
|
||||
*******************************************************************************/
|
||||
public class QQQProcessTableManager
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(QQQProcessTableManager.class);
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static Integer getQQQProcessId(QInstance qInstance, String processName) throws QException
|
||||
{
|
||||
/////////////////////////////
|
||||
// look in the cache table //
|
||||
/////////////////////////////
|
||||
GetInput getInput = new GetInput();
|
||||
getInput.setTableName(QQQProcessesMetaDataProvider.QQQ_PROCESS_CACHE_TABLE_NAME);
|
||||
getInput.setUniqueKey(MapBuilder.of("name", processName));
|
||||
GetOutput getOutput = new GetAction().execute(getInput);
|
||||
|
||||
////////////////////////
|
||||
// upon cache miss... //
|
||||
////////////////////////
|
||||
if(getOutput.getRecord() == null)
|
||||
{
|
||||
QProcessMetaData processMetaData = qInstance.getProcess(processName);
|
||||
if(processMetaData == null)
|
||||
{
|
||||
LOG.info("No such process", logPair("processName", processName));
|
||||
return (null);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////
|
||||
// insert the record (into the table, not the cache) //
|
||||
///////////////////////////////////////////////////////
|
||||
InsertInput insertInput = new InsertInput();
|
||||
insertInput.setTableName(QQQProcess.TABLE_NAME);
|
||||
insertInput.setRecords(List.of(new QRecord().withValue("name", processName).withValue("label", processMetaData.getLabel())));
|
||||
InsertOutput insertOutput = new InsertAction().execute(insertInput);
|
||||
|
||||
///////////////////////////////////
|
||||
// repeat the get from the cache //
|
||||
///////////////////////////////////
|
||||
getOutput = new GetAction().execute(getInput);
|
||||
}
|
||||
|
||||
return getOutput.getRecord().getValueInteger("id");
|
||||
}
|
||||
}
|
@ -0,0 +1,134 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.processes;
|
||||
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.audits.AuditLevel;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.audits.QAuditRules;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSourceType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.Capability;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.UniqueKey;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.cache.CacheOf;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.cache.CacheUseCase;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Provides meta data for the QQQProcess table, PVS, and a cache table.
|
||||
*******************************************************************************/
|
||||
public class QQQProcessesMetaDataProvider
|
||||
{
|
||||
public static final String QQQ_PROCESS_CACHE_TABLE_NAME = QQQProcess.TABLE_NAME + "Cache";
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void defineAll(QInstance instance, String persistentBackendName, String cacheBackendName, Consumer<QTableMetaData> backendDetailEnricher) throws QException
|
||||
{
|
||||
instance.addTable(defineQQQProcess(persistentBackendName, backendDetailEnricher));
|
||||
instance.addTable(defineQQQProcessCache(cacheBackendName, backendDetailEnricher));
|
||||
instance.addPossibleValueSource(defineQQQProcessPossibleValueSource());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QTableMetaData defineQQQProcess(String backendName, Consumer<QTableMetaData> backendDetailEnricher) throws QException
|
||||
{
|
||||
QTableMetaData table = new QTableMetaData()
|
||||
.withName(QQQProcess.TABLE_NAME)
|
||||
.withLabel("Process")
|
||||
.withBackendName(backendName)
|
||||
.withAuditRules(new QAuditRules().withAuditLevel(AuditLevel.NONE))
|
||||
.withRecordLabelFormat("%s")
|
||||
.withRecordLabelFields("label")
|
||||
.withPrimaryKeyField("id")
|
||||
.withUniqueKey(new UniqueKey("name"))
|
||||
.withFieldsFromEntity(QQQProcess.class)
|
||||
.withoutCapabilities(Capability.TABLE_INSERT, Capability.TABLE_UPDATE, Capability.TABLE_DELETE);
|
||||
|
||||
if(backendDetailEnricher != null)
|
||||
{
|
||||
backendDetailEnricher.accept(table);
|
||||
}
|
||||
|
||||
return (table);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QTableMetaData defineQQQProcessCache(String backendName, Consumer<QTableMetaData> backendDetailEnricher) throws QException
|
||||
{
|
||||
QTableMetaData table = new QTableMetaData()
|
||||
.withName(QQQ_PROCESS_CACHE_TABLE_NAME)
|
||||
.withBackendName(backendName)
|
||||
.withAuditRules(new QAuditRules().withAuditLevel(AuditLevel.NONE))
|
||||
.withRecordLabelFormat("%s")
|
||||
.withRecordLabelFields("label")
|
||||
.withPrimaryKeyField("id")
|
||||
.withUniqueKey(new UniqueKey("name"))
|
||||
.withFieldsFromEntity(QQQProcess.class)
|
||||
.withCacheOf(new CacheOf()
|
||||
.withSourceTable(QQQProcess.TABLE_NAME)
|
||||
.withUseCase(new CacheUseCase()
|
||||
.withType(CacheUseCase.Type.UNIQUE_KEY_TO_UNIQUE_KEY)
|
||||
.withCacheSourceMisses(false)
|
||||
.withCacheUniqueKey(new UniqueKey("name"))
|
||||
.withSourceUniqueKey(new UniqueKey("name"))
|
||||
.withDoCopySourcePrimaryKeyToCache(true)
|
||||
)
|
||||
);
|
||||
|
||||
if(backendDetailEnricher != null)
|
||||
{
|
||||
backendDetailEnricher.accept(table);
|
||||
}
|
||||
|
||||
return (table);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QPossibleValueSource defineQQQProcessPossibleValueSource()
|
||||
{
|
||||
return (new QPossibleValueSource()
|
||||
.withType(QPossibleValueSourceType.TABLE)
|
||||
.withName(QQQProcess.TABLE_NAME)
|
||||
.withTableName(QQQProcess.TABLE_NAME))
|
||||
.withOrderByField("label");
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,285 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.savedbulkloadprofiles;
|
||||
|
||||
|
||||
import java.time.Instant;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QField;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.DynamicDefaultValueBehavior;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.ValueTooLongBehavior;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.TablesPossibleValueSourceMetaDataProvider;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Entity bean for the savedBulkLoadProfile table
|
||||
*******************************************************************************/
|
||||
public class SavedBulkLoadProfile extends QRecordEntity
|
||||
{
|
||||
public static final String TABLE_NAME = "savedBulkLoadProfile";
|
||||
|
||||
@QField(isEditable = false)
|
||||
private Integer id;
|
||||
|
||||
@QField(isEditable = false)
|
||||
private Instant createDate;
|
||||
|
||||
@QField(isEditable = false)
|
||||
private Instant modifyDate;
|
||||
|
||||
@QField(isRequired = true, maxLength = 250, valueTooLongBehavior = ValueTooLongBehavior.TRUNCATE_ELLIPSIS, label = "Profile Name")
|
||||
private String label;
|
||||
|
||||
@QField(possibleValueSourceName = TablesPossibleValueSourceMetaDataProvider.NAME, maxLength = 250, valueTooLongBehavior = ValueTooLongBehavior.ERROR, label = "Table", isRequired = true)
|
||||
private String tableName;
|
||||
|
||||
@QField(maxLength = 250, valueTooLongBehavior = ValueTooLongBehavior.ERROR, dynamicDefaultValueBehavior = DynamicDefaultValueBehavior.USER_ID, label = "Owner")
|
||||
private String userId;
|
||||
|
||||
@QField(label = "Mapping JSON")
|
||||
private String mappingJson;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public SavedBulkLoadProfile()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public SavedBulkLoadProfile(QRecord qRecord) throws QException
|
||||
{
|
||||
populateFromQRecord(qRecord);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for id
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Integer getId()
|
||||
{
|
||||
return id;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for id
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setId(Integer id)
|
||||
{
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for createDate
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Instant getCreateDate()
|
||||
{
|
||||
return createDate;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for createDate
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setCreateDate(Instant createDate)
|
||||
{
|
||||
this.createDate = createDate;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for modifyDate
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Instant getModifyDate()
|
||||
{
|
||||
return modifyDate;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for modifyDate
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setModifyDate(Instant modifyDate)
|
||||
{
|
||||
this.modifyDate = modifyDate;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for label
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getLabel()
|
||||
{
|
||||
return label;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for label
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setLabel(String label)
|
||||
{
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for label
|
||||
**
|
||||
*******************************************************************************/
|
||||
public SavedBulkLoadProfile withLabel(String label)
|
||||
{
|
||||
this.label = label;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for tableName
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getTableName()
|
||||
{
|
||||
return tableName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for tableName
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setTableName(String tableName)
|
||||
{
|
||||
this.tableName = tableName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for tableName
|
||||
**
|
||||
*******************************************************************************/
|
||||
public SavedBulkLoadProfile withTableName(String tableName)
|
||||
{
|
||||
this.tableName = tableName;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for userId
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getUserId()
|
||||
{
|
||||
return userId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for userId
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setUserId(String userId)
|
||||
{
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for userId
|
||||
**
|
||||
*******************************************************************************/
|
||||
public SavedBulkLoadProfile withUserId(String userId)
|
||||
{
|
||||
this.userId = userId;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for mappingJson
|
||||
*******************************************************************************/
|
||||
public String getMappingJson()
|
||||
{
|
||||
return (this.mappingJson);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for mappingJson
|
||||
*******************************************************************************/
|
||||
public void setMappingJson(String mappingJson)
|
||||
{
|
||||
this.mappingJson = mappingJson;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for mappingJson
|
||||
*******************************************************************************/
|
||||
public SavedBulkLoadProfile withMappingJson(String mappingJson)
|
||||
{
|
||||
this.mappingJson = mappingJson;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,141 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.savedbulkloadprofiles;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.actions.values.ValueBehaviorApplier;
|
||||
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.FieldDisplayBehavior;
|
||||
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.core.utils.StringUtils;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class SavedBulkLoadProfileJsonFieldDisplayValueFormatter implements FieldDisplayBehavior<SavedBulkLoadProfileJsonFieldDisplayValueFormatter>
|
||||
{
|
||||
private static SavedBulkLoadProfileJsonFieldDisplayValueFormatter savedReportJsonFieldDisplayValueFormatter = null;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Singleton constructor
|
||||
*******************************************************************************/
|
||||
private SavedBulkLoadProfileJsonFieldDisplayValueFormatter()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Singleton accessor
|
||||
*******************************************************************************/
|
||||
public static SavedBulkLoadProfileJsonFieldDisplayValueFormatter getInstance()
|
||||
{
|
||||
if(savedReportJsonFieldDisplayValueFormatter == null)
|
||||
{
|
||||
savedReportJsonFieldDisplayValueFormatter = new SavedBulkLoadProfileJsonFieldDisplayValueFormatter();
|
||||
}
|
||||
return (savedReportJsonFieldDisplayValueFormatter);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public SavedBulkLoadProfileJsonFieldDisplayValueFormatter getDefault()
|
||||
{
|
||||
return getInstance();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void apply(ValueBehaviorApplier.Action action, List<QRecord> recordList, QInstance instance, QTableMetaData table, QFieldMetaData field)
|
||||
{
|
||||
for(QRecord record : CollectionUtils.nonNullList(recordList))
|
||||
{
|
||||
if(field.getName().equals("mappingJson"))
|
||||
{
|
||||
String mappingJson = record.getValueString("mappingJson");
|
||||
if(StringUtils.hasContent(mappingJson))
|
||||
{
|
||||
try
|
||||
{
|
||||
record.setDisplayValue("mappingJson", jsonToDisplayValue(mappingJson));
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
record.setDisplayValue("mappingJson", "Invalid Mapping...");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
private String jsonToDisplayValue(String mappingJson)
|
||||
{
|
||||
JSONObject jsonObject = new JSONObject(mappingJson);
|
||||
|
||||
List<String> parts = new ArrayList<>();
|
||||
|
||||
if(jsonObject.has("fieldList"))
|
||||
{
|
||||
JSONArray fieldListArray = jsonObject.getJSONArray("fieldList");
|
||||
parts.add(fieldListArray.length() + " field" + StringUtils.plural(fieldListArray.length()));
|
||||
}
|
||||
|
||||
if(jsonObject.has("hasHeaderRow"))
|
||||
{
|
||||
boolean hasHeaderRow = jsonObject.getBoolean("hasHeaderRow");
|
||||
parts.add((hasHeaderRow ? "With" : "Without") + " header row");
|
||||
}
|
||||
|
||||
if(jsonObject.has("layout"))
|
||||
{
|
||||
String layout = jsonObject.getString("layout");
|
||||
parts.add("Layout: " + StringUtils.allCapsToMixedCase(layout));
|
||||
}
|
||||
|
||||
return StringUtils.join("; ", parts);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,168 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.savedbulkloadprofiles;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.audits.AuditLevel;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.audits.QAuditRules;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinOn;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.sharing.ShareScopePossibleValueMetaDataProducer;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.sharing.ShareableAudienceType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.sharing.ShareableTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QFieldSection;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.Tier;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.UniqueKey;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.savedbulkloadprofiles.DeleteSavedBulkLoadProfileProcess;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.savedbulkloadprofiles.QuerySavedBulkLoadProfileProcess;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.savedbulkloadprofiles.StoreSavedBulkLoadProfileProcess;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class SavedBulkLoadProfileMetaDataProvider
|
||||
{
|
||||
public static final String SHARED_SAVED_BULK_LOAD_PROFILE_JOIN_SAVED_BULK_LOAD_PROFILE = "sharedSavedBulkLoadProfileJoinSavedBulkLoadProfile";
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void defineAll(QInstance instance, String recordTablesBackendName, Consumer<QTableMetaData> backendDetailEnricher) throws QException
|
||||
{
|
||||
instance.addTable(defineSavedBulkLoadProfileTable(recordTablesBackendName, backendDetailEnricher));
|
||||
instance.addPossibleValueSource(QPossibleValueSource.newForTable(SavedBulkLoadProfile.TABLE_NAME));
|
||||
|
||||
/////////////////////////////////////
|
||||
// todo - param to enable sharing? //
|
||||
/////////////////////////////////////
|
||||
instance.addTable(defineSharedSavedBulkLoadProfileTable(recordTablesBackendName, backendDetailEnricher));
|
||||
instance.addJoin(defineSharedSavedBulkLoadProfileJoinSavedBulkLoadProfile());
|
||||
if(instance.getPossibleValueSource(ShareScopePossibleValueMetaDataProducer.NAME) == null)
|
||||
{
|
||||
instance.addPossibleValueSource(new ShareScopePossibleValueMetaDataProducer().produce(new QInstance()));
|
||||
}
|
||||
|
||||
////////////////////////////////////
|
||||
// processes for working with 'em //
|
||||
////////////////////////////////////
|
||||
instance.add(StoreSavedBulkLoadProfileProcess.getProcessMetaData());
|
||||
instance.add(QuerySavedBulkLoadProfileProcess.getProcessMetaData());
|
||||
instance.add(DeleteSavedBulkLoadProfileProcess.getProcessMetaData());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private QJoinMetaData defineSharedSavedBulkLoadProfileJoinSavedBulkLoadProfile()
|
||||
{
|
||||
return (new QJoinMetaData()
|
||||
.withName(SHARED_SAVED_BULK_LOAD_PROFILE_JOIN_SAVED_BULK_LOAD_PROFILE)
|
||||
.withLeftTable(SharedSavedBulkLoadProfile.TABLE_NAME)
|
||||
.withRightTable(SavedBulkLoadProfile.TABLE_NAME)
|
||||
.withType(JoinType.MANY_TO_ONE)
|
||||
.withJoinOn(new JoinOn("savedBulkLoadProfileId", "id")));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QTableMetaData defineSavedBulkLoadProfileTable(String backendName, Consumer<QTableMetaData> backendDetailEnricher) throws QException
|
||||
{
|
||||
QTableMetaData table = new QTableMetaData()
|
||||
.withName(SavedBulkLoadProfile.TABLE_NAME)
|
||||
.withLabel("Bulk Load Profile")
|
||||
.withIcon(new QIcon().withName("drive_folder_upload"))
|
||||
.withRecordLabelFormat("%s")
|
||||
.withRecordLabelFields("label")
|
||||
.withBackendName(backendName)
|
||||
.withPrimaryKeyField("id")
|
||||
.withFieldsFromEntity(SavedBulkLoadProfile.class)
|
||||
.withAuditRules(new QAuditRules().withAuditLevel(AuditLevel.FIELD))
|
||||
.withSection(new QFieldSection("identity", new QIcon().withName("badge"), Tier.T1, List.of("id", "label", "tableName")))
|
||||
.withSection(new QFieldSection("details", new QIcon().withName("text_snippet"), Tier.T2, List.of("userId", "mappingJson")))
|
||||
.withSection(new QFieldSection("dates", new QIcon().withName("calendar_month"), Tier.T3, List.of("createDate", "modifyDate")));
|
||||
|
||||
table.getField("mappingJson").withBehavior(SavedBulkLoadProfileJsonFieldDisplayValueFormatter.getInstance());
|
||||
table.getField("mappingJson").setLabel("Mapping");
|
||||
|
||||
table.withShareableTableMetaData(new ShareableTableMetaData()
|
||||
.withSharedRecordTableName(SharedSavedBulkLoadProfile.TABLE_NAME)
|
||||
.withAssetIdFieldName("savedBulkLoadProfileId")
|
||||
.withScopeFieldName("scope")
|
||||
.withThisTableOwnerIdFieldName("userId")
|
||||
.withAudienceType(new ShareableAudienceType().withName("user").withFieldName("userId")));
|
||||
|
||||
if(backendDetailEnricher != null)
|
||||
{
|
||||
backendDetailEnricher.accept(table);
|
||||
}
|
||||
|
||||
return (table);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QTableMetaData defineSharedSavedBulkLoadProfileTable(String backendName, Consumer<QTableMetaData> backendDetailEnricher) throws QException
|
||||
{
|
||||
QTableMetaData table = new QTableMetaData()
|
||||
.withName(SharedSavedBulkLoadProfile.TABLE_NAME)
|
||||
.withLabel("Shared Bulk Load Profile")
|
||||
.withIcon(new QIcon().withName("share"))
|
||||
.withRecordLabelFormat("%s")
|
||||
.withRecordLabelFields("savedBulkLoadProfileId")
|
||||
.withBackendName(backendName)
|
||||
.withUniqueKey(new UniqueKey("savedBulkLoadProfileId", "userId"))
|
||||
.withPrimaryKeyField("id")
|
||||
.withFieldsFromEntity(SharedSavedBulkLoadProfile.class)
|
||||
// todo - security key
|
||||
.withAuditRules(new QAuditRules().withAuditLevel(AuditLevel.FIELD))
|
||||
.withSection(new QFieldSection("identity", new QIcon().withName("badge"), Tier.T1, List.of("id", "savedBulkLoadProfileId", "userId")))
|
||||
.withSection(new QFieldSection("data", new QIcon().withName("text_snippet"), Tier.T2, List.of("scope")))
|
||||
.withSection(new QFieldSection("dates", new QIcon().withName("calendar_month"), Tier.T3, List.of("createDate", "modifyDate")));
|
||||
|
||||
if(backendDetailEnricher != null)
|
||||
{
|
||||
backendDetailEnricher.accept(table);
|
||||
}
|
||||
|
||||
return (table);
|
||||
}
|
||||
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user