mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-22 06:58:45 +00:00
Compare commits
3 Commits
snapshot-f
...
feature/re
Author | SHA1 | Date | |
---|---|---|---|
af4cce4639 | |||
a339e166ce | |||
9ee2e4d4d7 |
@ -48,7 +48,11 @@ commands:
|
|||||||
- run:
|
- run:
|
||||||
name: Run Maven Verify
|
name: Run Maven Verify
|
||||||
command: |
|
command: |
|
||||||
mvn -s .circleci/mvn-settings.xml -T4 verify
|
mvn -s .circleci/mvn-settings.xml -T4 verify | tee mvn-verify.log
|
||||||
|
- store_artifacts:
|
||||||
|
name: Full Maven Verify Log
|
||||||
|
path: mvn-verify.log
|
||||||
|
destination: mvn-verify
|
||||||
- store_jacoco_site:
|
- store_jacoco_site:
|
||||||
module: qqq-backend-core
|
module: qqq-backend-core
|
||||||
- store_jacoco_site:
|
- store_jacoco_site:
|
||||||
|
@ -1,174 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.fields;
|
|
||||||
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.function.Function;
|
|
||||||
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.utils.CollectionUtils;
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Field behavior that changes the whitespace of string values.
|
|
||||||
*******************************************************************************/
|
|
||||||
public enum WhiteSpaceBehavior implements FieldBehavior<WhiteSpaceBehavior>, FieldBehaviorForFrontend, FieldFilterBehavior<WhiteSpaceBehavior>
|
|
||||||
{
|
|
||||||
NONE(null),
|
|
||||||
REMOVE_ALL_WHITESPACE((String s) -> s.chars().filter(c -> !Character.isWhitespace(c)).collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append).toString()),
|
|
||||||
TRIM((String s) -> s.trim()),
|
|
||||||
TRIM_LEFT((String s) -> s.stripLeading()),
|
|
||||||
TRIM_RIGHT((String s) -> s.stripTrailing());
|
|
||||||
|
|
||||||
|
|
||||||
private final Function<String, String> function;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
WhiteSpaceBehavior(Function<String, String> function)
|
|
||||||
{
|
|
||||||
this.function = function;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
@Override
|
|
||||||
public WhiteSpaceBehavior getDefault()
|
|
||||||
{
|
|
||||||
return (NONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
@Override
|
|
||||||
public void apply(ValueBehaviorApplier.Action action, List<QRecord> recordList, QInstance instance, QTableMetaData table, QFieldMetaData field)
|
|
||||||
{
|
|
||||||
if(this.equals(NONE))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch(this)
|
|
||||||
{
|
|
||||||
case REMOVE_ALL_WHITESPACE, TRIM, TRIM_LEFT, TRIM_RIGHT -> applyFunction(recordList, table, field);
|
|
||||||
default -> throw new IllegalStateException("Unexpected enum value: " + this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
private void applyFunction(List<QRecord> recordList, QTableMetaData table, QFieldMetaData field)
|
|
||||||
{
|
|
||||||
String fieldName = field.getName();
|
|
||||||
for(QRecord record : CollectionUtils.nonNullList(recordList))
|
|
||||||
{
|
|
||||||
String value = record.getValueString(fieldName);
|
|
||||||
if(value != null && function != null)
|
|
||||||
{
|
|
||||||
record.setValue(fieldName, function.apply(value));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
@Override
|
|
||||||
public Serializable applyToFilterCriteriaValue(Serializable value, QInstance instance, QTableMetaData table, QFieldMetaData field)
|
|
||||||
{
|
|
||||||
if(this.equals(NONE) || function == null)
|
|
||||||
{
|
|
||||||
return (value);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(value instanceof String s)
|
|
||||||
{
|
|
||||||
String newValue = function.apply(s);
|
|
||||||
if(!Objects.equals(value, newValue))
|
|
||||||
{
|
|
||||||
return (newValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (value);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
@Override
|
|
||||||
public boolean allowMultipleBehaviorsOfThisType()
|
|
||||||
{
|
|
||||||
return (false);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
@Override
|
|
||||||
public List<String> validateBehaviorConfiguration(QTableMetaData tableMetaData, QFieldMetaData fieldMetaData)
|
|
||||||
{
|
|
||||||
if(this == NONE)
|
|
||||||
{
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
|
|
||||||
List<String> errors = new ArrayList<>();
|
|
||||||
String errorSuffix = " field [" + fieldMetaData.getName() + "] in table [" + tableMetaData.getName() + "]";
|
|
||||||
|
|
||||||
if(fieldMetaData.getType() != null)
|
|
||||||
{
|
|
||||||
if(!fieldMetaData.getType().isStringLike())
|
|
||||||
{
|
|
||||||
errors.add("A WhiteSpaceBehavior was a applied to a non-String-like field:" + errorSuffix);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (errors);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,280 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.fields;
|
|
||||||
|
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import com.kingsrook.qqq.backend.core.BaseTest;
|
|
||||||
import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
|
|
||||||
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
|
||||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
|
||||||
import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction;
|
|
||||||
import com.kingsrook.qqq.backend.core.actions.values.ValueBehaviorApplier;
|
|
||||||
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.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.model.metadata.QInstance;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
|
||||||
import com.kingsrook.qqq.backend.core.utils.TestUtils;
|
|
||||||
import com.kingsrook.qqq.backend.core.utils.collections.ListBuilder;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Unit test for WhiteSpaceBehavior
|
|
||||||
*******************************************************************************/
|
|
||||||
class WhiteSpaceBehaviorTest extends BaseTest
|
|
||||||
{
|
|
||||||
public static final String FIELD = "firstName";
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
@Test
|
|
||||||
void testNone()
|
|
||||||
{
|
|
||||||
assertNull(applyToRecord(WhiteSpaceBehavior.NONE, new QRecord(), ValueBehaviorApplier.Action.INSERT).getValue(FIELD));
|
|
||||||
assertNull(applyToRecord(WhiteSpaceBehavior.NONE, new QRecord().withValue(FIELD, null), ValueBehaviorApplier.Action.INSERT).getValue(FIELD));
|
|
||||||
assertEquals("John", applyToRecord(WhiteSpaceBehavior.NONE, new QRecord().withValue(FIELD, "John"), ValueBehaviorApplier.Action.INSERT).getValue(FIELD));
|
|
||||||
|
|
||||||
assertEquals(ListBuilder.of("J. ohn", null, "Jane\n"), applyToRecords(WhiteSpaceBehavior.NONE, List.of(
|
|
||||||
new QRecord().withValue(FIELD, "J. ohn"),
|
|
||||||
new QRecord(),
|
|
||||||
new QRecord().withValue(FIELD, "Jane\n")),
|
|
||||||
ValueBehaviorApplier.Action.INSERT).stream().map(r -> r.getValueString(FIELD)).toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
@Test
|
|
||||||
void testRemoveWhiteSpace()
|
|
||||||
{
|
|
||||||
assertNull(applyToRecord(WhiteSpaceBehavior.REMOVE_ALL_WHITESPACE, new QRecord(), ValueBehaviorApplier.Action.INSERT).getValue(FIELD));
|
|
||||||
assertNull(applyToRecord(WhiteSpaceBehavior.REMOVE_ALL_WHITESPACE, new QRecord().withValue(FIELD, null), ValueBehaviorApplier.Action.INSERT).getValue(FIELD));
|
|
||||||
assertEquals("doobeedoobeedoo", applyToRecord(WhiteSpaceBehavior.REMOVE_ALL_WHITESPACE, new QRecord().withValue(FIELD, "doo bee doo\n bee doo"), ValueBehaviorApplier.Action.INSERT).getValue(FIELD));
|
|
||||||
|
|
||||||
assertEquals(ListBuilder.of("thisistheway", null, "thatwastheway"), applyToRecords(WhiteSpaceBehavior.REMOVE_ALL_WHITESPACE, List.of(
|
|
||||||
new QRecord().withValue(FIELD, "this is\rthe way \t"),
|
|
||||||
new QRecord(),
|
|
||||||
new QRecord().withValue(FIELD, "that was the way\n")),
|
|
||||||
ValueBehaviorApplier.Action.INSERT).stream().map(r -> r.getValueString(FIELD)).toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
@Test
|
|
||||||
void testTrimWhiteSpace()
|
|
||||||
{
|
|
||||||
assertNull(applyToRecord(WhiteSpaceBehavior.TRIM, new QRecord(), ValueBehaviorApplier.Action.INSERT).getValue(FIELD));
|
|
||||||
assertNull(applyToRecord(WhiteSpaceBehavior.TRIM, new QRecord().withValue(FIELD, null), ValueBehaviorApplier.Action.INSERT).getValue(FIELD));
|
|
||||||
assertEquals("doo bee doo\n bee doo", applyToRecord(WhiteSpaceBehavior.TRIM, new QRecord().withValue(FIELD, " doo bee doo\n bee doo\r \n\n"), ValueBehaviorApplier.Action.INSERT).getValue(FIELD));
|
|
||||||
|
|
||||||
assertEquals(ListBuilder.of("this is\rthe way", null, "that was the way"), applyToRecords(WhiteSpaceBehavior.TRIM, List.of(
|
|
||||||
new QRecord().withValue(FIELD, " this is\rthe way \t"),
|
|
||||||
new QRecord(),
|
|
||||||
new QRecord().withValue(FIELD, "that was the way\n")),
|
|
||||||
ValueBehaviorApplier.Action.INSERT).stream().map(r -> r.getValueString(FIELD)).toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
@Test
|
|
||||||
void testTrimLeftWhiteSpace()
|
|
||||||
{
|
|
||||||
assertNull(applyToRecord(WhiteSpaceBehavior.TRIM_LEFT, new QRecord(), ValueBehaviorApplier.Action.INSERT).getValue(FIELD));
|
|
||||||
assertNull(applyToRecord(WhiteSpaceBehavior.TRIM_LEFT, new QRecord().withValue(FIELD, null), ValueBehaviorApplier.Action.INSERT).getValue(FIELD));
|
|
||||||
assertEquals("doo bee doo\n bee doo\r \n\n", applyToRecord(WhiteSpaceBehavior.TRIM_LEFT, new QRecord().withValue(FIELD, " doo bee doo\n bee doo\r \n\n"), ValueBehaviorApplier.Action.INSERT).getValue(FIELD));
|
|
||||||
|
|
||||||
assertEquals(ListBuilder.of("this is\rthe way \t", null, "that was the way\n"), applyToRecords(WhiteSpaceBehavior.TRIM_LEFT, List.of(
|
|
||||||
new QRecord().withValue(FIELD, " this is\rthe way \t"),
|
|
||||||
new QRecord(),
|
|
||||||
new QRecord().withValue(FIELD, " \n that was the way\n")),
|
|
||||||
ValueBehaviorApplier.Action.INSERT).stream().map(r -> r.getValueString(FIELD)).toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
@Test
|
|
||||||
void testTrimRightWhiteSpace()
|
|
||||||
{
|
|
||||||
assertNull(applyToRecord(WhiteSpaceBehavior.TRIM_RIGHT, new QRecord(), ValueBehaviorApplier.Action.INSERT).getValue(FIELD));
|
|
||||||
assertNull(applyToRecord(WhiteSpaceBehavior.TRIM_RIGHT, new QRecord().withValue(FIELD, null), ValueBehaviorApplier.Action.INSERT).getValue(FIELD));
|
|
||||||
assertEquals(" doo bee doo\n bee doo", applyToRecord(WhiteSpaceBehavior.TRIM_RIGHT, new QRecord().withValue(FIELD, " doo bee doo\n bee doo\r \n\n"), ValueBehaviorApplier.Action.INSERT).getValue(FIELD));
|
|
||||||
|
|
||||||
assertEquals(ListBuilder.of(" this is\rthe way", null, " \n that was the way"), applyToRecords(WhiteSpaceBehavior.TRIM_RIGHT, List.of(
|
|
||||||
new QRecord().withValue(FIELD, " this is\rthe way \t"),
|
|
||||||
new QRecord(),
|
|
||||||
new QRecord().withValue(FIELD, " \n that was the way\n")),
|
|
||||||
ValueBehaviorApplier.Action.INSERT).stream().map(r -> r.getValueString(FIELD)).toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
private QRecord applyToRecord(WhiteSpaceBehavior behavior, QRecord record, ValueBehaviorApplier.Action action)
|
|
||||||
{
|
|
||||||
return (applyToRecords(behavior, List.of(record), action).get(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
private List<QRecord> applyToRecords(WhiteSpaceBehavior behavior, List<QRecord> records, ValueBehaviorApplier.Action action)
|
|
||||||
{
|
|
||||||
QTableMetaData table = QContext.getQInstance().getTable(TestUtils.TABLE_NAME_PERSON_MEMORY);
|
|
||||||
behavior.apply(action, records, QContext.getQInstance(), table, table.getField(FIELD));
|
|
||||||
return (records);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
@Test
|
|
||||||
void testReads() throws QException
|
|
||||||
{
|
|
||||||
TestUtils.insertDefaultShapes(QContext.getQInstance());
|
|
||||||
|
|
||||||
List<QRecord> records = QueryAction.execute(TestUtils.TABLE_NAME_SHAPE, null);
|
|
||||||
assertEquals(Set.of("Triangle", "Square", "Circle"), records.stream().map(r -> r.getValueString("name")).collect(Collectors.toSet()));
|
|
||||||
|
|
||||||
QFieldMetaData field = QContext.getQInstance().getTable(TestUtils.TABLE_NAME_SHAPE).getField("name");
|
|
||||||
field.setBehaviors(Set.of(CaseChangeBehavior.TO_UPPER_CASE));
|
|
||||||
|
|
||||||
records = QueryAction.execute(TestUtils.TABLE_NAME_SHAPE, null);
|
|
||||||
assertEquals(Set.of("TRIANGLE", "SQUARE", "CIRCLE"), records.stream().map(r -> r.getValueString("name")).collect(Collectors.toSet()));
|
|
||||||
|
|
||||||
field.setBehaviors(Set.of(CaseChangeBehavior.TO_LOWER_CASE));
|
|
||||||
assertEquals("triangle", GetAction.execute(TestUtils.TABLE_NAME_SHAPE, 1).getValueString("name"));
|
|
||||||
|
|
||||||
field.setBehaviors(Set.of(CaseChangeBehavior.NONE));
|
|
||||||
assertEquals("Triangle", GetAction.execute(TestUtils.TABLE_NAME_SHAPE, 1).getValueString("name"));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
@Test
|
|
||||||
void testWrites() throws QException
|
|
||||||
{
|
|
||||||
Integer id = 100;
|
|
||||||
|
|
||||||
QFieldMetaData field = QContext.getQInstance().getTable(TestUtils.TABLE_NAME_SHAPE).getField("name");
|
|
||||||
field.setBehaviors(Set.of(CaseChangeBehavior.TO_UPPER_CASE));
|
|
||||||
new InsertAction().execute(new InsertInput(TestUtils.TABLE_NAME_SHAPE).withRecord(new QRecord().withValue("id", id).withValue("name", "Octagon")));
|
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// turn off the to-upper-case behavior, so we'll see what was actually inserted //
|
|
||||||
//////////////////////////////////////////////////////////////////////////////////
|
|
||||||
field.setBehaviors(Collections.emptySet());
|
|
||||||
assertEquals("OCTAGON", GetAction.execute(TestUtils.TABLE_NAME_SHAPE, id).getValueString("name"));
|
|
||||||
|
|
||||||
////////////////////////////////////////////
|
|
||||||
// change to toLowerCase and do an update //
|
|
||||||
////////////////////////////////////////////
|
|
||||||
field.setBehaviors(Set.of(CaseChangeBehavior.TO_LOWER_CASE));
|
|
||||||
new UpdateAction().execute(new UpdateInput(TestUtils.TABLE_NAME_SHAPE).withRecord(new QRecord().withValue("id", id).withValue("name", "Octagon")));
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// turn off the to-lower-case behavior, so we'll see what was actually updated to //
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
field.setBehaviors(Collections.emptySet());
|
|
||||||
assertEquals("octagon", GetAction.execute(TestUtils.TABLE_NAME_SHAPE, id).getValueString("name"));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
@Test
|
|
||||||
void testFilter()
|
|
||||||
{
|
|
||||||
QInstance qInstance = QContext.getQInstance();
|
|
||||||
QTableMetaData table = qInstance.getTable(TestUtils.TABLE_NAME_SHAPE);
|
|
||||||
QFieldMetaData field = table.getField("name");
|
|
||||||
field.setBehaviors(Set.of(CaseChangeBehavior.TO_UPPER_CASE));
|
|
||||||
assertEquals("SQUARE", CaseChangeBehavior.TO_UPPER_CASE.applyToFilterCriteriaValue("square", qInstance, table, field));
|
|
||||||
|
|
||||||
field.setBehaviors(Set.of(CaseChangeBehavior.TO_LOWER_CASE));
|
|
||||||
assertEquals("triangle", CaseChangeBehavior.TO_LOWER_CASE.applyToFilterCriteriaValue("Triangle", qInstance, table, field));
|
|
||||||
|
|
||||||
field.setBehaviors(Set.of(CaseChangeBehavior.NONE));
|
|
||||||
assertEquals("Circle", CaseChangeBehavior.NONE.applyToFilterCriteriaValue("Circle", qInstance, table, field));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
@Test
|
|
||||||
void testValidation()
|
|
||||||
{
|
|
||||||
QTableMetaData table = QContext.getQInstance().getTable(TestUtils.TABLE_NAME_SHAPE);
|
|
||||||
|
|
||||||
///////////////////////////////////////////
|
|
||||||
// should be no errors on a string field //
|
|
||||||
///////////////////////////////////////////
|
|
||||||
assertTrue(CaseChangeBehavior.TO_UPPER_CASE.validateBehaviorConfiguration(table, table.getField("name")).isEmpty());
|
|
||||||
|
|
||||||
//////////////////////////////////////////
|
|
||||||
// should be an error on a number field //
|
|
||||||
//////////////////////////////////////////
|
|
||||||
assertEquals(1, CaseChangeBehavior.TO_LOWER_CASE.validateBehaviorConfiguration(table, table.getField("id")).size());
|
|
||||||
|
|
||||||
/////////////////////////////////////////
|
|
||||||
// NONE should be allowed on any field //
|
|
||||||
/////////////////////////////////////////
|
|
||||||
assertTrue(CaseChangeBehavior.NONE.validateBehaviorConfiguration(table, table.getField("id")).isEmpty());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -47,9 +47,14 @@
|
|||||||
|
|
||||||
<!-- 3rd party deps specifically for this module -->
|
<!-- 3rd party deps specifically for this module -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.openjdk.nashorn</groupId>
|
<groupId>org.graalvm.js</groupId>
|
||||||
<artifactId>nashorn-core</artifactId>
|
<artifactId>js</artifactId>
|
||||||
<version>15.4</version>
|
<version>22.3.0</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.graalvm.js</groupId>
|
||||||
|
<artifactId>js-scriptengine</artifactId>
|
||||||
|
<version>22.3.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- Common deps for all qqq modules -->
|
<!-- Common deps for all qqq modules -->
|
||||||
|
@ -21,11 +21,9 @@
|
|||||||
|
|
||||||
package com.kingsrook.qqq.languages.javascript;
|
package com.kingsrook.qqq.languages.javascript;
|
||||||
|
|
||||||
|
// Javax imports removed for GraalVM migration
|
||||||
|
|
||||||
|
|
||||||
import javax.script.Bindings;
|
|
||||||
import javax.script.ScriptEngine;
|
|
||||||
import javax.script.ScriptEngineManager;
|
|
||||||
import javax.script.ScriptException;
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
@ -39,14 +37,11 @@ import com.kingsrook.qqq.backend.core.actions.scripts.logging.QCodeExecutionLogg
|
|||||||
import com.kingsrook.qqq.backend.core.exceptions.QCodeException;
|
import com.kingsrook.qqq.backend.core.exceptions.QCodeException;
|
||||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||||
import com.kingsrook.qqq.backend.core.utils.ExceptionUtils;
|
|
||||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||||
import org.apache.commons.lang.NotImplementedException;
|
import org.apache.commons.lang.NotImplementedException;
|
||||||
import org.openjdk.nashorn.api.scripting.NashornScriptEngineFactory;
|
import org.graalvm.polyglot.Context;
|
||||||
import org.openjdk.nashorn.api.scripting.ScriptObjectMirror;
|
import org.graalvm.polyglot.Source;
|
||||||
import org.openjdk.nashorn.internal.runtime.ECMAException;
|
import org.graalvm.polyglot.Value;
|
||||||
import org.openjdk.nashorn.internal.runtime.ParserException;
|
|
||||||
import org.openjdk.nashorn.internal.runtime.Undefined;
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -91,57 +86,85 @@ public class QJavaScriptExecutor implements QCodeExecutor
|
|||||||
{
|
{
|
||||||
return (new BigDecimal(d));
|
return (new BigDecimal(d));
|
||||||
}
|
}
|
||||||
else if(object instanceof Undefined)
|
else if(object instanceof Value val)
|
||||||
{
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// well, we always said we wanted javascript to treat null & undefined the same way... here's our chance //
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
return (null);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(object instanceof ScriptObjectMirror scriptObjectMirror)
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if("Date".equals(scriptObjectMirror.getClassName()))
|
//////////////////////////////////////////////////
|
||||||
|
// treat JavaScript null/undefined as Java null //
|
||||||
|
//////////////////////////////////////////////////
|
||||||
|
if(val.isNull() || val.isHostObject() && val.asHostObject() == null)
|
||||||
{
|
{
|
||||||
////////////////////////////////////////////////////////////////////
|
return null;
|
||||||
// looks like the js Date is in UTC (is that because our JVM is?) //
|
}
|
||||||
// so the instant being in UTC matches //
|
////////////////
|
||||||
////////////////////////////////////////////////////////////////////
|
// primitives //
|
||||||
Double millis = (Double) scriptObjectMirror.callMember("getTime");
|
////////////////
|
||||||
Instant instant = Instant.ofEpochMilli(millis.longValue());
|
if(val.isString())
|
||||||
return (instant);
|
{
|
||||||
|
return val.asString();
|
||||||
|
}
|
||||||
|
if(val.isBoolean())
|
||||||
|
{
|
||||||
|
return val.asBoolean();
|
||||||
|
}
|
||||||
|
if(val.isNumber())
|
||||||
|
{
|
||||||
|
//////////////////////////////////////////
|
||||||
|
// preserve integer types when possible //
|
||||||
|
//////////////////////////////////////////
|
||||||
|
if(val.fitsInInt())
|
||||||
|
{
|
||||||
|
return val.asInt();
|
||||||
|
}
|
||||||
|
else if(val.fitsInLong())
|
||||||
|
{
|
||||||
|
return val.asLong();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new BigDecimal(val.asDouble());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//////////////////////////////////////////////
|
||||||
|
// detect JS Date by existence of getTime() //
|
||||||
|
//////////////////////////////////////////////
|
||||||
|
if(val.hasMember("getTime") && val.canInvokeMember("getTime"))
|
||||||
|
{
|
||||||
|
double millis = val.invokeMember("getTime").asDouble();
|
||||||
|
return Instant.ofEpochMilli((long) millis);
|
||||||
|
}
|
||||||
|
////////////
|
||||||
|
// arrays //
|
||||||
|
////////////
|
||||||
|
if(val.hasArrayElements())
|
||||||
|
{
|
||||||
|
List<Object> result = new ArrayList<>();
|
||||||
|
long size = val.getArraySize();
|
||||||
|
for(long i = 0; i < size; i++)
|
||||||
|
{
|
||||||
|
result.add(convertObjectToJava(val.getArrayElement(i)));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
/////////////
|
||||||
|
// objects //
|
||||||
|
/////////////
|
||||||
|
if(val.hasMembers())
|
||||||
|
{
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
for(String key : val.getMemberKeys())
|
||||||
|
{
|
||||||
|
result.put(key, convertObjectToJava(val.getMember(key)));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch(Exception e)
|
catch(Exception e)
|
||||||
{
|
{
|
||||||
LOG.debug("Error unwrapping javascript date", e);
|
LOG.debug("Error converting GraalVM value", e);
|
||||||
}
|
|
||||||
|
|
||||||
if(scriptObjectMirror.isArray())
|
|
||||||
{
|
|
||||||
List<Object> result = new ArrayList<>();
|
|
||||||
for(String key : scriptObjectMirror.keySet())
|
|
||||||
{
|
|
||||||
result.add(Integer.parseInt(key), convertObjectToJava(scriptObjectMirror.get(key)));
|
|
||||||
}
|
|
||||||
return (result);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// last thing we know to try (though really, there's probably some check we should have around this) //
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
Map<String, Object> result = new HashMap<>();
|
|
||||||
for(String key : scriptObjectMirror.keySet())
|
|
||||||
{
|
|
||||||
result.put(key, convertObjectToJava(scriptObjectMirror.get(key)));
|
|
||||||
}
|
|
||||||
return (result);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return QCodeExecutor.super.convertObjectToJava(object);
|
return QCodeExecutor.super.convertObjectToJava(object);
|
||||||
}
|
}
|
||||||
catch(Exception e)
|
catch(Exception e)
|
||||||
@ -166,8 +189,13 @@ public class QJavaScriptExecutor implements QCodeExecutor
|
|||||||
if(object instanceof Instant i)
|
if(object instanceof Instant i)
|
||||||
{
|
{
|
||||||
long millis = (i.getEpochSecond() * 1000 + i.getLong(ChronoField.MILLI_OF_SECOND));
|
long millis = (i.getEpochSecond() * 1000 + i.getLong(ChronoField.MILLI_OF_SECOND));
|
||||||
ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
|
Context context = Context.newBuilder("js")
|
||||||
return engine.eval("new Date(" + millis + ")");
|
.allowAllAccess(true)
|
||||||
|
.allowExperimentalOptions(true)
|
||||||
|
.option("js.ecmascript-version", "2022")
|
||||||
|
.build();
|
||||||
|
Value jsDate = context.eval("js", "new Date(" + millis + ")");
|
||||||
|
return jsDate.asHostObject();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -186,30 +214,27 @@ public class QJavaScriptExecutor implements QCodeExecutor
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
private Serializable runInline(String code, Map<String, Serializable> inputContext, QCodeExecutionLoggerInterface executionLogger) throws QCodeException
|
private Serializable runInline(String code, Map<String, Serializable> inputContext, QCodeExecutionLoggerInterface executionLogger) throws QCodeException
|
||||||
{
|
{
|
||||||
new NashornScriptEngineFactory();
|
Context context = Context.newBuilder("js")
|
||||||
ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
|
.allowAllAccess(true)
|
||||||
|
.allowExperimentalOptions(true)
|
||||||
//////////////////////////////////////////////
|
.option("js.ecmascript-version", "2022")
|
||||||
// setup the javascript environment/context //
|
.build();
|
||||||
//////////////////////////////////////////////
|
// Populate GraalJS bindings from the inputContext
|
||||||
Bindings bindings = engine.createBindings();
|
Value bindingsScope = context.getBindings("js");
|
||||||
bindings.putAll(inputContext);
|
for(Map.Entry<String, Serializable> entry : inputContext.entrySet())
|
||||||
|
|
||||||
if(!bindings.containsKey("logger"))
|
|
||||||
{
|
{
|
||||||
bindings.put("logger", executionLogger);
|
bindingsScope.putMember(entry.getKey(), entry.getValue());
|
||||||
}
|
}
|
||||||
|
// Ensure logger is available
|
||||||
////////////////////////////////////////////////////////////////////////
|
if(!bindingsScope.hasMember("logger"))
|
||||||
// wrap the user's code in an immediately-invoked function expression //
|
{
|
||||||
// if the user's code (%s below) returns - then our IIFE is done. //
|
bindingsScope.putMember("logger", executionLogger);
|
||||||
// if the user's code doesn't return, but instead created a 'script' //
|
}
|
||||||
// variable, with a 'main' function on it (e.g., from a compiled //
|
// wrap the user's code in an immediately-invoked function expression
|
||||||
// type script file), then call main function and return its result. //
|
|
||||||
////////////////////////////////////////////////////////////////////////
|
|
||||||
String codeToRun = """
|
String codeToRun = """
|
||||||
(function userDefinedFunction()
|
(function userDefinedFunction()
|
||||||
{
|
{
|
||||||
|
'use strict';
|
||||||
%s
|
%s
|
||||||
|
|
||||||
var mainFunction = null;
|
var mainFunction = null;
|
||||||
@ -232,66 +257,25 @@ public class QJavaScriptExecutor implements QCodeExecutor
|
|||||||
Serializable output;
|
Serializable output;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
output = (Serializable) engine.eval(codeToRun, bindings);
|
Source source = Source.newBuilder("js", codeToRun, "batchName.js")
|
||||||
|
.mimeType("application/javascript+module")
|
||||||
|
.build();
|
||||||
|
Value result = context.eval(source);
|
||||||
|
output = (Serializable) result.asHostObject();
|
||||||
}
|
}
|
||||||
catch(ScriptException se)
|
catch(Exception se)
|
||||||
{
|
{
|
||||||
QCodeException qCodeException = getQCodeExceptionFromScriptException(se);
|
// We no longer have ScriptException, so wrap as QCodeException
|
||||||
throw (qCodeException);
|
throw new QCodeException("Error during JavaScript execution: " + se.getMessage(), se);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (output);
|
return (output);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
private QCodeException getQCodeExceptionFromScriptException(ScriptException se)
|
// getQCodeExceptionFromScriptException is now unused (ScriptException/Nashorn removed)
|
||||||
{
|
|
||||||
boolean isParserException = ExceptionUtils.findClassInRootChain(se, ParserException.class) != null;
|
|
||||||
boolean isUserThrownException = ExceptionUtils.findClassInRootChain(se, ECMAException.class) != null;
|
|
||||||
|
|
||||||
String message = se.getMessage();
|
|
||||||
String errorContext = null;
|
|
||||||
if(message != null)
|
|
||||||
{
|
|
||||||
message = message.replaceFirst(" in <eval>.*", "");
|
|
||||||
message = message.replaceFirst("<eval>:\\d+:\\d+", "");
|
|
||||||
|
|
||||||
if(message.contains("\n"))
|
|
||||||
{
|
|
||||||
String[] parts = message.split("\n", 2);
|
|
||||||
message = parts[0];
|
|
||||||
errorContext = parts[1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int actualScriptLineNumber = se.getLineNumber() - 2;
|
|
||||||
|
|
||||||
String prefix = "Script Exception";
|
|
||||||
boolean includeColumn = true;
|
|
||||||
boolean includeContext = false;
|
|
||||||
if(isParserException)
|
|
||||||
{
|
|
||||||
prefix = "Script parser exception";
|
|
||||||
includeContext = true;
|
|
||||||
}
|
|
||||||
else if(isUserThrownException)
|
|
||||||
{
|
|
||||||
prefix = "Script threw an exception";
|
|
||||||
includeColumn = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
QCodeException qCodeException = new QCodeException(prefix + " at line " + actualScriptLineNumber + (includeColumn ? (" column " + se.getColumnNumber()) : "") + ": " + message);
|
|
||||||
if(includeContext)
|
|
||||||
{
|
|
||||||
qCodeException.setContext(errorContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (qCodeException);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,106 +0,0 @@
|
|||||||
/*
|
|
||||||
* QQQ - Low-code Application Framework for Engineers.
|
|
||||||
* Copyright (C) 2021-2023. 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.api.actions;
|
|
||||||
|
|
||||||
|
|
||||||
import com.kingsrook.qqq.api.model.APIVersionRange;
|
|
||||||
import com.kingsrook.qqq.api.model.metadata.fields.ApiFieldMetaData;
|
|
||||||
import com.kingsrook.qqq.api.model.metadata.fields.ApiFieldMetaDataContainer;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
|
||||||
import com.kingsrook.qqq.backend.core.utils.ObjectUtils;
|
|
||||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
|
||||||
import org.apache.commons.lang.BooleanUtils;
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** utility methods for working with fields
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
public class ApiFieldUtils
|
|
||||||
{
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
public static boolean isIncluded(String apiName, QFieldMetaData field)
|
|
||||||
{
|
|
||||||
ApiFieldMetaData apiFieldMetaData = getApiFieldMetaData(apiName, field);
|
|
||||||
if(apiFieldMetaData != null && BooleanUtils.isTrue(apiFieldMetaData.getIsExcluded()))
|
|
||||||
{
|
|
||||||
return (false);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (true);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
public static APIVersionRange getApiVersionRangeForRemovedField(String apiName, QFieldMetaData field)
|
|
||||||
{
|
|
||||||
ApiFieldMetaData apiFieldMetaData = getApiFieldMetaData(apiName, field);
|
|
||||||
if(apiFieldMetaData != null && apiFieldMetaData.getInitialVersion() != null)
|
|
||||||
{
|
|
||||||
if(StringUtils.hasContent(apiFieldMetaData.getFinalVersion()))
|
|
||||||
{
|
|
||||||
return (APIVersionRange.betweenAndIncluding(apiFieldMetaData.getInitialVersion(), apiFieldMetaData.getFinalVersion()));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw (new IllegalStateException("RemovedApiFieldMetaData for field [" + field.getName() + "] did not specify a finalVersion."));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw (new IllegalStateException("RemovedApiFieldMetaData for field [" + field.getName() + "] did not specify an initialVersion."));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
public static APIVersionRange getApiVersionRange(String apiName, QFieldMetaData field)
|
|
||||||
{
|
|
||||||
ApiFieldMetaData apiFieldMetaData = getApiFieldMetaData(apiName, field);
|
|
||||||
if(apiFieldMetaData != null && apiFieldMetaData.getInitialVersion() != null)
|
|
||||||
{
|
|
||||||
return (APIVersionRange.afterAndIncluding(apiFieldMetaData.getInitialVersion()));
|
|
||||||
}
|
|
||||||
|
|
||||||
return (APIVersionRange.none());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
public static ApiFieldMetaData getApiFieldMetaData(String apiName, QFieldMetaData field)
|
|
||||||
{
|
|
||||||
return ObjectUtils.tryAndRequireNonNullElse(() -> ApiFieldMetaDataContainer.of(field).getApiFieldMetaData(apiName), new ApiFieldMetaData());
|
|
||||||
}
|
|
||||||
}
|
|
@ -726,7 +726,7 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
|
|||||||
ApiProcessMetaData apiProcessMetaData = pair.getA();
|
ApiProcessMetaData apiProcessMetaData = pair.getA();
|
||||||
QProcessMetaData processMetaData = pair.getB();
|
QProcessMetaData processMetaData = pair.getB();
|
||||||
|
|
||||||
addProcessEndpoints(qInstance, apiInstanceMetaData, basePath, openAPI, tableProcessesTag, apiProcessMetaData, processMetaData, apiVersion);
|
addProcessEndpoints(qInstance, apiInstanceMetaData, basePath, openAPI, tableProcessesTag, apiProcessMetaData, processMetaData);
|
||||||
|
|
||||||
usedProcessNames.add(processMetaData.getName());
|
usedProcessNames.add(processMetaData.getName());
|
||||||
}
|
}
|
||||||
@ -761,7 +761,7 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
|
|||||||
.withName(tag)
|
.withName(tag)
|
||||||
.withDescription(tag));
|
.withDescription(tag));
|
||||||
|
|
||||||
addProcessEndpoints(qInstance, apiInstanceMetaData, basePath, openAPI, tag, apiProcessMetaData, processMetaData, apiVersion);
|
addProcessEndpoints(qInstance, apiInstanceMetaData, basePath, openAPI, tag, apiProcessMetaData, processMetaData);
|
||||||
|
|
||||||
usedProcessNames.add(processMetaData.getName());
|
usedProcessNames.add(processMetaData.getName());
|
||||||
}
|
}
|
||||||
@ -807,14 +807,14 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
private void addProcessEndpoints(QInstance qInstance, ApiInstanceMetaData apiInstanceMetaData, String basePath, OpenAPI openAPI, String tag, ApiProcessMetaData apiProcessMetaData, QProcessMetaData processMetaData, APIVersion apiVersion)
|
private void addProcessEndpoints(QInstance qInstance, ApiInstanceMetaData apiInstanceMetaData, String basePath, OpenAPI openAPI, String tag, ApiProcessMetaData apiProcessMetaData, QProcessMetaData processMetaData)
|
||||||
{
|
{
|
||||||
String processApiPath = ApiProcessUtils.getProcessApiPath(qInstance, processMetaData, apiProcessMetaData, apiInstanceMetaData);
|
String processApiPath = ApiProcessUtils.getProcessApiPath(qInstance, processMetaData, apiProcessMetaData, apiInstanceMetaData);
|
||||||
|
|
||||||
///////////////////////////
|
///////////////////////////
|
||||||
// do the process itself //
|
// do the process itself //
|
||||||
///////////////////////////
|
///////////////////////////
|
||||||
Path path = generateProcessSpecPathObject(apiInstanceMetaData, apiProcessMetaData, processMetaData, ListBuilder.of(tag), apiVersion);
|
Path path = generateProcessSpecPathObject(apiInstanceMetaData, apiProcessMetaData, processMetaData, ListBuilder.of(tag));
|
||||||
openAPI.getPaths().put(basePath + processApiPath, path);
|
openAPI.getPaths().put(basePath + processApiPath, path);
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////
|
||||||
@ -872,7 +872,7 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
private Path generateProcessSpecPathObject(ApiInstanceMetaData apiInstanceMetaData, ApiProcessMetaData apiProcessMetaData, QProcessMetaData processMetaData, List<String> tags, APIVersion apiVersion)
|
private Path generateProcessSpecPathObject(ApiInstanceMetaData apiInstanceMetaData, ApiProcessMetaData apiProcessMetaData, QProcessMetaData processMetaData, List<String> tags)
|
||||||
{
|
{
|
||||||
String description = apiProcessMetaData.getDescription();
|
String description = apiProcessMetaData.getDescription();
|
||||||
if(!StringUtils.hasContent(description))
|
if(!StringUtils.hasContent(description))
|
||||||
@ -926,13 +926,10 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
|
|||||||
}
|
}
|
||||||
|
|
||||||
for(QFieldMetaData field : CollectionUtils.nonNullList(queryStringParams.getFields()))
|
for(QFieldMetaData field : CollectionUtils.nonNullList(queryStringParams.getFields()))
|
||||||
{
|
|
||||||
if(ApiFieldUtils.isIncluded(apiName, field) && ApiFieldUtils.getApiVersionRange(apiName, field).includes(apiVersion))
|
|
||||||
{
|
{
|
||||||
parameters.add(processFieldToParameter(apiInstanceMetaData, field).withIn("query"));
|
parameters.add(processFieldToParameter(apiInstanceMetaData, field).withIn("query"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/////////////////////
|
/////////////////////
|
||||||
// Body as content //
|
// Body as content //
|
||||||
|
@ -31,9 +31,11 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import com.kingsrook.qqq.api.model.APIVersion;
|
import com.kingsrook.qqq.api.model.APIVersion;
|
||||||
|
import com.kingsrook.qqq.api.model.APIVersionRange;
|
||||||
import com.kingsrook.qqq.api.model.actions.GetTableApiFieldsInput;
|
import com.kingsrook.qqq.api.model.actions.GetTableApiFieldsInput;
|
||||||
import com.kingsrook.qqq.api.model.actions.GetTableApiFieldsOutput;
|
import com.kingsrook.qqq.api.model.actions.GetTableApiFieldsOutput;
|
||||||
import com.kingsrook.qqq.api.model.metadata.fields.ApiFieldMetaData;
|
import com.kingsrook.qqq.api.model.metadata.fields.ApiFieldMetaData;
|
||||||
|
import com.kingsrook.qqq.api.model.metadata.fields.ApiFieldMetaDataContainer;
|
||||||
import com.kingsrook.qqq.api.model.metadata.tables.ApiTableMetaData;
|
import com.kingsrook.qqq.api.model.metadata.tables.ApiTableMetaData;
|
||||||
import com.kingsrook.qqq.api.model.metadata.tables.ApiTableMetaDataContainer;
|
import com.kingsrook.qqq.api.model.metadata.tables.ApiTableMetaDataContainer;
|
||||||
import com.kingsrook.qqq.backend.core.actions.AbstractQActionFunction;
|
import com.kingsrook.qqq.backend.core.actions.AbstractQActionFunction;
|
||||||
@ -43,6 +45,8 @@ 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.model.metadata.tables.QTableMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||||
import com.kingsrook.qqq.backend.core.utils.ObjectUtils;
|
import com.kingsrook.qqq.backend.core.utils.ObjectUtils;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||||
|
import org.apache.commons.lang.BooleanUtils;
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -156,7 +160,7 @@ public class GetTableApiFieldsAction extends AbstractQActionFunction<GetTableApi
|
|||||||
fieldList.sort(Comparator.comparing(QFieldMetaData::getLabel));
|
fieldList.sort(Comparator.comparing(QFieldMetaData::getLabel));
|
||||||
for(QFieldMetaData field : fieldList)
|
for(QFieldMetaData field : fieldList)
|
||||||
{
|
{
|
||||||
if(ApiFieldUtils.isIncluded(input.getApiName(), field) && ApiFieldUtils.getApiVersionRange(input.getApiName(), field).includes(version))
|
if(!isExcluded(input.getApiName(), field) && getApiVersionRange(input.getApiName(), field).includes(version))
|
||||||
{
|
{
|
||||||
fields.add(field);
|
fields.add(field);
|
||||||
}
|
}
|
||||||
@ -167,7 +171,7 @@ public class GetTableApiFieldsAction extends AbstractQActionFunction<GetTableApi
|
|||||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
for(QFieldMetaData field : CollectionUtils.nonNullList(getRemovedApiFields(input.getApiName(), table)))
|
for(QFieldMetaData field : CollectionUtils.nonNullList(getRemovedApiFields(input.getApiName(), table)))
|
||||||
{
|
{
|
||||||
if(ApiFieldUtils.isIncluded(input.getApiName(), field) && ApiFieldUtils.getApiVersionRangeForRemovedField(input.getApiName(), field).includes(version))
|
if(!isExcluded(input.getApiName(), field) && getApiVersionRangeForRemovedField(input.getApiName(), field).includes(version))
|
||||||
{
|
{
|
||||||
fields.add(field);
|
fields.add(field);
|
||||||
}
|
}
|
||||||
@ -180,6 +184,73 @@ public class GetTableApiFieldsAction extends AbstractQActionFunction<GetTableApi
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private boolean isExcluded(String apiName, QFieldMetaData field)
|
||||||
|
{
|
||||||
|
ApiFieldMetaData apiFieldMetaData = getApiFieldMetaData(apiName, field);
|
||||||
|
if(apiFieldMetaData != null && BooleanUtils.isTrue(apiFieldMetaData.getIsExcluded()))
|
||||||
|
{
|
||||||
|
return (true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private APIVersionRange getApiVersionRangeForRemovedField(String apiName, QFieldMetaData field)
|
||||||
|
{
|
||||||
|
ApiFieldMetaData apiFieldMetaData = getApiFieldMetaData(apiName, field);
|
||||||
|
if(apiFieldMetaData != null && apiFieldMetaData.getInitialVersion() != null)
|
||||||
|
{
|
||||||
|
if(StringUtils.hasContent(apiFieldMetaData.getFinalVersion()))
|
||||||
|
{
|
||||||
|
return (APIVersionRange.betweenAndIncluding(apiFieldMetaData.getInitialVersion(), apiFieldMetaData.getFinalVersion()));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw (new IllegalStateException("RemovedApiFieldMetaData for field [" + field.getName() + "] did not specify a finalVersion."));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw (new IllegalStateException("RemovedApiFieldMetaData for field [" + field.getName() + "] did not specify an initialVersion."));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private APIVersionRange getApiVersionRange(String apiName, QFieldMetaData field)
|
||||||
|
{
|
||||||
|
ApiFieldMetaData apiFieldMetaData = getApiFieldMetaData(apiName, field);
|
||||||
|
if(apiFieldMetaData != null && apiFieldMetaData.getInitialVersion() != null)
|
||||||
|
{
|
||||||
|
return (APIVersionRange.afterAndIncluding(apiFieldMetaData.getInitialVersion()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return (APIVersionRange.none());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private static ApiFieldMetaData getApiFieldMetaData(String apiName, QFieldMetaData field)
|
||||||
|
{
|
||||||
|
return ObjectUtils.tryAndRequireNonNullElse(() -> ApiFieldMetaDataContainer.of(field).getApiFieldMetaData(apiName), new ApiFieldMetaData());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
|
Reference in New Issue
Block a user