Compare commits
33
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d7511b28a1 | ||
|
|
e061a1a4a1 | ||
|
|
93e5e7a000
|
||
|
|
b50515a8e8
|
||
|
|
401e7ca471
|
||
|
|
2fd63e734b
|
||
|
|
9c42b3d913
|
||
|
|
2cb2646fe1 | ||
|
|
006ae730d7
|
||
|
|
55edcf4070
|
||
|
|
5802fc4adc
|
||
|
|
6337bfe838 | ||
|
|
b5787bbe6b | ||
|
|
6ac6a0366c
|
||
|
|
ea506f6de6
|
||
|
|
5574af58ed
|
||
|
|
f0dbd484d6
|
||
|
|
d0052ea879 | ||
|
|
79f04c373d | ||
|
|
174de92ca5 | ||
|
|
d1243e4526 | ||
|
|
845c92bfe2 | ||
|
|
b253e9990e | ||
|
|
4f27fd1db3 | ||
|
|
20e38e05d3 | ||
|
|
68e85e6ed3
|
||
|
|
8a6f345cb5 | ||
|
|
4cf841dfea
|
||
|
|
811b079b8a
|
||
|
|
5f47b112ab
|
||
|
|
5bda16c6f2 | ||
|
|
a74dd21fa0
|
||
|
|
2d2eef54c4
|
No files matched your search
@@ -0,0 +1 @@
|
||||
Central Java verification, tests and secret scan are supplied by the organization-scoped workflow.
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(wc:*)",
|
||||
"Bash(mvn clean compile:*)",
|
||||
"Bash(mvn compile:*)",
|
||||
"Bash(find:*)",
|
||||
"Bash(mvn test:*)",
|
||||
"Bash(git add:*)",
|
||||
"Bash(git commit:*)",
|
||||
"Bash(git push:*)",
|
||||
"Bash(mvn clean test:*)",
|
||||
"Bash(gh pr create:*)",
|
||||
"Bash(gh pr merge:*)",
|
||||
"Bash(git checkout:*)",
|
||||
"Bash(git pull:*)",
|
||||
"Bash(git merge:*)",
|
||||
"mcp__circleci-mcp-server__get_latest_pipeline_status",
|
||||
"mcp__circleci-mcp-server__get_build_failure_logs"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -3,5 +3,5 @@ on:
|
||||
push:
|
||||
jobs:
|
||||
maven:
|
||||
uses: self:KofTwentyTwo/ci-workflows/.gitea/workflows/maven.yaml@v1
|
||||
uses: self:KofTwentyTwo/ci-workflows/.gitea/workflows/maven.yaml@main
|
||||
secrets: inherit
|
||||
+1
-5
@@ -1,5 +1,6 @@
|
||||
# Maven
|
||||
target/
|
||||
.flattened-pom.xml
|
||||
*.jar
|
||||
*.war
|
||||
*.ear
|
||||
@@ -21,8 +22,3 @@ Thumbs.db
|
||||
|
||||
# Generated
|
||||
generated/
|
||||
|
||||
.claude/
|
||||
|
||||
# flatten-maven-plugin output
|
||||
.flattened-pom.xml
|
||||
+2
-2
@@ -7,10 +7,10 @@ api_checks: false
|
||||
docs_only: false
|
||||
release_on_main: false
|
||||
release_tag_policy: stable
|
||||
test_branch_policy: non-release-no-hotfix-integration
|
||||
test_branch_policy: non-release
|
||||
publish:
|
||||
snapshot: true
|
||||
release_candidate: true
|
||||
release: true
|
||||
hotfix: true
|
||||
asciidoc: false
|
||||
release_completion: false
|
||||
+33
-4
@@ -7,6 +7,39 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.2.0] - 2026-11-06
|
||||
|
||||
Published as `com.kingsrook.qbits:qbit-geo-data:0.2.0` on the QRun-IO Gitea Maven
|
||||
registry. Requires Java 21 and QQQ 4.1 (built against `qqq-bom-pom` 4.1.0-RC.21).
|
||||
This is the first release that works on QQQ 4.1; 0.1.0 was built against QQQ 0.35.0.
|
||||
|
||||
### Changed
|
||||
- Build against the published QQQ 4.1 core (`qqq-bom-pom` 4.1.0-RC.21) instead of 0.35.0.
|
||||
- `GeoDataQBitProducer.VERSION` is `0.2.0`.
|
||||
- Import the project's `junit-bom` 5.10.0 ahead of the QQQ BOM so every JUnit artifact
|
||||
converges on 5.10.0 and Surefire discovers the tests.
|
||||
- CI runs on Gitea through the shared `KofTwentyTwo/ci-workflows` Maven workflow
|
||||
(`main` verifies; a `vX.Y.Z` tag publishes to the Gitea registry).
|
||||
|
||||
### Fixed
|
||||
- `Country`, `StateProvince` and `City` ask for their tables (`produceTableMetaData`)
|
||||
and expose the setters `QRecordEntity` needs to discover fields, so the producer
|
||||
creates `<prefix>_country`, `<prefix>_stateProvince` and `<prefix>_city` on core 4.1
|
||||
instead of zero tables. The producer tolerates a table without fields.
|
||||
- `GeoDataSyncStep` resolves the bundled `countryAlpha2` / `stateCode` source codes to
|
||||
the persisted `countryId` / `stateProvinceId` before matching, so states and cities
|
||||
are persisted (0.1.0 reported success after writing 250 countries and zero states or
|
||||
cities because those source columns do not exist in the schema).
|
||||
- Cities match on `stateProvinceId`, `name`, `latitude` and `longitude` at the schema's
|
||||
seven-decimal precision; city names are not unique within a subdivision.
|
||||
- Numeric comparisons use `BigDecimal` value equality, so a second run reports zero
|
||||
inserts, updates or deactivations.
|
||||
- An inactive existing row whose source entry returns is reactivated.
|
||||
- Insert and update results are checked; a row rejected by the backend fails the sync
|
||||
with a `QException` instead of reporting success.
|
||||
|
||||
## [0.1.0] - 2025-12-28
|
||||
|
||||
### Added
|
||||
- Multi-module structure (qbit-geo-data-core + tools)
|
||||
- Country, StateProvince, City entities with sync process
|
||||
@@ -19,10 +52,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Changed
|
||||
- Upgraded to QQQ 0.35.0 with Java 21 support
|
||||
- Re-pinned to qbit-build-parent 2.0.0 (QQQ 4.0.0); opt-in `qqq-snapshot` Maven profile builds against QQQ 4.1.0-SNAPSHOT
|
||||
|
||||
### Fixed
|
||||
- Published module poms name their parent by its real version instead of `${revision}` (flatten-maven-plugin)
|
||||
|
||||
## [0.1.0-alpha] - 2024-12-28
|
||||
|
||||
|
||||
@@ -9,4 +9,4 @@ Reviewed knowledge for this repo and the wider QQQ platform lives in the second-
|
||||
(reviewed commit `f7889fdf7495` on `main`, 2026-07-04)
|
||||
- QBit mechanics refresher: `R:/Git.Local/KofTwentyTwo/second-brain/knowledge/qqq/architecture/metadata-model.md`
|
||||
|
||||
Key cautions from the review: entities produce PossibleValueSources only (`produceTableMetaData` defaults false — no tables are registered); the sync step's natural keys (`countryAlpha2`/`stateCode`) don't exist in the schema; the `geoDataSync` process name collides on multi-instance registration; current first-party pom/README declarations align with Apache-2.0 LICENSE/NOTICE; main and develop have diverged.
|
||||
Key cautions from the review: entities produce PossibleValueSources only (`produceTableMetaData` defaults false — no tables are registered); the sync step's natural keys (`countryAlpha2`/`stateCode`) don't exist in the schema; the `geoDataSync` process name collides on multi-instance registration; pom/README still declare AGPL-3.0 while LICENSE/NOTICE are Apache-2.0; main and develop have diverged.
|
||||
@@ -7,16 +7,33 @@ Geographic reference data QBit providing countries, states/provinces, and cities
|
||||
|
||||
## Quick Start
|
||||
|
||||
The current release is **0.2.0**. It requires Java 21 and QQQ 4.1 (built against
|
||||
`qqq-bom-pom` 4.1.0-RC.21). See [CHANGELOG.md](CHANGELOG.md).
|
||||
|
||||
Add the dependency to your `pom.xml`:
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>com.kingsrook.qbits</groupId>
|
||||
<artifactId>qbit-geo-data-core</artifactId>
|
||||
<version>${qbit-geo-data.version}</version>
|
||||
<artifactId>qbit-geo-data</artifactId>
|
||||
<version>0.2.0</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
Releases are published to the QRun-IO Gitea Maven registry, which requires
|
||||
authentication for reads as well as writes. Add the repository and a matching
|
||||
`<server>` entry (with your own Gitea username and token) in your private Maven
|
||||
`settings.xml`:
|
||||
|
||||
```xml
|
||||
<repository>
|
||||
<id>qrun-io</id>
|
||||
<url>https://git.kof22.com/api/packages/QRun-IO/maven</url>
|
||||
<releases><enabled>true</enabled></releases>
|
||||
<snapshots><enabled>false</enabled></snapshots>
|
||||
</repository>
|
||||
```
|
||||
|
||||
## What You Get
|
||||
|
||||
This QBit provides three entities with pre-populated reference data:
|
||||
@@ -162,11 +179,4 @@ The generator creates a changelog with your prefix substituted and only includes
|
||||
|
||||
## License
|
||||
|
||||
Both Maven child modules set `qbit.noticesDirectory` to their parent directory,
|
||||
where this repository's `LICENSE` and `NOTICE` live. This supports the shared
|
||||
notice packaging correction in `qbit-build-parent`; it takes effect after adopting
|
||||
a parent version containing that correction. Existing public parent 2.0.0 is
|
||||
unchanged. Verify each generated module archive against these root files when
|
||||
integrating the new parent.
|
||||
|
||||
Apache-2.0 - See [LICENSE](LICENSE)
|
||||
AGPL-3.0 - See [LICENSE](LICENSE)
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>com.kingsrook</groupId>
|
||||
<artifactId>qbit-build-parent</artifactId>
|
||||
<version>2.2.1</version>
|
||||
<version>1.5.1</version>
|
||||
</parent>
|
||||
|
||||
<groupId>com.kingsrook.qbits</groupId>
|
||||
@@ -21,8 +21,8 @@
|
||||
|
||||
<licenses>
|
||||
<license>
|
||||
<name>Apache License, Version 2.0</name>
|
||||
<url>https://www.apache.org/licenses/LICENSE-2.0</url>
|
||||
<name>GNU Affero General Public License v3.0</name>
|
||||
<url>https://www.gnu.org/licenses/agpl-3.0.html</url>
|
||||
<distribution>repo</distribution>
|
||||
</license>
|
||||
</licenses>
|
||||
@@ -50,8 +50,8 @@
|
||||
</modules>
|
||||
|
||||
<properties>
|
||||
<qqq.version>4.1.0-RC.26</qqq.version> <!-- feeds qbit-build-parent's qqq-bom-pom import -->
|
||||
<revision>0.3.0-SNAPSHOT</revision>
|
||||
<revision>0.2.0-RC.2</revision>
|
||||
<qqq.version>4.1.0-RC.21</qqq.version>
|
||||
<maven.compiler.source>21</maven.compiler.source>
|
||||
<maven.compiler.target>21</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
@@ -59,11 +59,10 @@
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.json</groupId>
|
||||
<artifactId>json</artifactId>
|
||||
<version>20231013</version>
|
||||
</dependency>
|
||||
<!-- One JUnit line. The QQQ 4.1 BOM inherits junit-jupiter-engine/params at 6.0.2 from qqq-parent-project, while this pom's JUnit BOM
|
||||
manages junit-jupiter, the API and the platform at 5.10.0; the mixed engine fails discovery ("TestEngine with ID 'junit-jupiter'
|
||||
failed to discover tests") and no test runs. Import this pom's own JUnit version first (first import wins) so every JUnit
|
||||
artifact converges on 5.10.0. -->
|
||||
<dependency>
|
||||
<groupId>org.junit</groupId>
|
||||
<artifactId>junit-bom</artifactId>
|
||||
@@ -71,31 +70,31 @@
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<!-- Published QQQ 4.1 core (minimum QQQ 4.1), imported ahead of the parent's BOM. -->
|
||||
<dependency>
|
||||
<groupId>com.kingsrook.qqq</groupId>
|
||||
<artifactId>qqq-bom-pom</artifactId>
|
||||
<version>${qqq.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.json</groupId>
|
||||
<artifactId>json</artifactId>
|
||||
<version>20231013</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.assertj</groupId>
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<version>3.27.7</version>
|
||||
<version>3.27.3</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<build>
|
||||
<pluginManagement>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.11.0</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>3.1.2</version>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
<plugins>
|
||||
<!-- Replace ${revision} with its value in the poms that get installed and published. -->
|
||||
<!-- Resolve the CI-friendly ${revision} in the installed and deployed POMs so -->
|
||||
<!-- consumers of the published artifacts can resolve the parent chain. -->
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>flatten-maven-plugin</artifactId>
|
||||
@@ -113,7 +112,7 @@
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>flatten.clean</id>
|
||||
<id>flatten-clean</id>
|
||||
<phase>clean</phase>
|
||||
<goals>
|
||||
<goal>clean</goal>
|
||||
@@ -122,28 +121,19 @@
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
<pluginManagement>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.11.0</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>3.1.2</version>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
</build>
|
||||
|
||||
<profiles>
|
||||
<!-- Opt-in check against the next qqq line: mvn -B verify -Pqqq-snapshot -->
|
||||
<!-- (override the version with -Dqqq.snapshot.version=...). Default builds -->
|
||||
<!-- take the qqq version only from qbit-build-parent. -->
|
||||
<profile>
|
||||
<id>qqq-snapshot</id>
|
||||
<properties>
|
||||
<qqq.snapshot.version>4.1.0-RC.26</qqq.snapshot.version>
|
||||
</properties>
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.kingsrook.qqq</groupId>
|
||||
<artifactId>qqq-bom-pom</artifactId>
|
||||
<version>${qqq.snapshot.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
</profile>
|
||||
</profiles>
|
||||
</project>
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Java 21+
|
||||
- Java 17+
|
||||
- Maven 3.8+
|
||||
- QQQ 4.0.0+
|
||||
- QQQ 0.23.0+
|
||||
|
||||
## Creating Your Data QBit
|
||||
|
||||
|
||||
@@ -13,10 +13,6 @@
|
||||
<artifactId>qbit-geo-data</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<properties>
|
||||
<qbit.noticesDirectory>${project.basedir}/..</qbit.noticesDirectory>
|
||||
</properties>
|
||||
|
||||
<name>QBit Geographic Data</name>
|
||||
<description>Geographic reference data for QQQ applications - countries, states/provinces, and cities</description>
|
||||
|
||||
|
||||
+5
-1
@@ -28,7 +28,7 @@ public class GeoDataQBitProducer implements QBitProducer
|
||||
{
|
||||
public static final String GROUP_ID = "com.kingsrook.qbits";
|
||||
public static final String ARTIFACT_ID = "qbit-geo-data";
|
||||
public static final String VERSION = "0.1.0";
|
||||
public static final String VERSION = "0.2.0";
|
||||
|
||||
private GeoDataQBitConfig config;
|
||||
|
||||
@@ -126,6 +126,10 @@ public class GeoDataQBitProducer implements QBitProducer
|
||||
*******************************************************************************/
|
||||
private void updateForeignKeyReferences(QTableMetaData table)
|
||||
{
|
||||
if(table.getFields() == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
table.getFields().values().forEach(field ->
|
||||
{
|
||||
if(field.getPossibleValueSourceName() != null)
|
||||
|
||||
@@ -14,7 +14,7 @@ import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.producers.annotations.QMetaDataProducingEntity;
|
||||
|
||||
|
||||
@QMetaDataProducingEntity(producePossibleValueSource = true)
|
||||
@QMetaDataProducingEntity(produceTableMetaData = true, producePossibleValueSource = true)
|
||||
public class City extends QRecordEntity
|
||||
{
|
||||
public static final String TABLE_NAME = "city";
|
||||
@@ -204,4 +204,103 @@ public class City extends QRecordEntity
|
||||
{
|
||||
return modifyDate;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for id (QRecordEntity discovers a field only from a getter and setter pair).
|
||||
*******************************************************************************/
|
||||
public void setId(Integer id)
|
||||
{
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for stateProvinceId (QRecordEntity discovers a field only from a getter and setter pair).
|
||||
*******************************************************************************/
|
||||
public void setStateProvinceId(Integer stateProvinceId)
|
||||
{
|
||||
this.stateProvinceId = stateProvinceId;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for name (QRecordEntity discovers a field only from a getter and setter pair).
|
||||
*******************************************************************************/
|
||||
public void setName(String name)
|
||||
{
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for asciiName (QRecordEntity discovers a field only from a getter and setter pair).
|
||||
*******************************************************************************/
|
||||
public void setAsciiName(String asciiName)
|
||||
{
|
||||
this.asciiName = asciiName;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for population (QRecordEntity discovers a field only from a getter and setter pair).
|
||||
*******************************************************************************/
|
||||
public void setPopulation(Integer population)
|
||||
{
|
||||
this.population = population;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for latitude (QRecordEntity discovers a field only from a getter and setter pair).
|
||||
*******************************************************************************/
|
||||
public void setLatitude(BigDecimal latitude)
|
||||
{
|
||||
this.latitude = latitude;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for longitude (QRecordEntity discovers a field only from a getter and setter pair).
|
||||
*******************************************************************************/
|
||||
public void setLongitude(BigDecimal longitude)
|
||||
{
|
||||
this.longitude = longitude;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for timezone (QRecordEntity discovers a field only from a getter and setter pair).
|
||||
*******************************************************************************/
|
||||
public void setTimezone(String timezone)
|
||||
{
|
||||
this.timezone = timezone;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for isActive (QRecordEntity discovers a field only from a getter and setter pair).
|
||||
*******************************************************************************/
|
||||
public void setIsActive(Boolean isActive)
|
||||
{
|
||||
this.isActive = isActive;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for createDate (QRecordEntity discovers a field only from a getter and setter pair).
|
||||
*******************************************************************************/
|
||||
public void setCreateDate(Instant createDate)
|
||||
{
|
||||
this.createDate = createDate;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for modifyDate (QRecordEntity discovers a field only from a getter and setter pair).
|
||||
*******************************************************************************/
|
||||
public void setModifyDate(Instant modifyDate)
|
||||
{
|
||||
this.modifyDate = modifyDate;
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.producers.annotations.QMetaDataProducingEntity;
|
||||
|
||||
|
||||
@QMetaDataProducingEntity(producePossibleValueSource = true)
|
||||
@QMetaDataProducingEntity(produceTableMetaData = true, producePossibleValueSource = true)
|
||||
public class Country extends QRecordEntity
|
||||
{
|
||||
public static final String TABLE_NAME = "country";
|
||||
@@ -171,4 +171,85 @@ public class Country extends QRecordEntity
|
||||
{
|
||||
return modifyDate;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for id (QRecordEntity discovers a field only from a getter and setter pair).
|
||||
*******************************************************************************/
|
||||
public void setId(Integer id)
|
||||
{
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for alpha2Code (QRecordEntity discovers a field only from a getter and setter pair).
|
||||
*******************************************************************************/
|
||||
public void setAlpha2Code(String alpha2Code)
|
||||
{
|
||||
this.alpha2Code = alpha2Code;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for alpha3Code (QRecordEntity discovers a field only from a getter and setter pair).
|
||||
*******************************************************************************/
|
||||
public void setAlpha3Code(String alpha3Code)
|
||||
{
|
||||
this.alpha3Code = alpha3Code;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for numericCode (QRecordEntity discovers a field only from a getter and setter pair).
|
||||
*******************************************************************************/
|
||||
public void setNumericCode(Integer numericCode)
|
||||
{
|
||||
this.numericCode = numericCode;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for name (QRecordEntity discovers a field only from a getter and setter pair).
|
||||
*******************************************************************************/
|
||||
public void setName(String name)
|
||||
{
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for officialName (QRecordEntity discovers a field only from a getter and setter pair).
|
||||
*******************************************************************************/
|
||||
public void setOfficialName(String officialName)
|
||||
{
|
||||
this.officialName = officialName;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for isActive (QRecordEntity discovers a field only from a getter and setter pair).
|
||||
*******************************************************************************/
|
||||
public void setIsActive(Boolean isActive)
|
||||
{
|
||||
this.isActive = isActive;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for createDate (QRecordEntity discovers a field only from a getter and setter pair).
|
||||
*******************************************************************************/
|
||||
public void setCreateDate(Instant createDate)
|
||||
{
|
||||
this.createDate = createDate;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for modifyDate (QRecordEntity discovers a field only from a getter and setter pair).
|
||||
*******************************************************************************/
|
||||
public void setModifyDate(Instant modifyDate)
|
||||
{
|
||||
this.modifyDate = modifyDate;
|
||||
}
|
||||
}
|
||||
+73
-1
@@ -13,7 +13,7 @@ import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.producers.annotations.QMetaDataProducingEntity;
|
||||
|
||||
|
||||
@QMetaDataProducingEntity(producePossibleValueSource = true)
|
||||
@QMetaDataProducingEntity(produceTableMetaData = true, producePossibleValueSource = true)
|
||||
public class StateProvince extends QRecordEntity
|
||||
{
|
||||
public static final String TABLE_NAME = "stateProvince";
|
||||
@@ -155,4 +155,76 @@ public class StateProvince extends QRecordEntity
|
||||
{
|
||||
return modifyDate;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for id (QRecordEntity discovers a field only from a getter and setter pair).
|
||||
*******************************************************************************/
|
||||
public void setId(Integer id)
|
||||
{
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for countryId (QRecordEntity discovers a field only from a getter and setter pair).
|
||||
*******************************************************************************/
|
||||
public void setCountryId(Integer countryId)
|
||||
{
|
||||
this.countryId = countryId;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for code (QRecordEntity discovers a field only from a getter and setter pair).
|
||||
*******************************************************************************/
|
||||
public void setCode(String code)
|
||||
{
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for name (QRecordEntity discovers a field only from a getter and setter pair).
|
||||
*******************************************************************************/
|
||||
public void setName(String name)
|
||||
{
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for subdivisionType (QRecordEntity discovers a field only from a getter and setter pair).
|
||||
*******************************************************************************/
|
||||
public void setSubdivisionType(String subdivisionType)
|
||||
{
|
||||
this.subdivisionType = subdivisionType;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for isActive (QRecordEntity discovers a field only from a getter and setter pair).
|
||||
*******************************************************************************/
|
||||
public void setIsActive(Boolean isActive)
|
||||
{
|
||||
this.isActive = isActive;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for createDate (QRecordEntity discovers a field only from a getter and setter pair).
|
||||
*******************************************************************************/
|
||||
public void setCreateDate(Instant createDate)
|
||||
{
|
||||
this.createDate = createDate;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for modifyDate (QRecordEntity discovers a field only from a getter and setter pair).
|
||||
*******************************************************************************/
|
||||
public void setModifyDate(Instant modifyDate)
|
||||
{
|
||||
this.modifyDate = modifyDate;
|
||||
}
|
||||
}
|
||||
+60
-13
@@ -4,13 +4,15 @@
|
||||
** Syncs all three entity types (Country, StateProvince, City) from bundled
|
||||
** JSON files into database tables. Performs natural key upsert:
|
||||
** - Country: by alpha2Code
|
||||
** - StateProvince: by countryAlpha2 + code
|
||||
** - City: by countryAlpha2 + stateCode + name
|
||||
** - StateProvince: by countryId + code (countryAlpha2 resolved to the persisted country)
|
||||
** - City: by stateProvinceId + name + latitude + longitude (codes resolved to the persisted state)
|
||||
*******************************************************************************/
|
||||
package com.kingsrook.qbits.geodata.sync;
|
||||
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
@@ -62,9 +64,39 @@ public class GeoDataSyncStep implements BackendStep
|
||||
// Sync in order: countries first (no dependencies), then states, then //
|
||||
// cities (which may reference states) //
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
int countriesInserted = syncTable(prefix + "_country", "/data/countries.json", List.of("alpha2Code"));
|
||||
int statesInserted = syncTable(prefix + "_stateProvince", "/data/states.json", List.of("countryAlpha2", "code"));
|
||||
int citiesInserted = syncTable(prefix + "_city", "/data/cities.json", List.of("countryAlpha2", "stateCode", "name"));
|
||||
int countriesInserted = syncTable(prefix + "_country", loadJsonData("/data/countries.json"), List.of("alpha2Code"));
|
||||
Map<String, QRecord> countries = queryExisting(prefix + "_country", List.of("alpha2Code"));
|
||||
List<QRecord> states = loadJsonData("/data/states.json");
|
||||
for(QRecord state : states)
|
||||
{
|
||||
QRecord country = countries.get(state.getValueString("countryAlpha2"));
|
||||
if(country == null)
|
||||
{
|
||||
throw new QException("Geo Data state references an unavailable country");
|
||||
}
|
||||
state.setValue("countryId", country.getValue("id"));
|
||||
state.getValues().remove("countryAlpha2");
|
||||
}
|
||||
int statesInserted = syncTable(prefix + "_stateProvince", states, List.of("countryId", "code"));
|
||||
Map<String, QRecord> persistedStates = queryExisting(prefix + "_stateProvince", List.of("countryId", "code"));
|
||||
List<QRecord> cities = loadJsonData("/data/cities.json");
|
||||
for(QRecord city : cities)
|
||||
{
|
||||
QRecord country = countries.get(city.getValueString("countryAlpha2"));
|
||||
QRecord state = country == null ? null : persistedStates.get(country.getValue("id") + "|" + city.getValueString("stateCode"));
|
||||
if(state == null)
|
||||
{
|
||||
throw new QException("Geo Data city references an unavailable state");
|
||||
}
|
||||
city.setValue("stateProvinceId", state.getValue("id"));
|
||||
for(String coordinate : List.of("latitude", "longitude"))
|
||||
{
|
||||
city.setValue(coordinate, new BigDecimal(city.getValue(coordinate).toString()).setScale(7, RoundingMode.HALF_UP));
|
||||
}
|
||||
city.getValues().remove("countryAlpha2");
|
||||
city.getValues().remove("stateCode");
|
||||
}
|
||||
int citiesInserted = syncTable(prefix + "_city", cities, List.of("stateProvinceId", "name", "latitude", "longitude"));
|
||||
|
||||
LOG.info("Geo data sync complete",
|
||||
logPair("prefix", prefix),
|
||||
@@ -83,17 +115,15 @@ public class GeoDataSyncStep implements BackendStep
|
||||
** Sync a single table from JSON resource.
|
||||
** Returns count of records processed.
|
||||
*******************************************************************************/
|
||||
private int syncTable(String tableName, String resourcePath, List<String> naturalKeyFields) throws QException
|
||||
private int syncTable(String tableName, List<QRecord> sourceRecords, List<String> naturalKeyFields) throws QException
|
||||
{
|
||||
LOG.info("Syncing table",
|
||||
logPair("table", tableName),
|
||||
logPair("resource", resourcePath),
|
||||
logPair("naturalKey", naturalKeyFields));
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
// 1. Load source data from classpath JSON //
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
List<QRecord> sourceRecords = loadJsonData(resourcePath);
|
||||
LOG.info("Loaded source data", logPair("count", sourceRecords.size()));
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
@@ -119,7 +149,7 @@ public class GeoDataSyncStep implements BackendStep
|
||||
source.setValue("isActive", true);
|
||||
toInsert.add(source);
|
||||
}
|
||||
else if(hasChanges(source, existing))
|
||||
else if(hasChanges(source, existing) || !Boolean.TRUE.equals(existing.getValueBoolean("isActive")))
|
||||
{
|
||||
source.setValue("id", existing.getValue("id"));
|
||||
source.setValue("isActive", true);
|
||||
@@ -182,7 +212,7 @@ public class GeoDataSyncStep implements BackendStep
|
||||
key.append("|");
|
||||
}
|
||||
Object value = record.getValue(field);
|
||||
key.append(value != null ? value.toString() : "");
|
||||
key.append(value instanceof Number ? new BigDecimal(value.toString()).stripTrailingZeros().toPlainString() : value != null ? value.toString() : "");
|
||||
}
|
||||
return key.toString();
|
||||
}
|
||||
@@ -265,7 +295,14 @@ public class GeoDataSyncStep implements BackendStep
|
||||
}
|
||||
Object sourceValue = source.getValue(fieldName);
|
||||
Object existingValue = existing.getValue(fieldName);
|
||||
if(!Objects.equals(sourceValue, existingValue))
|
||||
if(sourceValue instanceof Number && existingValue instanceof Number)
|
||||
{
|
||||
if(new BigDecimal(sourceValue.toString()).compareTo(new BigDecimal(existingValue.toString())) != 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if(!Objects.equals(sourceValue, existingValue))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -283,7 +320,7 @@ public class GeoDataSyncStep implements BackendStep
|
||||
InsertInput insertInput = new InsertInput();
|
||||
insertInput.setTableName(tableName);
|
||||
insertInput.setRecords(records);
|
||||
new InsertAction().execute(insertInput);
|
||||
requireSuccess(tableName, records.size(), new InsertAction().execute(insertInput).getRecords());
|
||||
}
|
||||
|
||||
|
||||
@@ -296,6 +333,16 @@ public class GeoDataSyncStep implements BackendStep
|
||||
UpdateInput updateInput = new UpdateInput();
|
||||
updateInput.setTableName(tableName);
|
||||
updateInput.setRecords(records);
|
||||
new UpdateAction().execute(updateInput);
|
||||
requireSuccess(tableName, records.size(), new UpdateAction().execute(updateInput).getRecords());
|
||||
}
|
||||
/*******************************************************************************
|
||||
** Do not report a successful sync when a native action rejected rows.
|
||||
*******************************************************************************/
|
||||
private void requireSuccess(String tableName, int expected, List<QRecord> records) throws QException
|
||||
{
|
||||
if(records == null || records.size() != expected || records.stream().anyMatch(record -> !record.getErrors().isEmpty()))
|
||||
{
|
||||
throw new QException("Geo Data persistence failed for table " + tableName);
|
||||
}
|
||||
}
|
||||
}
|
||||
+33
-1
@@ -78,6 +78,38 @@ class GeoDataQBitProducerTest
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** The enabled entities produce their tables, on the configured backend, with the
|
||||
** prefix; the possible value sources point at tables that exist. (On core 4.1 an
|
||||
** entity annotation only produces a table when it asks for it.)
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testProduce_createsTablesAndPossibleValueSourcesPointAtThem() throws QException
|
||||
{
|
||||
QInstance qInstance = new QInstance();
|
||||
qInstance.addBackend(new QBackendMetaData().withName("rdbms"));
|
||||
|
||||
new GeoDataQBitProducer()
|
||||
.withConfig(new GeoDataQBitConfig().withBackendName("rdbms").withTableNamePrefix("geo")
|
||||
.withEnableCountries(true).withEnableStateProvinces(true).withEnableCities(true))
|
||||
.produce(qInstance, "geo");
|
||||
|
||||
assertThat(qInstance.getTables().keySet()).contains("geo_country", "geo_stateProvince", "geo_city");
|
||||
assertThat(qInstance.getTable("geo_city").getBackendName()).isEqualTo("rdbms");
|
||||
assertThat(qInstance.getTable("geo_country").getPrimaryKeyField()).isEqualTo("id");
|
||||
assertThat(qInstance.getTable("geo_country").getFields().keySet()).contains("alpha2Code", "name", "isActive");
|
||||
assertThat(qInstance.getTable("geo_city").getFields().keySet()).contains("name");
|
||||
for(var pvs : qInstance.getPossibleValueSources().values())
|
||||
{
|
||||
if(pvs.getTableName() != null)
|
||||
{
|
||||
assertThat(qInstance.getTable(pvs.getTableName())).as(pvs.getName()).isNotNull();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Test that producer stores config in QBit metadata.
|
||||
*******************************************************************************/
|
||||
@@ -133,6 +165,6 @@ class GeoDataQBitProducerTest
|
||||
{
|
||||
assertThat(GeoDataQBitProducer.GROUP_ID).isEqualTo("com.kingsrook.qbits");
|
||||
assertThat(GeoDataQBitProducer.ARTIFACT_ID).isEqualTo("qbit-geo-data");
|
||||
assertThat(GeoDataQBitProducer.VERSION).isEqualTo("0.1.0");
|
||||
assertThat(GeoDataQBitProducer.VERSION).isEqualTo("0.2.0");
|
||||
}
|
||||
}
|
||||
@@ -13,10 +13,6 @@
|
||||
<artifactId>qbit-geo-data-tools</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<properties>
|
||||
<qbit.noticesDirectory>${project.basedir}/..</qbit.noticesDirectory>
|
||||
</properties>
|
||||
|
||||
<name>QBit Geographic Data - Tools</name>
|
||||
<description>Data acquisition and formatting utilities for geo-data</description>
|
||||
|
||||
|
||||
Reference in new issue
Block a user