Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aa9e6c5563
|
||
|
|
03127c17a3
|
||
|
|
e4d4b9277b
|
||
|
|
d37fc6c51f
|
||
|
|
1dc87efceb
|
||
|
|
6918b1643a | ||
|
|
0e361584dc | ||
|
|
6995077319
|
||
|
|
5db6927c8e
|
||
|
|
fba6f7f903
|
||
|
|
139927057a
|
||
|
|
587b1fef3f
|
||
|
|
cef870f46e | ||
|
|
adefa969c8 | ||
|
|
2ae5150012 | ||
|
|
1383d92f0a | ||
|
|
d9c313ffca | ||
|
|
45facd1b31 | ||
|
|
253e15b77c | ||
|
|
fa731308f1 | ||
|
|
93ebe812fc | ||
|
|
2c403b77c8 | ||
|
|
e96b49beb6 | ||
|
|
64723faa2a | ||
|
|
d745a3753a | ||
|
|
68f6a6309b | ||
|
|
3cf53b5f1a | ||
|
|
a73162babb | ||
|
|
4e9c047fdb | ||
|
|
6ae6f3dfdd | ||
|
|
2f87fbb31a | ||
|
|
819b3da7d7 | ||
|
|
b9eaa6cf48 | ||
|
|
e6a2ce7f58 | ||
|
|
3ae9356462 |
No files matched your search
@@ -1 +0,0 @@
|
||||
Central Java verification, tests and secret scan are supplied by the organization-scoped workflow.
|
||||
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
]
|
||||
}
|
||||
}
|
||||
+5
-1
@@ -1,6 +1,5 @@
|
||||
# Maven
|
||||
target/
|
||||
.flattened-pom.xml
|
||||
*.jar
|
||||
*.war
|
||||
*.ear
|
||||
@@ -22,3 +21,8 @@ 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
|
||||
test_branch_policy: non-release-no-hotfix-integration
|
||||
publish:
|
||||
snapshot: true
|
||||
release_candidate: true
|
||||
release: true
|
||||
hotfix: true
|
||||
asciidoc: false
|
||||
release_completion: false
|
||||
+4
-33
@@ -7,39 +7,6 @@ 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
|
||||
@@ -52,6 +19,10 @@ This is the first release that works on QQQ 4.1; 0.1.0 was built against QQQ 0.3
|
||||
|
||||
### 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; pom/README still declare AGPL-3.0 while LICENSE/NOTICE are Apache-2.0; 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; current first-party pom/README declarations align with Apache-2.0 LICENSE/NOTICE; main and develop have diverged.
|
||||
@@ -7,33 +7,16 @@ 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</artifactId>
|
||||
<version>0.2.0</version>
|
||||
<artifactId>qbit-geo-data-core</artifactId>
|
||||
<version>${qbit-geo-data.version}</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:
|
||||
@@ -179,4 +162,11 @@ The generator creates a changelog with your prefix substituted and only includes
|
||||
|
||||
## License
|
||||
|
||||
AGPL-3.0 - See [LICENSE](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)
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>com.kingsrook</groupId>
|
||||
<artifactId>qbit-build-parent</artifactId>
|
||||
<version>1.5.1</version>
|
||||
<version>2.2.1</version>
|
||||
</parent>
|
||||
|
||||
<groupId>com.kingsrook.qbits</groupId>
|
||||
@@ -21,8 +21,8 @@
|
||||
|
||||
<licenses>
|
||||
<license>
|
||||
<name>GNU Affero General Public License v3.0</name>
|
||||
<url>https://www.gnu.org/licenses/agpl-3.0.html</url>
|
||||
<name>Apache License, Version 2.0</name>
|
||||
<url>https://www.apache.org/licenses/LICENSE-2.0</url>
|
||||
<distribution>repo</distribution>
|
||||
</license>
|
||||
</licenses>
|
||||
@@ -50,8 +50,8 @@
|
||||
</modules>
|
||||
|
||||
<properties>
|
||||
<revision>0.2.0</revision>
|
||||
<qqq.version>4.1.0-RC.21</qqq.version>
|
||||
<qqq.version>4.1.0-RC.26</qqq.version> <!-- feeds qbit-build-parent's qqq-bom-pom import -->
|
||||
<revision>0.3.0-SNAPSHOT</revision>
|
||||
<maven.compiler.source>21</maven.compiler.source>
|
||||
<maven.compiler.target>21</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
@@ -59,10 +59,11 @@
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<!-- 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.json</groupId>
|
||||
<artifactId>json</artifactId>
|
||||
<version>20231013</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit</groupId>
|
||||
<artifactId>junit-bom</artifactId>
|
||||
@@ -70,31 +71,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.3</version>
|
||||
<version>3.27.7</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>
|
||||
<!-- Resolve the CI-friendly ${revision} in the installed and deployed POMs so -->
|
||||
<!-- consumers of the published artifacts can resolve the parent chain. -->
|
||||
<!-- Replace ${revision} with its value in the poms that get installed and published. -->
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>flatten-maven-plugin</artifactId>
|
||||
@@ -112,7 +113,7 @@
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>flatten-clean</id>
|
||||
<id>flatten.clean</id>
|
||||
<phase>clean</phase>
|
||||
<goals>
|
||||
<goal>clean</goal>
|
||||
@@ -121,19 +122,28 @@
|
||||
</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 17+
|
||||
- Java 21+
|
||||
- Maven 3.8+
|
||||
- QQQ 0.23.0+
|
||||
- QQQ 4.0.0+
|
||||
|
||||
## Creating Your Data QBit
|
||||
|
||||
|
||||
@@ -13,6 +13,10 @@
|
||||
<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>
|
||||
|
||||
|
||||
+1
-5
@@ -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.2.0";
|
||||
public static final String VERSION = "0.1.0";
|
||||
|
||||
private GeoDataQBitConfig config;
|
||||
|
||||
@@ -126,10 +126,6 @@ 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(produceTableMetaData = true, producePossibleValueSource = true)
|
||||
@QMetaDataProducingEntity(producePossibleValueSource = true)
|
||||
public class City extends QRecordEntity
|
||||
{
|
||||
public static final String TABLE_NAME = "city";
|
||||
@@ -204,103 +204,4 @@ 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(produceTableMetaData = true, producePossibleValueSource = true)
|
||||
@QMetaDataProducingEntity(producePossibleValueSource = true)
|
||||
public class Country extends QRecordEntity
|
||||
{
|
||||
public static final String TABLE_NAME = "country";
|
||||
@@ -171,85 +171,4 @@ 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;
|
||||
}
|
||||
}
|
||||
+1
-73
@@ -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(produceTableMetaData = true, producePossibleValueSource = true)
|
||||
@QMetaDataProducingEntity(producePossibleValueSource = true)
|
||||
public class StateProvince extends QRecordEntity
|
||||
{
|
||||
public static final String TABLE_NAME = "stateProvince";
|
||||
@@ -155,76 +155,4 @@ 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;
|
||||
}
|
||||
}
|
||||
+13
-60
@@ -4,15 +4,13 @@
|
||||
** Syncs all three entity types (Country, StateProvince, City) from bundled
|
||||
** JSON files into database tables. Performs natural key upsert:
|
||||
** - Country: by alpha2Code
|
||||
** - StateProvince: by countryId + code (countryAlpha2 resolved to the persisted country)
|
||||
** - City: by stateProvinceId + name + latitude + longitude (codes resolved to the persisted state)
|
||||
** - StateProvince: by countryAlpha2 + code
|
||||
** - City: by countryAlpha2 + stateCode + name
|
||||
*******************************************************************************/
|
||||
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;
|
||||
@@ -64,39 +62,9 @@ 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", 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"));
|
||||
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"));
|
||||
|
||||
LOG.info("Geo data sync complete",
|
||||
logPair("prefix", prefix),
|
||||
@@ -115,15 +83,17 @@ public class GeoDataSyncStep implements BackendStep
|
||||
** Sync a single table from JSON resource.
|
||||
** Returns count of records processed.
|
||||
*******************************************************************************/
|
||||
private int syncTable(String tableName, List<QRecord> sourceRecords, List<String> naturalKeyFields) throws QException
|
||||
private int syncTable(String tableName, String resourcePath, 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()));
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
@@ -149,7 +119,7 @@ public class GeoDataSyncStep implements BackendStep
|
||||
source.setValue("isActive", true);
|
||||
toInsert.add(source);
|
||||
}
|
||||
else if(hasChanges(source, existing) || !Boolean.TRUE.equals(existing.getValueBoolean("isActive")))
|
||||
else if(hasChanges(source, existing))
|
||||
{
|
||||
source.setValue("id", existing.getValue("id"));
|
||||
source.setValue("isActive", true);
|
||||
@@ -212,7 +182,7 @@ public class GeoDataSyncStep implements BackendStep
|
||||
key.append("|");
|
||||
}
|
||||
Object value = record.getValue(field);
|
||||
key.append(value instanceof Number ? new BigDecimal(value.toString()).stripTrailingZeros().toPlainString() : value != null ? value.toString() : "");
|
||||
key.append(value != null ? value.toString() : "");
|
||||
}
|
||||
return key.toString();
|
||||
}
|
||||
@@ -295,14 +265,7 @@ public class GeoDataSyncStep implements BackendStep
|
||||
}
|
||||
Object sourceValue = source.getValue(fieldName);
|
||||
Object existingValue = existing.getValue(fieldName);
|
||||
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))
|
||||
if(!Objects.equals(sourceValue, existingValue))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -320,7 +283,7 @@ public class GeoDataSyncStep implements BackendStep
|
||||
InsertInput insertInput = new InsertInput();
|
||||
insertInput.setTableName(tableName);
|
||||
insertInput.setRecords(records);
|
||||
requireSuccess(tableName, records.size(), new InsertAction().execute(insertInput).getRecords());
|
||||
new InsertAction().execute(insertInput);
|
||||
}
|
||||
|
||||
|
||||
@@ -333,16 +296,6 @@ public class GeoDataSyncStep implements BackendStep
|
||||
UpdateInput updateInput = new UpdateInput();
|
||||
updateInput.setTableName(tableName);
|
||||
updateInput.setRecords(records);
|
||||
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);
|
||||
}
|
||||
new UpdateAction().execute(updateInput);
|
||||
}
|
||||
}
|
||||
+1
-33
@@ -78,38 +78,6 @@ 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.
|
||||
*******************************************************************************/
|
||||
@@ -165,6 +133,6 @@ class GeoDataQBitProducerTest
|
||||
{
|
||||
assertThat(GeoDataQBitProducer.GROUP_ID).isEqualTo("com.kingsrook.qbits");
|
||||
assertThat(GeoDataQBitProducer.ARTIFACT_ID).isEqualTo("qbit-geo-data");
|
||||
assertThat(GeoDataQBitProducer.VERSION).isEqualTo("0.2.0");
|
||||
assertThat(GeoDataQBitProducer.VERSION).isEqualTo("0.1.0");
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,10 @@
|
||||
<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