Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
821632586e
|
||
|
|
1da0afa587
|
||
|
|
b50515a8e8
|
||
|
|
401e7ca471
|
||
|
|
2fd63e734b
|
||
|
|
afa9166f9c
|
||
|
|
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.
|
||||||
+10
-53
@@ -1,57 +1,14 @@
|
|||||||
version: 2.1
|
version: 2.1
|
||||||
|
setup: true
|
||||||
orbs:
|
orbs:
|
||||||
qqq-orb: kingsrook/qqq-orb@0.5.0
|
qqq-orb: kingsrook/qqq-orb@0.8
|
||||||
|
|
||||||
workflows:
|
workflows:
|
||||||
|
setup:
|
||||||
test_only:
|
|
||||||
jobs:
|
jobs:
|
||||||
- qqq-orb/mvn_test_only:
|
- qqq-orb/generate_pipeline:
|
||||||
context: [ qqq-maven-registry-credentials ]
|
orb_version: '0.8'
|
||||||
filters:
|
filters:
|
||||||
branches:
|
branches:
|
||||||
ignore: /(develop|main|release/.*|hotfix/.*|integration.*)/
|
ignore: automation/qbit-rc-pending
|
||||||
tags:
|
tags:
|
||||||
only: []
|
only: /^v\d+\.\d+\.\d+$/
|
||||||
|
|
||||||
publish_snapshot:
|
|
||||||
jobs:
|
|
||||||
- qqq-orb/mvn_publish:
|
|
||||||
context: [ qqq-maven-registry-credentials ]
|
|
||||||
branch_type: snapshot
|
|
||||||
filters:
|
|
||||||
branches:
|
|
||||||
only:
|
|
||||||
- develop
|
|
||||||
|
|
||||||
publish_release_candidate:
|
|
||||||
jobs:
|
|
||||||
- qqq-orb/mvn_publish:
|
|
||||||
context: [ qqq-maven-registry-credentials ]
|
|
||||||
branch_type: release_candidate
|
|
||||||
filters:
|
|
||||||
branches:
|
|
||||||
only:
|
|
||||||
- /release\/.*/
|
|
||||||
|
|
||||||
publish_release:
|
|
||||||
jobs:
|
|
||||||
- qqq-orb/mvn_publish:
|
|
||||||
context: [ qqq-maven-registry-credentials ]
|
|
||||||
branch_type: release
|
|
||||||
filters:
|
|
||||||
branches:
|
|
||||||
only: [ main ]
|
|
||||||
tags:
|
|
||||||
only: [ /v.*/ ]
|
|
||||||
|
|
||||||
publish_hotfix_release:
|
|
||||||
jobs:
|
|
||||||
- qqq-orb/mvn_publish:
|
|
||||||
context: [ qqq-maven-registry-credentials ]
|
|
||||||
branch_type: hotfix
|
|
||||||
filters:
|
|
||||||
branches:
|
|
||||||
only:
|
|
||||||
- /hotfix\/.*/
|
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
name: build
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
jobs:
|
||||||
|
maven:
|
||||||
|
uses: self:KofTwentyTwo/ci-workflows/.gitea/workflows/maven.yaml@v1
|
||||||
|
secrets: inherit
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
name: CodeQL
|
||||||
|
'on':
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- develop
|
||||||
|
- release/**
|
||||||
|
- hotfix/**
|
||||||
|
pull_request: {}
|
||||||
|
schedule:
|
||||||
|
- cron: 41 16 * * 0
|
||||||
|
workflow_dispatch: {}
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
actions: read
|
||||||
|
security-events: write
|
||||||
|
packages: read
|
||||||
|
concurrency:
|
||||||
|
group: codeql-${{ github.event.pull_request.number || github.ref }}
|
||||||
|
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||||
|
jobs:
|
||||||
|
analyze:
|
||||||
|
uses: QRun-IO/qqq-orb/.github/workflows/qbit-codeql.yml@c288a8864fe9054f68410e1d78f5863aea4c4d22
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
# Maven
|
# Maven
|
||||||
target/
|
target/
|
||||||
|
.flattened-pom.xml
|
||||||
*.jar
|
*.jar
|
||||||
*.war
|
*.war
|
||||||
*.ear
|
*.ear
|
||||||
|
|||||||
+16
@@ -0,0 +1,16 @@
|
|||||||
|
profile: qbit
|
||||||
|
orb_version: '0.8'
|
||||||
|
default_branch: main
|
||||||
|
java_version: '21'
|
||||||
|
browsers: true
|
||||||
|
api_checks: false
|
||||||
|
docs_only: false
|
||||||
|
release_on_main: false
|
||||||
|
release_tag_policy: stable
|
||||||
|
test_branch_policy: non-release
|
||||||
|
publish:
|
||||||
|
snapshot: true
|
||||||
|
release_candidate: true
|
||||||
|
release: true
|
||||||
|
asciidoc: false
|
||||||
|
release_completion: false
|
||||||
@@ -7,6 +7,39 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased]
|
## [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
|
### Added
|
||||||
- Multi-module structure (qbit-geo-data-core + tools)
|
- Multi-module structure (qbit-geo-data-core + tools)
|
||||||
- Country, StateProvince, City entities with sync process
|
- Country, StateProvince, City entities with sync process
|
||||||
|
|||||||
@@ -7,16 +7,33 @@ Geographic reference data QBit providing countries, states/provinces, and cities
|
|||||||
|
|
||||||
## Quick Start
|
## 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`:
|
Add the dependency to your `pom.xml`:
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.kingsrook.qbits</groupId>
|
<groupId>com.kingsrook.qbits</groupId>
|
||||||
<artifactId>qbit-geo-data-core</artifactId>
|
<artifactId>qbit-geo-data</artifactId>
|
||||||
<version>${qbit-geo-data.version}</version>
|
<version>0.2.0</version>
|
||||||
</dependency>
|
</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
|
## What You Get
|
||||||
|
|
||||||
This QBit provides three entities with pre-populated reference data:
|
This QBit provides three entities with pre-populated reference data:
|
||||||
|
|||||||
@@ -50,7 +50,8 @@
|
|||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<revision>0.1.0</revision>
|
<revision>0.2.0</revision>
|
||||||
|
<qqq.version>4.1.0-RC.21</qqq.version>
|
||||||
<maven.compiler.source>21</maven.compiler.source>
|
<maven.compiler.source>21</maven.compiler.source>
|
||||||
<maven.compiler.target>21</maven.compiler.target>
|
<maven.compiler.target>21</maven.compiler.target>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
@@ -58,11 +59,10 @@
|
|||||||
|
|
||||||
<dependencyManagement>
|
<dependencyManagement>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<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
|
||||||
<groupId>org.json</groupId>
|
manages junit-jupiter, the API and the platform at 5.10.0; the mixed engine fails discovery ("TestEngine with ID 'junit-jupiter'
|
||||||
<artifactId>json</artifactId>
|
failed to discover tests") and no test runs. Import this pom's own JUnit version first (first import wins) so every JUnit
|
||||||
<version>20231013</version>
|
artifact converges on 5.10.0. -->
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.junit</groupId>
|
<groupId>org.junit</groupId>
|
||||||
<artifactId>junit-bom</artifactId>
|
<artifactId>junit-bom</artifactId>
|
||||||
@@ -70,6 +70,19 @@
|
|||||||
<type>pom</type>
|
<type>pom</type>
|
||||||
<scope>import</scope>
|
<scope>import</scope>
|
||||||
</dependency>
|
</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>
|
<dependency>
|
||||||
<groupId>org.assertj</groupId>
|
<groupId>org.assertj</groupId>
|
||||||
<artifactId>assertj-core</artifactId>
|
<artifactId>assertj-core</artifactId>
|
||||||
@@ -79,6 +92,35 @@
|
|||||||
</dependencyManagement>
|
</dependencyManagement>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<!-- 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>
|
||||||
|
<version>1.7.3</version>
|
||||||
|
<configuration>
|
||||||
|
<updatePomFile>true</updatePomFile>
|
||||||
|
<flattenMode>resolveCiFriendliesOnly</flattenMode>
|
||||||
|
</configuration>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>flatten</id>
|
||||||
|
<phase>process-resources</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>flatten</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
<execution>
|
||||||
|
<id>flatten-clean</id>
|
||||||
|
<phase>clean</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>clean</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
<pluginManagement>
|
<pluginManagement>
|
||||||
<plugins>
|
<plugins>
|
||||||
<plugin>
|
<plugin>
|
||||||
|
|||||||
+5
-1
@@ -28,7 +28,7 @@ public class GeoDataQBitProducer implements QBitProducer
|
|||||||
{
|
{
|
||||||
public static final String GROUP_ID = "com.kingsrook.qbits";
|
public static final String GROUP_ID = "com.kingsrook.qbits";
|
||||||
public static final String ARTIFACT_ID = "qbit-geo-data";
|
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;
|
private GeoDataQBitConfig config;
|
||||||
|
|
||||||
@@ -126,6 +126,10 @@ public class GeoDataQBitProducer implements QBitProducer
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
private void updateForeignKeyReferences(QTableMetaData table)
|
private void updateForeignKeyReferences(QTableMetaData table)
|
||||||
{
|
{
|
||||||
|
if(table.getFields() == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
table.getFields().values().forEach(field ->
|
table.getFields().values().forEach(field ->
|
||||||
{
|
{
|
||||||
if(field.getPossibleValueSourceName() != null)
|
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;
|
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 class City extends QRecordEntity
|
||||||
{
|
{
|
||||||
public static final String TABLE_NAME = "city";
|
public static final String TABLE_NAME = "city";
|
||||||
@@ -204,4 +204,103 @@ public class City extends QRecordEntity
|
|||||||
{
|
{
|
||||||
return modifyDate;
|
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;
|
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 class Country extends QRecordEntity
|
||||||
{
|
{
|
||||||
public static final String TABLE_NAME = "country";
|
public static final String TABLE_NAME = "country";
|
||||||
@@ -171,4 +171,85 @@ public class Country extends QRecordEntity
|
|||||||
{
|
{
|
||||||
return modifyDate;
|
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;
|
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 class StateProvince extends QRecordEntity
|
||||||
{
|
{
|
||||||
public static final String TABLE_NAME = "stateProvince";
|
public static final String TABLE_NAME = "stateProvince";
|
||||||
@@ -155,4 +155,76 @@ public class StateProvince extends QRecordEntity
|
|||||||
{
|
{
|
||||||
return modifyDate;
|
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
|
** Syncs all three entity types (Country, StateProvince, City) from bundled
|
||||||
** JSON files into database tables. Performs natural key upsert:
|
** JSON files into database tables. Performs natural key upsert:
|
||||||
** - Country: by alpha2Code
|
** - Country: by alpha2Code
|
||||||
** - StateProvince: by countryAlpha2 + code
|
** - StateProvince: by countryId + code (countryAlpha2 resolved to the persisted country)
|
||||||
** - City: by countryAlpha2 + stateCode + name
|
** - City: by stateProvinceId + name + latitude + longitude (codes resolved to the persisted state)
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package com.kingsrook.qbits.geodata.sync;
|
package com.kingsrook.qbits.geodata.sync;
|
||||||
|
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.RoundingMode;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@@ -62,9 +64,39 @@ public class GeoDataSyncStep implements BackendStep
|
|||||||
// Sync in order: countries first (no dependencies), then states, then //
|
// Sync in order: countries first (no dependencies), then states, then //
|
||||||
// cities (which may reference states) //
|
// cities (which may reference states) //
|
||||||
//////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////
|
||||||
int countriesInserted = syncTable(prefix + "_country", "/data/countries.json", List.of("alpha2Code"));
|
int countriesInserted = syncTable(prefix + "_country", loadJsonData("/data/countries.json"), List.of("alpha2Code"));
|
||||||
int statesInserted = syncTable(prefix + "_stateProvince", "/data/states.json", List.of("countryAlpha2", "code"));
|
Map<String, QRecord> countries = queryExisting(prefix + "_country", List.of("alpha2Code"));
|
||||||
int citiesInserted = syncTable(prefix + "_city", "/data/cities.json", List.of("countryAlpha2", "stateCode", "name"));
|
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",
|
LOG.info("Geo data sync complete",
|
||||||
logPair("prefix", prefix),
|
logPair("prefix", prefix),
|
||||||
@@ -83,17 +115,15 @@ public class GeoDataSyncStep implements BackendStep
|
|||||||
** Sync a single table from JSON resource.
|
** Sync a single table from JSON resource.
|
||||||
** Returns count of records processed.
|
** 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",
|
LOG.info("Syncing table",
|
||||||
logPair("table", tableName),
|
logPair("table", tableName),
|
||||||
logPair("resource", resourcePath),
|
|
||||||
logPair("naturalKey", naturalKeyFields));
|
logPair("naturalKey", naturalKeyFields));
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////
|
||||||
// 1. Load source data from classpath JSON //
|
// 1. Load source data from classpath JSON //
|
||||||
/////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////
|
||||||
List<QRecord> sourceRecords = loadJsonData(resourcePath);
|
|
||||||
LOG.info("Loaded source data", logPair("count", sourceRecords.size()));
|
LOG.info("Loaded source data", logPair("count", sourceRecords.size()));
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////
|
||||||
@@ -119,7 +149,7 @@ public class GeoDataSyncStep implements BackendStep
|
|||||||
source.setValue("isActive", true);
|
source.setValue("isActive", true);
|
||||||
toInsert.add(source);
|
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("id", existing.getValue("id"));
|
||||||
source.setValue("isActive", true);
|
source.setValue("isActive", true);
|
||||||
@@ -182,7 +212,7 @@ public class GeoDataSyncStep implements BackendStep
|
|||||||
key.append("|");
|
key.append("|");
|
||||||
}
|
}
|
||||||
Object value = record.getValue(field);
|
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();
|
return key.toString();
|
||||||
}
|
}
|
||||||
@@ -265,7 +295,14 @@ public class GeoDataSyncStep implements BackendStep
|
|||||||
}
|
}
|
||||||
Object sourceValue = source.getValue(fieldName);
|
Object sourceValue = source.getValue(fieldName);
|
||||||
Object existingValue = existing.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;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -283,7 +320,7 @@ public class GeoDataSyncStep implements BackendStep
|
|||||||
InsertInput insertInput = new InsertInput();
|
InsertInput insertInput = new InsertInput();
|
||||||
insertInput.setTableName(tableName);
|
insertInput.setTableName(tableName);
|
||||||
insertInput.setRecords(records);
|
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 updateInput = new UpdateInput();
|
||||||
updateInput.setTableName(tableName);
|
updateInput.setTableName(tableName);
|
||||||
updateInput.setRecords(records);
|
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.
|
** Test that producer stores config in QBit metadata.
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@@ -133,6 +165,6 @@ class GeoDataQBitProducerTest
|
|||||||
{
|
{
|
||||||
assertThat(GeoDataQBitProducer.GROUP_ID).isEqualTo("com.kingsrook.qbits");
|
assertThat(GeoDataQBitProducer.GROUP_ID).isEqualTo("com.kingsrook.qbits");
|
||||||
assertThat(GeoDataQBitProducer.ARTIFACT_ID).isEqualTo("qbit-geo-data");
|
assertThat(GeoDataQBitProducer.ARTIFACT_ID).isEqualTo("qbit-geo-data");
|
||||||
assertThat(GeoDataQBitProducer.VERSION).isEqualTo("0.1.0");
|
assertThat(GeoDataQBitProducer.VERSION).isEqualTo("0.2.0");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user