Commit Graph
100 Commits
Author SHA1 Message Date
Axel Uhl 6b9460355b bug6318: route TestORCPerformanceCurve online fetch through the impl via x-friends
Drop the hardcoded ORC bulk-download URL from TestORCPerformanceCurve.initialize()
and instead obtain the authenticated per-country bulk JSON stream from
ORCPublicCertificateDatabaseImpl.openBulkCertificatesJsonStream(CountryCode). All
endpoint knowledge (host, path, action, ext) now lives in the impl, the online fetch
gains the Authorization header it was previously missing, and no method has to be
added to the public ORCPublicCertificateDatabase interface. The .impl package is
reachable from the test because it is exported with an x-friends restriction naming
com.sap.sailing.domain.test; clean up the duplicated friend entry in the manifest.

Assisted-By: Claude Opus 4.8 (claude-opus-5-5)
2026-10-17 11:15:50 +02:00
Axel Uhl 182e7a00dd bug6318: remove now-unused logger field from TestORCPerformanceCurve
The logger field became unused once the country-overview-based online fetch
was replaced with a fixed GER bulk fetch in setUp (its only use was the
"Trying to read country certificates for ..." info line). Remove the field
and the now-unused java.util.logging.Logger import so the workspace stays
warning-free.

Assisted-By: Claude Opus 4.8 (claude-opus-5-5)
2026-10-17 10:27:55 +02:00
Axel Uhl 933111da2e bug6318: rewrite ORC public certificate search onto live WPub.dll JSON actions
The legacy data.orc.org WPub.dll XML entry points the adapter depended on
were decommissioned: search() POSTed action=ListCert and
getCountriesWithValidCertificates() GET /RMS, both parsed as XML. They now
302-redirect to HTML / return empty bodies, so parsing as XML tripped the
DOCTYPE / NPE path and failed every ORC test.

Rework the adapter onto the two surviving JSON actions:
  - ?action=DownBoatRMS&RefNo=<ref>&ext=json  (single certificate)
  - ?action=DownRMS&CountryId=<IOC>&ext=json  (per-country bulk)
The global DownRMS (no CountryId) still yields the Countries list used by
getCountriesWithValidCertificates().

ORCPublicCertificateDatabaseImpl:
  - new httpGetJson() (HttpGet + lax redirects + Basic auth; inline
    BOMInputStream charset detection; JSONParser), wrapping org.json.simple
    ParseException in IOException so no org.json type leaks into signatures;
  - parseHandleFromJson() mapping the rich rms-row schema (NatAuth->country,
    BIN->fileId, GPH, RefNo, Family, Age_Year->yearBuilt, IssueDate) with
    tolerant asString/asDouble/asInteger helpers and familyFromString();
  - search(): RefNo-direct via DownBoatRMS, else per-country bulk via DownRMS
    with client-side matchesClientFilters() (name/sail/boatclass/year); the
    JSON actions return only valid certs, so includeInvalid is retained for
    API compatibility but no longer branches;
  - getCountriesWithValidCertificates() reads the top-level Countries array,
    skips the ORC pseudo-entry; throws clause narrowed to IOException;
  - searchForUpdate() now queries by RefNo (dxtName no longer exists);
  - fuzzy search derives the issuing country from the sail-number prefix
    (countryFromSailNumber) so bulk lookups have a CountryId;
  - dead XML parseHandle(Node)/parseCountryOverview(Node) removed.

ORCPublicCertificateDatabase: narrow getCountriesWithValidCertificates()
throws to IOException; Javadoc updated to the JSON actions; https.

Tests: FailIfNoValidOrcCertificateRule probes a fixed strong country (GER)
instead of the now-unavailable cert-count ranking. TestORCPublicCertificate-
Database replaces the dead FRA00013881/SOULMATE fixture with a live
self-seeding pickSeedHandle(); all live tests gated by
@FailIfNoValidOrcCertificates. TestORCPerformanceCurve.setUp fetches the GER
bulk JSON directly. Full com.sap.sailing.domain.orc package: 28/28 green.

Assisted-By: Claude Opus 4.8 (claude-opus-5-5)
2026-10-17 09:44:42 +02:00
Axel Uhl 44e9eedfbc bu6202: release notes 2026-10-12 22:17:59 +02:00
Axel Uhl bc79a79b0c Merge branch 'eclipse-main' into bug6202 2026-10-12 21:55:15 +02:00
Axel Uhl fbff5aedf4 Merge branch 'eclipse-main' into translation 2026-10-08 18:45:17 +02:00
Axel UhlandCopilot b334fac7f5 bug6316: activate Java-11 fragment profile for all JDKs >= 9
The with-java11-non-android-relevant profile was bounded by an upper JDK
version, so with Maven running on JDK 25 the routeconverterjava11extension
fragment was missing from the test runtime. On docker-* branches (tests
on JDK 8 with JavaSE-11.profile) routeconverter then loaded javax.xml.bind
from rt.jar via boot delegation, while Tycho 5's new test TCCL exposes
jaxb-impl's META-INF/services entry, so the bundled impl (wired to the
jaxb-api bundle) got picked, causing JAXB ClassCastExceptions and
IllegalAnnotationsExceptions in com.sap.sailing.server.trackfiles.test.

Assisted-By: Claude Opus 5.5

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-07 21:56:18 +02:00
Axel Uhl 9092744d4a bug6241 aftermath: dispatch regatta raceAdded catch-up outside computeIfAbsent to fix replication initial-load deadlock
The runWhenDoneLoading rewrite in 15dbbc03a1 (companion bug6241
listener-leak fix) made runWhenDoneLoading register a race-removal
listener on the race's regatta via TrackedRegatta.addRaceListener. When
a replica performs its initial load from a master
(RacingEventServiceImpl.initiallyFillFromInternal), it registers a
synchronous RaceAdditionListener on each deserialized regatta. Those
regattas already contain their races, so addRaceListener's catch-up loop
fires raceAdded for every existing race -- and it did so from INSIDE the
ConcurrentHashMap.computeIfAbsent mapping function, because the
SynchronousRunnableExecutor runs the dispatch inline. raceAdded then
called runWhenDoneLoading, which re-entered addRaceListener and nested a
second computeIfAbsent on the same raceListeners map while the outer one
still held the key's reservation node. ConcurrentHashMap forbids the
mapping function from modifying the map, so the nested update spun/blocked
forever on the reservation node -- a single-threaded self-deadlock, not a
lock-ordering deadlock.

This affected production: a replica would hang indefinitely during
initial load from a non-empty master (both automatic replica startup via
the replication Activator and operator-initiated replication). It also
wedged the CI build, which runs the replication initial-load tests;
TrackedRaceWithSensorFixStoreContentsReplicationTest hung until the 6h
GitHub Actions cap cancelled the job. The normal getOrCreateTrackedRegatta
path was unaffected because the listener there is registered on a
freshly-created empty regatta (zero-race catch-up, no re-entry).

Fix: TrackedRegattaImpl.addRaceListener now captures the catch-up work in
a holder inside the mapping function and runs it AFTER computeIfAbsent
returns, with the entry fully installed and no reservation node held. The
holder is populated only when a new entry was actually created, preserving
the duplicate-registration collapse, and the catch-up dispatch still runs
under the trackedRacesLock read lock, so ordering relative to enqueEvent
is unchanged. A nested addRaceListener for a different listener key now
proceeds normally.

TrackedRaceWithSensorFixStoreContentsReplicationTest now passes in ~7s.

Assisted-By: Claude (Anthropic claude-4.8-opus)
2026-10-07 14:07:58 +02:00
Axel Uhl 6cb7d291e5 bug6316: add junit-platform-suite bundles to target platform for Tycho 5
Tycho 4.0.x implicitly contributed the JUnit Platform "suite" bundles to the
tycho-surefire test runtime, so our target platform never had to provide
org.junit.platform.suite.api itself. Tycho 5.0.4 no longer injects them, which
broke test resolution for every *.test bundle with:

  Cannot resolve project dependencies:
    You requested to install 'java.package; org.junit.platform.suite.api
    [1.0.0,2.0.0)' but it could not be found

The 2024-12 release repository we already reference for JUnit 5 (Platform
1.11.3 / Jupiter 5.11.3) also provides the split suite artifacts, so add them
from the same repository and include them as bundles:

  junit-platform-suite-api / -commons / -engine (+ .source) 1.11.3

suite-engine is included as well, at effectively no cost, to also support
running JUnit Platform @Suite classes via the suite engine.

Assisted-By: Claude Opus 4.8
2026-10-06 19:46:29 +02:00
Axel Uhl 3cda4269cd Merge branch 'eclipse-main' into bug6316 2026-10-06 18:10:51 +02:00
Axel Uhl b7f5edf70e Bump pinned Apache Maven from 3.9.11 to 3.9.16
3.9.16 is the newest release on the Maven 3.9.x line (which Tycho 4.0.x
requires) and is the version the GitHub runner image shipped as default
before image 20261004.327.1 bumped the default to the Tycho-incompatible
3.10.0. Pinning the image's former default avoids a per-run download.

Assisted-By: Claude Opus 4.8
2026-10-06 18:08:33 +02:00
Axel Uhl defa035a70 bug6316: upgrade Eclipse Tycho from 4.0.12 to 5.0.4
Tycho 4.0.x is end-of-life (4.0.13 was the final release); upstream
recommends migrating to the 5.x main line. Tycho 5.x requires Java 21+ as
its runtime VM, which release.yml already satisfies (Maven runs on JDK
25), and still targets the Maven 3.9.x line, so the existing Maven 3.9.11
pin remains valid.

Only java/pom.xml is bumped. The standalone java/com.amazon.aws.aws-java-api
module is deliberately left at Tycho 1.7.0: it builds its local p2 repo
under JDK 8, and Tycho 5.x cannot run on Java 8.

This is an initial probe to see how the release build reacts; serialization/
replication validation (see Bugzilla 3859 note in java/pom.xml) still pending.

Assisted-By: Claude Opus 4.8
2026-10-06 17:40:15 +02:00
Axel Uhl 2550cecea3 Pin Apache Maven to 3.9.11 for Tycho 4.0.x compatibility
The GitHub-hosted ubuntu runner image bumped its default Apache Maven
from 3.9.x to 3.10.0 (image ubuntu24/20261004.327). Tycho 4.0.x requires
the Maven 3.9.x line; under Maven 3.10.0 Tycho's Sisu/Guice component
wiring fails at extension bootstrap with "No implementation for
MavenTargetLocationFactory was bound" while provisioning
DefaultTargetPlatformFactory, breaking the build.

Pin Maven to 3.9.11 via setup-maven in every workflow that runs Maven
(release, build-aws-sdk-p2, build-sailing-base-p2, build-jetty-p2) so the
build no longer depends on whatever Maven the runner image ships. The
action is SHA-pinned consistently (stCarolas/setup-maven v5.1).

Assisted-By: Claude Opus 4.8
2026-10-06 17:33:04 +02:00
Axel Uhl b516c7957e fixed typo in wiki tutorials home.md 2026-10-06 13:13:21 +02:00
Axel Uhl 179a3ee25c Merge remote-tracking branch 'eclipse/main' into eclipse-main 2026-10-06 13:05:21 +02:00
Axel Uhl ab8a04f8e7 Merge branch 'eclipse-main' into hyperspace 2026-10-06 13:04:14 +02:00
Axel Uhl 024d1d65be hyperspace: raise Gradle Java source/target from 8 to 17
JDK 21 flagged source/target 8 as obsolete. The Gradle app build already
runs under JDK 17 (buildAndUpdateProduct.sh / release.yml), and AGP 8.7
permits Java 17, so bump sourceCompatibility/targetCompatibility and the
ext compat vars to 17. This covers the 4 mobile modules and the 8 java/
library subprojects compiled by Gradle; the separate Tycho/Maven OSGi
build is unaffected (its BREE pins it to Java 8 independently).

Assisted-By: Claude (claude-opus-5-5)
2026-10-06 11:34:23 +02:00
Axel Uhl 51f9c0ee6b sorted links in Wiki/tutorial support.sapsailing.com section 2026-10-06 00:09:06 +02:00
Axel Uhl baa80a0126 fixed link from planning.md to smartphone-tracking-general 2026-10-05 21:01:47 +02:00
Axel Uhl 98a09bd4e7 Merge remote-tracking branch 'eclipse/main' into eclipse-main 2026-10-05 21:01:42 +02:00
Axel Uhl 22e789d921 Bind httpprinter debug socket to loopback only
Fixes CodeQL py/bind-socket-all-network-interfaces by binding the
local debug HTTP printer to 127.0.0.1 instead of all interfaces
(host = ''). The script is a small local debugging utility, so
restricting it to loopback does not affect its intended use.

Assisted-By: Claude Opus 4.8
2026-10-05 18:37:26 +02:00
Axel Uhl 42bdfb2cb4 checking MDI URL for trusted domains also from admin console UI 2026-10-05 18:18:24 +02:00
Axel Uhl b3ab8d8d62 Merge pull request #36 from SAP-Sailing-Analytics/renovate/lock-file-maintenance
Lock file maintenance
2026-10-05 12:12:42 +02:00
Axel Uhl b70837f803 Merge pull request #35 from SAP-Sailing-Analytics/renovate/rubygems-sinatra-vulnerability
Update dependency sinatra to v4 [SECURITY]
2026-10-05 12:12:00 +02:00
Axel Uhl 88a8587f91 Merge pull request #34 from SAP-Sailing-Analytics/renovate/rubygems-webrick-vulnerability
Update dependency webrick to v1.8.2 [SECURITY]
2026-10-05 12:11:32 +02:00
Axel Uhl 4bcf7cabad Merge pull request #33 from SAP-Sailing-Analytics/renovate/rubygems-rack-vulnerability
Update dependency rack to v2.2.23 [SECURITY]
2026-10-05 12:11:15 +02:00
Axel Uhl 05f8a658c9 hyperspace: fix Play Services version skew crashing QR scanner (Buoy Pinger)
After the Android 16 / AGP 8 / JDK 21 upgrades, opening the QR code scanner
in the Buoy Pinger app crashed with NoClassDefFoundError for the internal
GMS classes zzdkv / zzbgl when constructing BarcodeDetector.Builder.

Root cause: play-services-maps:17.0.1 forced play-services-basement/base up to
17.0.0 in the Buoy Pinger graph, while play-services-vision stayed at 12.0.1.
Vision 12.0.1 references internal classes that existed in basement 12.x but
were removed in 17.0.0, so they fail to resolve at runtime. Race Manager was
unaffected only because it pulls no maps dependency, keeping its whole GMS
stack at 12.x.

Fix (preserving minSdk 16 for old tracking/pinging hardware):
- Bump play-services-vision 12.0.1 -> 19.0.0. Vision 19.0.0 depends on
  basement/base 17.0.0 (the versions maps 17.0.1 already pulls) and keeps
  minSdk 14; the BarcodeDetector API is unchanged, so no barcode code changes.
- Bump play-services-location 12.0.1 -> 21.0.1 so Race Manager's fused-location
  calls do not hit the same basement mismatch once vision raises basement; it
  resolves to basement 18.1.0 (minSdk 14), consistent across all three apps.
- Modernize WindFragment's deprecated LocationRequest API (create()/setPriority/
  setInterval/setFastestInterval and LocationRequest.PRIORITY_HIGH_ACCURACY) to
  LocationRequest.Builder with Priority.PRIORITY_HIGH_ACCURACY.

Verified: all three app modules (Buoy Pinger, Tracking, Race Manager) compile
cleanly against the bumped versions, with no location deprecation warnings.
Runtime QR scan not yet smoke-tested on a device.

Assisted-By: Claude Opus 4.8 (claude-opus-5-5)
2026-10-05 11:40:26 +02:00
Axel UhlandCopilot bd7ce48e65 hyperspace: target Android 16 (API level 36) as required by Google Play
Since August 31, 2026 Google Play rejects app updates not targeting API
level 36 ("Target SDK of artifact is too low: <versionCode>").

Build:
- AGP 8.7.3 -> 8.13.2 (8.7 supports at most API 35; 8.13 supports 36.1
  and works with our Gradle 8.14.3 / JDK 21)
- compileSdk 33 -> 36, targetSdk 35 -> 36, buildTools 34.0.0 -> 36.0.0
- azure-pipelines/main.yml: androidVersion 35 -> 36

Apps (all via the shared library, so all apps get it):
- Predictive back: opt out with android:enableOnBackInvokedCallback="false"
  so that the existing onBackPressed() overrides and KEYCODE_BACK handling
  keep being invoked on Android 16.
- Large screens: temporarily opt out of ignored orientation/resizability
  restrictions on sw600dp displays with
  PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY (ignored from API 37 on).
- Edge-to-edge, enforced without opt-out for targetSdk 36: new
  SystemBarInsetsHandler, registered by LoggableApplication on Android 15+
  devices, pads each activity's content view by the system bar, display
  cutout and (unless adjustPan/adjustNothing) keyboard insets so content
  no longer hides behind the status and navigation/button bar, and fills
  the status bar area with the theme's colorPrimaryDark as before.

Document the androidVersion / Play target API requirement in
building-and-deploying.md.

Assisted-By: Claude Opus 5.5

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-05 01:35:05 +02:00
Axel Uhl c37c5f5502 bug6312: allow operators to set the initial admin password via SAILING_ADMIN_INITIAL_PASSWORD
When a fresh instance starts with an empty security store (one that does
not replicate its security service from a master), initEmptyStore() creates
the default "admin" user. Until now this always used the well-known password
"admin", created the account without enforcing a strong password, and logged
the password in cleartext.

Operators can now set the SAILING_ADMIN_INITIAL_PASSWORD environment variable
to supply the initial admin password. When set to a non-blank value, that
value is used and a strong password is enforced; the password value is never
logged. When unset, the previous "admin" default is kept for backward
compatibility (test / docker-deploy / CI) but a security warning is logged
recommending the variable be set, and noting that future versions may require
it. The cleartext password is no longer logged in either case.

This is opt-in and non-breaking; forcing the variable is left to a later
change after a deprecation period.

Assisted-By: Claude (Opus)
2026-10-04 23:00:56 +02:00
Axel Uhl b2632c3882 Stop logging OAuth tokens, secrets, and request tokens
OAuthRealm and SecurityServiceImpl logged OAuth credentials in cleartext:
the access token, its token value, secret, and raw provider response, as
well as the request token and verifier. These are sensitive values that
should never reach log files, where they widen the blast radius if logs
are leaked or aggregated.

Replace them with non-sensitive success/failure messages that identify
the provider only. The request-token/verifier lines are also dropped to
FINE since they carry no diagnostic value beyond the provider name.

Assisted-By: Claude (Opus)
2026-10-04 22:47:17 +02:00
Axel Uhl 46222d2d65 added configuration/listSailingAnalyticsServers script;
with valid AWS credentials in the env vars (see awsmfa) this script
will use findSailingAnalyticsServers and show the process configurations
found on them under /home/sailing/servers/*
2026-10-04 21:00:49 +02:00
Axel UhlandCopilot 34e3c45de8 hyperspace: rename release APKs to NAAS allow-listed names in Fastlane
Central Signing failed with HTTP 406 ("The requested list doesn't exist in
the library files") after 9e17c0b453 switched files2sign.json to the AGP 8
default <module>-release-unsigned.apk names: Non-ABAP Assembly allow-lists
the APK file name together with group/artifact ID per MoMa assembly, and
the allow-listed names are <module>-unsigned.apk.

Restore the allow-listed names in files2sign.json and let the releaseBuild
lane rename the APKs reported by the gradle action from
*-release-unsigned.apk to *-unsigned.apk, failing the lane if none is
found. This replaces the applicationVariants outputFileName rename dropped
in the AGP 8 migration without relying on the deprecated Variant API that
AGP 9 removes. Update building-and-deploying.md accordingly.

Assisted-By: Claude Opus 5.5

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-04 20:48:26 +02:00
Axel UhlandCopilot 9e17c0b453 hyperspace: match files2sign.json APK names to AGP 8 default output names
The AGP 8 migration (5a78d3e8e3) dropped the applicationVariants
outputFileName rename that stripped "-release" from the APK name, so the
build now produces <module>-release-unsigned.apk. The central signing stage
of the sap-mobile-pipeline template signs Binaries/Android/<file> literally
as listed in files2sign.json, which still named <module>-unsigned.apk and
failed with "File does not exist". Point files2sign.json at the AGP default
names instead of reintroducing the deprecated Variant API rename, which
AGP 9 removes. Document the naming requirement and update the JDK version
in building-and-deploying.md.

Assisted-By: Claude Opus 5.5

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-04 20:15:34 +02:00
Axel Uhl 9bc3a67c12 Bump sapGradleVersion to 8.14.3 to match the Gradle wrapper
Assisted-By: Claude Opus 4.8 (claude-opus-5-5)
2026-10-02 21:17:37 +02:00
Axel Uhl ffec282970 Merge branch 'eclipse-main' into hyperspace 2026-10-02 21:11:13 +02:00
Axel Uhl c8e7076053 Fix fatal lintDebug errors so Android apps stay Play-Store publishable under AGP 8
Under AGP 8 the CI 'build' task runs lintDebug as fatal. After the JDK 21 /
Gradle 8.14.3 / AGP 8.7.3 migration this surfaced several blockers that the
old Fastlane assembleRelease path never ran lint against.

Google Play SDK Index (RiskyLibrary):
- play-services-maps 12.0.1 is flagged as outdated and blocks publishing.
  Split the shared play_services version variable into per-artifact versions
  (maps/location/vision) so modules can diverge, and bump maps to 17.0.1 (its
  minSdk floor is still 14, so no app needs a higher minSdk).
- Move play-services out of the shared library: it now exposes vision only
  (the shared QR/barcode scanner). maps goes to Buoy Pinger (the only app with
  a real map UI); location goes to Race Manager (WindFragment); the dead
  play-services-gcm declaration is dropped from the tracking app.
- Race Manager no longer depends on maps: AppPreferences stored a wind
  position as com.google.android.gms.maps.model.LatLng purely as a
  (double,double) holder. Replace it with the existing domain type
  com.sap.sse.common.Position / com.sap.sse.common.impl.DegreePosition.

targetSdk 35 runtime-correctness (local lint checks, not Play Console gates):
- TrackingFragment: register the GPS-disabled receiver with
  RECEIVER_NOT_EXPORTED on API 33+ (guarded native flag; androidx.core 1.0.0
  lacks the ContextCompat overload), suppressing UnspecifiedRegisterReceiverFlag.
- TrackingService: scope the gpsDisabled broadcast to this app's package so it
  reaches the now non-exported receiver (fixes UnsafeImplicitIntentLaunch).
- RaceStateService / RaceLogPollingService: guard setExact with
  canScheduleExactAlarms() on API 31+, falling back to inexact set().
- RaceStateService: declare POST_NOTIFICATIONS and guard the notify() update
  with a runtime permission check on API 33+.

MissingInflatedId false positives: the referenced ids live in qualified layout
variants (e.g. race_schedule_start_time_normal.xml) that lint cannot resolve
from R.layout.race_schedule_start_time; suppress MissingInflatedId on the three
affected onCreateView methods.

Verified: ./gradlew lintDebug assembleRelease (JDK 21) and the CI gate
./gradlew build (JDK 17) both BUILD SUCCESSFUL with 0 fatal lint errors; all
three apps' apk/.version artifacts regenerate.

Assisted-By: Claude Opus 4.8
2026-10-02 19:07:29 +02:00
Axel Uhl 084ce18ee2 Revert "Upgrade Gradle wrapper from 7.6 to 8.14.3 for JDK 21 support"
This reverts commit 700bbcb1d2.
2026-10-02 17:56:58 +02:00
Axel Uhl 5a78d3e8e3 Migrate Android build to AGP 8.7.3 for Gradle 8.14.3 / JDK 21
CI dropped JDK 17 for JDK 21; Gradle 7.6 cannot run on JDK 21, so the
wrapper was bumped to 8.14.3 (700bbcb1d2). That alone is insufficient:
AGP 7.2.2 references IncrementalTaskInputs (removed in Gradle 8.0), so
assembleRelease failed. Gradle 8.x and AGP must move together.

Changes:
- Root build.gradle: AGP 7.2.2 -> 8.7.3; grgit 2.3.0 -> 5.3.0 (classpath
  and plugins block); ext.buildTools 30.0.3 -> 34.0.0 (AGP 8.7 floor).
- gradle.properties: enable AndroidX via the Jetifier shim
  (android.useAndroidX=true, android.enableJetifier=true) and set
  android.nonTransitiveRClass=false to restore the merged R so appcompat
  attrs (e.g. R.attr.colorAccent) resolve as before.
- All 4 mobile/ modules: package= in AndroidManifest.xml -> namespace in
  build.gradle; packagingOptions -> packaging; re-enable BuildConfig
  generation (buildFeatures { buildConfig true }), off by default in AGP 8.
- 3 app modules: port the removed applicationVariants APK-copy to the AGP 8
  androidComponents Artifacts API (CopyApkToWww consumes SingleArtifact.APK
  via BuiltArtifactsLoader), reproducing the <packageName>.apk and
  <packageName>.version output in java/com.sap.sailing.www/apps/.
- Jetifier rewrites dependency bytecode but not our own sources, so migrate
  all android.support.* imports/FQNs/XML tags to androidx.* across mobile/.
- AGP 8 makes R fields non-final; convert every switch(R.id...) to if/else
  (18 files) since case labels require constant expressions.

assembleRelease is BUILD SUCCESSFUL under JDK 21 / Gradle 8.14.3 and
produces the same APK and .version artifacts as before. Remaining warnings
are Gradle-10 space-assignment deprecations only; we are intentionally on 8.x.

Assisted-By: Claude Opus 4.8 (claude-opus-5-5)
2026-10-02 17:54:22 +02:00
Axel Uhl 700bbcb1d2 Upgrade Gradle wrapper from 7.6 to 8.14.3 for JDK 21 support
The CI JDK was upgraded from 17 to 21, but Gradle 7.6 only supports
running on JDK up to 19. Running it on JDK 21 made the Groovy script
compiler reject the JDK-21 (class file major version 65) settings.gradle
with 'Unsupported class file major version 65' / 'Could not open settings
generic class cache'. Gradle 8.5 is the first release to support JDK 21;
8.14.3 is the latest 8.x (supports JDK 21-24) and avoids the breaking
changes of the 9.x line. Verified locally: 'gradlew help' configures the
:mobile Android project BUILD SUCCESSFUL under JDK 21.

Assisted-By: Claude Opus 4.8
2026-10-02 16:39:35 +02:00
Axel Uhl 15dbbc03a1 Make TrackedRace.runWhenDoneLoading fire at most once per registration
On ARCHIVE (replay-heavy) startup the restore counters could drift:
numberofracesrestoreddoneloading short and numberofracesstillloading
negative (observed -1). The negative count traced to a single race whose
stillLoading-- callback fired twice against one matching raceAdded +1.

Every restored race arms runWhenDoneLoading twice, from two independent
sites with two distinct runnables (restore path -> doneLoading++, and
RaceAdditionListener.raceAdded -> stillLoading--), each meant to fire
once. The old runWhenDoneLoading removed its listener inside
statusChanged with no one-shot guard. Because notifyListeners snapshots
the listener set under lock and then iterates the snapshot outside the
lock, a removeListener happening during dispatch does not shorten an
already-in-flight iteration. When a race crossed past-LOADING twice
back-to-back under concurrent status pushes (loader finish plus the
stopTracking teardown), a second dispatch snapshotted the listener array
before the first dispatch's removeListener landed, so the callback fired
on both transitions -> two decrements vs one increment -> net -1 (and a
symmetric +1 over-count on doneLoading).

Rewrite runWhenDoneLoading to mirror runWhenPastLoading: a settled
AtomicBoolean CAS makes firing strictly one-shot per registration no
matter how many times or along how many concurrent paths the transition
is delivered, plus a cooperating regatta raceRemoved listener that tears
down and settles-without-firing if the race is removed while still
PREPARED/LOADING/ERROR (closing the companion bug6241 listener-leak
class).

Add TrackedRaceRunWhenDoneLoadingTest as contract tests for the
single-fire / already-done / removed-before-done / fire-then-remove
behaviours. Note these do not reproduce the production concurrency
interleaving: setStatus dispatches statusChanged synchronously and
sequentially, so single-threaded JUnit cannot deterministically recreate
the snapshot/remove race; the fix rests on the settled CAS.

Assisted-By: Claude (Anthropic claude-4.8-opus)
2026-10-02 15:40:14 +02:00
Axel Uhl d1faacfb5b Reapply "trying JDK 21 for Azure mobile app builds"
This reverts commit 35482cad75.
2026-10-02 15:20:31 +02:00
Axel Uhl 35482cad75 Revert "trying JDK 21 for Azure mobile app builds"
This reverts commit 4d924ef358.
2026-10-02 15:18:08 +02:00
Axel Uhl 4d924ef358 trying JDK 21 for Azure mobile app builds 2026-10-02 14:27:28 +02:00
Axel Uhl f5aea106a9 release notes for MPC candidate time comparison fix 2026-10-01 17:23:01 +02:00
Axel Uhl 9de68eb70d bug6279: prove candidate time comparator overflow with a regression test
The StartAndEndAwareTimeBasedCandidateComparator compared candidate time
points with (int) (o1.getTimePoint().asMillis() - o2.getTimePoint().asMillis()).
Epoch milliseconds are ~1.7e12, so for candidates more than Integer.MAX_VALUE
ms (~24.8 days) apart the 64-bit difference is truncated to 32 bits and its
sign can flip, turning the comparator into a non-total order. On the "my"
server this pinned all background-executor threads in TreeSet/NavigableSet
navigation and starved the maneuver-calculation queue (live candidate set
span was ~355.9 days, 14.3x the int limit).

CandidateComparatorOverflowTest reproduces the mechanism in isolation against
plain long epoch millis, independent of any domain type:
- truncatingCastReportsACyclicOrder: a single >2^31 ms step overflows and
  flips the sign, producing the cycle T_LOW > T_MID > T_HIGH > T_LOW.
- treeSetNavigationIsConsistentOnlyWithFixedComparator: the broken order makes
  TreeSet first()/last() disagree with the true chronological extremes.
- convergenceLoopTerminatesOnlyWithFixedComparator: an updateCandidates-shaped
  loop over a realistically sized multi-cluster set never drains under the
  buggy comparator (hits a 1,000,000-iteration cap) but drains in ~50 passes
  with Long.compare.

Assisted-By: Claude Opus 4.8 (Claude Code)
2026-10-01 17:09:08 +02:00
Axel Uhl 770bad9fca properly compare candidate time points in mark passing calculator;
the old code subtracted the millisecond representation of the time
points from one another and cast the result to an int which may have
silently led to an overflow in case the difference between the time
points was greater than 49 days. For races with open-ended tracking time
ranges and sporadic fixes along that full time range may have caused
problems here.
2026-10-01 15:59:43 +02:00
Axel Uhl bf333bf5bd worked on onboarding.md improvements 2026-10-01 10:22:32 +02:00
Axel Uhl d980f1744f Wiki improvements 2026-10-01 10:04:39 +02:00
Axel Uhl 0cb1df2686 bug2626: make testMaxSpeedCacheRaceCondition deterministic
The barrier-based synchronization introduced earlier let the second query's
re-entry into computeMaxSpeed(...) await the auto-reset CyclicBarrier a second
time. Only the three fix-adding threads plus the first query's stale
computation ever reach the barrier, so on the second generation the number of
arriving parties depended on how many times computeMaxSpeed(...) got re-entered
via cacheLookup(...)'s recursive getMaxSpeed(entryTo, to) stitching after the
cache was invalidated. That count varies with thread scheduling: locally it
happened to reach the barrier's party count and returned fast, while in CI it
did not, so await(20s) timed out and the write lock was reported held for more
than 10s, failing the run consistently.

Gate the barrier await with an AtomicBoolean so that exactly the first
computeMaxSpeed(...) invocation waits on the barrier. Every later invocation
(the second query and all its recursive sub-range computations) skips it,
removing the scheduling-dependent second-generation rendezvous entirely.

Assisted-By: Claude Opus 4.8 (claude-opus-4-8)
2026-10-01 08:30:23 +02:00
Axel Uhl e4360b299e make testMaxSpeedCacheRaceCondition deterministic
The test reproduced a MaxSpeedCache race by letting a stale
computeMaxSpeed(...) result get cached after invalidating GPS fixes had
been added. It relied on a fixed Thread.sleep(1000) inside computeMaxSpeed
to "hope" the three fix-adding threads had reached the point of contending
for the cache's write lock. Under CI load this timing assumption broke and
the test failed with a CyclicBarrier TimeoutException.

Replace the sleep with an explicit CyclicBarrier (fixThreadsReadyToContend,
parties=4): the first query's stale computation and all three fix-adding
threads rendezvous on it, so the stale result only completes once the
invalidations are guaranteed to be about to contend. Hoist the threads to
named locals and join all of them, establishing happens-before between the
invalidations and the second query before asserting the recomputed maximum
speed. This removes the timing dependency and makes the ordering
deterministic.

Assisted-By: Claude Opus 4.8 (claude-opus-5-5)
2026-10-01 00:52:27 +02:00
Axel Uhl ee6bef722d bug6308: removed extra package import from com.sap.sailing.gwt.ui 2026-09-30 20:54:35 +02:00
Axel Uhl 3af2d7cfcc Merge branch 'eclipse-main' into bug6308 2026-09-30 20:49:06 +02:00
Axel Uhl 27653b7e6e further details for MoMa handling for Android app builds 2026-09-30 20:42:45 +02:00
Axel Uhl fe19925dbd Upgraded Android apps from version 1.4.125 to 1.4.126 2026-09-30 17:04:23 +02:00
Axel Uhl 62adb85016 Merge branch 'eclipse-main' into hyperspace 2026-09-30 17:04:09 +02:00
Axel Uhl 0ea45039cb adjusted MoMa URLs for building Android mobile apps 2026-09-30 17:03:50 +02:00
Axel Uhl cd4a4885e2 Merge remote-tracking branch 'githubsapsailing/hyperspace' into hyperspace 2026-09-30 15:06:52 +02:00
Axel Uhl b0bd857569 drop HttpRequest connection close in Android apps;
this may have dated back to very old Android versions <2.3
and is no longer needed; dropping it should raise chances a socket / TLS
connection can be re-used across multiple HTTPS requests, improving
performance specifically on links with high latency
2026-09-30 14:36:26 +02:00
Axel Uhl 9d1b63a066 Merge remote-tracking branch 'eclipse/main' into eclipse-main 2026-09-30 14:26:33 +02:00
Axel Uhl 9c192c0465 upgraded gradle to 7.2.2 2026-09-30 14:25:56 +02:00
Axel Uhl 2abd5d3ad1 fixed incorrect link in tutorial home.md 2026-09-30 14:21:28 +02:00
Axel Uhl e32e2da979 Merge branch 'eclipse-main' into translation 2026-09-29 12:59:26 +02:00
Axel Uhl a9b2cf834e (re-)added missing sailinsight i18n messages;
those had accidentally gotten deleted by commit
31003728e9dd5915a4c1c3d4c79d2ee24cb34128, crossing a correponding change
in the original repo, followed by a somewhat careless copy/overwrite
from here.
2026-09-29 10:46:51 +02:00
Axel Uhl ebbc42cbc8 Merge branch 'eclipse-main' into bug6308 2026-09-28 23:12:02 +02:00
Axel Uhl a08b307758 accept as available also when numberofracesstillloading<=0 2026-09-28 23:10:48 +02:00
Axel Uhl c96bdae53c Merge branch 'eclipse-main' into bug6308 2026-09-28 22:58:01 +02:00
Axel Uhl 83cb23c78a Add one-shot migration to strip/remove legacy random= nonce docs from restore queue
Legacy documents in CONNECTIVITY_PARAMS_FOR_RACES_TO_BE_RESTORED stored the raw
paramURL including a random=<nonce> query parameter. Current keying (via
TracTracRaceTrackerImpl.getParamURLStrippedOfRandomParam) writes and deletes
using the paramURL stripped of that nonce, so the raw legacy copies can never be
matched by replaceOne/deleteOne. They linger indefinitely, inflate the restore
queue, and are re-attempted on every server start.

This mongosh migration auto-discovers every database in a replica set, skips
those without the collection, and for each nonce-bearing document either deletes
it (when a stripped twin already exists) or strips the random= parameter in
place (when it is the only copy). Dry-run by default; APPLY=1 executes and first
copies each affected document into a per-DB _NONCE_BACKUP collection for
rollback.

Assisted-By: Claude Opus 4.8 (claude-opus-5-5)
2026-09-28 22:52:42 +02:00
Axel Uhl b41b1e1e40 improving logs for race restore records, quoting regatta name 2026-09-28 21:59:02 +02:00
Axel Uhl 27898cfc7c Merge branch 'eclipse-main' into bug6308 2026-09-28 17:22:09 +02:00
Axel Uhl edc2c0dffa sap issue 81: fixed use of wrong environment name in imageupgrade 2026-09-28 17:13:09 +02:00
Axel Uhl aca9abe6c5 bug6308: admin release notes update 2026-09-28 17:12:49 +02:00
Axel Uhl e0eda7f660 bug6308: using waitUntilRacesLoaded=true for primary health check 2026-09-28 17:11:45 +02:00
Axel Uhl 4ae62a1d6f bug6308: moved WAIT_UNTIL_RACES_LOADED constant to new common LandscapeConstants 2026-09-28 16:35:16 +02:00
Axel Uhl bbe59e19a6 bug6308: start out with >0 races to restore, if restoring races is requested 2026-09-28 15:00:58 +02:00
Axel Uhl ead3b3c400 sap issue #81: launch T4_G_MEDIUM for ARM64 images for upgrade 2026-09-28 15:00:04 +02:00
Axel Uhl cc9548dc0a moved ai-agent-sailing-knowledge.md to wiki/projects and linked from Home.md 2026-09-28 14:59:19 +02:00
Axel Uhl 415bc45c24 Merge remote-tracking branch 'sap/maptiles-imageupgrade' into maptiles-imageupgrade 2026-09-24 15:46:32 +02:00
Axel Uhl ca26fcf06a maptiles: no mongo clearing for maptiles server image 2026-09-24 15:42:03 +02:00
Axel Uhl bc71893d6e maptiles: probe for uv before running deploy_linux_host.py
The local branch of setup-maptiles-instance.sh runs
./linux_host/deploy_linux_host.py, which requires the "uv" tool. When uv
is not installed locally, the deployment failed. Probe for uv before the
pushd/deploy step and, if it is missing, prompt the user (defaulting to
No) before installing it via the official astral.sh installer. After a
confirmed install, source ~/.local/bin/env so uv is on PATH in the
current shell, then re-verify and abort with a clear message if it is
still missing or the user declines.

Assisted-By: Claude (claude-4.8-opus)
2026-09-24 15:38:10 +02:00
Axel Uhl 7d88d3b2cc Merge branch 'eclipse-main' into maptiles-imageupgrade 2026-09-24 14:56:21 +02:00
Axel Uhl 00f2f59508 Merge remote-tracking branch 'eclipse/main' into eclipse-main 2026-09-24 14:52:51 +02:00
Axel Uhl c63fc22189 De-flake leaderboard Selenium tests: wait for regatta leaderboard row before reading details
createRegattaLeaderboard() returned as soon as the create dialog's OK was
pressed, without waiting for the newly created regatta leaderboard row to be
rendered into the leaderboards cell table. A subsequent getLeaderboardDetails()
call could then race the AJAX round-trip, find no matching row yet, and return
null, causing NullPointerExceptions downstream (e.g. in linkRace()).

Wait until the new row (named after the regatta, see RegattaDescriptor.toString())
has been rendered before returning, mirroring createFlexibleLeaderboard(). This
fixes the intermittent failures of TestColumnsInLeaderboard.testLeaderboardPanelRefresh
and TestSwitchingOffStartOfRaceInference; the other callers of
createRegattaLeaderboard() were verified to still pass.

Assisted-By: Claude Opus 4.8 (Claude Code)
2026-09-24 14:50:37 +02:00
Axel Uhl b6e6fdff11 maptiles: use local_versions=false to allow for first download;
auto_update=false keeps server from fetching new tiles, also on re-boot
2026-09-24 13:42:53 +02:00
Axel Uhl ea15d72c7a maptiles: use local_versions=true 2026-09-24 13:26:56 +02:00
Axel Uhl bbabfc5b8a use bake.jsonc instead of config.jsonc for maptiles image creation 2026-09-24 11:57:20 +02:00
Axel Uhl 1174c119bc downstream issue 81: fixed use of SERVER in maptiles install 2026-09-24 10:55:19 +02:00
Axel Uhl cbbee2244e downstream issue 81: draft for maptiles server set-up and update prep 2026-09-24 10:47:11 +02:00
Axel Uhl df6bfd4a38 use SAP JVM 8 version 8.1.112 2026-09-24 00:25:03 +02:00
Axel Uhl 856597da52 Display wind chart times in the browser's local time zone
Introduce a moxieapps-Highcharts Time wrapper (useUTC/timezone/timezoneOffset)
and Highcharts.Options.setTime(Time), and switch the vertical wind chart and the
device-mapping chart to render datetime axes in the browser's local time zone.

With the bundled Highcharts 12.1.2 the browser-local zone is selected via
useUTC=false (equivalent to timezone=undefined); the v11-only special value
"local" is no longer valid in v12. Also ensure the global time option is applied
via Highcharts.setOptions(...) before the affected Chart is constructed, since
setOptions only affects charts created afterwards.

Assisted-By: Claude (claude-4.8-opus)
2026-09-23 15:02:22 +02:00
Axel Uhl 1271df590d added hint to CLAUDE.md to using AssistAI MCP server if available 2026-09-23 14:05:36 +02:00
Axel Uhl 0281136cc7 Fix flaky UserGroupApiTest Selenium-4 click race on server-configuration update
The self-service checkbox in LocalServerManagementPanel fired its
config-write RPC with a plain AsyncCallback and signalled progress via an
"updating" DOM attribute. Under Selenium 4 the W3C click() returns before
the browser dispatches the checkbox's ValueChangeHandler, so LocalServerPO
could observe "updating != true" and proceed before the RPC had even been
fired, letting createEvent run before the server was in self-service mode.

Bring the panel into the harness's standard pending-Ajax mechanism: mark the
updateServerConfiguration RPC with a MarkedAsyncCallback on a dedicated
category, and replace the bespoke "updating" barrier in LocalServerPO with
the proven baseline-before-click + monotonic finished-call-counter idiom
(as in LeaderboardPage.refresh()). Because the finished-call count only ever
increases, this is immune both to the click-return gap and to a boolean flag
toggling back between polls.

Also encapsulate the PENDING_AJAX_CALLS JavaScript: the literal now lives
once in AjaxCallsExecuted, which exposes getNumberOfFinishedCalls(...);
AjaxCallsComplete reuses the shared constant, and PageObject delegates via
getNumberOfFinishedAjaxRequests(...). LocalServerPO no longer contains JS.

Assisted-By: Claude (claude-4.8-opus)
2026-09-23 13:57:29 +02:00
Axel Uhl b6cee7c9dc bug6227: release notes enhancement about incompatible change 2026-09-23 10:30:03 +02:00
Axel Uhl 424b168567 further reduced memory for SailingServer (No Proxy) launch 2026-09-23 01:14:36 +02:00
Axel Uhl f65d57db1f Merge branch 'eclipse-main' into bug6227 2026-09-23 00:58:53 +02:00
Axel Uhl 3e0537753f Add Yngling keelboat class
Register the Yngling one-design keelboat in the BoatClassMasterdata enum
with its LOA (6.35 m), beam (1.73 m), monohull hull type and downwind-sail
flag (symmetric spinnaker). Wire the class to a 140x140 transparent PNG
derived from the International Yngling Association class insignia via the
GWT BoatClassImageResources ClientBundle and the BoatClassImageResolver
display-name map, and add it to the KeelBoatWithGennaker vector graphics in
BoatClassVectorGraphicsResolver so it renders as a keelboat on the race map
rather than falling back to the default spinnaker-dinghy shape.

Also document the addition in the "What's New" (SailingAnalyticsNotes.html)
and Admin Console release notes (release_notes_admin.html) under September
2026.

Assisted-By: Claude Opus 4.8 (claude-opus-5-5)
2026-09-23 00:56:51 +02:00
Axel Uhl 596b19e877 reduced heap size for launch configs slighly 2026-09-23 00:45:16 +02:00
Axel Uhl 1a07ce63b6 bug6227: stream wind-track count as bare int and share MDI progress-step constants
Switch the wind-track section count on both MDI wire-format ends from
writeObject(Integer)/readObject to writeInt/readInt. The count is now a
bare 4-byte inline int carrying no class descriptor and occupying no
handle-table entry; the null sentinel remains the authority on section
end, the count only drives progress. Javadoc/comments on both ends
updated accordingly.

Introduce shared named constants for the MDI progress schedule
(PROGRESS_TRANSFER_STARTED, PROGRESS_TRANSFER_COMPLETED,
PROGRESS_GRAPH_APPLIED, PROGRESS_WIND_TRACKS_DONE, PROGRESS_COMPLETE)
and derive the two 0.2 band widths from the milestones
(PROGRESS_WIND_TRACKS_BAND_WIDTH, PROGRESS_SENSOR_FIXES_BAND_WIDTH) so
the bands can never drift from the milestones. Replaces the redundant
0.03/0.3/0.6/0.8/1.0/0.2 float literals scattered across the import
progress call sites with a single source of truth.

Assisted-By: Claude Opus 4.8 (claude-opus-5)
2026-09-23 00:31:10 +02:00
Axel Uhl e4baf5e416 bug6227: add TimePoint.plusResolution()/minusResolution() and convert resolution-tick call sites
Introduces two convenience methods on TimePoint that step by one resolution
unit rather than a hard-coded millisecond:

  TimePoint plusResolution()  == plus(getResolution())
  TimePoint minusResolution() == minus(getResolution())

They are implemented once in AbstractTimePoint and saturate at EndOfTime /
BeginningOfTime just like plus/minus do. Expressing "the next/previous
representable instant" through getResolution() keeps these sites correct
should a finer-resolution TimePoint ever be introduced, instead of assuming
one-millisecond granularity.

All plus(1)/minus(1) call sites whose receiver is a TimePoint and whose intent
is a single resolution tick were converted to plusResolution()/minusResolution()
across production and tests. Deliberately left unchanged:
  - plus(1)/minus(1) on Duration receivers (not TimePoints)
  - TimePointTest overflow/saturation tests that exercise plus(long)/minus(long)
    with the literal 1 on purpose

Assisted-By: Claude Opus 4.8 (claude-opus-5)
2026-09-22 23:18:13 +02:00
Axel Uhl 62f95da4bd bug6227: add queryable TimePoint.getResolution() and use it for inclusive->exclusive range end
Introduce TimePoint.getResolution() returning the smallest positive Duration by which two
distinct instants of an implementation can differ, implemented in AbstractTimePoint as
Duration.ONE_MILLISECOND (all current impls are millisecond-backed). TopLevelMasterData's
inclusive-to-exclusive conversion for device mapping ranges now uses
toInclusive.plus(toInclusive.getResolution()) instead of a hard-coded +1ms, so a
single-instant PING mapping stays a non-empty range and the code remains correct should a
finer-resolution TimePoint ever be introduced.

Assisted-By: Claude Opus 4.8 (claude-opus-5)
2026-09-22 23:02:46 +02:00
Axel Uhl 43d0e89336 bug6227: fix single-instant PING mappings being dropped during MDI
RegattaLogDeviceMappingEvent.getToInclusive() is an inclusive end, but
TimeRange.to() is exclusive. TopLevelMasterData.addRangeIfMappingEvent
built TimeRange.create(from, toInclusive); for a single-instant PING
mapping (from == toInclusive, e.g. a pinged mark) this is an empty range,
which MultiTimeRange silently discards. The device is then never streamed
and its single fix never reaches the importing instance.

Convert the inclusive end to an exclusive one by adding one TimePoint
resolution unit (currently 1ms), so [t, t] becomes the non-empty range
[t, t+1ms), and load the corresponding fixes with toIsInclusive == false
in MasterDataResource.writeRaceLogTrackingFixes. Update the related
Javadoc on both sides accordingly.

Adds MasterDataImportTest.testMasterDataImportPingedMarkSingleInstantFix,
which reproduces the drop (single-instant inclusive mapping with one
stored fix) and now passes.

Assisted-By: Claude Opus 4.8 (claude-opus-5)
2026-09-22 22:58:49 +02:00