From dceb0ee142c31eb805434fd533e81abb26f73ab3 Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Tue, 19 Dec 2023 15:12:52 -0600 Subject: [PATCH 1/4] CE-775 add timeouts to outbound http calls --- .../module/api/actions/BaseAPIActionUtil.java | 67 ++++++++++- .../api/actions/BaseAPIActionUtilTest.java | 107 ++++++++++++++++++ 2 files changed, 173 insertions(+), 1 deletion(-) diff --git a/qqq-backend-module-api/src/main/java/com/kingsrook/qqq/backend/module/api/actions/BaseAPIActionUtil.java b/qqq-backend-module-api/src/main/java/com/kingsrook/qqq/backend/module/api/actions/BaseAPIActionUtil.java index bdcb5fe6..a14b23b1 100644 --- a/qqq-backend-module-api/src/main/java/com/kingsrook/qqq/backend/module/api/actions/BaseAPIActionUtil.java +++ b/qqq-backend-module-api/src/main/java/com/kingsrook/qqq/backend/module/api/actions/BaseAPIActionUtil.java @@ -81,6 +81,7 @@ import org.apache.http.HttpEntity; import org.apache.http.HttpEntityEnclosingRequest; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; +import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpDelete; import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; @@ -1048,7 +1049,7 @@ public class BaseAPIActionUtil ////////////////////////////////////////////////////// // make sure to use closeable client to avoid leaks // ////////////////////////////////////////////////////// - try(CloseableHttpClient httpClient = HttpClientBuilder.create().build()) + try(CloseableHttpClient httpClient = buildHttpClient()) { //////////////////////////////////////////////////////////// // call utility methods that populate data in the request // @@ -1153,6 +1154,25 @@ public class BaseAPIActionUtil + /******************************************************************************* + ** Build the default HttpClient used by the makeRequest method + *******************************************************************************/ + protected CloseableHttpClient buildHttpClient() + { + /////////////////////////////////////////////////////////////////////////////////////// + // do we want this?? .setConnectionManager(new PoolingHttpClientConnectionManager()) // + // needs some good scrutiny. // + /////////////////////////////////////////////////////////////////////////////////////// + return HttpClientBuilder.create() + .setDefaultRequestConfig(RequestConfig.custom() + .setConnectTimeout(getConnectionTimeoutMillis()) + .setConnectionRequestTimeout(getConnectionRequestTimeoutMillis()) + .setSocketTimeout(getSocketTimeoutMillis()).build()) + .build(); + } + + + /******************************************************************************* ** *******************************************************************************/ @@ -1439,6 +1459,51 @@ public class BaseAPIActionUtil + /******************************************************************************* + ** For the HttpClientBuilder RequestConfig, specify its ConnectionTimeout. See + ** - https://www.baeldung.com/httpclient-timeout + ** - https://hc.apache.org/httpcomponents-client-5.1.x/current/httpclient5/apidocs/org/apache/hc/client5/http/config/RequestConfig.Builder.html + *******************************************************************************/ + protected int getConnectionTimeoutMillis() + { + ////////////// + // 1 minute // + ////////////// + return (60 * 1000); + } + + + + /******************************************************************************* + ** For the HttpClientBuilder RequestConfig, specify its ConnectionRequestTimeout. See + ** - https://www.baeldung.com/httpclient-timeout + ** - https://hc.apache.org/httpcomponents-client-5.1.x/current/httpclient5/apidocs/org/apache/hc/client5/http/config/RequestConfig.Builder.html + *******************************************************************************/ + protected int getConnectionRequestTimeoutMillis() + { + ////////////// + // 1 minute // + ////////////// + return (60 * 1000); + } + + + + /******************************************************************************* + ** For the HttpClientBuilder RequestConfig, specify its ConnectionRequestTimeout. See + ** - https://www.baeldung.com/httpclient-timeout + ** - https://hc.apache.org/httpcomponents-client-5.1.x/current/httpclient5/apidocs/org/apache/hc/client5/http/config/RequestConfig.Builder.html + *******************************************************************************/ + protected int getSocketTimeoutMillis() + { + /////////////// + // 3 minutes // + /////////////// + return (3 * 60 * 1000); + } + + + /******************************************************************************* ** *******************************************************************************/ diff --git a/qqq-backend-module-api/src/test/java/com/kingsrook/qqq/backend/module/api/actions/BaseAPIActionUtilTest.java b/qqq-backend-module-api/src/test/java/com/kingsrook/qqq/backend/module/api/actions/BaseAPIActionUtilTest.java index 25b1af88..4fb4d8a9 100644 --- a/qqq-backend-module-api/src/test/java/com/kingsrook/qqq/backend/module/api/actions/BaseAPIActionUtilTest.java +++ b/qqq-backend-module-api/src/test/java/com/kingsrook/qqq/backend/module/api/actions/BaseAPIActionUtilTest.java @@ -22,11 +22,18 @@ package com.kingsrook.qqq.backend.module.api.actions; +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.io.PrintWriter; import java.io.Serializable; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketTimeoutException; import java.nio.charset.StandardCharsets; import java.util.Base64; import java.util.List; import java.util.Map; +import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import com.kingsrook.qqq.backend.core.actions.tables.CountAction; import com.kingsrook.qqq.backend.core.actions.tables.DeleteAction; @@ -36,6 +43,7 @@ import com.kingsrook.qqq.backend.core.actions.tables.QueryAction; import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction; import com.kingsrook.qqq.backend.core.context.QContext; import com.kingsrook.qqq.backend.core.exceptions.QException; +import com.kingsrook.qqq.backend.core.logging.QLogger; import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput; import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountOutput; import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput; @@ -82,6 +90,8 @@ import static org.junit.jupiter.api.Assertions.fail; *******************************************************************************/ class BaseAPIActionUtilTest extends BaseTest { + private static final QLogger LOG = QLogger.getLogger(BaseAPIActionUtilTest.class); + private static MockApiUtilsHelper mockApiUtilsHelper = new MockApiUtilsHelper(); @@ -822,6 +832,103 @@ class BaseAPIActionUtilTest extends BaseTest + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testTimeouts() throws QException + { + ShortTimeoutActionUtil shortTimeoutActionUtil = new ShortTimeoutActionUtil(); + shortTimeoutActionUtil.setBackendMetaData((APIBackendMetaData) QContext.getQInstance().getBackend(TestUtils.MOCK_BACKEND_NAME)); + + ///////////////////////////////////////////////////////////// + // make sure we work correctly with a large enough timeout // + ///////////////////////////////////////////////////////////// + { + startSimpleHttpServer(8888); + HttpGet request = new HttpGet("http://localhost:8888"); + shortTimeoutActionUtil.setTimeoutMillis(3000); + + shortTimeoutActionUtil.makeRequest(QContext.getQInstance().getTable(TestUtils.MOCK_TABLE_NAME), request); + } + + //////////////////////////////////////////////// + // make sure we fail with a too-small timeout // + //////////////////////////////////////////////// + { + startSimpleHttpServer(8889); + HttpGet request = new HttpGet("http://localhost:8889"); + shortTimeoutActionUtil.setTimeoutMillis(1); + + assertThatThrownBy(() -> shortTimeoutActionUtil.makeRequest(QContext.getQInstance().getTable(TestUtils.MOCK_TABLE_NAME), request)) + .hasRootCauseInstanceOf(SocketTimeoutException.class) + .rootCause().hasMessageContaining("timed out"); + } + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + private static void startSimpleHttpServer(int port) + { + Executors.newSingleThreadExecutor().submit(() -> + { + LOG.info("Listening on " + port); + try(ServerSocket serverSocket = new ServerSocket(port)) + { + Socket clientSocket = serverSocket.accept(); + PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true); + BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream())); + String greeting = in.readLine(); + LOG.info("Read: " + greeting); + SleepUtils.sleep(1, TimeUnit.SECONDS); + out.println("HTTP/1.1 200 OK"); + out.close(); + clientSocket.close(); + } + catch(Exception e) + { + LOG.info("Exception in simple http server", e); + } + }); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + static class ShortTimeoutActionUtil extends BaseAPIActionUtil + { + private int timeoutMillis = 1; + + + + /******************************************************************************* + ** Setter for timeoutMillis + ** + *******************************************************************************/ + public void setTimeoutMillis(int timeoutMillis) + { + this.timeoutMillis = timeoutMillis; + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Override + protected int getSocketTimeoutMillis() + { + return (timeoutMillis); + } + } + + + /******************************************************************************* ** *******************************************************************************/ From db2e5fb7fc916f345e12999b1500d5e5e2306dd6 Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Tue, 19 Dec 2023 15:32:15 -0600 Subject: [PATCH 2/4] CE-775 Add some sleep to help timeout test --- .../backend/module/api/actions/BaseAPIActionUtilTest.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/qqq-backend-module-api/src/test/java/com/kingsrook/qqq/backend/module/api/actions/BaseAPIActionUtilTest.java b/qqq-backend-module-api/src/test/java/com/kingsrook/qqq/backend/module/api/actions/BaseAPIActionUtilTest.java index 4fb4d8a9..60923ea5 100644 --- a/qqq-backend-module-api/src/test/java/com/kingsrook/qqq/backend/module/api/actions/BaseAPIActionUtilTest.java +++ b/qqq-backend-module-api/src/test/java/com/kingsrook/qqq/backend/module/api/actions/BaseAPIActionUtilTest.java @@ -893,6 +893,11 @@ class BaseAPIActionUtilTest extends BaseTest LOG.info("Exception in simple http server", e); } }); + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // give time for the thread w/ the listening socket to start before returning control to the thread that's going to try to connect to it // + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + SleepUtils.sleep(100, TimeUnit.MILLISECONDS); } From fd185687858633577309920bae185972b863a532 Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Wed, 20 Dec 2023 14:18:34 -0600 Subject: [PATCH 3/4] Only apply mysql result set optimization per a system property, default to false. --- .../rdbms/actions/RDBMSQueryAction.java | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/qqq-backend-module-rdbms/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSQueryAction.java b/qqq-backend-module-rdbms/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSQueryAction.java index 36d23362..b93b52f2 100644 --- a/qqq-backend-module-rdbms/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSQueryAction.java +++ b/qqq-backend-module-rdbms/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSQueryAction.java @@ -39,6 +39,7 @@ import com.kingsrook.qqq.backend.core.actions.interfaces.QueryInterface; import com.kingsrook.qqq.backend.core.actions.tables.helpers.ActionTimeoutHelper; import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException; +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.JoinsContext; import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter; @@ -64,6 +65,19 @@ public class RDBMSQueryAction extends AbstractRDBMSAction implements QueryInterf private ActionTimeoutHelper actionTimeoutHelper; + private static boolean mysqlResultSetOptimizationEnabled = false; + + static + { + try + { + mysqlResultSetOptimizationEnabled = new QMetaDataVariableInterpreter().getBooleanFromPropertyOrEnvironment("qqq.rdbms.mysql.resultSetOptimizationEnabled", "QQQ_RDBMS_MYSQL_RESULT_SET_OPTIMIZATION_ENABLED", false); + } + catch(Exception e) + { + LOG.warn("Error reading property/env for mysqlResultSetOptimizationEnabled", e); + } + } /******************************************************************************* @@ -342,22 +356,19 @@ public class RDBMSQueryAction extends AbstractRDBMSAction implements QueryInterf *******************************************************************************/ private PreparedStatement createStatement(Connection connection, String sql, QueryInput queryInput) throws SQLException { - PreparedStatement statement; - if(connection.getClass().getName().startsWith("com.mysql")) + if(mysqlResultSetOptimizationEnabled && connection.getClass().getName().startsWith("com.mysql")) { ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // mysql "optimization", presumably here - from Result Set section of https://dev.mysql.com/doc/connector-j/en/connector-j-reference-implementation-notes.html // // without this change, we saw ~10 seconds of "wait" time, before results would start to stream out of a large query (e.g., > 1,000,000 rows). // // with this change, we start to get results immediately, and the total runtime also seems lower... // ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - statement = connection.prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); + PreparedStatement statement = connection.prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); statement.setFetchSize(Integer.MIN_VALUE); + return (statement); } - else - { - statement = connection.prepareStatement(sql); - } - return (statement); + + return (connection.prepareStatement(sql)); } From 8fc2b548ee4dd35f3847a03c72101a39be6318b8 Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Thu, 21 Dec 2023 15:28:34 -0600 Subject: [PATCH 4/4] Add isEnabled method to meta-data producers; Put interface on top of MetaDataProducer, for times when someone wants that; update MetaDataProducerHelper to work w/ the interface. --- .../core/model/MetaDataProducerInterface.java | 78 +++++++++++++++++++ .../core/model/metadata/MetaDataProducer.java | 26 +------ .../metadata/MetaDataProducerHelper.java | 59 ++++++++------ .../metadata/MetaDataProducerHelperTest.java | 11 +++ .../TestAbstractMetaDataProducer.java | 48 ++++++++++++ .../TestDisabledMetaDataProducer.java | 59 ++++++++++++++ .../TestImplementsMetaDataProducer.java | 48 ++++++++++++ .../TestNoInterfacesExtendsObject.java | 46 +++++++++++ ...estNoValidConstructorMetaDataProducer.java | 58 ++++++++++++++ 9 files changed, 387 insertions(+), 46 deletions(-) create mode 100644 qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/MetaDataProducerInterface.java create mode 100644 qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/metadata/producers/TestAbstractMetaDataProducer.java create mode 100644 qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/metadata/producers/TestDisabledMetaDataProducer.java create mode 100644 qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/metadata/producers/TestImplementsMetaDataProducer.java create mode 100644 qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/metadata/producers/TestNoInterfacesExtendsObject.java create mode 100644 qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/metadata/producers/TestNoValidConstructorMetaDataProducer.java diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/MetaDataProducerInterface.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/MetaDataProducerInterface.java new file mode 100644 index 00000000..45bd2ca6 --- /dev/null +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/MetaDataProducerInterface.java @@ -0,0 +1,78 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2023. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.kingsrook.qqq.backend.core.model; + + +import com.kingsrook.qqq.backend.core.exceptions.QException; +import com.kingsrook.qqq.backend.core.model.metadata.QInstance; +import com.kingsrook.qqq.backend.core.model.metadata.TopLevelMetaDataInterface; + + +/******************************************************************************* + ** Interface for classes that know how to produce meta data objects. Useful with + ** MetaDataProducerHelper, to put point at a package full of these, and populate + ** your whole QInstance. + ** + ** See also MetaDataProducer - an implementer of this interface, which actually + ** came first, and is fine to extend if producing a meta-data class is all your + ** clas means to do (nice and "Single-responsibility principle"). + ** + ** But, in some applications you may want to, for example, have one class that + ** defines a process step, and also produces the meta-data for that process, so + ** your whole process can just be one class - so then just have your step class + ** implement this interface. or, same idea for a QRecordEntity that provides + ** its own TableMetaData. + *******************************************************************************/ +public interface MetaDataProducerInterface +{ + int DEFAULT_SORT_ORDER = 500; + + + /******************************************************************************* + ** Produce the metaData object. Generally, you don't want to add it to the instance + ** yourself - but the instance is there in case you need it to get other metaData. + *******************************************************************************/ + T produce(QInstance qInstance) throws QException; + + + /******************************************************************************* + ** In case this producer needs to run before (or after) others, this method + ** can control influence that (e.g., if used by MetaDataProducerHelper). + ** + ** Smaller values run first. + *******************************************************************************/ + default int getSortOrder() + { + return (DEFAULT_SORT_ORDER); + } + + + /******************************************************************************* + ** turn this producer on or off - e.g., maybe based on an env value. + ** + *******************************************************************************/ + default boolean isEnabled() + { + return (true); + } + +} diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/MetaDataProducer.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/MetaDataProducer.java index 1b3e2148..4207a132 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/MetaDataProducer.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/MetaDataProducer.java @@ -22,7 +22,7 @@ package com.kingsrook.qqq.backend.core.model.metadata; -import com.kingsrook.qqq.backend.core.exceptions.QException; +import com.kingsrook.qqq.backend.core.model.MetaDataProducerInterface; /******************************************************************************* @@ -30,29 +30,7 @@ import com.kingsrook.qqq.backend.core.exceptions.QException; ** MetaDataProducerHelper, to put point at a package full of these, and populate ** your whole QInstance. *******************************************************************************/ -public abstract class MetaDataProducer +public abstract class MetaDataProducer implements MetaDataProducerInterface { - public static final int DEFAULT_SORT_ORDER = 500; - - - - /******************************************************************************* - ** Produce the metaData object. Generally, you don't want to add it to the instance - ** yourself - but the instance is there in case you need it to get other metaData. - *******************************************************************************/ - public abstract T produce(QInstance qInstance) throws QException; - - - - /******************************************************************************* - ** In case this producer needs to run before (or after) others, this method - ** can control influence that (e.g., if used by MetaDataProducerHelper). - ** - ** Smaller values run first. - *******************************************************************************/ - public int getSortOrder() - { - return (DEFAULT_SORT_ORDER); - } } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/MetaDataProducerHelper.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/MetaDataProducerHelper.java index 7769c329..7a52a890 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/MetaDataProducerHelper.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/MetaDataProducerHelper.java @@ -30,6 +30,7 @@ import java.util.Comparator; import java.util.List; import com.google.common.reflect.ClassPath; import com.kingsrook.qqq.backend.core.logging.QLogger; +import com.kingsrook.qqq.backend.core.model.MetaDataProducerInterface; import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData; import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair; @@ -44,7 +45,7 @@ public class MetaDataProducerHelper /******************************************************************************* - ** Recursively find all classes in the given package, that extend MetaDataProducer, + ** Recursively find all classes in the given package, that implement MetaDataProducerInterface ** run them, and add their output to the given qInstance. ** ** Note - they'll be sorted by the sortOrder they provide. @@ -54,8 +55,8 @@ public class MetaDataProducerHelper //////////////////////////////////////////////////////////////////////// // find all the meta data producer classes in (and under) the package // //////////////////////////////////////////////////////////////////////// - List> classesInPackage = getClassesInPackage(packageName); - List> producers = new ArrayList<>(); + List> classesInPackage = getClassesInPackage(packageName); + List> producers = new ArrayList<>(); for(Class aClass : classesInPackage) { try @@ -65,22 +66,29 @@ public class MetaDataProducerHelper continue; } - for(Constructor constructor : aClass.getConstructors()) + if(MetaDataProducerInterface.class.isAssignableFrom(aClass)) { - if(constructor.getParameterCount() == 0) + boolean foundValidConstructor = false; + for(Constructor constructor : aClass.getConstructors()) { - Object o = constructor.newInstance(); - if(o instanceof MetaDataProducer metaDataProducer) + if(constructor.getParameterCount() == 0) { - producers.add(metaDataProducer); + Object o = constructor.newInstance(); + producers.add((MetaDataProducerInterface) o); + foundValidConstructor = true; + break; } - break; + } + + if(!foundValidConstructor) + { + LOG.warn("Found a class which implements MetaDataProducerInterface, but it does not have a no-arg constructor, so it cannot be used.", logPair("class", aClass.getSimpleName())); } } } catch(Exception e) { - LOG.info("Error adding metaData from producer", e, logPair("producer", aClass.getSimpleName())); + LOG.warn("Error evaluating a possible meta-data producer class", e, logPair("class", aClass.getSimpleName())); } } @@ -89,8 +97,8 @@ public class MetaDataProducerHelper // after all other types (as apps often try to get other types from the instance) // //////////////////////////////////////////////////////////////////////////////////////////// producers.sort(Comparator - .comparing((MetaDataProducer p) -> p.getSortOrder()) - .thenComparing((MetaDataProducer p) -> + .comparing((MetaDataProducerInterface p) -> p.getSortOrder()) + .thenComparing((MetaDataProducerInterface p) -> { try { @@ -110,22 +118,29 @@ public class MetaDataProducerHelper } })); - ////////////////////////////////////////////////////////////// - // execute each one, adding their meta data to the instance // - ////////////////////////////////////////////////////////////// - for(MetaDataProducer producer : producers) + /////////////////////////////////////////////////////////////////////////// + // execute each one (if enabled), adding their meta data to the instance // + /////////////////////////////////////////////////////////////////////////// + for(MetaDataProducerInterface producer : producers) { - try + if(producer.isEnabled()) { - TopLevelMetaDataInterface metaData = producer.produce(instance); - if(metaData != null) + try { - metaData.addSelfToInstance(instance); + TopLevelMetaDataInterface metaData = producer.produce(instance); + if(metaData != null) + { + metaData.addSelfToInstance(instance); + } + } + catch(Exception e) + { + LOG.warn("error executing metaDataProducer", logPair("producer", producer.getClass().getSimpleName()), e); } } - catch(Exception e) + else { - LOG.warn("error executing metaDataProducer", logPair("producer", producer.getClass().getSimpleName()), e); + LOG.debug("Not using producer which is not enabled", logPair("producer", producer.getClass().getSimpleName())); } } diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/metadata/MetaDataProducerHelperTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/metadata/MetaDataProducerHelperTest.java index 8c96b8ab..ab1ae66f 100644 --- a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/metadata/MetaDataProducerHelperTest.java +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/metadata/MetaDataProducerHelperTest.java @@ -23,8 +23,14 @@ package com.kingsrook.qqq.backend.core.model.metadata; import java.io.IOException; +import com.kingsrook.qqq.backend.core.model.metadata.producers.TestAbstractMetaDataProducer; +import com.kingsrook.qqq.backend.core.model.metadata.producers.TestDisabledMetaDataProducer; +import com.kingsrook.qqq.backend.core.model.metadata.producers.TestImplementsMetaDataProducer; import com.kingsrook.qqq.backend.core.model.metadata.producers.TestMetaDataProducer; +import com.kingsrook.qqq.backend.core.model.metadata.producers.TestNoInterfacesExtendsObject; +import com.kingsrook.qqq.backend.core.model.metadata.producers.TestNoValidConstructorMetaDataProducer; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -43,6 +49,11 @@ class MetaDataProducerHelperTest QInstance qInstance = new QInstance(); MetaDataProducerHelper.processAllMetaDataProducersInPackage(qInstance, "com.kingsrook.qqq.backend.core.model.metadata.producers"); assertTrue(qInstance.getTables().containsKey(TestMetaDataProducer.NAME)); + assertTrue(qInstance.getTables().containsKey(TestImplementsMetaDataProducer.NAME)); + assertFalse(qInstance.getTables().containsKey(TestNoValidConstructorMetaDataProducer.NAME)); + assertFalse(qInstance.getTables().containsKey(TestNoInterfacesExtendsObject.NAME)); + assertFalse(qInstance.getTables().containsKey(TestAbstractMetaDataProducer.NAME)); + assertFalse(qInstance.getTables().containsKey(TestDisabledMetaDataProducer.NAME)); } } \ No newline at end of file diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/metadata/producers/TestAbstractMetaDataProducer.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/metadata/producers/TestAbstractMetaDataProducer.java new file mode 100644 index 00000000..041c1473 --- /dev/null +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/metadata/producers/TestAbstractMetaDataProducer.java @@ -0,0 +1,48 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2023. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.kingsrook.qqq.backend.core.model.metadata.producers; + + +import com.kingsrook.qqq.backend.core.exceptions.QException; +import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducer; +import com.kingsrook.qqq.backend.core.model.metadata.QInstance; +import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; + + +/******************************************************************************* + ** + *******************************************************************************/ +public abstract class TestAbstractMetaDataProducer extends MetaDataProducer +{ + public static final String NAME = "TestAbstractMetaDataProducer"; + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Override + public QTableMetaData produce(QInstance qInstance) throws QException + { + return new QTableMetaData().withName(NAME); + } +} diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/metadata/producers/TestDisabledMetaDataProducer.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/metadata/producers/TestDisabledMetaDataProducer.java new file mode 100644 index 00000000..5d2c3be7 --- /dev/null +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/metadata/producers/TestDisabledMetaDataProducer.java @@ -0,0 +1,59 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2023. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.kingsrook.qqq.backend.core.model.metadata.producers; + + +import com.kingsrook.qqq.backend.core.exceptions.QException; +import com.kingsrook.qqq.backend.core.model.MetaDataProducerInterface; +import com.kingsrook.qqq.backend.core.model.metadata.QInstance; +import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; + + +/******************************************************************************* + ** + *******************************************************************************/ +public class TestDisabledMetaDataProducer implements MetaDataProducerInterface +{ + public static final String NAME = "Disabled"; + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Override + public boolean isEnabled() + { + return (false); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Override + public QTableMetaData produce(QInstance qInstance) throws QException + { + return new QTableMetaData().withName(NAME); + } +} diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/metadata/producers/TestImplementsMetaDataProducer.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/metadata/producers/TestImplementsMetaDataProducer.java new file mode 100644 index 00000000..14ce9359 --- /dev/null +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/metadata/producers/TestImplementsMetaDataProducer.java @@ -0,0 +1,48 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2023. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.kingsrook.qqq.backend.core.model.metadata.producers; + + +import com.kingsrook.qqq.backend.core.exceptions.QException; +import com.kingsrook.qqq.backend.core.model.MetaDataProducerInterface; +import com.kingsrook.qqq.backend.core.model.metadata.QInstance; +import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; + + +/******************************************************************************* + ** + *******************************************************************************/ +public class TestImplementsMetaDataProducer implements MetaDataProducerInterface +{ + public static final String NAME = "BuiltByProducerImplementingInterface"; + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Override + public QTableMetaData produce(QInstance qInstance) throws QException + { + return new QTableMetaData().withName(NAME); + } +} diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/metadata/producers/TestNoInterfacesExtendsObject.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/metadata/producers/TestNoInterfacesExtendsObject.java new file mode 100644 index 00000000..e7cc6a0f --- /dev/null +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/metadata/producers/TestNoInterfacesExtendsObject.java @@ -0,0 +1,46 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2023. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.kingsrook.qqq.backend.core.model.metadata.producers; + + +import com.kingsrook.qqq.backend.core.exceptions.QException; +import com.kingsrook.qqq.backend.core.model.metadata.QInstance; +import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; + + +/******************************************************************************* + ** + *******************************************************************************/ +public class TestNoInterfacesExtendsObject +{ + public static final String NAME = "TestNoInterfacesExtendsObject"; + + + + /******************************************************************************* + ** + *******************************************************************************/ + public QTableMetaData produce(QInstance qInstance) throws QException + { + return new QTableMetaData().withName(NAME); + } +} diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/metadata/producers/TestNoValidConstructorMetaDataProducer.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/metadata/producers/TestNoValidConstructorMetaDataProducer.java new file mode 100644 index 00000000..94463a54 --- /dev/null +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/metadata/producers/TestNoValidConstructorMetaDataProducer.java @@ -0,0 +1,58 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2023. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.kingsrook.qqq.backend.core.model.metadata.producers; + + +import com.kingsrook.qqq.backend.core.exceptions.QException; +import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducer; +import com.kingsrook.qqq.backend.core.model.metadata.QInstance; +import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; + + +/******************************************************************************* + ** + *******************************************************************************/ +public class TestNoValidConstructorMetaDataProducer extends MetaDataProducer +{ + public static final String NAME = "NoValidConstructor"; + + + + /******************************************************************************* + ** Constructor + ** + *******************************************************************************/ + public TestNoValidConstructorMetaDataProducer(boolean b) + { + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Override + public QTableMetaData produce(QInstance qInstance) throws QException + { + return new QTableMetaData().withName(NAME); + } +}