feat: stack BOM for qqq, qqq-esb, Next dashboard, and qBits (#3) #13

Merged
KofTwentyTwo merged 2 commits from feature/GH-3-stack-bom into develop 2026-09-26 13:13:15 +00:00
4 changed files with 591 additions and 0 deletions

No files matched your search

+1
View File
@@ -37,6 +37,7 @@
<modules>
<module>qqq-all-bom</module>
<module>qqq-all-bom-test</module>
<module>qqq-all-app</module>
</modules>
+254
View File
@@ -0,0 +1,254 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2026 QRunIO, LLC
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.kingsrook.qqq</groupId>
<artifactId>qqq-all-parent</artifactId>
<version>${revision}</version>
</parent>
<artifactId>qqq-all-bom-test</artifactId>
<packaging>jar</packaging>
<name>QQQ All - Stack BOM Test</name>
<description>Resolves every artifact the stack BOM manages. Build-time check only; not published.</description>
<properties>
<maven.deploy.skip>true</maven.deploy.skip>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.kingsrook.qqq</groupId>
<artifactId>qqq-all-bom</artifactId>
<version>${project.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!-- One versionless dependency per artifact the BOM manages, so resolving this -->
<!-- module's test classpath resolves each of them. StackBomTest fails if one is missing. -->
<dependencies>
<!-- QQQ modules, qqq-esb, and qqq-frontend-next (from qqq-bom-pom) -->
<dependency>
<groupId>com.kingsrook.qqq</groupId>
<artifactId>qqq-backend-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.kingsrook.qqq</groupId>
<artifactId>qqq-backend-module-rdbms</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.kingsrook.qqq</groupId>
<artifactId>qqq-backend-module-mongodb</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.kingsrook.qqq</groupId>
<artifactId>qqq-backend-module-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.kingsrook.qqq</groupId>
<artifactId>qqq-backend-module-filesystem</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.kingsrook.qqq</groupId>
<artifactId>qqq-backend-module-sqlite</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.kingsrook.qqq</groupId>
<artifactId>qqq-backend-module-postgres</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.kingsrook.qqq</groupId>
<artifactId>qqq-middleware-javalin</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.kingsrook.qqq</groupId>
<artifactId>qqq-middleware-slack</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.kingsrook.qqq</groupId>
<artifactId>qqq-middleware-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.kingsrook.qqq</groupId>
<artifactId>qqq-openapi</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.kingsrook.qqq</groupId>
<artifactId>qqq-middleware-picocli</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.kingsrook.qqq</groupId>
<artifactId>qqq-middleware-lambda</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.kingsrook.qqq</groupId>
<artifactId>qqq-middleware-health</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.kingsrook.qqq</groupId>
<artifactId>qqq-language-support-javascript</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.kingsrook.qqq</groupId>
<artifactId>qqq-utility-lambdas</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.kingsrook.qqq</groupId>
<artifactId>qqq-esb</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.kingsrook.qqq</groupId>
<artifactId>qqq-frontend-next</artifactId>
<scope>test</scope>
</dependency>
<!-- qBits -->
<dependency>
<groupId>com.kingsrook.qbits</groupId>
<artifactId>qbit-quick-search</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.kingsrook.qbits</groupId>
<artifactId>qbit-user-role-permissions</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.kingsrook.qbits</groupId>
<artifactId>qbit-customizable-table-views</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.kingsrook.qbits</groupId>
<artifactId>qbit-standard-process-trace</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.kingsrook.qbits</groupId>
<artifactId>qbit-webhooks</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.kingsrook.qbits</groupId>
<artifactId>qbit-workflows</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.kingsrook.qbits</groupId>
<artifactId>qbit-geo-data</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.kingsrook.qbits</groupId>
<artifactId>qbit-sftp-data-integration</artifactId>
<scope>test</scope>
</dependency>
<!-- JDBC drivers -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
<!-- Broker clients -->
<dependency>
<groupId>org.apache.artemis</groupId>
<artifactId>artemis-jakarta-client</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.rabbitmq.jms</groupId>
<artifactId>rabbitmq-jms</artifactId>
<scope>test</scope>
</dependency>
<!-- Test framework (not part of the stack) -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>6.0.2</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<!-- Writes the effective POM (BOM imports expanded) for StackBomTest to read. -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-help-plugin</artifactId>
<version>3.5.2</version>
<executions>
<execution>
<id>effective-pom</id>
<phase>generate-test-resources</phase>
<goals>
<goal>effective-pom</goal>
</goals>
<configuration>
<output>${project.build.directory}/effective-pom.xml</output>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,225 @@
/*
* Copyright 2026 QRunIO, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qqq.all.bom;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;
import javax.xml.parsers.DocumentBuilderFactory;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import static org.junit.jupiter.api.Assertions.assertEquals;
/*******************************************************************************
** Checks qqq-all-bom through this module's effective POM, which Maven writes
** with the BOM imports expanded: the BOM manages the whole stack, and this
** module depends on every stack artifact the BOM manages, so the build
** resolves each one.
*******************************************************************************/
class StackBomTest
{
private static final Path EFFECTIVE_POM = Path.of("target", "effective-pom.xml");
private static final Path BOM_POM = Path.of("..", "qqq-all-bom", "pom.xml");
private static List<Coordinate> managed;
private static List<Coordinate> dependencies;
private static List<Coordinate> bomDeclared;
/*******************************************************************************
** Read the managed and declared dependencies from the effective POM.
*******************************************************************************/
@BeforeAll
static void beforeAll() throws Exception
{
Element effectivePom = parse(EFFECTIVE_POM);
managed = readDependencies(child(effectivePom, "dependencyManagement"));
dependencies = readDependencies(effectivePom);
bomDeclared = readDependencies(child(parse(BOM_POM), "dependencyManagement")).stream()
.filter(coordinate -> !"import".equals(coordinate.scope()))
.toList();
}
/*******************************************************************************
** The BOM manages every artifact the qqq-all stack is built from.
*******************************************************************************/
@Test
void testManagesTheStack()
{
Map<String, String> managedVersions = managed.stream().collect(Collectors.toMap(Coordinate::key, Coordinate::version));
List<String> required = List.of(
"com.kingsrook.qqq:qqq-backend-core",
"com.kingsrook.qqq:qqq-esb",
"com.kingsrook.qqq:qqq-frontend-next",
"com.kingsrook.qbits:qbit-quick-search",
"com.kingsrook.qbits:qbit-user-role-permissions",
"com.kingsrook.qbits:qbit-customizable-table-views",
"com.kingsrook.qbits:qbit-standard-process-trace",
"com.kingsrook.qbits:qbit-webhooks",
"com.kingsrook.qbits:qbit-workflows",
"com.kingsrook.qbits:qbit-geo-data",
"com.kingsrook.qbits:qbit-sftp-data-integration",
"org.postgresql:postgresql",
"com.mysql:mysql-connector-j",
"org.xerial:sqlite-jdbc",
"com.h2database:h2",
"org.apache.artemis:artemis-jakarta-client",
"com.rabbitmq.jms:rabbitmq-jms");
List<String> missing = required.stream().filter(key -> !managedVersions.containsKey(key)).toList();
assertEquals(List.of(), missing, "Artifacts the stack BOM does not manage");
assertEquals("3.9.0", managedVersions.get("com.rabbitmq.jms:rabbitmq-jms"));
}
/*******************************************************************************
** This module depends on every stack artifact the BOM manages, so resolving
** its test classpath resolves each one from the configured repositories.
**
** Stack artifacts are the managed com.kingsrook artifacts (qqq modules,
** qqq-esb, qqq-frontend-next, qBits) plus everything qqq-all-bom declares
** itself. The third-party entries qqq-bom-pom inherits from its parent
** (Jackson, Netty, Jetty, and others) only align versions, so they are
** resolved when something depends on them rather than here.
*******************************************************************************/
@Test
void testDependsOnEveryStackArtifact()
{
Set<String> stack = managed.stream()
.filter(coordinate -> coordinate.groupId().startsWith("com.kingsrook."))
.map(Coordinate::key)
.collect(Collectors.toCollection(TreeSet::new));
bomDeclared.forEach(coordinate -> stack.add(coordinate.key()));
Set<String> declared = dependencies.stream().map(Coordinate::key).collect(Collectors.toSet());
List<String> notResolved = stack.stream().filter(key -> !declared.contains(key)).toList();
assertEquals(List.of(), notResolved, "Stack artifacts missing from qqq-all-bom-test's dependencies, so the build does not resolve them");
}
/*******************************************************************************
** Parse a POM file and return its project element.
*******************************************************************************/
private static Element parse(Path pom) throws Exception
{
Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(pom.toFile());
return (document.getDocumentElement());
}
/*******************************************************************************
** Read the dependency elements directly under parent's dependencies element.
*******************************************************************************/
private static List<Coordinate> readDependencies(Element parent)
{
List<Coordinate> coordinates = new ArrayList<>();
for(Element dependency : children(child(parent, "dependencies"), "dependency"))
{
coordinates.add(new Coordinate(
text(dependency, "groupId", null),
text(dependency, "artifactId", null),
text(dependency, "version", null),
text(dependency, "type", "jar"),
text(dependency, "classifier", ""),
text(dependency, "scope", "compile")));
}
return (coordinates);
}
/*******************************************************************************
** The single child element with the given name.
*******************************************************************************/
private static Element child(Element parent, String name)
{
List<Element> matches = children(parent, name);
assertEquals(1, matches.size(), "Expected one <" + name + "> under <" + parent.getTagName() + ">");
return (matches.get(0));
}
/*******************************************************************************
** The child elements with the given name (direct children only, so plugin
** dependencies are not mistaken for project dependencies).
*******************************************************************************/
private static List<Element> children(Element parent, String name)
{
List<Element> matches = new ArrayList<>();
for(Node node = parent.getFirstChild(); node != null; node = node.getNextSibling())
{
if(node instanceof Element element && element.getTagName().equals(name))
{
matches.add(element);
}
}
return (matches);
}
/*******************************************************************************
** Text of the named child element, or defaultValue when it is absent.
*******************************************************************************/
private static String text(Element parent, String name, String defaultValue)
{
List<Element> matches = children(parent, name);
return (matches.isEmpty() ? defaultValue : matches.get(0).getTextContent().trim());
}
/*******************************************************************************
** A dependency coordinate; key() is how Maven matches a dependency to its
** managed entry.
*******************************************************************************/
private record Coordinate(String groupId, String artifactId, String version, String type, String classifier, String scope)
{
/***************************************************************************
**
***************************************************************************/
String key()
{
String key = groupId + ":" + artifactId;
if(!type.equals("jar") || !classifier.isEmpty())
{
key += ":" + type + (classifier.isEmpty() ? "" : ":" + classifier);
}
return (key);
}
}
}
+111
View File
@@ -16,4 +16,115 @@
<name>QQQ All - Stack BOM</name>
<description>One tested version set for QQQ, the ESB, the Next dashboard, the included qBits, JDBC drivers, and broker clients.</description>
<properties>
<qqq.version>4.1.0-SNAPSHOT</qqq.version>
<!-- qBits: develop builds on qbit-build-parent 2.0.0; none has a release on that parent yet. -->
<qbit-quick-search.version>0.2.1-SNAPSHOT</qbit-quick-search.version>
<qbit-user-role-permissions.version>0.33.0-SNAPSHOT</qbit-user-role-permissions.version>
<qbit-customizable-table-views.version>0.5.0-SNAPSHOT</qbit-customizable-table-views.version>
<qbit-standard-process-trace.version>0.3.0-SNAPSHOT</qbit-standard-process-trace.version>
<qbit-webhooks.version>0.4.0-SNAPSHOT</qbit-webhooks.version>
<qbit-workflows.version>0.114.0-SNAPSHOT</qbit-workflows.version>
<qbit-geo-data.version>0.2.0-SNAPSHOT</qbit-geo-data.version>
<qbit-sftp-data-integration.version>0.4.0-SNAPSHOT</qbit-sftp-data-integration.version>
<!-- The versions qqq 4.1 builds and tests its backend modules and qqq-esb with. -->
<postgresql.version>42.7.12</postgresql.version>
<mysql-connector-j.version>9.5.0</mysql-connector-j.version>
<sqlite-jdbc.version>3.51.1.0</sqlite-jdbc.version>
<h2.version>2.4.240</h2.version>
<artemis.version>2.57.0</artemis.version>
<rabbitmq-jms.version>3.9.0</rabbitmq-jms.version>
</properties>
<dependencyManagement>
<dependencies>
<!-- QQQ modules, qqq-esb, and qqq-frontend-next. -->
<dependency>
<groupId>com.kingsrook.qqq</groupId>
<artifactId>qqq-bom-pom</artifactId>
<version>${qqq.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- qBits -->
<dependency>
<groupId>com.kingsrook.qbits</groupId>
<artifactId>qbit-quick-search</artifactId>
<version>${qbit-quick-search.version}</version>
</dependency>
<dependency>
<groupId>com.kingsrook.qbits</groupId>
<artifactId>qbit-user-role-permissions</artifactId>
<version>${qbit-user-role-permissions.version}</version>
</dependency>
<dependency>
<groupId>com.kingsrook.qbits</groupId>
<artifactId>qbit-customizable-table-views</artifactId>
<version>${qbit-customizable-table-views.version}</version>
</dependency>
<dependency>
<groupId>com.kingsrook.qbits</groupId>
<artifactId>qbit-standard-process-trace</artifactId>
<version>${qbit-standard-process-trace.version}</version>
</dependency>
<dependency>
<groupId>com.kingsrook.qbits</groupId>
<artifactId>qbit-webhooks</artifactId>
<version>${qbit-webhooks.version}</version>
</dependency>
<dependency>
<groupId>com.kingsrook.qbits</groupId>
<artifactId>qbit-workflows</artifactId>
<version>${qbit-workflows.version}</version>
</dependency>
<dependency>
<groupId>com.kingsrook.qbits</groupId>
<artifactId>qbit-geo-data</artifactId>
<version>${qbit-geo-data.version}</version>
</dependency>
<dependency>
<groupId>com.kingsrook.qbits</groupId>
<artifactId>qbit-sftp-data-integration</artifactId>
<version>${qbit-sftp-data-integration.version}</version>
</dependency>
<!-- JDBC drivers -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>${postgresql.version}</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>${mysql-connector-j.version}</version>
</dependency>
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
<version>${sqlite-jdbc.version}</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>${h2.version}</version>
</dependency>
<!-- Broker clients for qqq-esb -->
<dependency>
<groupId>org.apache.artemis</groupId>
<artifactId>artemis-jakarta-client</artifactId>
<version>${artemis.version}</version>
</dependency>
<dependency>
<groupId>com.rabbitmq.jms</groupId>
<artifactId>rabbitmq-jms</artifactId>
<version>${rabbitmq-jms.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>