mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-21 06:28:44 +00:00
Compare commits
26 Commits
feature/re
...
snapshot-f
Author | SHA1 | Date | |
---|---|---|---|
a5c65b9e67 | |||
48fbb3d054 | |||
bcca710316 | |||
6d749e9df6 | |||
81ffe1a286 | |||
6b49abb749 | |||
efb47b9cd6 | |||
29f2feb321 | |||
3537d2cfd1 | |||
634abe3822 | |||
93c7fbca25 | |||
ea40197893 | |||
38293b81d7 | |||
7b141c3f5b | |||
502095002c | |||
42a8d37493 | |||
6725704b13 | |||
48ac6a0a4f | |||
3f4d11b22a | |||
f147516e45 | |||
f3fe8a3c73 | |||
71dcf231db | |||
a20efabcf2 | |||
00b72e0338 | |||
b979e6545a | |||
7982cad794 |
@ -29,7 +29,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
|
||||
/*******************************************************************************
|
||||
** Version of AbstractQQQApplication that assumes all meta-data is produced
|
||||
** by MetaDataProducers in a single package.
|
||||
** by MetaDataProducers in (or under) a single package.
|
||||
*******************************************************************************/
|
||||
public abstract class AbstractMetaDataProducerBasedQQQApplication extends AbstractQQQApplication
|
||||
{
|
||||
|
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.instances.loaders.MetaDataLoaderHelper;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Version of AbstractQQQApplication that assumes all meta-data is defined in
|
||||
** config files (yaml, json, etc) under a given directory path.
|
||||
*******************************************************************************/
|
||||
public class ConfigFilesBasedQQQApplication extends AbstractQQQApplication
|
||||
{
|
||||
private final String path;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public ConfigFilesBasedQQQApplication(String path)
|
||||
{
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public QInstance defineQInstance() throws QException
|
||||
{
|
||||
QInstance qInstance = new QInstance();
|
||||
MetaDataLoaderHelper.processAllMetaDataFilesInDirectory(qInstance, path);
|
||||
return (qInstance);
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Version of AbstractQQQApplication that assumes all meta-data is produced
|
||||
** by MetaDataProducers in (or under) a single package (where you can pass that
|
||||
** package into the constructor, vs. the abstract base class, where you extend
|
||||
** it and override the getMetaDataPackageName method.
|
||||
*******************************************************************************/
|
||||
public class MetaDataProducerBasedQQQApplication extends AbstractMetaDataProducerBasedQQQApplication
|
||||
{
|
||||
private final String metaDataPackageName;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public MetaDataProducerBasedQQQApplication(String metaDataPackageName)
|
||||
{
|
||||
this.metaDataPackageName = metaDataPackageName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public MetaDataProducerBasedQQQApplication(Class<?> aClassInMetaDataPackage)
|
||||
{
|
||||
this(aClassInMetaDataPackage.getPackageName());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public String getMetaDataPackageName()
|
||||
{
|
||||
return (this.metaDataPackageName);
|
||||
}
|
||||
}
|
@ -289,7 +289,21 @@ public class QInstanceEnricher
|
||||
|
||||
if(table.getFields() != null)
|
||||
{
|
||||
table.getFields().values().forEach(this::enrichField);
|
||||
for(Map.Entry<String, QFieldMetaData> entry : table.getFields().entrySet())
|
||||
{
|
||||
String name = entry.getKey();
|
||||
QFieldMetaData field = entry.getValue();
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// in case the field wasn't given a name, use its key from the fields map //
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
if(!StringUtils.hasContent(field.getName()))
|
||||
{
|
||||
field.setName(name);
|
||||
}
|
||||
|
||||
enrichField(field);
|
||||
}
|
||||
|
||||
for(QSupplementalTableMetaData supplementalTableMetaData : CollectionUtils.nonNullMap(table.getSupplementalMetaData()).values())
|
||||
{
|
||||
|
@ -0,0 +1,510 @@
|
||||
/*
|
||||
* 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.instances.loaders;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.math.BigDecimal;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.kingsrook.qqq.backend.core.instances.QMetaDataVariableInterpreter;
|
||||
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.QMetaDataObject;
|
||||
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 com.kingsrook.qqq.backend.core.utils.YamlUtils;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import static com.kingsrook.qqq.backend.core.utils.ValueUtils.getValueAsInteger;
|
||||
import static com.kingsrook.qqq.backend.core.utils.ValueUtils.getValueAsString;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Abstract base class in hierarchy of classes that know how to construct &
|
||||
** populate QMetaDataObject instances, based on input streams (e.g., from files).
|
||||
*******************************************************************************/
|
||||
public abstract class AbstractMetaDataLoader<T extends QMetaDataObject>
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(AbstractMetaDataLoader.class);
|
||||
|
||||
private String fileName;
|
||||
|
||||
private List<LoadingProblem> problems = new ArrayList<>();
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public T fileToMetaDataObject(QInstance qInstance, InputStream inputStream, String fileName) throws QMetaDataLoaderException
|
||||
{
|
||||
this.fileName = fileName;
|
||||
Map<String, Object> map = fileToMap(inputStream, fileName);
|
||||
LoadingContext loadingContext = new LoadingContext(fileName, "/");
|
||||
return (mapToMetaDataObject(qInstance, map, loadingContext));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public abstract T mapToMetaDataObject(QInstance qInstance, Map<String, Object> map, LoadingContext context) throws QMetaDataLoaderException;
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
protected Map<String, Object> fileToMap(InputStream inputStream, String fileName) throws QMetaDataLoaderException
|
||||
{
|
||||
try
|
||||
{
|
||||
String string = IOUtils.toString(inputStream, StandardCharsets.UTF_8);
|
||||
string = StringUtils.ltrim(string);
|
||||
if(fileName.toLowerCase().endsWith(".json"))
|
||||
{
|
||||
return JsonUtils.toObject(string, new TypeReference<>() {});
|
||||
}
|
||||
else if(fileName.toLowerCase().endsWith(".yaml") || fileName.toLowerCase().endsWith(".yml"))
|
||||
{
|
||||
return YamlUtils.toMap(string);
|
||||
}
|
||||
|
||||
throw (new QMetaDataLoaderException("Unsupported file format (based on file name: " + fileName + ")"));
|
||||
}
|
||||
catch(IOException e)
|
||||
{
|
||||
throw new QMetaDataLoaderException("Error building map from file: " + fileName, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
*
|
||||
***************************************************************************/
|
||||
protected void reflectivelyMap(QInstance qInstance, QMetaDataObject targetObject, Map<String, Object> map, LoadingContext context)
|
||||
{
|
||||
Class<? extends QMetaDataObject> targetClass = targetObject.getClass();
|
||||
Set<String> usedFieldNames = new HashSet<>();
|
||||
|
||||
for(Method method : targetClass.getMethods())
|
||||
{
|
||||
try
|
||||
{
|
||||
if(method.getName().startsWith("set") && method.getParameterTypes().length == 1)
|
||||
{
|
||||
String propertyName = StringUtils.lcFirst(method.getName().substring(3));
|
||||
|
||||
if(map.containsKey(propertyName))
|
||||
{
|
||||
usedFieldNames.add(propertyName);
|
||||
Class<?> parameterType = method.getParameterTypes()[0];
|
||||
Object rawValue = map.get(propertyName);
|
||||
|
||||
try
|
||||
{
|
||||
Object mappedValue = reflectivelyMapValue(qInstance, method, parameterType, rawValue, context.descendToProperty(propertyName));
|
||||
method.invoke(targetObject, mappedValue);
|
||||
}
|
||||
catch(NoValueException nve)
|
||||
{
|
||||
///////////////////////
|
||||
// don't call setter //
|
||||
///////////////////////
|
||||
LOG.debug("at " + context + ": No value was mapped for property [" + propertyName + "] on " + targetClass.getSimpleName() + "." + method.getName() + ", raw value: [" + rawValue + "]");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
addProblem(new LoadingProblem(context, "Error reflectively mapping on " + targetClass.getName() + "." + method.getName(), e));
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////
|
||||
// mmm, slightly sus... //
|
||||
//////////////////////////
|
||||
map.remove("class");
|
||||
map.remove("version");
|
||||
|
||||
Set<String> unrecognizedKeys = new HashSet<>(map.keySet());
|
||||
unrecognizedKeys.removeAll(usedFieldNames);
|
||||
|
||||
if(!unrecognizedKeys.isEmpty())
|
||||
{
|
||||
addProblem(new LoadingProblem(context, unrecognizedKeys.size() + " Unrecognized " + StringUtils.plural(unrecognizedKeys, "property", "properties") + ": " + unrecognizedKeys));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
*
|
||||
***************************************************************************/
|
||||
public Object reflectivelyMapValue(QInstance qInstance, Method method, Class<?> parameterType, Object rawValue, LoadingContext context) throws Exception
|
||||
{
|
||||
if(rawValue instanceof String s && s.matches("^\\$\\{.+\\..+}"))
|
||||
{
|
||||
rawValue = new QMetaDataVariableInterpreter().interpret(s);
|
||||
LOG.debug("Interpreted raw value [" + s + "] as [" + StringUtils.maskAndTruncate(ValueUtils.getValueAsString(rawValue) + "]"));
|
||||
}
|
||||
|
||||
if(parameterType.equals(String.class))
|
||||
{
|
||||
return (getValueAsString(rawValue));
|
||||
}
|
||||
else if(parameterType.equals(Integer.class))
|
||||
{
|
||||
try
|
||||
{
|
||||
return (getValueAsInteger(rawValue));
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
addProblem(new LoadingProblem(context, "[" + rawValue + "] is not an Integer value."));
|
||||
}
|
||||
}
|
||||
else if(parameterType.equals(Boolean.class))
|
||||
{
|
||||
if("true".equals(rawValue) || Boolean.TRUE.equals(rawValue))
|
||||
{
|
||||
return (true);
|
||||
}
|
||||
else if("false".equals(rawValue) || Boolean.FALSE.equals(rawValue))
|
||||
{
|
||||
return (false);
|
||||
}
|
||||
else if(rawValue == null)
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
else
|
||||
{
|
||||
addProblem(new LoadingProblem(context, "[" + rawValue + "] is not a boolean value (must be 'true' or 'false')."));
|
||||
return (null);
|
||||
}
|
||||
}
|
||||
else if(parameterType.equals(boolean.class))
|
||||
{
|
||||
if("true".equals(rawValue) || Boolean.TRUE.equals(rawValue))
|
||||
{
|
||||
return (true);
|
||||
}
|
||||
else if("false".equals(rawValue) || Boolean.FALSE.equals(rawValue))
|
||||
{
|
||||
return (false);
|
||||
}
|
||||
else
|
||||
{
|
||||
addProblem(new LoadingProblem(context, rawValue + " is not a boolean value (must be 'true' or 'false')."));
|
||||
throw (new NoValueException());
|
||||
}
|
||||
}
|
||||
else if(parameterType.equals(List.class))
|
||||
{
|
||||
Type actualTypeArgument = ((ParameterizedType) method.getGenericParameterTypes()[0]).getActualTypeArguments()[0];
|
||||
Class<?> actualTypeClass = Class.forName(actualTypeArgument.getTypeName());
|
||||
|
||||
if(rawValue instanceof @SuppressWarnings("rawtypes")List valueList)
|
||||
{
|
||||
List<Object> mappedValueList = new ArrayList<>();
|
||||
for(Object o : valueList)
|
||||
{
|
||||
try
|
||||
{
|
||||
Object mappedValue = reflectivelyMapValue(qInstance, null, actualTypeClass, o, context);
|
||||
mappedValueList.add(mappedValue);
|
||||
}
|
||||
catch(NoValueException nve)
|
||||
{
|
||||
// leave off list
|
||||
}
|
||||
}
|
||||
return (mappedValueList);
|
||||
}
|
||||
}
|
||||
else if(parameterType.equals(Set.class))
|
||||
{
|
||||
Type actualTypeArgument = ((ParameterizedType) method.getGenericParameterTypes()[0]).getActualTypeArguments()[0];
|
||||
Class<?> actualTypeClass = Class.forName(actualTypeArgument.getTypeName());
|
||||
|
||||
if(rawValue instanceof @SuppressWarnings("rawtypes")List valueList)
|
||||
{
|
||||
Set<Object> mappedValueSet = new LinkedHashSet<>();
|
||||
for(Object o : valueList)
|
||||
{
|
||||
try
|
||||
{
|
||||
Object mappedValue = reflectivelyMapValue(qInstance, null, actualTypeClass, o, context);
|
||||
mappedValueSet.add(mappedValue);
|
||||
}
|
||||
catch(NoValueException nve)
|
||||
{
|
||||
// leave off list
|
||||
}
|
||||
}
|
||||
return (mappedValueSet);
|
||||
}
|
||||
}
|
||||
else if(parameterType.equals(Map.class))
|
||||
{
|
||||
Type keyType = ((ParameterizedType) method.getGenericParameterTypes()[0]).getActualTypeArguments()[0];
|
||||
if(!keyType.equals(String.class))
|
||||
{
|
||||
addProblem(new LoadingProblem(context, "Unsupported key type for " + method + " got [" + keyType + "], expected [String]"));
|
||||
throw new NoValueException();
|
||||
}
|
||||
// todo make sure string
|
||||
|
||||
Type actualTypeArgument = ((ParameterizedType) method.getGenericParameterTypes()[0]).getActualTypeArguments()[1];
|
||||
Class<?> actualTypeClass = Class.forName(actualTypeArgument.getTypeName());
|
||||
|
||||
if(rawValue instanceof @SuppressWarnings("rawtypes")Map valueMap)
|
||||
{
|
||||
Map<String, Object> mappedValueMap = new LinkedHashMap<>();
|
||||
for(Object o : valueMap.entrySet())
|
||||
{
|
||||
try
|
||||
{
|
||||
@SuppressWarnings("unchecked")
|
||||
Map.Entry<String, Object> entry = (Map.Entry<String, Object>) o;
|
||||
Object mappedValue = reflectivelyMapValue(qInstance, null, actualTypeClass, entry.getValue(), context);
|
||||
mappedValueMap.put(entry.getKey(), mappedValue);
|
||||
}
|
||||
catch(NoValueException nve)
|
||||
{
|
||||
// leave out of map
|
||||
}
|
||||
}
|
||||
return (mappedValueMap);
|
||||
}
|
||||
}
|
||||
else if(parameterType.isEnum())
|
||||
{
|
||||
String value = getValueAsString(rawValue);
|
||||
for(Object enumConstant : parameterType.getEnumConstants())
|
||||
{
|
||||
if(((Enum<?>) enumConstant).name().equals(value))
|
||||
{
|
||||
return (enumConstant);
|
||||
}
|
||||
}
|
||||
|
||||
addProblem(new LoadingProblem(context, "Unrecognized value [" + rawValue + "]. Expected one of: " + Arrays.toString(parameterType.getEnumConstants())));
|
||||
}
|
||||
else if(MetaDataLoaderRegistry.hasLoaderForClass(parameterType))
|
||||
{
|
||||
if(rawValue instanceof @SuppressWarnings("rawtypes")Map valueMap)
|
||||
{
|
||||
Class<? extends AbstractMetaDataLoader<?>> loaderClass = MetaDataLoaderRegistry.getLoaderForClass(parameterType);
|
||||
AbstractMetaDataLoader<?> loader = loaderClass.getConstructor().newInstance();
|
||||
//noinspection unchecked
|
||||
return (loader.mapToMetaDataObject(qInstance, valueMap, context));
|
||||
}
|
||||
}
|
||||
else if(QMetaDataObject.class.isAssignableFrom(parameterType))
|
||||
{
|
||||
if(rawValue instanceof @SuppressWarnings("rawtypes")Map valueMap)
|
||||
{
|
||||
QMetaDataObject childObject = (QMetaDataObject) parameterType.getConstructor().newInstance();
|
||||
//noinspection unchecked
|
||||
reflectivelyMap(qInstance, childObject, valueMap, context);
|
||||
return (childObject);
|
||||
}
|
||||
}
|
||||
else if(parameterType.equals(Serializable.class))
|
||||
{
|
||||
if(rawValue instanceof String
|
||||
|| rawValue instanceof Integer
|
||||
|| rawValue instanceof BigDecimal
|
||||
|| rawValue instanceof Boolean
|
||||
)
|
||||
{
|
||||
return rawValue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// todo clean up this message/level
|
||||
addProblem(new LoadingProblem(context, "No case for " + parameterType + " (arg to: " + method + ")"));
|
||||
}
|
||||
|
||||
throw new NoValueException();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// unclear if the below is needed. if so, useful to not re-write, but is hurting test coverage, so zombie until used //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
///***************************************************************************
|
||||
// *
|
||||
// ***************************************************************************/
|
||||
//protected ListOfMapOrMapOfMap getListOfMapOrMapOfMap(Map<String, Object> map, String key)
|
||||
//{
|
||||
// if(map.containsKey(key))
|
||||
// {
|
||||
// if(map.get(key) instanceof List)
|
||||
// {
|
||||
// return (new ListOfMapOrMapOfMap((List<Map<String, Object>>) map.get(key)));
|
||||
// }
|
||||
// else if(map.get(key) instanceof Map)
|
||||
// {
|
||||
// return (new ListOfMapOrMapOfMap((Map<String, Map<String, Object>>) map.get(key)));
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// LOG.warn("Expected list or map under key [" + key + "] while processing [" + getClass().getSimpleName() + "] from [" + fileName + "], but found: " + (map.get(key) == null ? "null" : map.get(key).getClass().getSimpleName()));
|
||||
// }
|
||||
// }
|
||||
|
||||
// return (null);
|
||||
//}
|
||||
|
||||
///***************************************************************************
|
||||
// *
|
||||
// ***************************************************************************/
|
||||
//protected List<Map<String, Object>> getListOfMap(Map<String, Object> map, String key)
|
||||
//{
|
||||
// if(map.containsKey(key))
|
||||
// {
|
||||
// if(map.get(key) instanceof List)
|
||||
// {
|
||||
// return (List<Map<String, Object>>) map.get(key);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// LOG.warn("Expected list under key [" + key + "] while processing [" + getClass().getSimpleName() + "] from [" + fileName + "], but found: " + (map.get(key) == null ? "null" : map.get(key).getClass().getSimpleName()));
|
||||
// }
|
||||
// }
|
||||
|
||||
// return (null);
|
||||
//}
|
||||
|
||||
///***************************************************************************
|
||||
// *
|
||||
// ***************************************************************************/
|
||||
//protected Map<String, Map<String, Object>> getMapOfMap(Map<String, Object> map, String key)
|
||||
//{
|
||||
// if(map.containsKey(key))
|
||||
// {
|
||||
// if(map.get(key) instanceof Map)
|
||||
// {
|
||||
// return (Map<String, Map<String, Object>>) map.get(key);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// LOG.warn("Expected map under key [" + key + "] while processing [" + getClass().getSimpleName() + "] from [" + fileName + "], but found: " + (map.get(key) == null ? "null" : map.get(key).getClass().getSimpleName()));
|
||||
// }
|
||||
// }
|
||||
|
||||
// return (null);
|
||||
//}
|
||||
|
||||
///***************************************************************************
|
||||
// **
|
||||
// ***************************************************************************/
|
||||
//protected record ListOfMapOrMapOfMap(List<Map<String, Object>> listOf, Map<String, Map<String, Object>> mapOf)
|
||||
//{
|
||||
// /*******************************************************************************
|
||||
// ** Constructor
|
||||
// **
|
||||
// *******************************************************************************/
|
||||
// public ListOfMapOrMapOfMap(List<Map<String, Object>> listOf)
|
||||
// {
|
||||
// this(listOf, null);
|
||||
// }
|
||||
|
||||
// /*******************************************************************************
|
||||
// ** Constructor
|
||||
// **
|
||||
// *******************************************************************************/
|
||||
// public ListOfMapOrMapOfMap(Map<String, Map<String, Object>> mapOf)
|
||||
// {
|
||||
// this(null, mapOf);
|
||||
// }
|
||||
//}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for fileName
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getFileName()
|
||||
{
|
||||
return fileName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
private static class NoValueException extends Exception
|
||||
{
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public NoValueException()
|
||||
{
|
||||
super("No value");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public void addProblem(LoadingProblem problem)
|
||||
{
|
||||
problems.add(problem);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for problems
|
||||
**
|
||||
*******************************************************************************/
|
||||
public List<LoadingProblem> getProblems()
|
||||
{
|
||||
return (problems);
|
||||
}
|
||||
}
|
@ -0,0 +1,120 @@
|
||||
/*
|
||||
* 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.instances.loaders;
|
||||
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import com.kingsrook.qqq.backend.core.instances.loaders.implementations.GenericMetaDataLoader;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QMetaDataObject;
|
||||
import com.kingsrook.qqq.backend.core.utils.ClassPathUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.memoization.AnyKey;
|
||||
import com.kingsrook.qqq.backend.core.utils.memoization.Memoization;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Generic implementation of AbstractMetaDataLoader, who "detects" the class
|
||||
** of meta data object to be created, then defers to an appropriate subclass
|
||||
** to do the work.
|
||||
*******************************************************************************/
|
||||
public class ClassDetectingMetaDataLoader extends AbstractMetaDataLoader<QMetaDataObject>
|
||||
{
|
||||
private static final Memoization<AnyKey, List<Class<?>>> memoizedMetaDataObjectClasses = new Memoization<>();
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
*
|
||||
***************************************************************************/
|
||||
public AbstractMetaDataLoader<?> getLoaderForFile(InputStream inputStream, String fileName) throws QMetaDataLoaderException
|
||||
{
|
||||
Map<String, Object> map = fileToMap(inputStream, fileName);
|
||||
return (getLoaderForMap(map));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
*
|
||||
***************************************************************************/
|
||||
public AbstractMetaDataLoader<?> getLoaderForMap(Map<String, Object> map) throws QMetaDataLoaderException
|
||||
{
|
||||
if(map.containsKey("class"))
|
||||
{
|
||||
String classProperty = ValueUtils.getValueAsString(map.get("class"));
|
||||
try
|
||||
{
|
||||
if(MetaDataLoaderRegistry.hasLoaderForSimpleName(classProperty))
|
||||
{
|
||||
Class<? extends AbstractMetaDataLoader<?>> loaderClass = MetaDataLoaderRegistry.getLoaderForSimpleName(classProperty);
|
||||
return (loaderClass.getConstructor().newInstance());
|
||||
}
|
||||
else
|
||||
{
|
||||
Optional<List<Class<?>>> metaDataClasses = memoizedMetaDataObjectClasses.getResult(AnyKey.getInstance(), k -> ClassPathUtils.getClassesContainingNameAndOfType("MetaData", QMetaDataObject.class));
|
||||
if(metaDataClasses.isEmpty())
|
||||
{
|
||||
throw (new QMetaDataLoaderException("Could not get list of metaDataObjects from class loader"));
|
||||
}
|
||||
|
||||
for(Class<?> c : metaDataClasses.get())
|
||||
{
|
||||
if(c.getSimpleName().equals(classProperty) && QMetaDataObject.class.isAssignableFrom(c))
|
||||
{
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<? extends QMetaDataObject> metaDataClass = (Class<? extends QMetaDataObject>) c;
|
||||
return new GenericMetaDataLoader<>(metaDataClass);
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new QMetaDataLoaderException("Unexpected class [" + classProperty + "] (not a QMetaDataObject; doesn't have a registered MetaDataLoader) specified in " + getFileName());
|
||||
}
|
||||
catch(QMetaDataLoaderException qmdle)
|
||||
{
|
||||
throw (qmdle);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw new QMetaDataLoaderException("Error handling class [" + classProperty + "] specified in " + getFileName(), e);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new QMetaDataLoaderException("Cannot detect meta-data type, because [class] attribute was not specified in file: " + getFileName());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public QMetaDataObject mapToMetaDataObject(QInstance qInstance, Map<String, Object> map, LoadingContext context) throws QMetaDataLoaderException
|
||||
{
|
||||
AbstractMetaDataLoader<?> loaderForMap = getLoaderForMap(map);
|
||||
return loaderForMap.mapToMetaDataObject(qInstance, map, context);
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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.loaders;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Record to track where loader objects are - e.g., what file they're on,
|
||||
** and at what property path within the file (e.g., helps report problems).
|
||||
*******************************************************************************/
|
||||
public record LoadingContext(String fileName, String propertyPath)
|
||||
{
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public LoadingContext descendToProperty(String propertyName)
|
||||
{
|
||||
return new LoadingContext(fileName, propertyPath + propertyName + "/");
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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.loaders;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** record that tracks a problem that was encountered when loading files.
|
||||
*******************************************************************************/
|
||||
public record LoadingProblem(LoadingContext context, String message, Exception exception) // todo Level if useful
|
||||
{
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public LoadingProblem(LoadingContext context, String message)
|
||||
{
|
||||
this(context, message, null);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "at[" + context.fileName() + "][" + context.propertyPath() + "]: " + message;
|
||||
}
|
||||
}
|
@ -0,0 +1,118 @@
|
||||
/*
|
||||
* 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.instances.loaders;
|
||||
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
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.QMetaDataObject;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.TopLevelMetaDataInterface;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.Pair;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** class that loads a directory full of meta data files into meta data objects,
|
||||
** and then sets all of them in a QInstance.
|
||||
*******************************************************************************/
|
||||
public class MetaDataLoaderHelper
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(MetaDataLoaderHelper.class);
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
*
|
||||
***************************************************************************/
|
||||
public static void processAllMetaDataFilesInDirectory(QInstance qInstance, String path) throws QException
|
||||
{
|
||||
List<Pair<File, AbstractMetaDataLoader<?>>> loaders = new ArrayList<>();
|
||||
|
||||
File directory = new File(path);
|
||||
processAllMetaDataFilesInDirectory(loaders, directory);
|
||||
|
||||
// todo - some version of sorting the loaders by type or possibly a sort field within the files (or file names)
|
||||
|
||||
for(Pair<File, AbstractMetaDataLoader<?>> pair : loaders)
|
||||
{
|
||||
File file = pair.getA();
|
||||
AbstractMetaDataLoader<?> loader = pair.getB();
|
||||
try(FileInputStream fileInputStream = new FileInputStream(file))
|
||||
{
|
||||
QMetaDataObject qMetaDataObject = loader.fileToMetaDataObject(qInstance, fileInputStream, file.getName());
|
||||
|
||||
if(CollectionUtils.nullSafeHasContents(loader.getProblems()))
|
||||
{
|
||||
loader.getProblems().forEach(System.out::println);
|
||||
}
|
||||
|
||||
if(qMetaDataObject instanceof TopLevelMetaDataInterface topLevelMetaData)
|
||||
{
|
||||
topLevelMetaData.addSelfToInstance(qInstance);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG.warn("Received a non-topLevelMetaDataObject from file: " + file.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.error("Error processing file: " + file.getAbsolutePath(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
*
|
||||
***************************************************************************/
|
||||
private static void processAllMetaDataFilesInDirectory(List<Pair<File, AbstractMetaDataLoader<?>>> loaders, File directory) throws QException
|
||||
{
|
||||
for(File file : Objects.requireNonNullElse(directory.listFiles(), new File[0]))
|
||||
{
|
||||
if(file.isDirectory())
|
||||
{
|
||||
processAllMetaDataFilesInDirectory(loaders, file);
|
||||
}
|
||||
else
|
||||
{
|
||||
try(FileInputStream fileInputStream = new FileInputStream(file))
|
||||
{
|
||||
AbstractMetaDataLoader<?> loader = new ClassDetectingMetaDataLoader().getLoaderForFile(fileInputStream, file.getName());
|
||||
loaders.add(Pair.of(file, loader));
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.error("Error processing file: " + file.getAbsolutePath(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,120 @@
|
||||
/*
|
||||
* 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.loaders;
|
||||
|
||||
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.instances.loaders.implementations.QTableMetaDataLoader;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.utils.ClassPathUtils;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class MetaDataLoaderRegistry
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(AbstractMetaDataLoader.class);
|
||||
|
||||
private static final Map<Class<?>, Class<? extends AbstractMetaDataLoader<?>>> registeredLoaders = new HashMap<>();
|
||||
private static final Map<String, Class<? extends AbstractMetaDataLoader<?>>> registeredLoadersByTargetSimpleName = new HashMap<>();
|
||||
|
||||
static
|
||||
{
|
||||
try
|
||||
{
|
||||
List<Class<?>> classesInPackage = ClassPathUtils.getClassesInPackage(QTableMetaDataLoader.class.getPackageName());
|
||||
for(Class<?> possibleLoaderClass : classesInPackage)
|
||||
{
|
||||
try
|
||||
{
|
||||
Type superClass = possibleLoaderClass.getGenericSuperclass();
|
||||
if(superClass.getTypeName().startsWith(AbstractMetaDataLoader.class.getName() + "<"))
|
||||
{
|
||||
Type actualTypeArgument = ((ParameterizedType) superClass).getActualTypeArguments()[0];
|
||||
if(actualTypeArgument instanceof Class)
|
||||
{
|
||||
//noinspection unchecked
|
||||
Class<? extends AbstractMetaDataLoader<?>> loaderClass = (Class<? extends AbstractMetaDataLoader<?>>) possibleLoaderClass;
|
||||
|
||||
Class<?> metaDataObjectType = Class.forName(actualTypeArgument.getTypeName());
|
||||
registeredLoaders.put(metaDataObjectType, loaderClass);
|
||||
registeredLoadersByTargetSimpleName.put(metaDataObjectType.getSimpleName(), loaderClass);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.info("Error on class: " + possibleLoaderClass, e);
|
||||
}
|
||||
}
|
||||
|
||||
System.out.println("Registered loaders: " + registeredLoadersByTargetSimpleName);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.error("Error in static init block for MetaDataLoaderRegistry", e);
|
||||
}
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public static boolean hasLoaderForClass(Class<?> metaDataClass)
|
||||
{
|
||||
return registeredLoaders.containsKey(metaDataClass);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public static Class<? extends AbstractMetaDataLoader<?>> getLoaderForClass(Class<?> metaDataClass)
|
||||
{
|
||||
return registeredLoaders.get(metaDataClass);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public static boolean hasLoaderForSimpleName(String targetSimpleName)
|
||||
{
|
||||
return registeredLoadersByTargetSimpleName.containsKey(targetSimpleName);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public static Class<? extends AbstractMetaDataLoader<?>> getLoaderForSimpleName(String targetSimpleName)
|
||||
{
|
||||
return registeredLoadersByTargetSimpleName.get(targetSimpleName);
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
/*
|
||||
* 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.instances.loaders;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class QMetaDataLoaderException extends Exception
|
||||
{
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QMetaDataLoaderException(String message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QMetaDataLoaderException(String message, Throwable cause)
|
||||
{
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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.instances.loaders.implementations;
|
||||
|
||||
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.instances.loaders.AbstractMetaDataLoader;
|
||||
import com.kingsrook.qqq.backend.core.instances.loaders.LoadingContext;
|
||||
import com.kingsrook.qqq.backend.core.instances.loaders.QMetaDataLoaderException;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QMetaDataObject;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class GenericMetaDataLoader<T extends QMetaDataObject> extends AbstractMetaDataLoader<T>
|
||||
{
|
||||
private final Class<T> metaDataClass;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public GenericMetaDataLoader(Class<T> metaDataClass)
|
||||
{
|
||||
this.metaDataClass = metaDataClass;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public T mapToMetaDataObject(QInstance qInstance, Map<String, Object> map, LoadingContext context) throws QMetaDataLoaderException
|
||||
{
|
||||
try
|
||||
{
|
||||
T object = metaDataClass.getConstructor().newInstance();
|
||||
reflectivelyMap(qInstance, object, map, context);
|
||||
return (object);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw (new QMetaDataLoaderException("Error loading metaData object of type " + metaDataClass.getSimpleName(), e));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
/*
|
||||
* 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.instances.loaders.implementations;
|
||||
|
||||
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.instances.loaders.AbstractMetaDataLoader;
|
||||
import com.kingsrook.qqq.backend.core.instances.loaders.LoadingContext;
|
||||
import com.kingsrook.qqq.backend.core.instances.loaders.QMetaDataLoaderException;
|
||||
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.processes.QBackendStepMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendStepMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QStepMetaData;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class QStepDataLoader extends AbstractMetaDataLoader<QStepMetaData>
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(QStepDataLoader.class);
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public QStepMetaData mapToMetaDataObject(QInstance qInstance, Map<String, Object> map, LoadingContext context) throws QMetaDataLoaderException
|
||||
{
|
||||
String stepType = ValueUtils.getValueAsString(map.get("stepType"));
|
||||
|
||||
if(!StringUtils.hasContent(stepType))
|
||||
{
|
||||
throw (new QMetaDataLoaderException("stepType was not specified for process step"));
|
||||
}
|
||||
|
||||
QStepMetaData step;
|
||||
if("backend".equalsIgnoreCase(stepType))
|
||||
{
|
||||
step = new QBackendStepMetaData();
|
||||
reflectivelyMap(qInstance, step, map, context);
|
||||
}
|
||||
else if("frontend".equalsIgnoreCase(stepType))
|
||||
{
|
||||
step = new QFrontendStepMetaData();
|
||||
reflectivelyMap(qInstance, step, map, context);
|
||||
}
|
||||
// todo - we have custom factory methods for this, so, maybe needs all custom loader?
|
||||
// else if("stateMachine".equalsIgnoreCase(stepType))
|
||||
// {
|
||||
// step = new QStateMachineStep();
|
||||
// reflectivelyMap(qInstance, step, map, context);
|
||||
// }
|
||||
else
|
||||
{
|
||||
throw (new QMetaDataLoaderException("Unsupported step stepType: " + stepType));
|
||||
}
|
||||
|
||||
return (step);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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.instances.loaders.implementations;
|
||||
|
||||
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.instances.loaders.AbstractMetaDataLoader;
|
||||
import com.kingsrook.qqq.backend.core.instances.loaders.LoadingContext;
|
||||
import com.kingsrook.qqq.backend.core.instances.loaders.QMetaDataLoaderException;
|
||||
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.tables.QTableMetaData;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class QTableMetaDataLoader extends AbstractMetaDataLoader<QTableMetaData>
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(QTableMetaDataLoader.class);
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public QTableMetaData mapToMetaDataObject(QInstance qInstance, Map<String, Object> map, LoadingContext context) throws QMetaDataLoaderException
|
||||
{
|
||||
QTableMetaData table = new QTableMetaData();
|
||||
|
||||
reflectivelyMap(qInstance, table, map, context);
|
||||
|
||||
// todo - handle QTableBackendDetails, based on backend's type
|
||||
|
||||
return (table);
|
||||
}
|
||||
|
||||
}
|
@ -31,6 +31,7 @@ import java.util.Objects;
|
||||
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;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QMetaDataObject;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
|
||||
@ -40,7 +41,7 @@ import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
*
|
||||
*******************************************************************************/
|
||||
@JsonDeserialize(using = QFilterCriteriaDeserializer.class)
|
||||
public class QFilterCriteria implements Serializable, Cloneable
|
||||
public class QFilterCriteria implements Serializable, Cloneable, QMetaDataObject
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(QFilterCriteria.class);
|
||||
|
||||
|
@ -23,13 +23,14 @@ package com.kingsrook.qqq.backend.core.model.actions.tables.query;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QMetaDataObject;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Bean representing an element of a query order-by clause.
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class QFilterOrderBy implements Serializable, Cloneable
|
||||
public class QFilterOrderBy implements Serializable, Cloneable, QMetaDataObject
|
||||
{
|
||||
private String fieldName;
|
||||
private boolean isAscending = true;
|
||||
|
@ -36,6 +36,7 @@ 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.query.expressions.AbstractFilterExpression;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.expressions.FilterVariableExpression;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QMetaDataObject;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
@ -45,7 +46,7 @@ import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
* Full "filter" for a query - a list of criteria and order-bys
|
||||
*
|
||||
*******************************************************************************/
|
||||
public class QQueryFilter implements Serializable, Cloneable
|
||||
public class QQueryFilter implements Serializable, Cloneable, QMetaDataObject
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(QQueryFilter.class);
|
||||
|
||||
|
@ -1245,7 +1245,7 @@ public class QInstance
|
||||
{
|
||||
this.supplementalMetaData = new HashMap<>();
|
||||
}
|
||||
this.supplementalMetaData.put(supplementalMetaData.getType(), supplementalMetaData);
|
||||
this.supplementalMetaData.put(supplementalMetaData.getName(), supplementalMetaData);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** interface common among all objects that can be considered qqq meta data -
|
||||
** e.g., stored in a QInstance.
|
||||
*******************************************************************************/
|
||||
public interface QMetaDataObject extends Serializable
|
||||
{
|
||||
}
|
@ -22,6 +22,7 @@
|
||||
package com.kingsrook.qqq.backend.core.model.metadata;
|
||||
|
||||
|
||||
import java.util.function.Supplier;
|
||||
import com.kingsrook.qqq.backend.core.instances.QInstanceValidator;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
|
||||
@ -30,20 +31,13 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
** Base-class for instance-level meta-data defined by some supplemental module, etc,
|
||||
** outside of qqq core
|
||||
*******************************************************************************/
|
||||
public abstract class QSupplementalInstanceMetaData implements TopLevelMetaDataInterface
|
||||
public interface QSupplementalInstanceMetaData extends TopLevelMetaDataInterface
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for type
|
||||
*******************************************************************************/
|
||||
public abstract String getType();
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void enrich(QTableMetaData table)
|
||||
default void enrich(QTableMetaData table)
|
||||
{
|
||||
////////////////////////
|
||||
// noop in base class //
|
||||
@ -55,7 +49,7 @@ public abstract class QSupplementalInstanceMetaData implements TopLevelMetaDataI
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void validate(QInstance qInstance, QInstanceValidator validator)
|
||||
default void validate(QInstance qInstance, QInstanceValidator validator)
|
||||
{
|
||||
////////////////////////
|
||||
// noop in base class //
|
||||
@ -68,9 +62,33 @@ public abstract class QSupplementalInstanceMetaData implements TopLevelMetaDataI
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void addSelfToInstance(QInstance qInstance)
|
||||
default void addSelfToInstance(QInstance qInstance)
|
||||
{
|
||||
qInstance.withSupplementalMetaData(this);
|
||||
}
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
static <S extends QSupplementalInstanceMetaData> S of(QInstance qInstance, String name)
|
||||
{
|
||||
return ((S) qInstance.getSupplementalMetaData(name));
|
||||
}
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
static <S extends QSupplementalInstanceMetaData> S ofOrWithNew(QInstance qInstance, String name, Supplier<S> supplier)
|
||||
{
|
||||
S s = (S) qInstance.getSupplementalMetaData(name);
|
||||
if(s == null)
|
||||
{
|
||||
s = supplier.get();
|
||||
s.addSelfToInstance(qInstance);
|
||||
}
|
||||
return (s);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ package com.kingsrook.qqq.backend.core.model.metadata;
|
||||
** Interface for meta-data classes that can be added directly (e.g, at the top
|
||||
** level) to a QInstance (such as a QTableMetaData - not a QFieldMetaData).
|
||||
*******************************************************************************/
|
||||
public interface TopLevelMetaDataInterface extends MetaDataProducerOutput
|
||||
public interface TopLevelMetaDataInterface extends MetaDataProducerOutput, QMetaDataObject
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
|
@ -22,10 +22,13 @@
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.audits;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QMetaDataObject;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class QAuditRules
|
||||
public class QAuditRules implements QMetaDataObject
|
||||
{
|
||||
private AuditLevel auditLevel;
|
||||
|
||||
|
@ -23,13 +23,14 @@ package com.kingsrook.qqq.backend.core.model.metadata.code;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QMetaDataObject;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Pointer to code to be ran by the qqq framework, e.g., for custom behavior -
|
||||
** maybe process steps, maybe customization to a table, etc.
|
||||
*******************************************************************************/
|
||||
public class QCodeReference implements Serializable
|
||||
public class QCodeReference implements Serializable, QMetaDataObject
|
||||
{
|
||||
private String name;
|
||||
private QCodeType codeType;
|
||||
|
@ -40,6 +40,7 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QField;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QMetaDataObject;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.help.HelpRole;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.help.QHelpContent;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
|
||||
@ -54,7 +55,7 @@ import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
** Meta-data to represent a single field in a table.
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class QFieldMetaData implements Cloneable
|
||||
public class QFieldMetaData implements Cloneable, QMetaDataObject
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(QFieldMetaData.class);
|
||||
|
||||
|
@ -25,6 +25,7 @@ package com.kingsrook.qqq.backend.core.model.metadata.help;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QMetaDataObject;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -41,7 +42,7 @@ import java.util.Set;
|
||||
** May be dynamically added to meta-data via (non-meta-) data - see
|
||||
** HelpContentMetaDataProvider and QInstanceHelpContentManager
|
||||
*******************************************************************************/
|
||||
public class QHelpContent
|
||||
public class QHelpContent implements QMetaDataObject
|
||||
{
|
||||
private String content;
|
||||
private HelpFormat format;
|
||||
|
@ -22,11 +22,14 @@
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.layout;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QMetaDataObject;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Interface shared by meta-data objects which can be placed into an App.
|
||||
** e.g., Tables, Processes, and Apps themselves (since they can be nested)
|
||||
*******************************************************************************/
|
||||
public interface QAppChildMetaData
|
||||
public interface QAppChildMetaData extends QMetaDataObject
|
||||
{
|
||||
/*******************************************************************************
|
||||
**
|
||||
|
@ -24,12 +24,13 @@ package com.kingsrook.qqq.backend.core.model.metadata.layout;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QMetaDataObject;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** A section of apps/tables/processes - a logical grouping.
|
||||
*******************************************************************************/
|
||||
public class QAppSection implements Cloneable
|
||||
public class QAppSection implements Cloneable, QMetaDataObject
|
||||
{
|
||||
private String name;
|
||||
private String label;
|
||||
|
@ -22,6 +22,9 @@
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.layout;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QMetaDataObject;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Icon to show associated with an App, Table, Process, etc.
|
||||
**
|
||||
@ -31,7 +34,7 @@ package com.kingsrook.qqq.backend.core.model.metadata.layout;
|
||||
** Future may allow something like a "namespace", and/or multiple icons for
|
||||
** use in different frontends, etc.
|
||||
*******************************************************************************/
|
||||
public class QIcon
|
||||
public class QIcon implements QMetaDataObject
|
||||
{
|
||||
private String name;
|
||||
private String path;
|
||||
|
@ -22,13 +22,14 @@
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.permissions;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QMetaDataObject;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class QPermissionRules implements Cloneable
|
||||
public class QPermissionRules implements Cloneable, QMetaDataObject
|
||||
{
|
||||
private PermissionLevel level;
|
||||
private DenyBehavior denyBehavior;
|
||||
|
@ -25,12 +25,13 @@ package com.kingsrook.qqq.backend.core.model.metadata.processes;
|
||||
import java.io.Serializable;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QMetaDataObject;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Definition of a UI component in a frontend process steps.
|
||||
*******************************************************************************/
|
||||
public class QFrontendComponentMetaData
|
||||
public class QFrontendComponentMetaData implements QMetaDataObject
|
||||
{
|
||||
private QComponentType type;
|
||||
|
||||
|
@ -320,12 +320,23 @@ public class QProcessMetaData implements QAppChildMetaData, MetaDataWithPermissi
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for stepList
|
||||
** Setter for stepList - note - calling this method ALSO overwrites the steps map!
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setStepList(List<QStepMetaData> stepList)
|
||||
{
|
||||
this.stepList = stepList;
|
||||
if(stepList == null)
|
||||
{
|
||||
this.stepList = null;
|
||||
this.steps = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.stepList = new ArrayList<>();
|
||||
this.steps = new HashMap<>();
|
||||
}
|
||||
|
||||
withStepList(stepList);
|
||||
}
|
||||
|
||||
|
||||
|
@ -185,4 +185,25 @@ public class QStateMachineStep extends QStepMetaData
|
||||
return (rs);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for subSteps
|
||||
*******************************************************************************/
|
||||
public void setSubSteps(List<QStepMetaData> subSteps)
|
||||
{
|
||||
this.subSteps = subSteps;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for subSteps
|
||||
*******************************************************************************/
|
||||
public QStateMachineStep withSubSteps(List<QStepMetaData> subSteps)
|
||||
{
|
||||
this.subSteps = subSteps;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QMetaDataObject;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.serialization.QStepMetaDataDeserializer;
|
||||
|
||||
@ -37,7 +38,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.serialization.QStepMetaData
|
||||
**
|
||||
*******************************************************************************/
|
||||
@JsonDeserialize(using = QStepMetaDataDeserializer.class)
|
||||
public abstract class QStepMetaData
|
||||
public abstract class QStepMetaData implements QMetaDataObject
|
||||
{
|
||||
private String name;
|
||||
private String label;
|
||||
|
@ -22,6 +22,7 @@
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.scheduleing;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QMetaDataObject;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
|
||||
|
||||
@ -35,7 +36,7 @@ import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
** same moment.
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class QScheduleMetaData
|
||||
public class QScheduleMetaData implements QMetaDataObject
|
||||
{
|
||||
private String schedulerName;
|
||||
private String description;
|
||||
|
@ -22,11 +22,14 @@
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.tables;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QMetaDataObject;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** definition of a qqq table that is "associated" with another table, e.g.,
|
||||
** managed along with it - such as child-records under a parent record.
|
||||
*******************************************************************************/
|
||||
public class Association
|
||||
public class Association implements QMetaDataObject
|
||||
{
|
||||
private String name;
|
||||
private String associatedTableName;
|
||||
|
@ -26,6 +26,7 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import com.kingsrook.qqq.backend.core.instances.QInstanceHelpContentManager;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QMetaDataObject;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.help.HelpRole;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.help.QHelpContent;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
|
||||
@ -36,7 +37,7 @@ import com.kingsrook.qqq.backend.core.utils.collections.MutableList;
|
||||
** A section of fields - a logical grouping.
|
||||
** TODO - this class should be named QTableSection!
|
||||
*******************************************************************************/
|
||||
public class QFieldSection
|
||||
public class QFieldSection implements QMetaDataObject
|
||||
{
|
||||
private String name;
|
||||
private String label;
|
||||
|
@ -25,6 +25,7 @@ package com.kingsrook.qqq.backend.core.model.metadata.tables;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QMetaDataObject;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
|
||||
|
||||
@ -32,7 +33,7 @@ import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
** Definition of a Unique Key (or "Constraint", if you wanna use fancy words)
|
||||
** on a QTable.
|
||||
*******************************************************************************/
|
||||
public class UniqueKey
|
||||
public class UniqueKey implements QMetaDataObject
|
||||
{
|
||||
private List<String> fieldNames;
|
||||
private String label;
|
||||
|
@ -22,11 +22,14 @@
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.tables.automation;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QMetaDataObject;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Table-automation meta-data to define how this table's per-record automation
|
||||
** status is tracked.
|
||||
*******************************************************************************/
|
||||
public class AutomationStatusTracking
|
||||
public class AutomationStatusTracking implements QMetaDataObject
|
||||
{
|
||||
private AutomationStatusTrackingType type;
|
||||
|
||||
|
@ -24,13 +24,14 @@ package com.kingsrook.qqq.backend.core.model.metadata.tables.automation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QMetaDataObject;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.scheduleing.QScheduleMetaData;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Details about how this table's record automations are set up.
|
||||
*******************************************************************************/
|
||||
public class QTableAutomationDetails
|
||||
public class QTableAutomationDetails implements QMetaDataObject
|
||||
{
|
||||
private AutomationStatusTracking statusTracking;
|
||||
private String providerName;
|
||||
|
@ -25,13 +25,14 @@ package com.kingsrook.qqq.backend.core.model.metadata.tables.automation;
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QMetaDataObject;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Definition of a specific action to run against a table
|
||||
*******************************************************************************/
|
||||
public class TableAutomationAction
|
||||
public class TableAutomationAction implements QMetaDataObject
|
||||
{
|
||||
private String name;
|
||||
private TriggerEvent triggerEvent;
|
||||
|
@ -61,6 +61,39 @@ public class ClassPathUtils
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** from https://stackoverflow.com/questions/520328/can-you-find-all-classes-in-a-package-using-reflection
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static List<Class<?>> getClassesContainingNameAndOfType(String nameContains, Class<?> type) throws IOException
|
||||
{
|
||||
List<Class<?>> classes = new ArrayList<>();
|
||||
ClassLoader loader = Thread.currentThread().getContextClassLoader();
|
||||
|
||||
for(ClassPath.ClassInfo info : getTopLevelClasses(loader))
|
||||
{
|
||||
try
|
||||
{
|
||||
if(info.getName().contains(nameContains))
|
||||
{
|
||||
Class<?> testClass = info.load();
|
||||
if(type.isAssignableFrom(testClass))
|
||||
{
|
||||
classes.add(testClass);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(Throwable t)
|
||||
{
|
||||
// ignore - comes up for non-class entries, like module-info
|
||||
}
|
||||
}
|
||||
|
||||
return (classes);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
|
@ -23,6 +23,7 @@ package com.kingsrook.qqq.backend.core.utils;
|
||||
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
@ -475,4 +476,59 @@ public class StringUtils
|
||||
return (s);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static String maskAndTruncate(String value)
|
||||
{
|
||||
return (maskAndTruncate(value, "** MASKED **", 6, 4));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static String maskAndTruncate(String value, String mask, int minLengthToMask, int charsToShowOnEnds)
|
||||
{
|
||||
if(!hasContent(value))
|
||||
{
|
||||
return ("");
|
||||
}
|
||||
|
||||
if(value.length() < minLengthToMask || value.length() < 2 * charsToShowOnEnds)
|
||||
{
|
||||
return mask;
|
||||
}
|
||||
|
||||
if(value.length() < charsToShowOnEnds * 3)
|
||||
{
|
||||
return (value.substring(0, charsToShowOnEnds) + mask);
|
||||
}
|
||||
|
||||
return (value.substring(0, charsToShowOnEnds) + mask + value.substring(value.length() - charsToShowOnEnds));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public static String nCopies(int n, String s)
|
||||
{
|
||||
return (nCopiesWithGlue(n, s, ""));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public static String nCopiesWithGlue(int n, String s, String glue)
|
||||
{
|
||||
return (StringUtils.join(glue, Collections.nCopies(n, s)));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -43,6 +43,9 @@ class AbstractMetaDataProducerBasedQQQApplicationTest extends BaseTest
|
||||
{
|
||||
QInstance qInstance = new TestApplication().defineQInstance();
|
||||
assertEquals(1, qInstance.getTables().size());
|
||||
assertEquals("fromProducer", qInstance.getTables().get("fromProducer").getName());
|
||||
assertEquals(1, qInstance.getProcesses().size());
|
||||
assertEquals("fromProducer", qInstance.getProcesses().get("fromProducer").getName());
|
||||
}
|
||||
|
||||
|
||||
|
@ -0,0 +1,66 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.BaseTest;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.instances.producers.TestMetaDataProducer;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Unit test for MetaDataProducerBasedQQQApplication
|
||||
*******************************************************************************/
|
||||
class MetaDataProducerBasedQQQApplicationTest extends BaseTest
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void test() throws QException
|
||||
{
|
||||
QInstance qInstance = new MetaDataProducerBasedQQQApplication(getClass().getPackage().getName() + ".producers").defineQInstance();
|
||||
assertEquals(1, qInstance.getTables().size());
|
||||
assertEquals("fromProducer", qInstance.getTables().get("fromProducer").getName());
|
||||
assertEquals(1, qInstance.getProcesses().size());
|
||||
assertEquals("fromProducer", qInstance.getProcesses().get("fromProducer").getName());
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testConstructorThatTakeClass() throws QException
|
||||
{
|
||||
QInstance qInstance = new MetaDataProducerBasedQQQApplication(TestMetaDataProducer.class).defineQInstance();
|
||||
assertEquals(1, qInstance.getTables().size());
|
||||
assertEquals("fromProducer", qInstance.getTables().get("fromProducer").getName());
|
||||
assertEquals(1, qInstance.getProcesses().size());
|
||||
assertEquals("fromProducer", qInstance.getProcesses().get("fromProducer").getName());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,136 @@
|
||||
/*
|
||||
* 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.loaders;
|
||||
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import com.kingsrook.qqq.backend.core.BaseTest;
|
||||
import com.kingsrook.qqq.backend.core.instances.loaders.implementations.GenericMetaDataLoader;
|
||||
import com.kingsrook.qqq.backend.core.instances.loaders.implementations.QTableMetaDataLoader;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Unit test for AbstractMetaDataLoader
|
||||
*******************************************************************************/
|
||||
class AbstractMetaDataLoaderTest extends BaseTest
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testVariousPropertyTypes() throws QMetaDataLoaderException
|
||||
{
|
||||
QProcessMetaData process = new GenericMetaDataLoader<>(QProcessMetaData.class).fileToMetaDataObject(new QInstance(), IOUtils.toInputStream("""
|
||||
class: QProcessMetaData
|
||||
version: 1
|
||||
name: myProcess
|
||||
tableName: someTable
|
||||
maxInputRecords: 1
|
||||
isHidden: true
|
||||
""", StandardCharsets.UTF_8), "myProcess.yaml");
|
||||
|
||||
assertEquals("myProcess", process.getName());
|
||||
assertEquals("someTable", process.getTableName());
|
||||
assertEquals(1, process.getMaxInputRecords());
|
||||
assertTrue(process.getIsHidden());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testProblems() throws QMetaDataLoaderException
|
||||
{
|
||||
{
|
||||
QTableMetaDataLoader loader = new QTableMetaDataLoader();
|
||||
loader.fileToMetaDataObject(new QInstance(), IOUtils.toInputStream("""
|
||||
class: QTableMetaData
|
||||
version: 1.0
|
||||
name: myTable
|
||||
something: foo
|
||||
isHidden: hi
|
||||
icon:
|
||||
name: account_tree
|
||||
size: big
|
||||
weight: bold
|
||||
fields:
|
||||
id:
|
||||
type: number
|
||||
uniqueKeys: sure!
|
||||
""", StandardCharsets.UTF_8), "myTable.yaml");
|
||||
|
||||
for(LoadingProblem problem : loader.getProblems())
|
||||
{
|
||||
System.out.println(problem);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
GenericMetaDataLoader<QProcessMetaData> loader = new GenericMetaDataLoader<>(QProcessMetaData.class);
|
||||
loader.fileToMetaDataObject(new QInstance(), IOUtils.toInputStream("""
|
||||
class: QProcessMetaData
|
||||
version: 1.0
|
||||
name: myProcess
|
||||
maxInputRecords: many
|
||||
""", StandardCharsets.UTF_8), "myProcess.yaml");
|
||||
|
||||
for(LoadingProblem problem : loader.getProblems())
|
||||
{
|
||||
System.out.println(problem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testEnvironmentValues() throws QMetaDataLoaderException
|
||||
{
|
||||
System.setProperty("myProcess.tableName", "someTable");
|
||||
System.setProperty("myProcess.maxInputRecords", "47");
|
||||
|
||||
GenericMetaDataLoader<QProcessMetaData> loader = new GenericMetaDataLoader<>(QProcessMetaData.class);
|
||||
QProcessMetaData processMetaData = loader.fileToMetaDataObject(new QInstance(), IOUtils.toInputStream("""
|
||||
class: QProcessMetaData
|
||||
version: 1.0
|
||||
name: myProcess
|
||||
tableName: ${prop.myProcess.tableName}
|
||||
maxInputRecords: ${prop.myProcess.maxInputRecords}
|
||||
""", StandardCharsets.UTF_8), "myProcess.yaml");
|
||||
|
||||
assertEquals("someTable", processMetaData.getTableName());
|
||||
assertEquals(47, processMetaData.getMaxInputRecords());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,115 @@
|
||||
/*
|
||||
* 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.instances.loaders;
|
||||
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import com.kingsrook.qqq.backend.core.BaseTest;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QMetaDataObject;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Unit test for ClassDetectingMetaDataLoader
|
||||
*******************************************************************************/
|
||||
class ClassDetectingMetaDataLoaderTest extends BaseTest
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testBasicSuccess() throws QMetaDataLoaderException
|
||||
{
|
||||
QMetaDataObject qMetaDataObject = new ClassDetectingMetaDataLoader().fileToMetaDataObject(new QInstance(), IOUtils.toInputStream("""
|
||||
class: QTableMetaData
|
||||
version: 1
|
||||
name: myTable
|
||||
backendName: someBackend
|
||||
""", StandardCharsets.UTF_8), "myTable.yaml");
|
||||
|
||||
assertThat(qMetaDataObject).isInstanceOf(QTableMetaData.class);
|
||||
QTableMetaData qTableMetaData = (QTableMetaData) qMetaDataObject;
|
||||
assertEquals("myTable", qTableMetaData.getName());
|
||||
assertEquals("someBackend", qTableMetaData.getBackendName());
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testProcess() throws QMetaDataLoaderException
|
||||
{
|
||||
QMetaDataObject qMetaDataObject = new ClassDetectingMetaDataLoader().fileToMetaDataObject(new QInstance(), IOUtils.toInputStream("""
|
||||
class: QProcessMetaData
|
||||
version: 1
|
||||
name: myProcess
|
||||
tableName: someTable
|
||||
""", StandardCharsets.UTF_8), "myProcess.yaml");
|
||||
|
||||
assertThat(qMetaDataObject).isInstanceOf(QProcessMetaData.class);
|
||||
QProcessMetaData qProcessMetaData = (QProcessMetaData) qMetaDataObject;
|
||||
assertEquals("myProcess", qProcessMetaData.getName());
|
||||
assertEquals("someTable", qProcessMetaData.getTableName());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testUnknownClassFails()
|
||||
{
|
||||
assertThatThrownBy(() -> new ClassDetectingMetaDataLoader().fileToMetaDataObject(new QInstance(), IOUtils.toInputStream("""
|
||||
class: ya whatever
|
||||
version: 1
|
||||
name: myTable
|
||||
""", StandardCharsets.UTF_8), "whatever.yaml"))
|
||||
.isInstanceOf(QMetaDataLoaderException.class)
|
||||
.hasMessageContaining("Unexpected class");
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testMissingClassAttributeFails()
|
||||
{
|
||||
assertThatThrownBy(() -> new ClassDetectingMetaDataLoader().fileToMetaDataObject(new QInstance(), IOUtils.toInputStream("""
|
||||
version: 1
|
||||
name: myTable
|
||||
""", StandardCharsets.UTF_8), "aTable.yaml"))
|
||||
.isInstanceOf(QMetaDataLoaderException.class)
|
||||
.hasMessageContaining("[class] attribute was not specified");
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,111 @@
|
||||
/*
|
||||
* 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.instances.loaders;
|
||||
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import com.kingsrook.qqq.backend.core.BaseTest;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Unit test for MetaDataLoaderHelper
|
||||
*******************************************************************************/
|
||||
class MetaDataLoaderHelperTest extends BaseTest
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void test() throws Exception
|
||||
{
|
||||
Path tempDirectory = Files.createTempDirectory(getClass().getSimpleName());
|
||||
|
||||
writeFile("myTable", ".yaml", tempDirectory, """
|
||||
class: QTableMetaData
|
||||
version: 1
|
||||
name: myTable
|
||||
label: This is My Table
|
||||
primaryKeyField: id
|
||||
fields:
|
||||
id:
|
||||
name: id
|
||||
type: INTEGER
|
||||
name:
|
||||
name: name
|
||||
type: STRING
|
||||
createDate:
|
||||
name: createDate
|
||||
type: DATE_TIME
|
||||
""");
|
||||
|
||||
writeFile("yourTable", ".yaml", tempDirectory, """
|
||||
class: QTableMetaData
|
||||
version: 1
|
||||
name: yourTable
|
||||
label: Someone else's table
|
||||
primaryKeyField: id
|
||||
fields:
|
||||
id:
|
||||
name: id
|
||||
type: INTEGER
|
||||
name:
|
||||
name: name
|
||||
type: STRING
|
||||
""");
|
||||
|
||||
QInstance qInstance = new QInstance();
|
||||
MetaDataLoaderHelper.processAllMetaDataFilesInDirectory(qInstance, tempDirectory.toFile().getAbsolutePath());
|
||||
|
||||
assertEquals(2, qInstance.getTables().size());
|
||||
|
||||
QTableMetaData myTable = qInstance.getTable("myTable");
|
||||
assertEquals("This is My Table", myTable.getLabel());
|
||||
assertEquals(3, myTable.getFields().size());
|
||||
assertEquals("id", myTable.getField("id").getName());
|
||||
assertEquals(QFieldType.INTEGER, myTable.getField("id").getType());
|
||||
|
||||
QTableMetaData yourTable = qInstance.getTable("yourTable");
|
||||
assertEquals("Someone else's table", yourTable.getLabel());
|
||||
assertEquals(2, yourTable.getFields().size());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
void writeFile(String prefix, String suffix, Path directory, String content) throws IOException
|
||||
{
|
||||
FileUtils.writeStringToFile(File.createTempFile(prefix, suffix, directory.toFile()), content, StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
/*
|
||||
* 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.instances.loaders;
|
||||
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.BaseTest;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
||||
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.QFrontendStepMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Unit test for loading a QProcessMetaData (doesn't need its own loader yet,
|
||||
** but is still a valuable high-level test target).
|
||||
*******************************************************************************/
|
||||
class QProcessMetaDataLoaderTest extends BaseTest
|
||||
{
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testYaml() throws QMetaDataLoaderException
|
||||
{
|
||||
ClassDetectingMetaDataLoader metaDataLoader = new ClassDetectingMetaDataLoader();
|
||||
QProcessMetaData process = (QProcessMetaData) metaDataLoader.fileToMetaDataObject(new QInstance(), IOUtils.toInputStream("""
|
||||
class: QProcessMetaData
|
||||
version: 1.0
|
||||
name: myProcess
|
||||
stepList:
|
||||
- name: myBackendStep
|
||||
stepType: backend
|
||||
code:
|
||||
name: com.kingsrook.test.processes.MyBackendStep
|
||||
- name: myFrontendStep
|
||||
stepType: frontend
|
||||
components:
|
||||
- type: HELP_TEXT
|
||||
values:
|
||||
foo: bar
|
||||
- type: VIEW_FORM
|
||||
viewFields:
|
||||
- name: myField
|
||||
type: STRING
|
||||
- name: yourField
|
||||
type: DATE
|
||||
""", StandardCharsets.UTF_8), "myProcess.yaml");
|
||||
|
||||
CollectionUtils.nonNullList(metaDataLoader.getProblems()).forEach(System.out::println);
|
||||
|
||||
assertEquals("myProcess", process.getName());
|
||||
assertEquals(2, process.getAllSteps().size());
|
||||
|
||||
QBackendStepMetaData myBackendStep = process.getBackendStep("myBackendStep");
|
||||
assertNotNull(myBackendStep, "myBackendStep should not be null");
|
||||
// todo - propagate this? assertEquals("myBackendStep", myBackendStep.getName());
|
||||
assertEquals("com.kingsrook.test.processes.MyBackendStep", myBackendStep.getCode().getName());
|
||||
|
||||
QFrontendStepMetaData myFrontendStep = process.getFrontendStep("myFrontendStep");
|
||||
assertNotNull(myFrontendStep, "myFrontendStep should not be null");
|
||||
assertEquals(2, myFrontendStep.getComponents().size());
|
||||
assertEquals(QComponentType.HELP_TEXT, myFrontendStep.getComponents().get(0).getType());
|
||||
assertEquals(Map.of("foo", "bar"), myFrontendStep.getComponents().get(0).getValues());
|
||||
assertEquals(QComponentType.VIEW_FORM, myFrontendStep.getComponents().get(1).getType());
|
||||
|
||||
assertEquals(2, myFrontendStep.getViewFields().size());
|
||||
assertEquals("myField", myFrontendStep.getViewFields().get(0).getName());
|
||||
assertEquals(QFieldType.STRING, myFrontendStep.getViewFields().get(0).getType());
|
||||
assertEquals("yourField", myFrontendStep.getViewFields().get(1).getName());
|
||||
assertEquals(QFieldType.DATE, myFrontendStep.getViewFields().get(1).getType());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,202 @@
|
||||
/*
|
||||
* 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.instances.loaders;
|
||||
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import com.kingsrook.qqq.backend.core.BaseTest;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.instances.loaders.implementations.QTableMetaDataLoader;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.permissions.DenyBehavior;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.permissions.PermissionLevel;
|
||||
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.Tier;
|
||||
import com.kingsrook.qqq.backend.core.utils.TestUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.YamlUtils;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Unit test for QTableMetaDataLoader
|
||||
*******************************************************************************/
|
||||
class QTableMetaDataLoaderTest extends BaseTest
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
@Disabled("Not quite yet passing - is a good goal to get to though!")
|
||||
void testToYaml() throws QMetaDataLoaderException
|
||||
{
|
||||
QTableMetaData expectedTable = QContext.getQInstance().getTable(TestUtils.TABLE_NAME_PERSON_MEMORY);
|
||||
String expectedYaml = YamlUtils.toYaml(expectedTable);
|
||||
QTableMetaData actualTable = new QTableMetaDataLoader().fileToMetaDataObject(new QInstance(), IOUtils.toInputStream(expectedYaml, StandardCharsets.UTF_8), "person.yaml");
|
||||
String actualYaml = YamlUtils.toYaml(actualTable);
|
||||
assertEquals(expectedYaml, actualYaml);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testYaml() throws QMetaDataLoaderException
|
||||
{
|
||||
QTableMetaData table = new QTableMetaDataLoader().fileToMetaDataObject(new QInstance(), IOUtils.toInputStream("""
|
||||
class: QTableMetaData
|
||||
version: 1.0
|
||||
name: myTable
|
||||
icon:
|
||||
name: account_tree
|
||||
fields:
|
||||
id:
|
||||
name: id
|
||||
type: INTEGER
|
||||
name:
|
||||
name: name
|
||||
type: STRING
|
||||
uniqueKeys:
|
||||
- label: Name
|
||||
fieldNames:
|
||||
- name
|
||||
associations:
|
||||
- name: A1
|
||||
associatedTableName: yourTable
|
||||
joinName: myTableJoinYourTable
|
||||
- name: A2
|
||||
associatedTableName: theirTable
|
||||
joinName: myTableJoinTheirTable
|
||||
permissionRules:
|
||||
level: READ_WRITE_PERMISSIONS
|
||||
denyBehavior: HIDDEN
|
||||
permissionBaseName: myTablePermissions
|
||||
customPermissionChecker:
|
||||
name: com.kingsrook.SomeChecker
|
||||
codeType: JAVA
|
||||
## todo recordSecurityLocks
|
||||
## todo auditRules
|
||||
## todo backendDetails
|
||||
## todo automationDetails
|
||||
sections:
|
||||
- name: identity
|
||||
label: Identity
|
||||
icon:
|
||||
name: badge
|
||||
tier: T1
|
||||
fieldNames:
|
||||
- id
|
||||
- firstName
|
||||
- lastName
|
||||
customizers:
|
||||
postQueryRecord:
|
||||
name: com.kingsrook.SomePostQuery
|
||||
codeType: JAVA
|
||||
preDeleteRecord:
|
||||
name: com.kingsrook.SomePreDelete
|
||||
codeType: JAVA
|
||||
disabledCapabilities:
|
||||
- TABLE_COUNT
|
||||
- QUERY_STATS
|
||||
""", StandardCharsets.UTF_8), "myTable.yaml");
|
||||
|
||||
assertEquals("myTable", table.getName());
|
||||
|
||||
assertEquals(2, table.getFields().size());
|
||||
// assertEquals("id", table.getFields().get("id").getName());
|
||||
assertEquals(QFieldType.INTEGER, table.getFields().get("id").getType());
|
||||
// assertEquals("name", table.getFields().get("name").getName());
|
||||
assertEquals(QFieldType.STRING, table.getFields().get("name").getType());
|
||||
|
||||
assertNotNull(table.getIcon());
|
||||
assertEquals("account_tree", table.getIcon().getName());
|
||||
|
||||
assertEquals(1, table.getUniqueKeys().size());
|
||||
assertEquals(List.of("name"), table.getUniqueKeys().get(0).getFieldNames());
|
||||
assertEquals("Name", table.getUniqueKeys().get(0).getLabel());
|
||||
|
||||
assertEquals(2, table.getAssociations().size());
|
||||
assertEquals("A1", table.getAssociations().get(0).getName());
|
||||
assertEquals("theirTable", table.getAssociations().get(1).getAssociatedTableName());
|
||||
|
||||
assertNotNull(table.getPermissionRules());
|
||||
assertEquals(PermissionLevel.READ_WRITE_PERMISSIONS, table.getPermissionRules().getLevel());
|
||||
assertEquals(DenyBehavior.HIDDEN, table.getPermissionRules().getDenyBehavior());
|
||||
assertEquals("myTablePermissions", table.getPermissionRules().getPermissionBaseName());
|
||||
assertNotNull(table.getPermissionRules().getCustomPermissionChecker());
|
||||
assertEquals("com.kingsrook.SomeChecker", table.getPermissionRules().getCustomPermissionChecker().getName());
|
||||
assertEquals(QCodeType.JAVA, table.getPermissionRules().getCustomPermissionChecker().getCodeType());
|
||||
|
||||
assertEquals(1, table.getSections().size());
|
||||
assertEquals("identity", table.getSections().get(0).getName());
|
||||
assertEquals(Tier.T1, table.getSections().get(0).getTier());
|
||||
assertEquals(List.of("id", "firstName", "lastName"), table.getSections().get(0).getFieldNames());
|
||||
|
||||
assertEquals(2, table.getCustomizers().size());
|
||||
assertEquals("com.kingsrook.SomePostQuery", table.getCustomizers().get(TableCustomizers.POST_QUERY_RECORD.getRole()).getName());
|
||||
assertEquals("com.kingsrook.SomePreDelete", table.getCustomizers().get(TableCustomizers.PRE_DELETE_RECORD.getRole()).getName());
|
||||
|
||||
assertEquals(Set.of(Capability.TABLE_COUNT, Capability.QUERY_STATS), table.getDisabledCapabilities());
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testSimpleJson() throws QMetaDataLoaderException
|
||||
{
|
||||
QTableMetaData table = new QTableMetaDataLoader().fileToMetaDataObject(new QInstance(), IOUtils.toInputStream("""
|
||||
{
|
||||
"class": "QTableMetaData",
|
||||
"version": "1.0",
|
||||
"name": "myTable",
|
||||
"fields":
|
||||
{
|
||||
"id": {"name": "id", "type": "INTEGER"},
|
||||
"name": {"name": "name", "type": "STRING"}
|
||||
}
|
||||
}
|
||||
""", StandardCharsets.UTF_8), "myTable.json");
|
||||
|
||||
assertEquals("myTable", table.getName());
|
||||
assertEquals(2, table.getFields().size());
|
||||
assertEquals("id", table.getFields().get("id").getName());
|
||||
assertEquals(QFieldType.INTEGER, table.getFields().get("id").getType());
|
||||
assertEquals("name", table.getFields().get("name").getName());
|
||||
assertEquals(QFieldType.STRING, table.getFields().get("name").getType());
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
/*
|
||||
* 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.loaders.implementations;
|
||||
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import com.kingsrook.qqq.backend.core.BaseTest;
|
||||
import com.kingsrook.qqq.backend.core.instances.loaders.LoadingContext;
|
||||
import com.kingsrook.qqq.backend.core.instances.loaders.QMetaDataLoaderException;
|
||||
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.processes.QProcessMetaData;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Unit test for GenericMetaDataLoader - providing coverage for AbstractMetaDataLoader.
|
||||
*******************************************************************************/
|
||||
class GenericMetaDataLoaderTest extends BaseTest
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testProcess() throws QMetaDataLoaderException
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// trying to get some coverage of various types in here (for Abstract loader) //
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
QProcessMetaData process = new GenericMetaDataLoader<>(QProcessMetaData.class).fileToMetaDataObject(new QInstance(), IOUtils.toInputStream("""
|
||||
class: QProcessMetaData
|
||||
version: 1
|
||||
name: myProcess
|
||||
tableName: someTable
|
||||
maxInputRecords: 1
|
||||
isHidden: true
|
||||
""", StandardCharsets.UTF_8), "myProcess.yaml");
|
||||
|
||||
assertEquals("myProcess", process.getName());
|
||||
assertEquals("someTable", process.getTableName());
|
||||
assertEquals(1, process.getMaxInputRecords());
|
||||
assertTrue(process.getIsHidden());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** just here for coverage of this class, as we're failing to hit it otherwise.
|
||||
*******************************************************************************/
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
@Test
|
||||
void testNoValueException()
|
||||
{
|
||||
assertThatThrownBy(() -> new GenericMetaDataLoader(QBackendMetaData.class).reflectivelyMapValue(new QInstance(), null, GenericMetaDataLoaderTest.class, "rawValue", new LoadingContext("test.yaml", "/")));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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.instances.producers.subpackage;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
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.processes.QProcessMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class TestProcessMetaDataProducer implements MetaDataProducerInterface<QProcessMetaData>
|
||||
{
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public QProcessMetaData produce(QInstance qInstance) throws QException
|
||||
{
|
||||
return new QProcessMetaData().withName("fromProducer");
|
||||
}
|
||||
|
||||
}
|
@ -333,4 +333,76 @@ class StringUtilsTest extends BaseTest
|
||||
assertEquals("a", StringUtils.emptyToNull("a"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testNCopies()
|
||||
{
|
||||
assertEquals("", StringUtils.nCopies(0, "a"));
|
||||
assertEquals("a", StringUtils.nCopies(1, "a"));
|
||||
assertEquals("aa", StringUtils.nCopies(2, "a"));
|
||||
assertEquals("ab ab ab ", StringUtils.nCopies(3, "ab "));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testNCopiesWithGlue()
|
||||
{
|
||||
assertEquals("", StringUtils.nCopiesWithGlue(0, "a", ""));
|
||||
assertEquals("", StringUtils.nCopiesWithGlue(0, "a", ","));
|
||||
assertEquals("a", StringUtils.nCopiesWithGlue(1, "a", ","));
|
||||
assertEquals("aa", StringUtils.nCopiesWithGlue(2, "a", ""));
|
||||
assertEquals("a,a", StringUtils.nCopiesWithGlue(2, "a", ","));
|
||||
assertEquals("ab ab ab", StringUtils.nCopiesWithGlue(3, "ab", " "));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testMaskAndTruncate()
|
||||
{
|
||||
assertEquals("", StringUtils.maskAndTruncate(null));
|
||||
assertEquals("", StringUtils.maskAndTruncate(""));
|
||||
assertEquals("** MASKED **", StringUtils.maskAndTruncate("1"));
|
||||
assertEquals("** MASKED **", StringUtils.maskAndTruncate("12"));
|
||||
assertEquals("** MASKED **", StringUtils.maskAndTruncate("123"));
|
||||
assertEquals("** MASKED **", StringUtils.maskAndTruncate("1234"));
|
||||
assertEquals("** MASKED **", StringUtils.maskAndTruncate("12345"));
|
||||
assertEquals("** MASKED **", StringUtils.maskAndTruncate("123456"));
|
||||
assertEquals("** MASKED **", StringUtils.maskAndTruncate("1234567"));
|
||||
assertEquals("1234** MASKED **", StringUtils.maskAndTruncate("12345678"));
|
||||
assertEquals("1234** MASKED **", StringUtils.maskAndTruncate("123456789"));
|
||||
assertEquals("1234** MASKED **", StringUtils.maskAndTruncate("1234567890"));
|
||||
assertEquals("1234** MASKED **", StringUtils.maskAndTruncate("12345678901"));
|
||||
assertEquals("1234** MASKED **9012", StringUtils.maskAndTruncate("123456789012"));
|
||||
assertEquals("1234** MASKED **6789", StringUtils.maskAndTruncate("123456789" + StringUtils.nCopies(100, "xyz") + "123456789"));
|
||||
|
||||
assertEquals("***", StringUtils.maskAndTruncate("12", "***", 3, 1));
|
||||
assertEquals("1***3", StringUtils.maskAndTruncate("123", "***", 3, 1));
|
||||
assertEquals("1***4", StringUtils.maskAndTruncate("1234", "***", 3, 1));
|
||||
assertEquals("1***5", StringUtils.maskAndTruncate("12345", "***", 3, 1));
|
||||
assertEquals("12***", StringUtils.maskAndTruncate("12345", "***", 3, 2));
|
||||
assertEquals("12***56", StringUtils.maskAndTruncate("123456", "***", 3, 2));
|
||||
|
||||
assertEquals("***", StringUtils.maskAndTruncate("12", "***", 3, 4));
|
||||
assertEquals("***", StringUtils.maskAndTruncate("123", "***", 3, 4));
|
||||
assertEquals("***", StringUtils.maskAndTruncate("1234", "***", 3, 4));
|
||||
assertEquals("***", StringUtils.maskAndTruncate("12345", "***", 3, 4));
|
||||
assertEquals("***", StringUtils.maskAndTruncate("12345", "***", 3, 4));
|
||||
assertEquals("***", StringUtils.maskAndTruncate("123456", "***", 3, 4));
|
||||
assertEquals("1234***", StringUtils.maskAndTruncate("1234567890", "***", 3, 4));
|
||||
assertEquals("1234***", StringUtils.maskAndTruncate("12345678901", "***", 3, 4));
|
||||
assertEquals("1234***9012", StringUtils.maskAndTruncate("123456789012", "***", 3, 4));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class ApiInstanceMetaDataContainer extends QSupplementalInstanceMetaData
|
||||
public class ApiInstanceMetaDataContainer implements QSupplementalInstanceMetaData
|
||||
{
|
||||
private Map<String, ApiInstanceMetaData> apis;
|
||||
|
||||
@ -71,17 +71,6 @@ public class ApiInstanceMetaDataContainer extends QSupplementalInstanceMetaData
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public String getType()
|
||||
{
|
||||
return (ApiSupplementType.NAME);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
|
@ -208,7 +208,7 @@ public class QJavalinImplementation
|
||||
*******************************************************************************/
|
||||
public QJavalinImplementation(QInstance qInstance) throws QInstanceValidationException
|
||||
{
|
||||
this(qInstance, new QJavalinMetaData());
|
||||
this(qInstance, QJavalinMetaData.ofOrWithNew(qInstance));
|
||||
}
|
||||
|
||||
|
||||
|
@ -22,25 +22,65 @@
|
||||
package com.kingsrook.qqq.backend.javalin;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QSupplementalInstanceMetaData;
|
||||
import com.kingsrook.qqq.middleware.javalin.metadata.JavalinRouteProviderMetaData;
|
||||
import org.apache.logging.log4j.Level;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** MetaData specific to a QQQ Javalin server.
|
||||
*******************************************************************************/
|
||||
public class QJavalinMetaData
|
||||
public class QJavalinMetaData implements QSupplementalInstanceMetaData
|
||||
{
|
||||
public static final String NAME = "javalin";
|
||||
|
||||
private String uploadedFileArchiveTableName;
|
||||
|
||||
private boolean loggerDisabled = false;
|
||||
|
||||
// todo - should be a code reference!!
|
||||
private Function<QJavalinAccessLogger.LogEntry, Boolean> logFilter;
|
||||
|
||||
private boolean queryWithoutLimitAllowed = false;
|
||||
private Integer queryWithoutLimitDefault = 1000;
|
||||
private Level queryWithoutLimitLogLevel = Level.INFO;
|
||||
|
||||
private List<JavalinRouteProviderMetaData> routeProviders;
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public String getName()
|
||||
{
|
||||
return (NAME);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static QJavalinMetaData of(QInstance qInstance)
|
||||
{
|
||||
return QSupplementalInstanceMetaData.of(qInstance, NAME);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static QJavalinMetaData ofOrWithNew(QInstance qInstance)
|
||||
{
|
||||
return QSupplementalInstanceMetaData.ofOrWithNew(qInstance, NAME, QJavalinMetaData::new);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -241,4 +281,35 @@ public class QJavalinMetaData
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for routeProviders
|
||||
*******************************************************************************/
|
||||
public List<JavalinRouteProviderMetaData> getRouteProviders()
|
||||
{
|
||||
return (this.routeProviders);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for routeProviders
|
||||
*******************************************************************************/
|
||||
public void setRouteProviders(List<JavalinRouteProviderMetaData> routeProviders)
|
||||
{
|
||||
this.routeProviders = routeProviders;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for routeProviders
|
||||
*******************************************************************************/
|
||||
public QJavalinMetaData withRouteProviders(List<JavalinRouteProviderMetaData> routeProviders)
|
||||
{
|
||||
this.routeProviders = routeProviders;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -22,6 +22,8 @@
|
||||
package com.kingsrook.qqq.middleware.javalin;
|
||||
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
@ -31,11 +33,17 @@ import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.utils.ClassPathUtils;
|
||||
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 com.kingsrook.qqq.backend.javalin.QJavalinImplementation;
|
||||
import com.kingsrook.qqq.backend.javalin.QJavalinMetaData;
|
||||
import com.kingsrook.qqq.middleware.javalin.metadata.JavalinRouteProviderMetaData;
|
||||
import com.kingsrook.qqq.middleware.javalin.routeproviders.ProcessBasedRouter;
|
||||
import com.kingsrook.qqq.middleware.javalin.routeproviders.SimpleFileSystemDirectoryRouter;
|
||||
import com.kingsrook.qqq.middleware.javalin.specs.AbstractMiddlewareVersion;
|
||||
import com.kingsrook.qqq.middleware.javalin.specs.v1.MiddlewareVersionV1;
|
||||
import io.javalin.Javalin;
|
||||
import io.javalin.apibuilder.EndpointGroup;
|
||||
import io.javalin.http.Context;
|
||||
import org.apache.commons.lang.BooleanUtils;
|
||||
import org.eclipse.jetty.util.resource.Resource;
|
||||
@ -97,6 +105,12 @@ public class QApplicationJavalinServer
|
||||
{
|
||||
QInstance qInstance = application.defineValidatedQInstance();
|
||||
|
||||
QJavalinMetaData qJavalinMetaData = QJavalinMetaData.of(qInstance);
|
||||
if(qJavalinMetaData != null)
|
||||
{
|
||||
addRouteProvidersFromMetaData(qJavalinMetaData);
|
||||
}
|
||||
|
||||
service = Javalin.create(config ->
|
||||
{
|
||||
if(serveFrontendMaterialDashboard)
|
||||
@ -110,7 +124,7 @@ public class QApplicationJavalinServer
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
try(Resource resource = Resource.newClassPathResource("/material-dashboard-overlay"))
|
||||
{
|
||||
if(resource !=null)
|
||||
if(resource != null)
|
||||
{
|
||||
config.staticFiles.add("/material-dashboard-overlay");
|
||||
}
|
||||
@ -166,7 +180,14 @@ public class QApplicationJavalinServer
|
||||
for(QJavalinRouteProviderInterface routeProvider : CollectionUtils.nonNullList(additionalRouteProviders))
|
||||
{
|
||||
routeProvider.setQInstance(qInstance);
|
||||
config.router.apiBuilder(routeProvider.getJavalinEndpointGroup());
|
||||
|
||||
EndpointGroup javalinEndpointGroup = routeProvider.getJavalinEndpointGroup();
|
||||
if(javalinEndpointGroup != null)
|
||||
{
|
||||
config.router.apiBuilder(javalinEndpointGroup);
|
||||
}
|
||||
|
||||
routeProvider.acceptJavalinConfig(config);
|
||||
}
|
||||
});
|
||||
|
||||
@ -183,6 +204,8 @@ public class QApplicationJavalinServer
|
||||
service.before((Context context) -> context.header("Content-Type", "application/json"));
|
||||
service.after(QJavalinImplementation::clearQContext);
|
||||
|
||||
addNullResponseCharsetFixer();
|
||||
|
||||
////////////////////////////////////////////////
|
||||
// allow a configuration-customizer to be run //
|
||||
////////////////////////////////////////////////
|
||||
@ -196,6 +219,58 @@ public class QApplicationJavalinServer
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
** initial tests with the SimpleFileSystemDirectoryRouter would sometimes
|
||||
** have a Content-Type:text/html;charset=null !
|
||||
** which doesn't seem every valid (and at least it broke our unit test).
|
||||
** so, if w see charset=null in contentType, replace it with the system
|
||||
** default, which may not be 100% right, but has to be better than "null"...
|
||||
***************************************************************************/
|
||||
private void addNullResponseCharsetFixer()
|
||||
{
|
||||
service.after((Context context) ->
|
||||
{
|
||||
String contentType = context.res().getContentType();
|
||||
if(contentType != null && contentType.contains("charset=null"))
|
||||
{
|
||||
contentType = contentType.replace("charset=null", "charset=" + Charset.defaultCharset().name());
|
||||
context.res().setContentType(contentType);
|
||||
}
|
||||
System.out.println();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
private void addRouteProvidersFromMetaData(QJavalinMetaData qJavalinMetaData) throws QException
|
||||
{
|
||||
if(qJavalinMetaData == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for(JavalinRouteProviderMetaData routeProviderMetaData : CollectionUtils.nonNullList(qJavalinMetaData.getRouteProviders()))
|
||||
{
|
||||
if(StringUtils.hasContent(routeProviderMetaData.getProcessName()) && StringUtils.hasContent(routeProviderMetaData.getHostedPath()))
|
||||
{
|
||||
withAdditionalRouteProvider(new ProcessBasedRouter(routeProviderMetaData));
|
||||
}
|
||||
else if(StringUtils.hasContent(routeProviderMetaData.getFileSystemPath()) && StringUtils.hasContent(routeProviderMetaData.getHostedPath()))
|
||||
{
|
||||
withAdditionalRouteProvider(new SimpleFileSystemDirectoryRouter(routeProviderMetaData));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw (new QException("Error processing route provider - does not have sufficient fields set."));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@ -452,6 +527,21 @@ public class QApplicationJavalinServer
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter to add a single additionalRouteProvider
|
||||
*******************************************************************************/
|
||||
public QApplicationJavalinServer withAdditionalRouteProvider(QJavalinRouteProviderInterface additionalRouteProvider)
|
||||
{
|
||||
if(this.additionalRouteProviders == null)
|
||||
{
|
||||
this.additionalRouteProviders = new ArrayList<>();
|
||||
}
|
||||
this.additionalRouteProviders.add(additionalRouteProvider);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for MILLIS_BETWEEN_HOT_SWAPS
|
||||
*******************************************************************************/
|
||||
|
@ -24,6 +24,7 @@ package com.kingsrook.qqq.middleware.javalin;
|
||||
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import io.javalin.apibuilder.EndpointGroup;
|
||||
import io.javalin.config.JavalinConfig;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -42,6 +43,22 @@ public interface QJavalinRouteProviderInterface
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
EndpointGroup getJavalinEndpointGroup();
|
||||
default EndpointGroup getJavalinEndpointGroup()
|
||||
{
|
||||
/////////////////////////////
|
||||
// no endpoints at default //
|
||||
/////////////////////////////
|
||||
return (null);
|
||||
}
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
default void acceptJavalinConfig(JavalinConfig config)
|
||||
{
|
||||
/////////////////////
|
||||
// noop at default //
|
||||
/////////////////////
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,175 @@
|
||||
/*
|
||||
* 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.middleware.javalin.metadata;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QMetaDataObject;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class JavalinRouteProviderMetaData implements QMetaDataObject
|
||||
{
|
||||
private String hostedPath;
|
||||
|
||||
private String fileSystemPath;
|
||||
private String processName;
|
||||
|
||||
private List<String> methods;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public JavalinRouteProviderMetaData()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for hostedPath
|
||||
*******************************************************************************/
|
||||
public String getHostedPath()
|
||||
{
|
||||
return (this.hostedPath);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for hostedPath
|
||||
*******************************************************************************/
|
||||
public void setHostedPath(String hostedPath)
|
||||
{
|
||||
this.hostedPath = hostedPath;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for hostedPath
|
||||
*******************************************************************************/
|
||||
public JavalinRouteProviderMetaData withHostedPath(String hostedPath)
|
||||
{
|
||||
this.hostedPath = hostedPath;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for fileSystemPath
|
||||
*******************************************************************************/
|
||||
public String getFileSystemPath()
|
||||
{
|
||||
return (this.fileSystemPath);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for fileSystemPath
|
||||
*******************************************************************************/
|
||||
public void setFileSystemPath(String fileSystemPath)
|
||||
{
|
||||
this.fileSystemPath = fileSystemPath;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for fileSystemPath
|
||||
*******************************************************************************/
|
||||
public JavalinRouteProviderMetaData withFileSystemPath(String fileSystemPath)
|
||||
{
|
||||
this.fileSystemPath = fileSystemPath;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for processName
|
||||
*******************************************************************************/
|
||||
public String getProcessName()
|
||||
{
|
||||
return (this.processName);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for processName
|
||||
*******************************************************************************/
|
||||
public void setProcessName(String processName)
|
||||
{
|
||||
this.processName = processName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for processName
|
||||
*******************************************************************************/
|
||||
public JavalinRouteProviderMetaData withProcessName(String processName)
|
||||
{
|
||||
this.processName = processName;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for methods
|
||||
*******************************************************************************/
|
||||
public List<String> getMethods()
|
||||
{
|
||||
return (this.methods);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for methods
|
||||
*******************************************************************************/
|
||||
public void setMethods(List<String> methods)
|
||||
{
|
||||
this.methods = methods;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for methods
|
||||
*******************************************************************************/
|
||||
public JavalinRouteProviderMetaData withMethods(List<String> methods)
|
||||
{
|
||||
this.methods = methods;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
@ -193,6 +193,16 @@ public interface DownloadFileSupplementalAction
|
||||
***************************************************************************/
|
||||
class DownloadFileSupplementalActionOutput
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public DownloadFileSupplementalActionOutput()
|
||||
{
|
||||
////////////////////////////////////////////////////////////////
|
||||
// sorry, but here just to get test-coverage on this class... //
|
||||
////////////////////////////////////////////////////////////////
|
||||
int i = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,251 @@
|
||||
/*
|
||||
* 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.middleware.javalin.routeproviders;
|
||||
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.Serializable;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.actions.processes.RunProcessAction;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
import com.kingsrook.qqq.backend.javalin.QJavalinImplementation;
|
||||
import com.kingsrook.qqq.backend.javalin.QJavalinUtils;
|
||||
import com.kingsrook.qqq.middleware.javalin.QJavalinRouteProviderInterface;
|
||||
import com.kingsrook.qqq.middleware.javalin.metadata.JavalinRouteProviderMetaData;
|
||||
import io.javalin.apibuilder.ApiBuilder;
|
||||
import io.javalin.apibuilder.EndpointGroup;
|
||||
import io.javalin.http.Context;
|
||||
import io.javalin.http.HttpStatus;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class ProcessBasedRouter implements QJavalinRouteProviderInterface
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(ProcessBasedRouter.class);
|
||||
|
||||
private final String hostedPath;
|
||||
private final String processName;
|
||||
private final List<String> methods;
|
||||
private QInstance qInstance;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public ProcessBasedRouter(String hostedPath, String processName)
|
||||
{
|
||||
this(hostedPath, processName, null);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public ProcessBasedRouter(JavalinRouteProviderMetaData routeProvider)
|
||||
{
|
||||
this(routeProvider.getHostedPath(), routeProvider.getProcessName(), routeProvider.getMethods());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public ProcessBasedRouter(String hostedPath, String processName, List<String> methods)
|
||||
{
|
||||
this.hostedPath = hostedPath;
|
||||
this.processName = processName;
|
||||
|
||||
if(CollectionUtils.nullSafeHasContents(methods))
|
||||
{
|
||||
this.methods = methods;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.methods = List.of("GET");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public void setQInstance(QInstance qInstance)
|
||||
{
|
||||
this.qInstance = qInstance;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public EndpointGroup getJavalinEndpointGroup()
|
||||
{
|
||||
return (() ->
|
||||
{
|
||||
for(String method : methods)
|
||||
{
|
||||
switch(method.toLowerCase())
|
||||
{
|
||||
case "get" -> ApiBuilder.get(hostedPath, this::handleRequest);
|
||||
case "post" -> ApiBuilder.post(hostedPath, this::handleRequest);
|
||||
case "put" -> ApiBuilder.put(hostedPath, this::handleRequest);
|
||||
case "patch" -> ApiBuilder.patch(hostedPath, this::handleRequest);
|
||||
case "delete" -> ApiBuilder.delete(hostedPath, this::handleRequest);
|
||||
default -> throw (new IllegalArgumentException("Unrecognized method: " + method));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
private void handleRequest(Context context)
|
||||
{
|
||||
RunProcessInput input = new RunProcessInput();
|
||||
input.setProcessName(processName);
|
||||
|
||||
try
|
||||
{
|
||||
QJavalinImplementation.setupSession(context, input);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
context.header("WWW-Authenticate", "Basic realm=\"Access to this QQQ site\"");
|
||||
context.status(HttpStatus.UNAUTHORIZED);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
boolean authorized = false;
|
||||
String authorization = context.header("Authorization");
|
||||
if(authorization != null && authorization.matches("^Basic .+"))
|
||||
{
|
||||
String base64Authorization = authorization.substring("Basic ".length());
|
||||
String decoded = new String(Base64.getDecoder().decode(base64Authorization), StandardCharsets.UTF_8);
|
||||
String[] parts = decoded.split(":", 2);
|
||||
|
||||
QAuthenticationModuleDispatcher qAuthenticationModuleDispatcher = new QAuthenticationModuleDispatcher();
|
||||
QAuthenticationModuleInterface authenticationModule = qAuthenticationModuleDispatcher.getQModule(qInstance.getAuthentication());
|
||||
}
|
||||
|
||||
if(!authorized)
|
||||
{
|
||||
}
|
||||
|
||||
// todo - not always system-user session!!
|
||||
QContext.init(this.qInstance, new QSystemUserSession());
|
||||
*/
|
||||
|
||||
try
|
||||
{
|
||||
LOG.info("Running [" + processName + "] to serve [" + context.path() + "]...");
|
||||
|
||||
/////////////////////
|
||||
// run the process //
|
||||
/////////////////////
|
||||
input.setFrontendStepBehavior(RunProcessInput.FrontendStepBehavior.SKIP);
|
||||
input.addValue("path", context.path());
|
||||
input.addValue("method", context.method());
|
||||
input.addValue("pathParams", new HashMap<>(context.pathParamMap()));
|
||||
input.addValue("queryParams", new HashMap<>(context.queryParamMap()));
|
||||
input.addValue("formParams", new HashMap<>(context.formParamMap()));
|
||||
RunProcessOutput runProcessOutput = new RunProcessAction().execute(input);
|
||||
|
||||
/////////////////
|
||||
// status code //
|
||||
/////////////////
|
||||
Integer statusCode = runProcessOutput.getValueInteger("statusCode");
|
||||
if(statusCode != null)
|
||||
{
|
||||
context.status(statusCode);
|
||||
}
|
||||
|
||||
/////////////////
|
||||
// headers map //
|
||||
/////////////////
|
||||
Serializable headers = runProcessOutput.getValue("responseHeaders");
|
||||
if(headers instanceof Map headersMap)
|
||||
{
|
||||
for(Object key : headersMap.keySet())
|
||||
{
|
||||
context.header(ValueUtils.getValueAsString(key), ValueUtils.getValueAsString(headersMap.get(key)));
|
||||
}
|
||||
}
|
||||
|
||||
// todo - make the inputStream available to the process
|
||||
// maybe via the callback object??? input.setCallback(new QProcessCallback() {});
|
||||
// context.resultInputStream();
|
||||
|
||||
///////////////////
|
||||
// response body //
|
||||
///////////////////
|
||||
Serializable response = runProcessOutput.getValue("response");
|
||||
if(response instanceof String s)
|
||||
{
|
||||
context.result(s);
|
||||
}
|
||||
else if(response instanceof byte[] ba)
|
||||
{
|
||||
context.result(ba);
|
||||
}
|
||||
else if(response instanceof InputStream is)
|
||||
{
|
||||
context.result(is);
|
||||
}
|
||||
else
|
||||
{
|
||||
context.result(ValueUtils.getValueAsString(response));
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
QJavalinUtils.handleException(null, context, e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
QContext.clear();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
/*
|
||||
* 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.middleware.javalin.routeproviders;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.middleware.javalin.QJavalinRouteProviderInterface;
|
||||
import com.kingsrook.qqq.middleware.javalin.metadata.JavalinRouteProviderMetaData;
|
||||
import io.javalin.config.JavalinConfig;
|
||||
import io.javalin.http.staticfiles.Location;
|
||||
import io.javalin.http.staticfiles.StaticFileConfig;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class SimpleFileSystemDirectoryRouter implements QJavalinRouteProviderInterface
|
||||
{
|
||||
private final String hostedPath;
|
||||
private final String fileSystemPath;
|
||||
private QInstance qInstance;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public SimpleFileSystemDirectoryRouter(String hostedPath, String fileSystemPath)
|
||||
{
|
||||
this.hostedPath = hostedPath;
|
||||
this.fileSystemPath = fileSystemPath;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public SimpleFileSystemDirectoryRouter(JavalinRouteProviderMetaData routeProvider)
|
||||
{
|
||||
this(routeProvider.getHostedPath(), routeProvider.getFileSystemPath());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public void setQInstance(QInstance qInstance)
|
||||
{
|
||||
this.qInstance = qInstance;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public void acceptJavalinConfig(JavalinConfig config)
|
||||
{
|
||||
config.staticFiles.add((StaticFileConfig userConfig) ->
|
||||
{
|
||||
userConfig.hostedPath = hostedPath;
|
||||
userConfig.directory = fileSystemPath;
|
||||
userConfig.location = Location.EXTERNAL;
|
||||
});
|
||||
}
|
||||
}
|
@ -22,8 +22,10 @@
|
||||
package com.kingsrook.qqq.backend.javalin;
|
||||
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Paths;
|
||||
import java.sql.Connection;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
@ -95,6 +97,7 @@ import com.kingsrook.qqq.backend.core.processes.implementations.mock.MockBackend
|
||||
import com.kingsrook.qqq.backend.module.rdbms.jdbc.ConnectionManager;
|
||||
import com.kingsrook.qqq.backend.module.rdbms.jdbc.QueryManager;
|
||||
import com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSBackendMetaData;
|
||||
import com.kingsrook.qqq.middleware.javalin.metadata.JavalinRouteProviderMetaData;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
||||
@ -123,6 +126,8 @@ public class TestUtils
|
||||
public static final String SCREEN_0 = "screen0";
|
||||
public static final String SCREEN_1 = "screen1";
|
||||
|
||||
public static final String STATIC_SITE_PATH = Paths.get("").toAbsolutePath() + "/static-site";
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -184,10 +189,25 @@ public class TestUtils
|
||||
qInstance.addProcess(defineProcessScreenThenSleep());
|
||||
qInstance.addProcess(defineProcessPutsNullKeyInMap());
|
||||
qInstance.addProcess(defineProcessSimpleThrow());
|
||||
qInstance.addProcess(defineRouterProcess());
|
||||
qInstance.addReport(definePersonsReport());
|
||||
qInstance.addPossibleValueSource(definePossibleValueSourcePerson());
|
||||
defineWidgets(qInstance);
|
||||
|
||||
List<JavalinRouteProviderMetaData> routeProviders = new ArrayList<>();
|
||||
if(new File(STATIC_SITE_PATH).exists())
|
||||
{
|
||||
routeProviders.add(new JavalinRouteProviderMetaData()
|
||||
.withHostedPath("/statically-served")
|
||||
.withFileSystemPath(STATIC_SITE_PATH));
|
||||
}
|
||||
|
||||
routeProviders.add(new JavalinRouteProviderMetaData()
|
||||
.withHostedPath("/served-by-process/<pagePath>")
|
||||
.withProcessName("routerProcess"));
|
||||
|
||||
qInstance.withSupplementalMetaData(new QJavalinMetaData().withRouteProviders(routeProviders));
|
||||
|
||||
qInstance.addBackend(defineMemoryBackend());
|
||||
try
|
||||
{
|
||||
@ -206,6 +226,25 @@ public class TestUtils
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
*
|
||||
***************************************************************************/
|
||||
private static QProcessMetaData defineRouterProcess()
|
||||
{
|
||||
return (new QProcessMetaData()
|
||||
.withName("routerProcess")
|
||||
.withStep(new QBackendStepMetaData()
|
||||
.withName("step")
|
||||
.withCode(new QCodeReferenceLambda<BackendStep>((runBackendStepInput, runBackendStepOutput) ->
|
||||
{
|
||||
String path = runBackendStepInput.getValueString("path");
|
||||
runBackendStepOutput.addValue("response", "So you've asked for: " + path);
|
||||
}))
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@ -567,7 +606,6 @@ public class TestUtils
|
||||
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Define an interactive version of the 'greet people' process
|
||||
*******************************************************************************/
|
||||
@ -587,6 +625,7 @@ public class TestUtils
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Define a process with just one step that sleeps and then throws
|
||||
*******************************************************************************/
|
||||
|
@ -22,16 +22,23 @@
|
||||
package com.kingsrook.qqq.middleware.javalin;
|
||||
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.instances.AbstractQQQApplication;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.utils.SleepUtils;
|
||||
import com.kingsrook.qqq.backend.javalin.TestUtils;
|
||||
import com.kingsrook.qqq.middleware.javalin.specs.v1.MiddlewareVersionV1;
|
||||
import kong.unirest.HttpResponse;
|
||||
import kong.unirest.Unirest;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.json.JSONObject;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
@ -48,14 +55,27 @@ class QApplicationJavalinServerTest
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@BeforeEach
|
||||
void beforeEach() throws IOException
|
||||
{
|
||||
FileUtils.writeStringToFile(new File(TestUtils.STATIC_SITE_PATH + "/foo.html"), "Foo? Bar!", Charset.defaultCharset());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@AfterEach
|
||||
void afterEach()
|
||||
void afterEach() throws IOException
|
||||
{
|
||||
javalinServer.stop();
|
||||
TestApplication.callCount = 0;
|
||||
|
||||
FileUtils.deleteDirectory(new File(TestUtils.STATIC_SITE_PATH));
|
||||
}
|
||||
|
||||
|
||||
@ -123,6 +143,7 @@ class QApplicationJavalinServerTest
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -173,6 +194,42 @@ class QApplicationJavalinServerTest
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testStaticRouter() throws Exception
|
||||
{
|
||||
javalinServer = new QApplicationJavalinServer(getQqqApplication())
|
||||
.withServeFrontendMaterialDashboard(false)
|
||||
.withPort(PORT);
|
||||
javalinServer.start();
|
||||
|
||||
Unirest.config().setDefaultResponseEncoding("UTF-8");
|
||||
HttpResponse<String> response = Unirest.get("http://localhost:" + PORT + "/statically-served/foo.html").asString();
|
||||
assertEquals("Foo? Bar!", response.getBody());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testProcessRouter() throws Exception
|
||||
{
|
||||
javalinServer = new QApplicationJavalinServer(getQqqApplication())
|
||||
.withServeFrontendMaterialDashboard(false)
|
||||
.withPort(PORT);
|
||||
javalinServer.start();
|
||||
|
||||
HttpResponse<String> response = Unirest.get("http://localhost:" + PORT + "/served-by-process/foo.html").asString();
|
||||
assertEquals(200, response.getStatus());
|
||||
assertEquals("So you've asked for: /served-by-process/foo.html", response.getBody());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
|
@ -0,0 +1,82 @@
|
||||
/*
|
||||
* 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.sampleapp;
|
||||
|
||||
|
||||
import java.util.Arrays;
|
||||
import com.kingsrook.qqq.backend.core.instances.ConfigFilesBasedQQQApplication;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.middleware.javalin.QApplicationJavalinServer;
|
||||
import com.kingsrook.sampleapp.metadata.SampleMetaDataProvider;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class ConfigFileBasedSampleJavalinServer
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(ConfigFileBasedSampleJavalinServer.class);
|
||||
private final String path;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static void main(String[] args)
|
||||
{
|
||||
String path = "src/main/resources/metadata";
|
||||
if(args.length > 0)
|
||||
{
|
||||
path = args[0];
|
||||
System.out.println("Using path from args [" + path + "]");
|
||||
}
|
||||
|
||||
new ConfigFileBasedSampleJavalinServer(path).start();
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public ConfigFileBasedSampleJavalinServer(String path)
|
||||
{
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void start()
|
||||
{
|
||||
try
|
||||
{
|
||||
new QApplicationJavalinServer(new ConfigFilesBasedQQQApplication(path)).start();
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.error("Failed to start javalin server. See stack trace for details.", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -22,8 +22,10 @@
|
||||
package com.kingsrook.sampleapp;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.middleware.javalin.QApplicationJavalinServer;
|
||||
import com.kingsrook.qqq.middleware.javalin.routeproviders.SimpleFileSystemDirectoryRouter;
|
||||
import com.kingsrook.sampleapp.metadata.SampleMetaDataProvider;
|
||||
|
||||
|
||||
@ -53,7 +55,9 @@ public class SampleJavalinServer
|
||||
{
|
||||
try
|
||||
{
|
||||
new QApplicationJavalinServer(new SampleMetaDataProvider()).start();
|
||||
QApplicationJavalinServer javalinServer = new QApplicationJavalinServer(new SampleMetaDataProvider());
|
||||
javalinServer.withAdditionalRouteProvider(new SimpleFileSystemDirectoryRouter("/static-site", "/Users/dkelkhoff/tmp/static-site"));
|
||||
javalinServer.start();
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
|
@ -0,0 +1,5 @@
|
||||
---
|
||||
class: FilesystemBackendMetaData
|
||||
version: 1.0
|
||||
name: filesystem
|
||||
basePath: /tmp/sample-filesystem
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
class: QJavalinMetaData
|
||||
version: 1.0
|
||||
loggerDisabled: true
|
@ -0,0 +1,5 @@
|
||||
---
|
||||
class: QAuthenticationMetaData
|
||||
version: 1.0
|
||||
name: mock
|
||||
type: MOCK
|
@ -0,0 +1,10 @@
|
||||
---
|
||||
class: QAppMetaData
|
||||
version: 1.0
|
||||
name: people
|
||||
icon:
|
||||
name: person
|
||||
sections:
|
||||
- name: People
|
||||
tables:
|
||||
- person
|
@ -0,0 +1,41 @@
|
||||
---
|
||||
class: QTableMetaData
|
||||
version: 1.0
|
||||
name: person
|
||||
backendName: rdbms
|
||||
primaryKeyField: id
|
||||
recordLabelFormat: "%s %s"
|
||||
recordLabelFields:
|
||||
- firstName
|
||||
- lastName
|
||||
fields:
|
||||
id:
|
||||
type: INTEGER
|
||||
isEditable: false
|
||||
firstName:
|
||||
type: STRING
|
||||
lastName:
|
||||
type: STRING
|
||||
## Field(new QFieldMetaData("id", QFieldType.INTEGER).withIsEditable(false))
|
||||
## Field(new QFieldMetaData("createDate", QFieldType.DATE_TIME).withBackendName("create_date").withIsEditable(false))
|
||||
## Field(new QFieldMetaData("modifyDate", QFieldType.DATE_TIME).withBackendName("modify_date").withIsEditable(false))
|
||||
## Field(new QFieldMetaData("firstName", QFieldType.STRING).withBackendName("first_name").withIsRequired(true))
|
||||
## Field(new QFieldMetaData("lastName", QFieldType.STRING).withBackendName("last_name").withIsRequired(true))
|
||||
## Field(new QFieldMetaData("birthDate", QFieldType.DATE).withBackendName("birth_date"))
|
||||
## Field(new QFieldMetaData("email", QFieldType.STRING))
|
||||
## Field(new QFieldMetaData("isEmployed", QFieldType.BOOLEAN).withBackendName("is_employed"))
|
||||
## Field(new QFieldMetaData("annualSalary", QFieldType.DECIMAL).withBackendName("annual_salary").withDisplayFormat(DisplayFormat.CURRENCY))
|
||||
## Field(new QFieldMetaData("daysWorked", QFieldType.INTEGER).withBackendName("days_worked").withDisplayFormat(DisplayFormat.COMMAS))
|
||||
sections:
|
||||
- name: identity
|
||||
label: Identity
|
||||
icon:
|
||||
name: badge
|
||||
tier: T1
|
||||
fieldNames:
|
||||
- id
|
||||
- firstName
|
||||
- lastName
|
||||
## Section(new QFieldSection("basicInfo", "Basic Info", new QIcon("dataset"), Tier.T2, List.of("email", "birthDate")))
|
||||
## Section(new QFieldSection("employmentInfo", "Employment Info", new QIcon("work"), Tier.T2, List.of("isEmployed", "annualSalary", "daysWorked")))
|
||||
## Section(new QFieldSection("dates", "Dates", new QIcon("calendar_month"), Tier.T3, List.of("createDate", "modifyDate")));
|
@ -0,0 +1,10 @@
|
||||
---
|
||||
class: RDBMSBackendMetaData
|
||||
version: 1.0
|
||||
name: rdbms
|
||||
vendor: ${env.RDBMS_VENDOR}
|
||||
hostName: ${env.RDBMS_HOSTNAME}
|
||||
port: ${env.RDBMS_PORT}
|
||||
databaseName: ${env.RDBMS_DATABASE_NAME}
|
||||
username: ${env.RDBMS_USERNAME}
|
||||
password: ${env.RDBMS_PASSWORD}
|
Reference in New Issue
Block a user