From 656589b0df692480be60871bd95d1d5c40ee366b Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Wed, 29 Jun 2022 10:12:43 -0500 Subject: [PATCH] QQQ-14 checkpoint, pre-demo --- .../core/instances/InterpretableFields.java} | 39 +-- .../QMetaDataVariableInterpreter.java | 172 +++++++++++ .../actions/processes/RunProcessResult.java | 16 +- .../core/model/metadata/QSecretReader.java | 89 ------ .../etl/basic/BasicETLLoadFunction.java | 42 ++- .../etl/basic/BasicETLTransformFunction.java | 44 +++ .../QMetaDataVariableInterpreterTest.java | 281 ++++++++++++++++++ 7 files changed, 556 insertions(+), 127 deletions(-) rename src/{test/java/com/kingsrook/qqq/backend/core/model/metadata/QSecretReaderTest.java => main/java/com/kingsrook/qqq/backend/core/instances/InterpretableFields.java} (50%) create mode 100644 src/main/java/com/kingsrook/qqq/backend/core/instances/QMetaDataVariableInterpreter.java delete mode 100644 src/main/java/com/kingsrook/qqq/backend/core/model/metadata/QSecretReader.java create mode 100644 src/test/java/com/kingsrook/qqq/backend/core/instances/QMetaDataVariableInterpreterTest.java diff --git a/src/test/java/com/kingsrook/qqq/backend/core/model/metadata/QSecretReaderTest.java b/src/main/java/com/kingsrook/qqq/backend/core/instances/InterpretableFields.java similarity index 50% rename from src/test/java/com/kingsrook/qqq/backend/core/model/metadata/QSecretReaderTest.java rename to src/main/java/com/kingsrook/qqq/backend/core/instances/InterpretableFields.java index 91124829..ac26d6d2 100644 --- a/src/test/java/com/kingsrook/qqq/backend/core/model/metadata/QSecretReaderTest.java +++ b/src/main/java/com/kingsrook/qqq/backend/core/instances/InterpretableFields.java @@ -19,37 +19,22 @@ * along with this program. If not, see . */ -package com.kingsrook.qqq.backend.core.model.metadata; +package com.kingsrook.qqq.backend.core.instances; -import java.util.Map; -import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; /******************************************************************************* - ** Unit test for QSecretReader + ** Class-level annotation to declare what fields should run through the variable + ** interpreter - e.g., to be replaced with env-var values at run-time. *******************************************************************************/ -class QSecretReaderTest +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface InterpretableFields { - - /******************************************************************************* - ** - *******************************************************************************/ - @Test - void testReadSecret() - { - QSecretReader secretReader = new QSecretReader(); - String key = "CUSTOM_PROPERTY"; - String value = "ABCD-9876"; - secretReader.setCustomEnvironment(Map.of(key, value)); - - assertNull(secretReader.readSecret(null)); - assertEquals("foo", secretReader.readSecret("foo")); - assertNull(secretReader.readSecret("${env.NOT-" + key + "}")); - assertEquals(value, secretReader.readSecret("${env." + key + "}")); - assertEquals("${env.NOT-" + key, secretReader.readSecret("${env.NOT-" + key)); - } - -} \ No newline at end of file + String[] fieldNames() default {}; +} diff --git a/src/main/java/com/kingsrook/qqq/backend/core/instances/QMetaDataVariableInterpreter.java b/src/main/java/com/kingsrook/qqq/backend/core/instances/QMetaDataVariableInterpreter.java new file mode 100644 index 00000000..67030473 --- /dev/null +++ b/src/main/java/com/kingsrook/qqq/backend/core/instances/QMetaDataVariableInterpreter.java @@ -0,0 +1,172 @@ +/* + * 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 . + */ + +package com.kingsrook.qqq.backend.core.instances; + + +import java.lang.reflect.Method; +import java.util.Locale; +import java.util.Map; +import com.kingsrook.qqq.backend.core.exceptions.QException; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + + +/******************************************************************************* + ** To avoid having secrets (passwords, access keys, etc) committed into meta data + ** files, as well as to just let some meta data not be hard-coded, this class is + ** used by the Enricher to "promote" values, such as ${env.ACCESS_KEY} + ** to be read from the environment (or other secret providers (to be implemented)). + ** + ** Supported syntax / value sources are: + ** ${env.VAR} = system environment variables, e.g., export VAR=val + ** ${prop.VAR} = properties, e.g., -DVAR=val + ** ${literal.VAR} = get back a literal "VAR" (in case VAR matches some of the other supported syntax in here) + *******************************************************************************/ +public class QMetaDataVariableInterpreter +{ + private static final Logger LOG = LogManager.getLogger(QMetaDataVariableInterpreter.class); + + private Map customEnvironment; + + + + /******************************************************************************* + ** + *******************************************************************************/ + public void interpretObject(Object o) throws QException + { + /////////////////////////////////////////////////////////////////////////////////////////////////// + // get the InterpretableFields from the object's class - exiting if the annotation isn't present // + /////////////////////////////////////////////////////////////////////////////////////////////////// + InterpretableFields interpretableFields = o.getClass().getAnnotation(InterpretableFields.class); + if(interpretableFields == null) + { + return; + } + + ////////////////////////////////////////////////////////// + // iterate over interpretable fields, interpreting each // + ////////////////////////////////////////////////////////// + for(String fieldName : interpretableFields.fieldNames()) + { + try + { + /////////////////////////////////////////////////////////////////////////////////////// + // get the getter & setter methods for the field (getMethod will throw if not found) // + // enforce Strings-only at this time. // + /////////////////////////////////////////////////////////////////////////////////////// + String fieldNameUcFirst = fieldName.substring(0, 1).toUpperCase(Locale.ROOT) + fieldName.substring(1); + Method getter = o.getClass().getMethod("get" + fieldNameUcFirst); + Class fieldType = getter.getReturnType(); + if(!fieldType.equals(String.class)) + { + throw new QException("Interpretable field: " + fieldName + " on class " + o.getClass() + " is not a String (which is required at this time)"); + } + Method setter = o.getClass().getMethod("set" + fieldNameUcFirst, fieldType); + + ////////////////////////////////////////////////////////////////////////////////////////////// + // get the value - if it's null, move on, else, interpret it, and put it back in the object // + ////////////////////////////////////////////////////////////////////////////////////////////// + Object value = getter.invoke(o); + if(value == null) + { + continue; + } + String interpreted = interpret((String) value); + setter.invoke(o, interpreted); + } + catch(Exception e) + { + throw (new QException("Error interpreting variables in object " + o, e)); + } + } + } + + + + /******************************************************************************* + ** Interpret a value string, which may be a variable, into its run-time value. + ** + ** If input is null, output is null. + ** If input looks like ${env.X}, then the return value is the value of the env variable 'X' + ** If input looks like ${prop.X}, then the return value is the value of the system property 'X' + ** If input looks like ${literal.X}, then the return value is the literal 'X' + ** - used if you really want to get back the literal value, ${env.X}, for example. + ** Else the output is the input. + *******************************************************************************/ + public String interpret(String value) + { + if(value == null) + { + return (null); + } + + if(value.startsWith("${env.") && value.endsWith("}")) + { + String envVarName = value.substring(6).replaceFirst("}$", ""); + String envValue = getEnvironment().get(envVarName); + return (envValue); + } + + if(value.startsWith("${prop.") && value.endsWith("}")) + { + String propertyName = value.substring(7).replaceFirst("}$", ""); + String propertyValue = System.getProperty(propertyName); + return (propertyValue); + } + + if(value.startsWith("${literal.") && value.endsWith("}")) + { + String literalValue = value.substring(10).replaceFirst("}$", ""); + return (literalValue); + } + + return (value); + } + + + + /******************************************************************************* + ** Setter for customEnvironment - protected - meant to be called (at least at this + ** time), only in unit test + ** + *******************************************************************************/ + protected void setCustomEnvironment(Map customEnvironment) + { + this.customEnvironment = customEnvironment; + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + private Map getEnvironment() + { + if(this.customEnvironment != null) + { + return (this.customEnvironment); + } + + return System.getenv(); + } +} diff --git a/src/main/java/com/kingsrook/qqq/backend/core/model/actions/processes/RunProcessResult.java b/src/main/java/com/kingsrook/qqq/backend/core/model/actions/processes/RunProcessResult.java index 99f594e4..7415563d 100644 --- a/src/main/java/com/kingsrook/qqq/backend/core/model/actions/processes/RunProcessResult.java +++ b/src/main/java/com/kingsrook/qqq/backend/core/model/actions/processes/RunProcessResult.java @@ -36,7 +36,21 @@ import com.kingsrook.qqq.backend.core.model.data.QRecord; public class RunProcessResult extends AbstractQResult { private ProcessState processState; - private String error; + private String error; + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Override + public String toString() + { + return "RunProcessResult{error='" + error + + ",records.size()=" + (processState == null ? null : processState.getRecords().size()) + + ",values=" + (processState == null ? null : processState.getValues()) + + "}"; + } diff --git a/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/QSecretReader.java b/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/QSecretReader.java deleted file mode 100644 index 4b4ea3bc..00000000 --- a/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/QSecretReader.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * 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 . - */ - -package com.kingsrook.qqq.backend.core.model.metadata; - - -import java.util.Map; - - -/******************************************************************************* - ** To avoid having secrets (passwords, access keys, etc) committed into meta data - ** files, this class is used by the Enricher to "promote" values, such as ${env.ACCESS_KEY} - ** to be read from the environment (or other secret providers (to be implemented)). - *******************************************************************************/ -public class QSecretReader -{ - private Map customEnvironment; - - - - /******************************************************************************* - ** Translate a secret. - ** - ** If input is null, output is null. - ** If input looks like ${env.X}, then the return value is the value of the env variable 'X' - ** Else the output is the input. - *******************************************************************************/ - public String readSecret(String value) - { - if(value == null) - { - return (null); - } - - if(value.startsWith("${env.") && value.endsWith("}")) - { - String envVarName = value.substring(6).replaceFirst("}$", ""); - String envValue = getEnvironment().get(envVarName); - return (envValue); - } - - return (value); - } - - - - /******************************************************************************* - ** Setter for customEnvironment - protected - meant to be called (at least at this - ** time), only in unit test - ** - *******************************************************************************/ - protected void setCustomEnvironment(Map customEnvironment) - { - this.customEnvironment = customEnvironment; - } - - - - /******************************************************************************* - ** - *******************************************************************************/ - private Map getEnvironment() - { - if(this.customEnvironment != null) - { - return (this.customEnvironment); - } - - return System.getenv(); - } -} diff --git a/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/etl/basic/BasicETLLoadFunction.java b/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/etl/basic/BasicETLLoadFunction.java index 23380e56..350db227 100644 --- a/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/etl/basic/BasicETLLoadFunction.java +++ b/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/etl/basic/BasicETLLoadFunction.java @@ -22,6 +22,7 @@ package com.kingsrook.qqq.backend.core.processes.implementations.etl.basic; +import java.util.ArrayList; import java.util.List; import com.kingsrook.qqq.backend.core.actions.InsertAction; import com.kingsrook.qqq.backend.core.exceptions.QException; @@ -32,6 +33,8 @@ import com.kingsrook.qqq.backend.core.model.actions.processes.RunFunctionRequest import com.kingsrook.qqq.backend.core.model.actions.processes.RunFunctionResult; import com.kingsrook.qqq.backend.core.model.data.QRecord; import com.kingsrook.qqq.backend.core.utils.CollectionUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; /******************************************************************************* @@ -39,13 +42,22 @@ import com.kingsrook.qqq.backend.core.utils.CollectionUtils; *******************************************************************************/ public class BasicETLLoadFunction implements FunctionBody { + private static final Logger LOG = LogManager.getLogger(BasicETLLoadFunction.class); + + + + /******************************************************************************* + ** + *******************************************************************************/ @Override public void run(RunFunctionRequest runFunctionRequest, RunFunctionResult runFunctionResult) throws QException { ////////////////////////////////////////////////////// // exit early with no-op if no records made it here // ////////////////////////////////////////////////////// - if(CollectionUtils.nullSafeIsEmpty(runFunctionRequest.getRecords())) + List inputRecords = runFunctionRequest.getRecords(); + LOG.info("Received [" + inputRecords.size() + "] records to load"); + if(CollectionUtils.nullSafeIsEmpty(inputRecords)) { runFunctionResult.addValue(BasicETLProcess.FIELD_RECORD_COUNT, 0); return; @@ -55,7 +67,7 @@ public class BasicETLLoadFunction implements FunctionBody // put the destination table name in all records being inserted // ////////////////////////////////////////////////////////////////// String table = runFunctionRequest.getValueString(BasicETLProcess.FIELD_DESTINATION_TABLE); - for(QRecord record : runFunctionRequest.getRecords()) + for(QRecord record : inputRecords) { record.setTableName(table); } @@ -63,16 +75,26 @@ public class BasicETLLoadFunction implements FunctionBody ////////////////////////////////////////// // run an insert request on the records // ////////////////////////////////////////// - InsertRequest insertRequest = new InsertRequest(runFunctionRequest.getInstance()); - insertRequest.setSession(runFunctionRequest.getSession()); - insertRequest.setTableName(table); - insertRequest.setRecords(runFunctionRequest.getRecords()); + int recordsInserted = 0; + List outputRecords = new ArrayList<>(); + int pageSize = 1000; // todo - make this a field? - InsertAction insertAction = new InsertAction(); - InsertResult insertResult = insertAction.execute(insertRequest); + for(List page : CollectionUtils.getPages(inputRecords, pageSize)) + { + LOG.info("Inserting a page of [" + page.size() + "] records. Progress: " + recordsInserted + " loaded out of " + inputRecords.size() + " total"); + InsertRequest insertRequest = new InsertRequest(runFunctionRequest.getInstance()); + insertRequest.setSession(runFunctionRequest.getSession()); + insertRequest.setTableName(table); + insertRequest.setRecords(page); - runFunctionResult.setRecords(insertResult.getRecords()); - runFunctionResult.addValue(BasicETLProcess.FIELD_RECORD_COUNT, insertResult.getRecords().size()); + InsertAction insertAction = new InsertAction(); + InsertResult insertResult = insertAction.execute(insertRequest); + outputRecords.addAll(insertResult.getRecords()); + + recordsInserted += insertResult.getRecords().size(); + } + runFunctionResult.setRecords(outputRecords); + runFunctionResult.addValue(BasicETLProcess.FIELD_RECORD_COUNT, recordsInserted); } } diff --git a/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/etl/basic/BasicETLTransformFunction.java b/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/etl/basic/BasicETLTransformFunction.java index 9783e638..88bd9be5 100644 --- a/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/etl/basic/BasicETLTransformFunction.java +++ b/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/etl/basic/BasicETLTransformFunction.java @@ -22,6 +22,7 @@ package com.kingsrook.qqq.backend.core.processes.implementations.etl.basic; +import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; import com.kingsrook.qqq.backend.core.adapters.JsonToQFieldMappingAdapter; @@ -33,9 +34,12 @@ import com.kingsrook.qqq.backend.core.model.actions.shared.mapping.AbstractQFiel import com.kingsrook.qqq.backend.core.model.actions.shared.mapping.QKeyBasedFieldMapping; import com.kingsrook.qqq.backend.core.model.data.QRecord; import com.kingsrook.qqq.backend.core.model.metadata.QFieldMetaData; +import com.kingsrook.qqq.backend.core.model.metadata.QFieldType; import com.kingsrook.qqq.backend.core.model.metadata.QTableMetaData; import com.kingsrook.qqq.backend.core.utils.CollectionUtils; import com.kingsrook.qqq.backend.core.utils.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; /******************************************************************************* @@ -43,6 +47,8 @@ import com.kingsrook.qqq.backend.core.utils.StringUtils; *******************************************************************************/ public class BasicETLTransformFunction implements FunctionBody { + private static final Logger LOG = LogManager.getLogger(BasicETLTransformFunction.class); + @Override public void run(RunFunctionRequest runFunctionRequest, RunFunctionResult runFunctionResult) throws QException { @@ -72,11 +78,49 @@ public class BasicETLTransformFunction implements FunctionBody String tableName = runFunctionRequest.getValueString(BasicETLProcess.FIELD_DESTINATION_TABLE); QTableMetaData table = runFunctionRequest.getInstance().getTable(tableName); List mappedRecords = applyMapping(runFunctionRequest.getRecords(), table, keyBasedFieldMapping); + + removeNonNumericValuesFromMappedRecords(table, mappedRecords); + runFunctionResult.setRecords(mappedRecords); } + /******************************************************************************* + ** + *******************************************************************************/ + private void removeNonNumericValuesFromMappedRecords(QTableMetaData table, List records) + { + for(QRecord record : records) + { + for(QFieldMetaData field : table.getFields().values()) + { + Object value = record.getValue(field.getName()); + if(value != null && StringUtils.hasContent(String.valueOf(value))) + { + try + { + if(field.getType().equals(QFieldType.INTEGER)) + { + Integer.parseInt(String.valueOf(value)); + } + else if(field.getType().equals(QFieldType.DECIMAL)) + { + new BigDecimal(String.valueOf(value)); + } + } + catch(NumberFormatException nfe) + { + LOG.info("Removing non-numeric value [" + value + "] from field [" + field.getName() + "]"); + record.setValue(field.getName(), null); + } + } + } + } + } + + + /******************************************************************************* ** *******************************************************************************/ diff --git a/src/test/java/com/kingsrook/qqq/backend/core/instances/QMetaDataVariableInterpreterTest.java b/src/test/java/com/kingsrook/qqq/backend/core/instances/QMetaDataVariableInterpreterTest.java new file mode 100644 index 00000000..e3881b00 --- /dev/null +++ b/src/test/java/com/kingsrook/qqq/backend/core/instances/QMetaDataVariableInterpreterTest.java @@ -0,0 +1,281 @@ +/* + * 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 . + */ + +package com.kingsrook.qqq.backend.core.instances; + + +import java.util.Map; +import com.kingsrook.qqq.backend.core.exceptions.QException; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + + +/******************************************************************************* + ** Unit test for QSecretReader + *******************************************************************************/ +class QMetaDataVariableInterpreterTest +{ + + /******************************************************************************* + ** + *******************************************************************************/ + @BeforeEach + public void beforeEach() + { + System.setProperty("username", "joe"); + System.setProperty("password", "b1d3n"); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @AfterEach + public void afterEach() + { + System.clearProperty("username"); + System.clearProperty("password"); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testInterpretObject() throws QException + { + GoodTestClass goodTestClass = new GoodTestClass(); + goodTestClass.setUsername("${prop.username}"); + goodTestClass.setPassword("${prop.password}"); + + new QMetaDataVariableInterpreter().interpretObject(goodTestClass); + + assertEquals("joe", goodTestClass.getUsername()); + assertEquals("b1d3n", goodTestClass.getPassword()); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testBadAnnotatedObjects() + { + assertThrows(QException.class, () -> new QMetaDataVariableInterpreter().interpretObject(new BadTestClassAnnotatedInteger())); + assertThrows(QException.class, () -> new QMetaDataVariableInterpreter().interpretObject(new BadTestClassNoGetter())); + assertThrows(QException.class, () -> new QMetaDataVariableInterpreter().interpretObject(new BadTestClassNoSetter())); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testInterpretFromEnvironment() + { + QMetaDataVariableInterpreter secretReader = new QMetaDataVariableInterpreter(); + String key = "CUSTOM_PROPERTY"; + String value = "ABCD-9876"; + secretReader.setCustomEnvironment(Map.of(key, value)); + + assertNull(secretReader.interpret(null)); + assertEquals("foo", secretReader.interpret("foo")); + assertNull(secretReader.interpret("${env.NOT-" + key + "}")); + assertEquals(value, secretReader.interpret("${env." + key + "}")); + assertEquals("${env.NOT-" + key, secretReader.interpret("${env.NOT-" + key)); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testInterpretFromProperties() + { + QMetaDataVariableInterpreter secretReader = new QMetaDataVariableInterpreter(); + String key = "MY_PROPERTY"; + String value = "WXYZ-6789"; + System.setProperty(key, value); + + assertNull(secretReader.interpret(null)); + assertEquals("foo", secretReader.interpret("foo")); + assertNull(secretReader.interpret("${prop.NOT-" + key + "}")); + assertEquals(value, secretReader.interpret("${prop." + key + "}")); + assertEquals("${prop.NOT-" + key, secretReader.interpret("${prop.NOT-" + key)); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testInterpretLiterals() + { + QMetaDataVariableInterpreter secretReader = new QMetaDataVariableInterpreter(); + assertEquals("${env.X}", secretReader.interpret("${literal.${env.X}}")); + assertEquals("${prop.X}", secretReader.interpret("${literal.${prop.X}}")); + assertEquals("${literal.X}", secretReader.interpret("${literal.${literal.X}}")); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @InterpretableFields(fieldNames = { "username", "password" }) + public static class GoodTestClass + { + private String username; + private String password; + + + + /******************************************************************************* + ** Getter for username + ** + *******************************************************************************/ + public String getUsername() + { + return username; + } + + + + /******************************************************************************* + ** Setter for username + ** + *******************************************************************************/ + public void setUsername(String username) + { + this.username = username; + } + + + + /******************************************************************************* + ** Getter for password + ** + *******************************************************************************/ + public String getPassword() + { + return password; + } + + + + /******************************************************************************* + ** Setter for password + ** + *******************************************************************************/ + public void setPassword(String password) + { + this.password = password; + } + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @InterpretableFields(fieldNames = { "port" }) + public static class BadTestClassAnnotatedInteger + { + private Integer port; + + + + /******************************************************************************* + ** Getter for port + ** + *******************************************************************************/ + public Integer getPort() + { + return port; + } + + + + /******************************************************************************* + ** Setter for port + ** + *******************************************************************************/ + public void setPort(Integer port) + { + this.port = port; + } + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @InterpretableFields(fieldNames = { "foo" }) + public static class BadTestClassNoGetter + { + private String foo; + + + + /******************************************************************************* + ** Setter for foo + ** + *******************************************************************************/ + public void setFoo(String foo) + { + this.foo = foo; + } + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @InterpretableFields(fieldNames = { "foo" }) + public static class BadTestClassNoSetter + { + private String foo; + + + + /******************************************************************************* + ** Getter for foo + ** + *******************************************************************************/ + public String getFoo() + { + return foo; + } + } + +} \ No newline at end of file