.github/workflows/create-docker-image.yml
... ...
@@ -17,6 +17,18 @@ jobs:
17 17
if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }}
18 18
runs-on: ubuntu-latest
19 19
steps:
20
+ - name: Convert repo name to lowercase GHCR package
21
+ id: ghcr
22
+ env:
23
+ GITHUB_REPOSITORY: ${{ github.repository }}
24
+ run: |
25
+ # $GITHUB_REPOSITORY is in format owner/repo
26
+ OWNER=${GITHUB_REPOSITORY%%/*} # extract owner
27
+ REPO=${GITHUB_REPOSITORY##*/} # extract repo
28
+ OWNER_LOWER=$(echo "$OWNER" | tr '[:upper:]' '[:lower:]') # lowercase owner
29
+ REPO_LOWER=$(echo "$REPO" | tr '[:upper:]' '[:lower:]') # lowercase repo
30
+ PACKAGE="$OWNER_LOWER/$REPO_LOWER"
31
+ echo "PACKAGE=${PACKAGE}" >> $GITHUB_OUTPUT
20 32
- name: Checkout
21 33
uses: actions/checkout@v4
22 34
with:
... ...
@@ -50,7 +62,7 @@ jobs:
50 62
- name: Download release
51 63
shell: bash
52 64
run: |
53
- RELEASE_TAR_GZ_FILENAME=$( configuration/github-download-release-assets.sh ${{ secrets.GITHUB_TOKEN }} ${{ env.RELEASE_PREFIX }} )
65
+ RELEASE_TAR_GZ_FILENAME=$( configuration/github-download-release-assets.sh ${{ secrets.GITHUB_TOKEN }} ${{ env.RELEASE_PREFIX }} ${{ github.repository }} )
54 66
RELEASE=$( echo ${RELEASE_TAR_GZ_FILENAME} | sed -e 's/\.tar\.gz$//' )
55 67
if [ -n ${RELEASE_TAR_GZ_FILENAME} ]; then
56 68
mv ${RELEASE_TAR_GZ_FILENAME} docker/
... ...
@@ -60,7 +72,7 @@ jobs:
60 72
uses: docker/build-push-action@ca877d9245402d1537745e0e356eab47c3520991 # v6.13.0
61 73
with:
62 74
build-args: RELEASE=${{ env.RELEASE }}
63
- tags: ghcr.io/sap/sailing-analytics:${{ env.RELEASE }}${{ env.BRANCH == 'main' && github.event.inputs.release == '' && ',ghcr.io/sap/sailing-analytics:latest' || env.BRANCH == 'docker-17' && github.event.inputs.release == '' && ',ghcr.io/sap/sailing-analytics:latest-17' || env.BRANCH == 'docker-21' && github.event.inputs.release == '' && ',ghcr.io/sap/sailing-analytics:latest-21' || env.BRANCH == 'docker-24' && github.event.inputs.release == '' && ',ghcr.io/sap/sailing-analytics:latest-24' || '' }}
75
+ tags: ghcr.io/${{steps.ghcr.outputs.PACKAGE}}:${{ env.RELEASE }}${{ env.BRANCH == 'main' && github.event.inputs.release == '' && format(',ghcr.io/{0}:latest', steps.ghcr.outputs.PACKAGE) || env.BRANCH == 'docker-17' && github.event.inputs.release == '' && format(',ghcr.io/{0}:latest-17', steps.ghcr.outputs.PACKAGE) || env.BRANCH == 'docker-21' && github.event.inputs.release == '' && format(',ghcr.io/{0}:latest-21', steps.ghcr.outputs.PACKAGE) || env.BRANCH == 'docker-24' && github.event.inputs.release == '' && format(',ghcr.io/{0}:latest-24', steps.ghcr.outputs.PACKAGE) || '' }}
64 76
annotations: |
65 77
maintainer=axel.uhl@sap.com
66 78
index:org.opencontainers.image.title=Sailing Analytics
.github/workflows/merge-main-into-docker-17.yml
... ...
@@ -1,29 +0,0 @@
1
-name: Merge main branch into docker-24 after successful build
2
-on:
3
- workflow_run:
4
- workflows: [release]
5
- types: [completed]
6
- branches: [main]
7
- workflow_dispatch: {}
8
-jobs:
9
- merge-main-into-docker-24:
10
- permissions:
11
- contents: write
12
- if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }}
13
- runs-on: ubuntu-latest
14
- steps:
15
- - name: Checkout
16
- uses: actions/checkout@v4
17
- with:
18
- ref: docker-24
19
- fetch-depth: 0 # fetch the whole thing to make sure the histories merge
20
- - name: Merge main into docker-24
21
- uses: devmasx/merge-branch@854d3ac71ed1e9deb668e0074781b81fdd6e771f # v1.4.0
22
- env:
23
- GH_TOKEN: ${{ secrets.REPO_TOKEN_FOR_MERGE_AND_PUSH }}
24
- with:
25
- type: now
26
- from_branch: main
27
- target_branch: docker-24
28
- message: Auto-merging main into docker-24 after successful release build
29
- github_token: ${{ secrets.REPO_TOKEN_FOR_MERGE_AND_PUSH }}
.github/workflows/merge-main-into-docker-24.yml
... ...
@@ -0,0 +1,29 @@
1
+name: Merge main branch into docker-24 after successful build
2
+on:
3
+ workflow_run:
4
+ workflows: [release]
5
+ types: [completed]
6
+ branches: [main]
7
+ workflow_dispatch: {}
8
+jobs:
9
+ merge-main-into-docker-24:
10
+ permissions:
11
+ contents: write
12
+ if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }}
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ - name: Checkout
16
+ uses: actions/checkout@v4
17
+ with:
18
+ ref: docker-24
19
+ fetch-depth: 0 # fetch the whole thing to make sure the histories merge
20
+ - name: Merge main into docker-24
21
+ uses: devmasx/merge-branch@854d3ac71ed1e9deb668e0074781b81fdd6e771f # v1.4.0
22
+ env:
23
+ GH_TOKEN: ${{ secrets.REPO_TOKEN_FOR_MERGE_AND_PUSH }}
24
+ with:
25
+ type: now
26
+ from_branch: main
27
+ target_branch: docker-24
28
+ message: Auto-merging main into docker-24 after successful release build
29
+ github_token: ${{ secrets.REPO_TOKEN_FOR_MERGE_AND_PUSH }}
.github/workflows/release.yml
... ...
@@ -5,6 +5,7 @@ on:
5 5
- 'wiki/**'
6 6
- '.github/workflows/*'
7 7
- '**/*.md'
8
+ - 'README.md'
8 9
workflow_dispatch:
9 10
inputs:
10 11
skip_tests:
... ...
@@ -14,8 +15,8 @@ on:
14 15
description: Build with a local target platform (set to "true" to use local target platform)
15 16
default: 'false'
16 17
runner_cpus:
17
- description: Default is 16, but can be 4, 8, and 64 as well
18
- default: '16'
18
+ description: Empty means ubuntu-latest, usually 4 CPUs with little disk/memory, but if you have runners named ubuntu-latest-Xcpu, it can be 4, 8, 16, and 64 as well, using, e.g., ubuntu-latest-16cpu; default is ubuntu-latest
19
+ default: ''
19 20
firefox_version:
20 21
description: Version to use for Selenium tests; default is 137.0, but could, e.g., be "141.0" or "latest"
21 22
default: '140.0'
... ...
@@ -25,16 +26,36 @@ jobs:
25 26
permissions:
26 27
contents: write
27 28
checks: write
28
- runs-on: ubuntu-latest-${{ github.event.inputs.runner_cpus == '' && '16' || github.event.inputs.runner_cpus }}cpu
29
+ actions: read
30
+ runs-on: ${{ github.event.inputs.runner_cpus != '' && format('ubuntu-latest-{0}cpu', github.event.inputs.runner_cpus) || 'ubuntu-latest' }}
29 31
steps:
32
+ - name: Free Disk Space (Ubuntu)
33
+ uses: jlumbroso/free-disk-space@v1.3.1
34
+ with:
35
+ # this might remove tools that are actually needed,
36
+ # if set to "true" but frees about 6 GB
37
+ tool-cache: false
38
+ # all of these default to true, but feel free to set to
39
+ # "false" if necessary for your workflow
40
+ android: false
41
+ dotnet: true
42
+ haskell: true
43
+ large-packages: true
44
+ docker-images: false
45
+ swap-storage: true
30 46
- name: Allocate swap space
31 47
shell: bash
32 48
run: |
33
- sudo dd if=/dev/zero of=/mnt/large-swapfile bs=1G count=10
49
+ sudo dd if=/dev/zero of=/mnt/large-swapfile bs=1G count=30
34 50
sudo chmod 600 /mnt/large-swapfile
35 51
sudo mkswap /mnt/large-swapfile
36 52
sudo chown root:root /mnt/large-swapfile
37 53
sudo swapon /mnt/large-swapfile
54
+ - name: Create TMP space under /mnt/tmp
55
+ shell: bash
56
+ run: |
57
+ sudo mkdir /mnt/tmp
58
+ sudo chmod 777 /mnt/tmp
38 59
- name: Collect Workflow Telemetry
39 60
uses: catchpoint/workflow-telemetry-action@94c3c3d9567a0205de6da68a76c428ce4e769af1 # v2.0.0
40 61
with:
... ...
@@ -79,10 +100,18 @@ jobs:
79 100
ports: '5672:5672'
80 101
- shell: bash
81 102
env: # Or as an environment variable
82
- APP_PARAMETERS: "-Daws.region=eu-west-1 -Dgoogle.maps.authenticationparams=${{ secrets.GOOGLE_MAPS_AUTHENTICATIONPARAMS }} -Daws.s3.test.s3AccessId=${{ secrets.AWS_S3_TEST_S3ACCESSID }} -Daws.s3.test.s3AccessKey=${{ secrets.AWS_S3_TEST_S3ACCESSKEY }} -Dgeonames.org.usernames=${{ secrets.GEONAMES_ORG_USERNAMES }}"
103
+ TMP: /mnt/tmp
104
+ AWS_S3_TEST_S3ACCESSID: ${{ secrets.AWS_S3_TEST_S3ACCESSID }}
105
+ AWS_S3_TEST_S3ACCESSKEY: ${{ secrets.AWS_S3_TEST_S3ACCESSKEY }}
106
+ GEONAMES_ORG_USERNAMES: ${{ secrets.GEONAMES_ORG_USERNAMES }}
107
+ GOOGLE_MAPS_AUTHENTICATION_PARAMS: ${{ secrets.GOOGLE_MAPS_AUTHENTICATION_PARAMS }}
108
+ APP_PARAMETERS: "-Daws.region=eu-west-1"
83 109
JAVA8_HOME: ${{env.JAVA_HOME_8_X64}}
84 110
run: |
85
- ./configuration/buildAndUpdateProduct.sh -x ${{ github.event.inputs.runner_cpus == '' && '16' || github.event.inputs.runner_cpus }} ${{ github.event.inputs.skip_tests == 'true' && '-t' || '' }} ${{ github.event.inputs.local_target_platform == 'true' && '-v' || '' }} build 2>&1
111
+ ./configuration/buildAndUpdateProduct.sh -x ${{ github.event.inputs.runner_cpus == '' && '4' || github.event.inputs.runner_cpus }} ${{ github.event.inputs.skip_tests == 'true' && '-t' || '' }} ${{ github.event.inputs.local_target_platform == 'true' && '-v' || '' }} build 2>&1
112
+ - name: show disk stats
113
+ if: always()
114
+ run: df -h
86 115
- name: Upload build log
87 116
uses: actions/upload-artifact@v4
88 117
if: always()
... ...
@@ -156,7 +185,11 @@ jobs:
156 185
asset_name: release-notes.txt
157 186
asset_content_type: text/plain
158 187
- name: Trigger Hudson job
159
- if: always()
188
+ env:
189
+ HUDSON_JOB_USERNAME: ${{ secrets.HUDSON_JOB_USERNAME }}
190
+ HUDSON_JOB_PASSWORD: ${{ secrets.HUDSON_JOB_PASSWORD }}
191
+ HUDSON_JOB_TOKEN: ${{ secrets.HUDSON_JOB_TOKEN }}
192
+ if: ${{ always() && env.HUDSON_JOB_USERNAME != '' && env.HUDSON_JOB_PASSWORD != '' && env.HUDSON_JOB_TOKEN != '' }}
160 193
shell: bash
161 194
run: |
162 195
if [ "${{ github.ref }}" = "refs/heads/main" ]; then
Home.md
... ...
@@ -158,10 +158,8 @@ SAP is at the center of today’s technology revolution, developing innovations
158 158
* [[Monitoring Apache and RabbitMQ|wiki/misc/monitoring-apache-and-rabbitmq]]
159 159
160 160
## Projects
161
-* [[Analytics on a stick|wiki/projects/analytics-on-a-stick]]
162
-* [[Consolidating User Stores (bug 4006 / 4018)|wiki/projects/consolidating-user-stores]]
163
-* [[Cloud Infrastructure Orchestration|wiki/projects/cloud-orchestrator]]
164 161
* [[Management Console for Easier Administration|wiki/howto/development/management-console]]
162
+* [[Cloud Infrastructure Orchestration|wiki/projects/cloud-orchestrator]]
165 163
166 164
## Events and Planning
167 165
* [[Project Planning (bigger development)|wiki/events/planning]]
README.md
... ...
@@ -2,7 +2,7 @@
2 2
3 3
## About this Project
4 4
5
-The Sailing Analytics, formerly known as the "SAP Sailing Analytics," are a solution for portraying and analyzing sailing regattas, supporting training scenarios, and powering the vast archive at https://sapsailing.com. The solution consists of a cloud application with a web-based user interface, as well as three companion apps that integrate with the cloud application. This repository has the code for the cloud-based web application, and two of the three mobile apps (Buoy Pinger and Race Manager). The third companion app (Sail Insight) is found in [another repository](https://github.com/SailTracks/sailinsight).
5
+The Sailing Analytics, formerly known as the "SAP Sailing Analytics," are a solution for portraying and analyzing sailing regattas, supporting training scenarios, and powering the vast archive at https://sapsailing.com. The solution consists of a cloud application with a web-based user interface, as well as three companion apps that integrate with the cloud application. This repository has the code for the cloud-based web application, and two of the three mobile apps (Buoy Pinger and Race Manager). The third companion app (Sail Insight) is found in [another repository](https://github.com/SAP/sailing-analytics-sail-insight).
6 6
7 7
## Description
8 8
... ...
@@ -32,11 +32,11 @@ Based on the ``docker/docker-compose.yml`` definition you should end up with thr
32 32
33 33
Try a request to [``http://127.0.0.1:8888/index.html``](http://127.0.0.1:8888/index.html) or [``http://127.0.0.1:8888/gwt/status``](http://127.0.0.1:8888/gwt/status) to see if things worked.
34 34
35
-To use Java 17, use the ``docker-compose-17.yml`` file instead:
35
+To use Java 24, use the ``docker-compose-24.yml`` file instead:
36 36
37 37
```
38 38
cd docker
39
- docker-compose -f docker-compose-17.yml up
39
+ docker-compose -f docker-compose-24.yml up
40 40
```
41 41
42 42
## Requirements
... ...
@@ -74,12 +74,123 @@ See [here](https://www.sapsailing.com/gwt/Home.html#/imprint/:) for a list of co
74 74
75 75
## Building and Running
76 76
77
-This assumes you have completed the onboarding (see again [here](https://wiki.sapsailing.com/wiki/howto/onboarding)) successfully. To build, then invoke
77
+Builds usually run on [GitHub Actions](https://github.com/SAP/sailing-analytics/actions/workflows/release.yml) upon every push. A few repository secrets ensure that the build process has the permissions it needs. Pushes to the ``main``, ``docker-24`` and ``releases/*`` branches also publish a [release](https://github.com/SAP/sailing-analytics/releases) after a successful build.
78
+
79
+There are two options for building, detailed below; for both, you need to fulfill a few prerequisites.
80
+
81
+### Prerequisites
82
+
83
+If you have forked the repository and would like to run your own build, you will need the following prerequisites:
84
+
85
+#### Google Maps API Key
86
+
87
+Create or log on to your Google account and go to the [Google Cloud Console](https://console.cloud.google.com/apis/) and create an API key at least for the Maps JavaScript API. To later run the full product, while you're here, you can also create an API key for the YouTube Data API v3.
88
+
89
+#### AWS S3 Bucket for Upload Tests
90
+
91
+The build runs integration tests using the AWS API, requiring an access key with permission to upload to a bucket called ``sapsailing-automatic-upload-test``. Create that bucket in your AWS S3 environment. You can create an IAM user with CLI access and a permission policy that restricts write permissions to just that test bucket. You have to create the bucked in AWS region ``eu-west-1``. A permission policy for that user can look like this:
92
+
93
+```
94
+{
95
+ "Version": "2012-10-17",
96
+ "Statement": [
97
+ {
98
+ "Sid": "Stmt1422015883000",
99
+ "Effect": "Allow",
100
+ "Action": [
101
+ "s3:AbortMultipartUpload",
102
+ "s3:DeleteObject",
103
+ "s3:DeleteObjectVersion",
104
+ "s3:GetBucketAcl",
105
+ "s3:GetBucketCORS",
106
+ "s3:GetBucketLocation",
107
+ "s3:GetBucketLogging",
108
+ "s3:GetBucketNotification",
109
+ "s3:GetBucketPolicy",
110
+ "s3:GetBucketTagging",
111
+ "s3:GetBucketVersioning",
112
+ "s3:GetBucketWebsite",
113
+ "s3:GetLifecycleConfiguration",
114
+ "s3:GetObject",
115
+ "s3:GetObjectAcl",
116
+ "s3:GetObjectTorrent",
117
+ "s3:GetObjectVersion",
118
+ "s3:GetObjectVersionAcl",
119
+ "s3:GetObjectVersionTorrent",
120
+ "s3:ListAllMyBuckets",
121
+ "s3:ListBucket",
122
+ "s3:ListBucketMultipartUploads",
123
+ "s3:ListBucketVersions",
124
+ "s3:ListMultipartUploadParts",
125
+ "s3:PutBucketLogging",
126
+ "s3:PutBucketNotification",
127
+ "s3:PutBucketPolicy",
128
+ "s3:PutBucketTagging",
129
+ "s3:PutBucketVersioning",
130
+ "s3:PutBucketWebsite",
131
+ "s3:PutLifecycleConfiguration",
132
+ "s3:PutObject",
133
+ "s3:PutObjectAcl",
134
+ "s3:PutObjectVersionAcl",
135
+ "s3:RestoreObject"
136
+ ],
137
+ "Resource": [
138
+ "arn:aws:s3:::sapsailing-automatic-upload-test/*",
139
+ "arn:aws:s3:::sapsailing-automatic-upload-test"
140
+ ]
141
+ }
142
+ ]
143
+}
144
+```
145
+
146
+Create an access key for that user and note key ID and secret.
147
+
148
+#### Account(s) for geonames.org
149
+
150
+The build runs integration tests against [geonames.org](https://geonames.org). Use their login/sign-up form to create your user account and note its username.
151
+
152
+### Get the GitHub Actions build to work in your forked repository
153
+
154
+Assign the IDs and secrets from the prerequisites to repository secrets in your forked repository as follows:
155
+
156
+```
157
+AWS_S3_TEST_S3ACCESSID: {your-S3-test-bucket-upload-token-ID}
158
+AWS_S3_TEST_S3ACCESSKEY: {key-for-your-S3-token}
159
+GEONAMES_ORG_USERNAMES: {comma-separated-list-of-geonames.org-usernames}
160
+GOOGLE_MAPS_AUTHENTICATION_PARAMS: key={your-Google-Maps-API-key}
161
+```
162
+
163
+Then, manually trigger the ``release`` workflow with default options. You find the "Run workflow" drop-down in your forked repository under ``https://github.com/{your-github-user}/[your-repository-name}/actions/workflows/release.yml``.
164
+
165
+Release builds will trigger the ``create-docker-image`` workflow which will produce a Docker image of your release and publish it as a "ghcr" package in your repository. Note that package names are computed from the repository name by converting the latter to all lowercase characters. If you want to use your packages in the docker-compose configurations from the ``docker/`` folder, make sure to adjust the package name so it points to your own fork's package registry.
166
+
167
+If you want automatic validation of your changes to the ``main`` branch also for newer Java versions, you can use the ``merge-main-into-docker-24`` workflow. It requires another secret:
168
+
169
+```
170
+REPO_TOKEN_FOR_MERGE_AND_PUSH: {a-GitHub-token-enabled-for-push}
171
+```
172
+
173
+The workflow will launch automatically after a release has been performed for the ``main`` branch and will try to merge the latest ``main`` branch into ``docker-24`` and pushing the merge result if the merge was successful. This will then trigger a build for the ``docker-24`` branch which, if successul, will in turn produce a release and a docker image for use with Java 24.
174
+
175
+### Run a build locally
176
+
177
+This assumes you have completed the onboarding (see again [here](https://wiki.sapsailing.com/wiki/howto/onboarding)) successfully, including the Maven-specific parts such as having Maven installed (>= 3.9.x) and having a valid ``toolchains.xml`` file in your ~/.m2 folder. Furthermore, set and export the following environment variables:
178
+
179
+```
180
+export AWS_S3_TEST_S3ACCESSID={your-S3-test-bucket-upload-token-ID}
181
+export AWS_S3_TEST_S3ACCESSKEY={key-for-your-S3-token}
182
+export GEONAMES_ORG_USERNAMES={comma-separated-list-of-geonames.org-usernames}
183
+export GOOGLE_MAPS_AUTHENTICATION_PARAMS=key={your-Google-Maps-API-key}
184
+export JAVA8_HOME={location-of-your-JDK8}
185
+export JAVA_HOME={location-of-your-JDK17-or-newer}
186
+```
187
+
188
+To build, then invoke
78 189
79 190
```
80 191
configuration/buildAndUpdateProduct.sh build
81 192
```
82
-This will build the Android companion apps first, then the web application. If the build was successful you can install the product locally by invoking
193
+This will build the Android companion apps first, then the web application. If you lack a proper Android SDK set-up, consider using the ``-a`` option to skip building the Android apps. If the build was successful you can install the product locally by invoking
83 194
84 195
```
85 196
configuration/buildAndUpdateProduct.sh install [ -s <server-name> ]
... ...
@@ -99,9 +210,9 @@ Run the ``buildAndUpdateProduct.sh`` without any arguments to see the sub-comman
99 210
100 211
## Downloading, Installing and Running an Official Release
101 212
102
-You need to have Java 8 installed. Get one from [here](https://tools.eu1.hana.ondemand.com/#cloud). Either ensure that this JVM's ``java`` executable in on the ``PATH`` or set ``JAVA_HOME`` appropriately.
213
+You need to have Java 8 installed. Get one from, e.g., [here](https://tools.eu1.hana.ondemand.com/#cloud). Either ensure that this JVM's ``java`` executable in on the ``PATH`` or set ``JAVA_HOME`` appropriately.
103 214
104
-At [https://releases.sapsailing.com](https://releases.sapsailing.com) you find official product builds. To fetch and install one of them, make an empty directory, change into it and run the ``refreshInstance.sh`` command, e.g., like this:
215
+At [https://github.com/SAP/sailing-analytics/releases](https://github.com/SAP/sailing-analytics/releases) you find official product builds. To fetch and install one of them, make an empty directory, change into it and run the ``refreshInstance.sh`` command, e.g., like this:
105 216
```
106 217
mkdir sailinganalytics
107 218
cd sailinganalytics
... ...
@@ -113,7 +224,6 @@ This will download and install the latest release and configure it such that it
113 224
In addition to the necessary ``MONGODB_URI`` variable you may need to inject a few secrets into your runtime environment:
114 225
115 226
- ``MANAGE2SAIL_ACCESS_TOKEN`` access token for result and regatta structure import from the Manage2Sail regatta management system
116
-- ``IGTIMI_CLIENT_ID`` / ``IGTIMI_CLIENT_SECRET`` credentials for ``igtimi.com`` in case you have one or more WindBot devices that you would like to integrate with
117 227
- ``GOOGLE_MAPS_AUTHENTICATION_PARAMS`` as in ``"key=..."`` or ``"client=..."``, required to display the Google Map in the race viewer. Obtain a Google Maps key from the Google Cloud Developer console, e.g., [here](https://console.cloud.google.com/apis/dashboard).
118 228
- ``YOUTUBE_API_KEY`` as in ``"key=..."``, required to analyze time stamps and durations of YouTube videos when linking to races. Obtain a YouTube API key from the Google Cloud Developer console, e.g., [here](https://console.cloud.google.com/apis/dashboard).
119 229
REUSE.toml
... ...
@@ -2,6 +2,7 @@ version = 1
2 2
SPDX-PackageName = "sailing-analytics"
3 3
SPDX-PackageSupplier = "SAP Open Source Program Office ospo@sap.com"
4 4
SPDX-PackageDownloadLocation = "https://github.com/SAP/sailing-analytics"
5
+SPDX-PackageComment = "The code in this project may include calls to APIs (\"API Calls\") of\n SAP or third-party products or services developed outside of this project\n (\"External Products\").\n \"APIs\" means application programming interfaces, as well as their respective\n specifications and implementing code that allows software to communicate with\n other software.\n API Calls to External Products are not licensed under the open source license\n that governs this project. The use of such API Calls and related External\n Products are subject to applicable additional agreements with the relevant\n provider of the External Products. In no event shall the open source license\n that governs this project grant any rights in or to any External Products,or\n alter, expand or supersede any terms of the applicable additional agreements.\n If you have a valid license agreement with SAP for the use of a particular SAP\n External Product, then you may make use of any API Calls included in this\n project's code for that SAP External Product, subject to the terms of such\n license agreement. If you do not have a valid license agreement for the use of\n a particular SAP External Product, then you may only make use of any API Calls\n in this project for that SAP External Product for your internal, non-productive\n and non-commercial test and evaluation of such API Calls. Nothing herein grants\n you any rights to use or access any SAP External Product, or provide any third\n parties the right to use of access any SAP External Product, through API Calls."
5 6
6 7
[[annotations]]
7 8
path = "java/com.google.gwt.servlet/**"
configuration/buildAndUpdateProduct.sh
... ...
@@ -222,7 +222,7 @@ echo TMP will be used for java.io.tmpdir and is $TMP
222 222
if [ "$TMP" = "" ]; then
223 223
export TMP=/tmp
224 224
fi
225
-extra="${extra} -Dgwt.workers=${GWT_WORKERS} -Djava.io.tmpdir=$TMP"
225
+extra="${extra} -Dgwt.workers=${GWT_WORKERS} -Djava.io.tmpdir=$TMP -Dgwt.workDir=$TMP"
226 226
extra="${extra} -Djdk.xml.maxGeneralEntitySizeLimit=0 -Djdk.xml.totalEntitySizeLimit=0"
227 227
228 228
shift $((OPTIND-1))
configuration/environments_scripts/repo/usr/local/bin/update_authorized_keys_for_landscape_managers_if_changed
... ...
@@ -28,7 +28,8 @@ if [ "${curl_exit_code}" = "0" ]; then
28 28
logger -t sailing "New SSH key changes for landscape managers (${last_change_millis} newer than ${PREVIOUS_CHANGE})"
29 29
if update_authorized_keys_for_landscape_managers "${BEARER_TOKEN}" "${BASE_URL}" "${LOGON_USER_HOME}" ; then
30 30
logger -t sailing "Updating SSH keys for landscape managers successful; updating ${LAST_CHANGE_FILE}"
31
- echo ${last_change_millis} >${LAST_CHANGE_FILE}
31
+ # /var/run is writable only for root, so we need to be able to sudo:
32
+ sudo bash -c "echo ${last_change_millis} >${LAST_CHANGE_FILE}"
32 33
else
33 34
logger -t sailing "Updating SSH keys for landscape managers failed with exit code $?; not updating ${LAST_CHANGE_FILE}"
34 35
fi
configuration/environments_scripts/reverse_proxy/setup-disposable-reverse-proxy.sh
... ...
@@ -1,6 +1,7 @@
1 1
#!/bin/bash
2 2
3
-# Setup script for Amazon Linux 2023. May need to update macro definitions for the archive IP.
3
+# Setup script for Amazon Linux 2023. Use 50g root partition.
4
+# May need to update macro definitions for the archive IP.
4 5
# Parameter 1 is the IP and parameter 2 is the bearer token to be installed in the root home dir.
5 6
# Ensure that the security for requesting the metadata uses IMDSv1
6 7
if [[ "$#" -ne 2 ]]; then
configuration/github-copy-release-to-sapsailing-com.sh
... ...
@@ -17,7 +17,7 @@
17 17
# produce false matches for the "main-" prefix.
18 18
BEARER_TOKEN="${1}"
19 19
RELEASE_NAME_PREFIX="${2}"
20
-RELEASE_TAR_GZ_FILE_NAME=$( `dirname "${0}"`/github-download-release-assets.sh "${BEARER_TOKEN}" "${RELEASE_NAME_PREFIX}" )
20
+RELEASE_TAR_GZ_FILE_NAME=$( `dirname "${0}"`/github-download-release-assets.sh "${BEARER_TOKEN}" "${RELEASE_NAME_PREFIX}" SAP/sailing-analytics )
21 21
if [ "${RELEASE_TAR_GZ_FILE_NAME}" != "" ]; then
22 22
RELEASE_NAME=$( echo ${RELEASE_TAR_GZ_FILE_NAME} | sed -e 's/^\(.*\)-\([0-9]*\).tar.gz$/\1/' )
23 23
RELEASE_TIMESTAMP=$( echo ${RELEASE_TAR_GZ_FILE_NAME} | sed -e 's/^\(.*\)-\([0-9]*\).tar.gz$/\2/' )
configuration/github-download-release-assets.sh
... ...
@@ -5,9 +5,9 @@
5 5
# always be 0.
6 6
#
7 7
# Usage:
8
-# ./github-download-release-assets.sh {BEARER_TOKEN} {release-name-prefix}
8
+# ./github-download-release-assets.sh {BEARER_TOKEN} {release-name-prefix} {repository-name}
9 9
# For example:
10
-# ./github-download-release-assets.sh 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 main-
10
+# ./github-download-release-assets.sh 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 main- SAP/sailing-analytics
11 11
# which will download the latest release tar.gz and release-notes.txt of the main branch (main-xxxxxxxxxxx).
12 12
# Note the "-" at the end of the "main-" prefix specifier; this way we're making name
13 13
# clashes with releases whose name happens to start with "main" unlikely. This
... ...
@@ -15,7 +15,8 @@
15 15
# produce false matches for the "main-" prefix.
16 16
BEARER_TOKEN="${1}"
17 17
RELEASE_NAME_PREFIX="${2}"
18
-RELEASES=$( curl -L -H 'Authorization: Bearer '${BEARER_TOKEN} https://api.github.com/repos/SAP/sailing-analytics/releases 2>/dev/null )
18
+GITHUB_REPOSITORY="${3}"
19
+RELEASES=$( curl -L -H 'Authorization: Bearer '${BEARER_TOKEN} https://api.github.com/repos/${GITHUB_REPOSITORY}/releases 2>/dev/null )
19 20
RELEASE_NOTES_TXT_ASSET_ID=$( echo "${RELEASES}" | jq -r 'sort_by(.published_at) | reverse | map(select(.name | startswith("'${RELEASE_NAME_PREFIX}'")))[0].assets[] | select(.content_type=="text/plain").id' 2>/dev/null)
20 21
if [ "$?" -ne "0" ]; then
21 22
echo "No release with prefix ${RELEASE_NAME_PREFIX} found. Not trying to download/upload anything." >&2
... ...
@@ -26,7 +27,7 @@ else
26 27
RELEASE_TIMESTAMP=$( echo ${RELEASE_FULL_NAME} | sed -e 's/^\(.*\)-\([0-9]*\)$/\2/' )
27 28
echo "Found release ${RELEASE_FULL_NAME} with name ${RELEASE_NAME} and time stamp ${RELEASE_TIMESTAMP}, notes ID is ${RELEASE_NOTES_TXT_ASSET_ID}, tarball ID is ${RELEASE_TAR_GZ_ASSET_ID}" >&2
28 29
RELEASE_TAR_GZ_FILE_NAME="${RELEASE_FULL_NAME}.tar.gz"
29
- curl -o "${RELEASE_TAR_GZ_FILE_NAME}" -L -H 'Accept: application/octet-stream' -H 'Authorization: Bearer '${BEARER_TOKEN} 'https://api.github.com/repos/SAP/sailing-analytics/releases/assets/'${RELEASE_TAR_GZ_ASSET_ID}
30
- curl -o release-notes.txt -L -H 'Accept: application/octet-stream' -H 'Authorization: Bearer '${BEARER_TOKEN} 'https://api.github.com/repos/SAP/sailing-analytics/releases/assets/'${RELEASE_NOTES_TXT_ASSET_ID}
30
+ curl -o "${RELEASE_TAR_GZ_FILE_NAME}" -L -H 'Accept: application/octet-stream' -H 'Authorization: Bearer '${BEARER_TOKEN} 'https://api.github.com/repos/'${GITHUB_REPOSITORY}'/releases/assets/'${RELEASE_TAR_GZ_ASSET_ID}
31
+ curl -o release-notes.txt -L -H 'Accept: application/octet-stream' -H 'Authorization: Bearer '${BEARER_TOKEN} 'https://api.github.com/repos/'${GITHUB_REPOSITORY}'/releases/assets/'${RELEASE_NOTES_TXT_ASSET_ID}
31 32
echo "${RELEASE_TAR_GZ_FILE_NAME}"
32 33
fi
configuration/github-download-workflow-artifacts.sh
... ...
@@ -12,12 +12,13 @@
12 12
# an exit status of 2 is returned.
13 13
BRANCH="${1}"
14 14
BEARER_TOKEN="${2}"
15
+GITHUB_REPOSITORY="${3}"
15 16
UNIX_TIME=$( date +%s )
16 17
UNIX_DATE=$( date --iso-8601=second )
17 18
UNIX_TIME_YESTERDAY=$(( UNIX_TIME - 10*24*3600 )) # look back ten days in time, trying to catch even re-runs of older jobs
18 19
DATE_YESTERDAY=$( date --iso-8601=second -d @${UNIX_TIME_YESTERDAY} )
19 20
HEADERS_FILE=$( mktemp headersXXXXX )
20
-NEXT_PAGE="https://api.github.com/repos/SAP/sailing-analytics/actions/runs?created=${DATE_YESTERDAY/+/%2B}..${UNIX_DATE/+/%2B}&per_page=100"
21
+NEXT_PAGE="https://api.github.com/repos/${GITHUB_REPOSITORY}/actions/runs?created=${DATE_YESTERDAY/+/%2B}..${UNIX_DATE/+/%2B}&per_page=100"
21 22
ARTIFACTS_JSON=""
22 23
LATEST_RUN_STARTED_AT="0000-00-00T00:00:00Z"
23 24
# Now go through the pages as long as we have a non-empty NEXT_PAGE URL and find the completed "release" workflow that was started last
docker/makeImageForLatestRelease
... ...
@@ -1,5 +1,11 @@
1 1
#!/bin/bash
2
-release_prefix=$1
2
+release_prefix="$1"
3
+GITHUB_REPOSITORY="$2"
4
+OWNER=${GITHUB_REPOSITORY%%/*}
5
+OWNER_LOWER=$(echo "$OWNER" | tr '[:upper:]' '[:lower:]')
6
+REPO=${GITHUB_REPOSITORY##*/}
7
+REPO_LOWER=$(echo "$REPO" | tr '[:upper:]' '[:lower:]')
8
+PACKAGE="$OWNER_LOWER/$REPO_LOWER"
3 9
GITROOT="`dirname $0`/.."
4 10
DOCKERDIR="${GITROOT}/docker"
5 11
DOCKERFILE="$DOCKERDIR/Dockerfile"
... ...
@@ -8,7 +14,7 @@ if [ "${release_prefix}" = "" ]; then
8 14
release_prefix="main-"
9 15
fi
10 16
pushd "${DOCKERDIR}"
11
-RELEASE_TAR_GZ_FILENAME=$( ${GITROOT}/configuration/github-download-release-assets.sh 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 "${release_prefix}" )
17
+RELEASE_TAR_GZ_FILENAME=$( ${GITROOT}/configuration/github-download-release-assets.sh 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 "${release_prefix}" "${GITHUB_REPOSITORY}" )
12 18
if [ "${RELEASE_TAR_GZ_FILENAME}" = "" ]; then
13 19
echo "No release with prefix ${release_prefix} found" >&2
14 20
else
... ...
@@ -20,7 +26,7 @@ else
20 26
cp "$GITROOT/java/target/configuration/JavaSE-11.profile" "$DOCKERDIR"
21 27
cd "$DOCKERDIR"
22 28
docker buildx create --name=container --driver=docker-container --use --bootstrap
23
- docker buildx build --builder=container --platform=linux/amd64,linux/arm64 --build-arg RELEASE=${release} -t ghcr.io/sap/sailing-analytics:${release} --push .
29
+ docker buildx build --builder=container --platform=linux/amd64,linux/arm64 --build-arg RELEASE=${release} -t ghcr.io/${PACKAGE}:${release} --push .
24 30
echo "Cleaning up..."
25 31
rm start env.sh JavaSE-11.profile ${RELEASE_TAR_GZ_FILENAME} release-notes.txt
26 32
fi
docker/makeImageForLatestRelease-arm
... ...
@@ -1,5 +1,11 @@
1 1
#!/bin/bash
2
-release_prefix=$1
2
+release_prefix="$1"
3
+GITHUB_REPOSITORY="$2"
4
+OWNER=${GITHUB_REPOSITORY%%/*}
5
+OWNER_LOWER=$(echo "$OWNER" | tr '[:upper:]' '[:lower:]')
6
+REPO=${GITHUB_REPOSITORY##*/}
7
+REPO_LOWER=$(echo "$REPO" | tr '[:upper:]' '[:lower:]')
8
+PACKAGE="$OWNER_LOWER/$REPO_LOWER"
3 9
GITROOT="`dirname $0`/.."
4 10
DOCKERDIR="${GITROOT}/docker"
5 11
DOCKERFILE="$DOCKERDIR/Dockerfile"
... ...
@@ -8,7 +14,7 @@ if [ "${release_prefix}" = "" ]; then
8 14
release_prefix="main-"
9 15
fi
10 16
pushd "${DOCKERDIR}"
11
-RELEASE_TAR_GZ_FILENAME=$( ${GITROOT}/configuration/github-download-release-assets.sh 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 "${release_prefix}" )
17
+RELEASE_TAR_GZ_FILENAME=$( ${GITROOT}/configuration/github-download-release-assets.sh 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 "${release_prefix}" "${GITHUB_REPOSITORY}" )
12 18
if [ "${RELEASE_TAR_GZ_FILENAME}" = "" ]; then
13 19
echo "No release with prefix ${release_prefix} found" >&2
14 20
else
... ...
@@ -19,13 +25,13 @@ else
19 25
cp "$GITROOT/java/target/start" "$DOCKERDIR"
20 26
cp "$GITROOT/java/target/configuration/JavaSE-11.profile" "$DOCKERDIR"
21 27
cd "$DOCKERDIR"
22
- docker build --build-arg RELEASE=${release} -t ghcr.io/sap/sailing-analytics:${release}-arm .
28
+ docker build --build-arg RELEASE=${release} -t ghcr.io/${PACKAGE}:${release}-arm .
23 29
echo "Cleaning up..."
24 30
rm start env.sh JavaSE-11.profile ${RELEASE_TAR_GZ_FILENAME} release-notes.txt
25
- docker push ghcr.io/sap/sailing-analytics:${release}-arm
31
+ docker push ghcr.io/${PACKAGE}:${release}-arm
26 32
if [ "$SET_LATEST" = "1" ]; then
27
- docker tag ghcr.io/sap/sailing-analytics:${release}-arm ghcr.io/sap/sailing-analytics:latest-arm
28
- docker push ghcr.io/sap/sailing-analytics:latest-arm
33
+ docker tag ghcr.io/${PACKAGE}:${release}-arm ghcr.io/${PACKAGE}:latest-arm
34
+ docker push ghcr.io/${PACKAGE}:latest-arm
29 35
fi
30 36
fi
31 37
popd
docker/makeImageForLatestRelease-on-sapmachine11
... ...
@@ -1,5 +1,11 @@
1 1
#!/bin/bash
2
-release_prefix=$1
2
+release_prefix="$1"
3
+GITHUB_REPOSITORY="$2"
4
+OWNER=${GITHUB_REPOSITORY%%/*}
5
+OWNER_LOWER=$(echo "$OWNER" | tr '[:upper:]' '[:lower:]')
6
+REPO=${GITHUB_REPOSITORY##*/}
7
+REPO_LOWER=$(echo "$REPO" | tr '[:upper:]' '[:lower:]')
8
+PACKAGE="$OWNER_LOWER/$REPO_LOWER"
3 9
GITROOT="`dirname $0`/.."
4 10
DOCKERDIR="${GITROOT}/docker"
5 11
DOCKERFILE="$DOCKERDIR/Dockerfile"
... ...
@@ -8,7 +14,7 @@ if [ "${release_prefix}" = "" ]; then
8 14
release_prefix="docker-11-"
9 15
fi
10 16
pushd "${DOCKERDIR}"
11
-RELEASE_TAR_GZ_FILENAME=$( ${GITROOT}/configuration/github-download-release-assets.sh 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 "${release_prefix}" )
17
+RELEASE_TAR_GZ_FILENAME=$( ${GITROOT}/configuration/github-download-release-assets.sh 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 "${release_prefix}" "${GITHUB_REPOSITORY}" )
12 18
if [ "${RELEASE_TAR_GZ_FILENAME}" = "" ]; then
13 19
echo "No release with prefix ${release_prefix} found" >&2
14 20
else
... ...
@@ -19,13 +25,13 @@ else
19 25
cp "$GITROOT/java/target/start" "$DOCKERDIR"
20 26
cp "$GITROOT/java/target/configuration/JavaSE-11.profile" "$DOCKERDIR"
21 27
cd "$DOCKERDIR"
22
- docker build --build-arg RELEASE=${release} -t ghcr.io/sap/sailing-analytics:${release} -f Dockerfile_sapsailing_on_sapmachine11 .
28
+ docker build --build-arg RELEASE=${release} -t ghcr.io/${PACKAGE}:${release} -f Dockerfile_sapsailing_on_sapmachine11 .
23 29
echo "Cleaning up..."
24 30
rm start env.sh JavaSE-11.profile ${RELEASE_TAR_GZ_FILENAME} release-notes.txt
25
- docker push ghcr.io/sap/sailing-analytics:${release}
31
+ docker push ghcr.io/${PACKAGE}:${release}
26 32
if [ "$SET_LATEST" = "1" ]; then
27
- docker tag ghcr.io/sap/sailing-analytics:${release} ghcr.io/sap/sailing-analytics:latest-11
28
- docker push ghcr.io/sap/sailing-analytics:latest-11
33
+ docker tag ghcr.io/${PACKAGE}:${release} ghcr.io/${PACKAGE}:latest-11
34
+ docker push ghcr.io/${PACKAGE}:latest-11
29 35
fi
30 36
fi
31 37
popd
docker/makeImageForLatestRelease-on-sapmachine17
... ...
@@ -1,5 +1,11 @@
1 1
#!/bin/bash
2
-release_prefix=$1
2
+release_prefix="$1"
3
+GITHUB_REPOSITORY="$2"
4
+OWNER=${GITHUB_REPOSITORY%%/*}
5
+OWNER_LOWER=$(echo "$OWNER" | tr '[:upper:]' '[:lower:]')
6
+REPO=${GITHUB_REPOSITORY##*/}
7
+REPO_LOWER=$(echo "$REPO" | tr '[:upper:]' '[:lower:]')
8
+PACKAGE="$OWNER_LOWER/$REPO_LOWER"
3 9
GITROOT="`dirname $0`/.."
4 10
DOCKERDIR="${GITROOT}/docker"
5 11
DOCKERFILE="$DOCKERDIR/Dockerfile"
... ...
@@ -8,7 +14,7 @@ if [ "${release_prefix}" = "" ]; then
8 14
release_prefix="docker-17-"
9 15
fi
10 16
pushd "${DOCKERDIR}"
11
-RELEASE_TAR_GZ_FILENAME=$( ${GITROOT}/configuration/github-download-release-assets.sh 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 "${release_prefix}" )
17
+RELEASE_TAR_GZ_FILENAME=$( ${GITROOT}/configuration/github-download-release-assets.sh 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 "${release_prefix}" "${GITHUB_REPOSITORY}" )
12 18
if [ "${RELEASE_TAR_GZ_FILENAME}" = "" ]; then
13 19
echo "No release with prefix ${release_prefix} found" >&2
14 20
else
... ...
@@ -19,13 +25,13 @@ else
19 25
cp "$GITROOT/java/target/start" "$DOCKERDIR"
20 26
cp "$GITROOT/java/target/configuration/JavaSE-11.profile" "$DOCKERDIR"
21 27
cd "$DOCKERDIR"
22
- docker build --build-arg RELEASE=${release} -t ghcr.io/sap/sailing-analytics:${release} -f Dockerfile_sapsailing_on_sapmachine17 .
28
+ docker build --build-arg RELEASE=${release} -t ghcr.io/${PACKAGE}:${release} -f Dockerfile_sapsailing_on_sapmachine17 .
23 29
echo "Cleaning up..."
24 30
rm start env.sh JavaSE-11.profile ${RELEASE_TAR_GZ_FILENAME} release-notes.txt
25
- docker push ghcr.io/sap/sailing-analytics:${release}
31
+ docker push ghcr.io/${PACKAGE}:${release}
26 32
if [ "$SET_LATEST" = "1" ]; then
27
- docker tag ghcr.io/sap/sailing-analytics:${release} ghcr.io/sap/sailing-analytics:latest-17
28
- docker push ghcr.io/sap/sailing-analytics:latest-17
33
+ docker tag ghcr.io/${PACKAGE}:${release} ghcr.io/${PACKAGE}:latest-17
34
+ docker push ghcr.io/${PACKAGE}:latest-17
29 35
fi
30 36
fi
31 37
popd
java/com.sap.sailing.domain.igtimiadapter.persistence/src/com/sap/sailing/domain/igtimiadapter/persistence/impl/DomainObjectFactoryImpl.java
... ...
@@ -96,8 +96,8 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
96 96
final Document query = new Document();
97 97
appendMultiTimeRangeQuery(query, timeRanges);
98 98
query.append(FieldNames.IGTIMI_MESSAGES_DEVICE_SERIAL_NUMBER.name(), deviceSerialNumber);
99
- final Iterable<Document> queryResult = clientSessionOrNull == null ? messagesCollection.find(query)
100
- : messagesCollection.find(clientSessionOrNull, query);
99
+ final Iterable<Document> queryResult = (clientSessionOrNull == null ? messagesCollection.find(query)
100
+ : messagesCollection.find(clientSessionOrNull, query)).sort(Sorts.ascending(FieldNames.IGTIMI_MESSAGES_TIMESTAMP.name()));
101 101
result = Util.filter(Util.map(queryResult,
102 102
doc->{
103 103
try {
java/com.sap.sailing.domain.igtimiadapter.server/src/com/sap/sailing/domain/igtimiadapter/server/riot/RiotServer.java
... ...
@@ -53,9 +53,9 @@ import com.sap.sse.replication.Replicable;
53 53
* can be registered with this server using the {@link #addListener(BulkFixReceiver)} method.
54 54
* <p>
55 55
*
56
- * When combined with {@link IgtimiWindReceiver} (which is such a {@link BulkFixReceiver},
56
+ * When combined with {@link IgtimiWindReceiver} (which is such a {@link BulkFixReceiver}),
57 57
* {@link IgtimiWindListener}s can be registered on the wind receiver, just as they can
58
- * for a websocket connection.
58
+ * for a websocket connection.<p>
59 59
*
60 60
* The server uses <tt>java.nio</tt> and {@link ServerSocketChannel}s, avoiding the creation
61 61
* of a thread per connection.<p>
java/com.sap.sailing.domain.igtimiadapter.server/src/com/sap/sailing/domain/igtimiadapter/server/riot/impl/RiotServerImpl.java
... ...
@@ -337,24 +337,26 @@ public class RiotServerImpl extends AbstractReplicableWithObjectInputStream<Repl
337 337
// obtain the securityService only if connections exist; otherwise, we may still be in start-up mode
338 338
final SecurityService securityService = liveWebSocketConnections.isEmpty() ? null : getSecurityService();
339 339
for (final RiotWebsocketHandler webSocketClient : liveWebSocketConnections) {
340
- final User user = webSocketClient.getAuthenticatedUser();
341
- final OwnershipAnnotation deviceOwnership = securityService.getOwnership(device.getIdentifier());
342
- final AccessControlListAnnotation deviceAccessControlList = securityService.getAccessControlList(device.getIdentifier());
343
- if (!Util.isEmpty(Util.filter(daws, daw->{
344
- final OwnershipAnnotation dawOownership = securityService.getOwnership(daw.getIdentifier());
345
- final AccessControlListAnnotation dawAccessControlList = securityService.getAccessControlList(daw.getIdentifier());
346
- return PermissionChecker.isPermitted(
347
- daw.getIdentifier().getPermission(DefaultActions.READ),
340
+ if (webSocketClient.getDeviceSerialNumbers().contains(deviceSerialNumber)) {
341
+ final User user = webSocketClient.getAuthenticatedUser();
342
+ final OwnershipAnnotation deviceOwnership = securityService.getOwnership(device.getIdentifier());
343
+ final AccessControlListAnnotation deviceAccessControlList = securityService.getAccessControlList(device.getIdentifier());
344
+ if (!Util.isEmpty(Util.filter(daws, daw->{
345
+ final OwnershipAnnotation dawOownership = securityService.getOwnership(daw.getIdentifier());
346
+ final AccessControlListAnnotation dawAccessControlList = securityService.getAccessControlList(daw.getIdentifier());
347
+ return PermissionChecker.isPermitted(
348
+ daw.getIdentifier().getPermission(DefaultActions.READ),
349
+ user, securityService.getAllUser(),
350
+ dawOownership==null?null:dawOownership.getAnnotation(),
351
+ dawAccessControlList==null?null:dawAccessControlList.getAnnotation());
352
+ }))
353
+ && PermissionChecker.isPermitted(
354
+ device.getIdentifier().getPermission(DefaultActions.READ),
348 355
user, securityService.getAllUser(),
349
- dawOownership==null?null:dawOownership.getAnnotation(),
350
- dawAccessControlList==null?null:dawAccessControlList.getAnnotation());
351
- }))
352
- && PermissionChecker.isPermitted(
353
- device.getIdentifier().getPermission(DefaultActions.READ),
354
- user, securityService.getAllUser(),
355
- deviceOwnership==null?null:deviceOwnership.getAnnotation(),
356
- deviceAccessControlList==null?null:deviceAccessControlList.getAnnotation()) ) {
357
- webSocketClient.sendBytesByFuture(ByteBuffer.wrap(messageAsBytes));
356
+ deviceOwnership==null?null:deviceOwnership.getAnnotation(),
357
+ deviceAccessControlList==null?null:deviceAccessControlList.getAnnotation()) ) {
358
+ webSocketClient.sendBytesByFuture(ByteBuffer.wrap(messageAsBytes));
359
+ }
358 360
}
359 361
}
360 362
}
java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/shared/IgtimiWindReceiver.java
... ...
@@ -188,8 +188,9 @@ public class IgtimiWindReceiver implements BulkFixReceiver {
188 188
}
189 189
190 190
/**
191
- * Returns a wind fix produced out of an AWS/AWA and other fixes, as well as the fixes used for this, including a {@link BatteryLevel}
192
- * fix, if any was found, although this did technically not contribute to the production of the {@link Wind} object.
191
+ * Returns a wind fix produced out of an AWS/AWA and other fixes, as well as the fixes used for this (in case of
192
+ * interpolation between two fixes, the older one of the two is returned), including a {@link BatteryLevel} fix, if
193
+ * any was found, although this did technically not contribute to the production of the {@link Wind} object.
193 194
*/
194 195
private Pair<Wind, Set<Fix>> getWind(final TimePoint timePoint, String deviceSerialNumber) throws ClassNotFoundException, IOException, ParseException {
195 196
final Wind result;
... ...
@@ -213,7 +214,7 @@ public class IgtimiWindReceiver implements BulkFixReceiver {
213 214
final SpeedWithBearing apparentWindSpeedWithDirection = new KnotSpeedWithBearingImpl(aws.getKnots(), apparentWindDirection);
214 215
/*
215 216
* Hint from Brent Russell from Igtimi, at 2013-12-05 on the question whether to use GpsLatLong to
216
- * improve precision of boat speed / coarse over SOG/COG measurements:
217
+ * improve precision of boat speed / course over SOG/COG measurements:
217 218
*
218 219
* "Personally I would use COG/SOG exclusively, and if unhappy with the result add a small amount of
219 220
* smoothing and consider dropping samples as outliers if they cause a SOG discontinuity. The latter
... ...
@@ -260,7 +261,11 @@ public class IgtimiWindReceiver implements BulkFixReceiver {
260 261
/**
261 262
* Searches for the last fix at or before {@code timePoint} in the given {@link trackToCleanUp}. If such a fix is
262 263
* found, adds the given fix to {@code fixesUsed} and clears all fixes from the {@code trackToCleanUp} that are
263
- * earlier than or at the {@code timePoint} given.
264
+ * earlier than the {@code timePoint} given. This in particular preserves at least one live fix which will "hide"
265
+ * older, e.g., buffered fixes that may be received when a WindBot device sends them out-of-order. For old, buffered
266
+ * fixes this may still mean that could be dropped prior to being converted into a {@link Wind} object with an older
267
+ * time stamp, but usually---and particularly with buffered data from previous days---this shouldn't be a problem.
268
+ * Also, old data can still be imported at a later point in time.
264 269
* <p>
265 270
*
266 271
* This assumes that the {@code fix} passed has been "consumed" now, and earlier fixes will not be relevant anymore.
... ...
@@ -270,7 +275,7 @@ public class IgtimiWindReceiver implements BulkFixReceiver {
270 275
final FixType fix = trackToCleanUp.getLastFixAtOrBefore(timePoint);
271 276
if (fix != null) {
272 277
fixesUsed.add(fix);
273
- trackToCleanUp.removeAllUpToAndIncluding(fix);
278
+ trackToCleanUp.removeAllUpToExcluding(fix);
274 279
}
275 280
}
276 281
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/shared/tracking/impl/TrackImpl.java
... ...
@@ -292,6 +292,11 @@ public class TrackImpl<FixType extends Timed> implements Track<FixType> {
292 292
return result;
293 293
}
294 294
295
+ /**
296
+ * Calculates a linear interpolation of values based on their time points and a target time point that is expected
297
+ * to be in between (inclusive) the two time points for the two values. If the two time points for the two values
298
+ * are equal, the average of the two values is returned.
299
+ */
295 300
private <V, T> T timeBasedAverage(TimePoint timePoint, ScalableValue<V, T> value1, TimePoint timePoint1, ScalableValue<V, T> value2, TimePoint timePoint2) {
296 301
final T acc;
297 302
if (timePoint1.equals(timePoint2)) {
java/com.sap.sailing.domain.test/SailingDomainTest (No Proxy, Tunnel).launch
... ...
@@ -1,5 +1,6 @@
1 1
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2 2
<launchConfiguration type="org.eclipse.jdt.junit.launchconfig">
3
+ <booleanAttribute key="org.eclipse.debug.core.ATTR_FORCE_SYSTEM_CONSOLE_ENCODING" value="false"/>
3 4
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
4 5
<listEntry value="/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test"/>
5 6
</listAttribute>
... ...
@@ -10,7 +11,7 @@
10 11
<listEntry value="org.eclipse.debug.ui.launchGroup.debug"/>
11 12
<listEntry value="org.eclipse.debug.ui.launchGroup.run"/>
12 13
</listAttribute>
13
- <stringAttribute key="org.eclipse.jdt.junit.CONTAINER" value="=com.sap.sailing.domain.test/src=/test=/true=/&lt;com.sap.sailing.domain.test"/>
14
+ <stringAttribute key="org.eclipse.jdt.junit.CONTAINER" value="=com.sap.sailing.domain.test/src&lt;com.sap.sailing.domain.test"/>
14 15
<booleanAttribute key="org.eclipse.jdt.junit.KEEPRUNNING_ATTR" value="false"/>
15 16
<stringAttribute key="org.eclipse.jdt.junit.TESTNAME" value=""/>
16 17
<stringAttribute key="org.eclipse.jdt.junit.TEST_KIND" value="org.eclipse.jdt.junit.loader.junit4"/>
java/com.sap.sailing.domain.test/src/LeaderboardCourseChangeWithEliminationTest.java
... ...
@@ -1,14 +0,0 @@
1
-import com.sap.sailing.domain.base.Regatta;
2
-import com.sap.sailing.domain.leaderboard.RegattaLeaderboardWithEliminations;
3
-import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule;
4
-import com.sap.sailing.domain.leaderboard.impl.DelegatingRegattaLeaderboardWithCompetitorElimination;
5
-import com.sap.sailing.domain.test.LeaderboardCourseChangeTest;
6
-
7
-public class LeaderboardCourseChangeWithEliminationTest extends LeaderboardCourseChangeTest {
8
- @Override
9
- protected RegattaLeaderboardWithEliminations createRegattaLeaderboard(Regatta regatta,
10
- ThresholdBasedResultDiscardingRule discardingRule) {
11
- return new DelegatingRegattaLeaderboardWithCompetitorElimination(
12
- ()->super.createRegattaLeaderboard(regatta, discardingRule), "Test leaderboard with elimination");
13
- }
14
-}
java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardCourseChangeWithEliminationTest.java
... ...
@@ -0,0 +1,15 @@
1
+package com.sap.sailing.domain.test;
2
+
3
+import com.sap.sailing.domain.base.Regatta;
4
+import com.sap.sailing.domain.leaderboard.RegattaLeaderboardWithEliminations;
5
+import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule;
6
+import com.sap.sailing.domain.leaderboard.impl.DelegatingRegattaLeaderboardWithCompetitorElimination;
7
+
8
+public class LeaderboardCourseChangeWithEliminationTest extends LeaderboardCourseChangeTest {
9
+ @Override
10
+ protected RegattaLeaderboardWithEliminations createRegattaLeaderboard(Regatta regatta,
11
+ ThresholdBasedResultDiscardingRule discardingRule) {
12
+ return new DelegatingRegattaLeaderboardWithCompetitorElimination(
13
+ ()->super.createRegattaLeaderboard(regatta, discardingRule), "Test leaderboard with elimination");
14
+ }
15
+}
java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardScoringAndRankingTest.java
... ...
@@ -2918,16 +2918,10 @@ public class LeaderboardScoringAndRankingTest extends LeaderboardScoringAndRanki
2918 2918
assertTrue(Util.indexOf(rankedCompetitors, lastRaceGoldParticipant) < Util.indexOf(rankedCompetitors, silverParticipant));
2919 2919
}
2920 2920
}
2921
- // assert that theUntrackedCompetitorInLastRace ended up between the last race's silver and gold fleet participants
2922
- // based on the "extreme fleet" rule:
2921
+ // assert that theUntrackedCompetitorInLastRace ended up ranked worse than all participants of ranked fleets:
2923 2922
for (final Competitor c : competitors) {
2924 2923
if (c != theUntrackedCompetitorInLastRace) {
2925
- if (lastRaceGold.contains(c) || medal.contains(c)) {
2926
- assertTrue(Util.indexOf(rankedCompetitors, c) < Util.indexOf(rankedCompetitors, theUntrackedCompetitorInLastRace));
2927
- } else {
2928
- assertTrue(silver.contains(c));
2929
- assertTrue(Util.indexOf(rankedCompetitors, c) > Util.indexOf(rankedCompetitors, theUntrackedCompetitorInLastRace));
2930
- }
2924
+ assertTrue(Util.indexOf(rankedCompetitors, c) < Util.indexOf(rankedCompetitors, theUntrackedCompetitorInLastRace));
2931 2925
}
2932 2926
}
2933 2927
}
java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/OnlineTracTracBasedTest.java
... ...
@@ -149,12 +149,9 @@ public abstract class OnlineTracTracBasedTest extends AbstractTracTracLiveTest i
149 149
case Begin:
150 150
logger.info("Stored data begin");
151 151
lastStatus = new TrackedRaceStatusImpl(TrackedRaceStatusEnum.LOADING, 0);
152
- new Thread(()->{
153
- final RaceDefinition raceDefinition = domainFactory.getAndWaitForRaceDefinition(getTracTracRace().getId(), /* timeout in millis */ 10000);
154
- if (trackedRegatta != null && raceDefinition != null && trackedRegatta.getTrackedRace(raceDefinition) != null) {
155
- trackedRegatta.getTrackedRace(raceDefinition).onStatusChanged(OnlineTracTracBasedTest.this, lastStatus);
156
- }
157
- }, "waiting for race definition for race "+getTracTracRace().getId()).start();
152
+ if (getTrackedRace() != null) {
153
+ getTrackedRace().onStatusChanged(OnlineTracTracBasedTest.this, lastStatus);
154
+ }
158 155
break;
159 156
case End:
160 157
logger.info("Stored data end. Delaying status update on tracked race "+getTrackedRace()+" until all events queued in receivers so far have been processed");
java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/PassingInstructionParserTest.java
... ...
@@ -47,12 +47,11 @@ public class PassingInstructionParserTest extends OnlineTracTracBasedTest {
47 47
@Test
48 48
public void test() {
49 49
int i = 0;
50
- for(Waypoint w : getRace().getCourse().getWaypoints()){
51
- if(w.getPassingInstructions()==PassingInstruction.Starboard){
50
+ for (Waypoint w : getRace().getCourse().getWaypoints()) {
51
+ if (w.getPassingInstructions() == PassingInstruction.Starboard) {
52 52
i++;
53 53
}
54 54
}
55
- assertEquals(2,i);
55
+ assertEquals(2, i);
56 56
}
57
-
58 57
}
java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/ScoreCorrectionForUntrackedRacesTest.java
... ...
@@ -0,0 +1,112 @@
1
+package com.sap.sailing.domain.test;
2
+
3
+import static org.junit.jupiter.api.Assertions.assertFalse;
4
+import static org.junit.jupiter.api.Assertions.assertTrue;
5
+import static org.mockito.Mockito.mock;
6
+import static org.mockito.Mockito.when;
7
+
8
+import java.util.Arrays;
9
+import java.util.UUID;
10
+
11
+import org.junit.jupiter.api.BeforeEach;
12
+import org.junit.jupiter.api.Test;
13
+
14
+import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor;
15
+import com.sap.sailing.domain.abstractlog.impl.LogEventAuthorImpl;
16
+import com.sap.sailing.domain.abstractlog.race.RaceLog;
17
+import com.sap.sailing.domain.abstractlog.race.impl.RaceLogImpl;
18
+import com.sap.sailing.domain.abstractlog.race.tracking.analyzing.impl.RegisteredCompetitorsAnalyzer;
19
+import com.sap.sailing.domain.abstractlog.race.tracking.impl.RaceLogRegisterCompetitorEventImpl;
20
+import com.sap.sailing.domain.abstractlog.race.tracking.impl.RaceLogUseCompetitorsFromRaceLogEventImpl;
21
+import com.sap.sailing.domain.abstractlog.regatta.RegattaLog;
22
+import com.sap.sailing.domain.abstractlog.regatta.impl.RegattaLogImpl;
23
+import com.sap.sailing.domain.base.CompetitorWithBoat;
24
+import com.sap.sailing.domain.base.Fleet;
25
+import com.sap.sailing.domain.base.RaceColumnInSeries;
26
+import com.sap.sailing.domain.base.Series;
27
+import com.sap.sailing.domain.base.impl.FleetImpl;
28
+import com.sap.sailing.domain.base.impl.SeriesImpl;
29
+import com.sap.sailing.domain.leaderboard.Leaderboard;
30
+import com.sap.sailing.domain.leaderboard.SettableScoreCorrection;
31
+import com.sap.sailing.domain.leaderboard.impl.ScoreCorrectionImpl;
32
+import com.sap.sse.common.TimePoint;
33
+import com.sap.sse.common.Util.Pair;
34
+
35
+/**
36
+ * See bug6168; tests fleet-specific check for score corrections in race columns that suggest that this
37
+ * fleet has finished the race.
38
+ *
39
+ * @author Axel Uhl (d043530)
40
+ *
41
+ */
42
+public class ScoreCorrectionForUntrackedRacesTest extends StoredTrackBasedTest {
43
+ private RaceColumnInSeries f1;
44
+ private RaceColumnInSeries f2;
45
+ private Series series;
46
+ private Fleet gold;
47
+ private Fleet silver;
48
+ private RegattaLog regattaLog;
49
+ private RaceLog goldRaceLogF1;
50
+ private RaceLog goldRaceLogF2;
51
+ private RaceLog silverRaceLogF1;
52
+ private RaceLog silverRaceLogF2;
53
+ private SettableScoreCorrection scoreCorrection;
54
+ private CompetitorWithBoat c1;
55
+ private CompetitorWithBoat c2;
56
+ private AbstractLogEventAuthor author;
57
+ private Leaderboard leaderboard;
58
+
59
+ @BeforeEach
60
+ public void setUp() {
61
+ leaderboard = mock(Leaderboard.class);
62
+ c1 = createCompetitorWithBoat("C1");
63
+ c2 = createCompetitorWithBoat("C2");
64
+ author = new LogEventAuthorImpl("Test", 1);
65
+ gold = new FleetImpl("Gold", 1);
66
+ silver = new FleetImpl("Silver", 2);
67
+ series = new SeriesImpl("Default", /* isMedal */ false, /* isFleetsCanRunInParallel */ true, Arrays.asList(gold, silver), Arrays.asList("F1", "F2"), /* trackedRegattaRegistry */ null);
68
+ f1 = mock(RaceColumnInSeries.class);
69
+ when(f1.getName()).thenReturn("F1");
70
+ when(f1.getSeries()).thenReturn(series);
71
+ when(f1.getKey(c1)).thenReturn(new Pair<>(c1, f1));
72
+ when(f1.getKey(c2)).thenReturn(new Pair<>(c2, f1));
73
+ goldRaceLogF1 = new RaceLogImpl(UUID.randomUUID());
74
+ silverRaceLogF1 = new RaceLogImpl(UUID.randomUUID());
75
+ when(f1.getRaceLog(gold)).thenReturn(goldRaceLogF1);
76
+ when(f1.getRaceLog(silver)).thenReturn(silverRaceLogF1);
77
+ f2 = mock(RaceColumnInSeries.class);
78
+ when(f2.getName()).thenReturn("F2");
79
+ when(f2.getSeries()).thenReturn(series);
80
+ when(f2.getKey(c1)).thenReturn(new Pair<>(c1, f2));
81
+ when(f2.getKey(c2)).thenReturn(new Pair<>(c2, f2));
82
+ goldRaceLogF2 = new RaceLogImpl(UUID.randomUUID());
83
+ silverRaceLogF2 = new RaceLogImpl(UUID.randomUUID());
84
+ when(f2.getRaceLog(gold)).thenReturn(goldRaceLogF2);
85
+ when(f2.getRaceLog(silver)).thenReturn(silverRaceLogF2);
86
+ goldRaceLogF1.add(new RaceLogUseCompetitorsFromRaceLogEventImpl(TimePoint.now(), author, TimePoint.now(), UUID.randomUUID(), 0));
87
+ goldRaceLogF1.add(new RaceLogRegisterCompetitorEventImpl(TimePoint.now(), author, /* passId */ 0, c1));
88
+ goldRaceLogF2.add(new RaceLogUseCompetitorsFromRaceLogEventImpl(TimePoint.now(), author, TimePoint.now(), UUID.randomUUID(), 0));
89
+ goldRaceLogF2.add(new RaceLogRegisterCompetitorEventImpl(TimePoint.now(), author, /* passId */ 0, c1));
90
+ silverRaceLogF1.add(new RaceLogUseCompetitorsFromRaceLogEventImpl(TimePoint.now(), author, TimePoint.now(), UUID.randomUUID(), 0));
91
+ silverRaceLogF1.add(new RaceLogRegisterCompetitorEventImpl(TimePoint.now(), author, /* passId */ 0, c2));
92
+ silverRaceLogF2.add(new RaceLogUseCompetitorsFromRaceLogEventImpl(TimePoint.now(), author, TimePoint.now(), UUID.randomUUID(), 0));
93
+ silverRaceLogF2.add(new RaceLogRegisterCompetitorEventImpl(TimePoint.now(), author, /* passId */ 0, c2));
94
+ regattaLog = new RegattaLogImpl(UUID.randomUUID());
95
+ when(f1.getAllCompetitors(gold)).thenReturn(new RegisteredCompetitorsAnalyzer(goldRaceLogF1, regattaLog).analyze());
96
+ when(f1.getAllCompetitors(silver)).thenReturn(new RegisteredCompetitorsAnalyzer(silverRaceLogF1, regattaLog).analyze());
97
+ when(f2.getAllCompetitors(gold)).thenReturn(new RegisteredCompetitorsAnalyzer(goldRaceLogF2, regattaLog).analyze());
98
+ when(f2.getAllCompetitors(silver)).thenReturn(new RegisteredCompetitorsAnalyzer(silverRaceLogF2, regattaLog).analyze());
99
+ scoreCorrection = new ScoreCorrectionImpl(leaderboard);
100
+ scoreCorrection.correctScore(c1, f1, 1.0);
101
+ scoreCorrection.correctScore(c1, f2, 1.0);
102
+ scoreCorrection.correctScore(c2, f1, 1.0);
103
+ }
104
+
105
+ @Test
106
+ public void testNumberOfRacesWithCorrections() {
107
+ assertTrue(scoreCorrection.hasCorrectionForNonTrackedFleet(f1, gold));
108
+ assertTrue(scoreCorrection.hasCorrectionForNonTrackedFleet(f1, silver));
109
+ assertTrue(scoreCorrection.hasCorrectionForNonTrackedFleet(f2, gold));
110
+ assertFalse(scoreCorrection.hasCorrectionForNonTrackedFleet(f2, silver));
111
+ }
112
+}
java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/ScoreCorrection.java
... ...
@@ -4,6 +4,7 @@ import java.io.Serializable;
4 4
import java.util.concurrent.Callable;
5 5
6 6
import com.sap.sailing.domain.base.Competitor;
7
+import com.sap.sailing.domain.base.Fleet;
7 8
import com.sap.sailing.domain.base.RaceColumn;
8 9
import com.sap.sailing.domain.common.MaxPointsReason;
9 10
import com.sap.sailing.domain.leaderboard.caching.LeaderboardDTOCalculationReuseCache;
... ...
@@ -121,8 +122,9 @@ public interface ScoreCorrection extends Serializable {
121 122
* for any competitor who in <code>raceInLeaderboard</code> is not in a tracked race and hence the fleet assignment
122 123
* cannot be determined. This is helpful, e.g., for progress detection. If score corrections are present for such
123 124
* untracked competitors then all untracked fleets need to be assumed as finished.
125
+ * @param fleet TODO
124 126
*/
125
- boolean hasCorrectionForNonTrackedFleet(RaceColumn raceInLeaderboard);
127
+ boolean hasCorrectionForNonTrackedFleet(RaceColumn raceInLeaderboard, Fleet fleet);
126 128
127 129
/**
128 130
* @return all race columns for which this score corrections object has at least one correction; note that this
java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LeaderboardTotalRankComparator.java
... ...
@@ -290,7 +290,6 @@ public class LeaderboardTotalRankComparator implements Comparator<Competitor> {
290 290
}
291 291
}
292 292
}
293
- // TODO bug5877: pass leaderboard and competitors and a totalPointsSupplier (based on totalPointsCache, see call to compareByBetterScore below) to allow for identifying competitors promoted through to later medal race
294 293
int result = scoringScheme.compareByMedalRaceParticipation(zeroBasedIndexOfLastMedalSeriesInWhichO1Scored, zeroBasedIndexOfLastMedalSeriesInWhichO2Scored);
295 294
if (result == 0) {
296 295
result = defaultFleetBasedComparisonResult;
... ...
@@ -482,29 +481,26 @@ public class LeaderboardTotalRankComparator implements Comparator<Competitor> {
482 481
}
483 482
484 483
/**
485
- * If the race column only has one fleet, no decision is made and 0 is returned. Otherwise, if <code>fleet</code> is the
486
- * best fleet with others in the column being worse, return "better" (lesser; -1). If <code>fleet</code> is the worst fleet
487
- * with others in the column being better, return "worse" (greater; 1). Otherwise, return 0.
484
+ * If the race column only has one fleet, no decision is made and 0 is returned. Otherwise, if there are other fleets with
485
+ * a {@link Fleet#getOrdering() rank} different from that of <code>fleet</code>, we want to sort competitors with no fleet
486
+ * assignment to the "worse" end of the leaderboard, therefore returning 1. Otherwise, 0 is returned.
488 487
*/
489 488
private int extremeFleetComparison(RaceColumn raceColumn, Fleet fleet) {
490
- boolean allOthersAreGreater = true;
491
- boolean allOthersAreLess = true;
489
+ boolean greaterFleetExists = false;
490
+ boolean lesserFleetExists = false;
492 491
boolean othersExist = false;
493 492
for (Fleet f : raceColumn.getFleets()) {
494 493
if (f != fleet) {
495 494
othersExist = true;
496
- allOthersAreGreater = allOthersAreGreater && f.compareTo(fleet) > 0;
497
- allOthersAreLess = allOthersAreLess && f.compareTo(fleet) < 0;
495
+ greaterFleetExists = greaterFleetExists || f.compareTo(fleet) > 0;
496
+ lesserFleetExists = lesserFleetExists || f.compareTo(fleet) < 0;
498 497
}
499 498
}
500
- int result = 0;
501
- if (othersExist) {
502
- assert !(allOthersAreGreater && allOthersAreLess);
503
- if (allOthersAreGreater) {
504
- result = -1;
505
- } else if (allOthersAreLess) {
506
- result = 1;
507
- }
499
+ final int result;
500
+ if (othersExist && (greaterFleetExists || lesserFleetExists)) {
501
+ result = -1; // the competitor with no fleet is considered worse than a competitor in one of the ranked fleets
502
+ } else {
503
+ result = 0;
508 504
}
509 505
return result;
510 506
}
java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/ScoreCorrectionImpl.java
... ...
@@ -14,6 +14,7 @@ import java.util.logging.Logger;
14 14
15 15
import com.sap.sailing.domain.base.Competitor;
16 16
import com.sap.sailing.domain.base.Course;
17
+import com.sap.sailing.domain.base.Fleet;
17 18
import com.sap.sailing.domain.base.RaceColumn;
18 19
import com.sap.sailing.domain.base.RaceColumnInSeries;
19 20
import com.sap.sailing.domain.common.MaxPointsReason;
... ...
@@ -610,19 +611,17 @@ public class ScoreCorrectionImpl implements SettableScoreCorrection {
610 611
611 612
@Override
612 613
public boolean hasCorrectionFor(RaceColumn raceInLeaderboard) {
613
- return internalHasScoreCorrectionFor(raceInLeaderboard, /* considerOnlyUntrackedRaces */ false);
614
+ return internalHasScoreCorrectionFor(raceInLeaderboard);
614 615
}
615 616
616
- private boolean internalHasScoreCorrectionFor(RaceColumn raceInLeaderboard, boolean considerOnlyUntrackedRaces) {
617
- for (Pair<Competitor, RaceColumn> correctedScoresKey : correctedScores.keySet()) {
618
- if (correctedScoresKey.getB() == raceInLeaderboard &&
619
- (!considerOnlyUntrackedRaces || raceInLeaderboard.getTrackedRace(correctedScoresKey.getA()) == null)) {
617
+ private boolean internalHasScoreCorrectionFor(RaceColumn raceInLeaderboard) {
618
+ for (final Pair<Competitor, RaceColumn> correctedScoresKey : correctedScores.keySet()) {
619
+ if (correctedScoresKey.getB() == raceInLeaderboard) {
620 620
return true;
621 621
}
622 622
}
623 623
for (Pair<Competitor, RaceColumn> maxPointsReasonsKey : maxPointsReasons.keySet()) {
624
- if (maxPointsReasonsKey.getB() == raceInLeaderboard &&
625
- (!considerOnlyUntrackedRaces || raceInLeaderboard.getTrackedRace(maxPointsReasonsKey.getA()) == null)) {
624
+ if (maxPointsReasonsKey.getB() == raceInLeaderboard) {
626 625
return true;
627 626
}
628 627
}
... ...
@@ -630,8 +629,21 @@ public class ScoreCorrectionImpl implements SettableScoreCorrection {
630 629
}
631 630
632 631
@Override
633
- public boolean hasCorrectionForNonTrackedFleet(RaceColumn raceInLeaderboard) {
634
- return internalHasScoreCorrectionFor(raceInLeaderboard, /* considerOnlyUntrackedRaces */ true);
632
+ public boolean hasCorrectionForNonTrackedFleet(RaceColumn raceInLeaderboard, Fleet fleet) {
633
+ boolean result;
634
+ if (raceInLeaderboard.getTrackedRace(fleet) == null) {
635
+ result = false;
636
+ for (final Competitor competitor : raceInLeaderboard.getAllCompetitors(fleet)) {
637
+ final Pair<Competitor, RaceColumn> key = raceInLeaderboard.getKey(competitor);
638
+ if (correctedScores.containsKey(key) || maxPointsReasons.containsKey(key)) {
639
+ result = true;
640
+ break;
641
+ }
642
+ }
643
+ } else {
644
+ result = false;
645
+ }
646
+ return result;
635 647
}
636 648
637 649
@Override
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/DynamicTrackWithRemove.java
... ...
@@ -26,5 +26,5 @@ public interface DynamicTrackWithRemove<FixType extends Timed> extends DynamicTr
26 26
*/
27 27
boolean remove(FixType fix);
28 28
29
- void removeAllUpToAndIncluding(FixType fix);
29
+ void removeAllUpToExcluding(FixType fix);
30 30
}
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackWithRemoveImpl.java
... ...
@@ -27,7 +27,7 @@ public class DynamicTrackWithRemoveImpl<FixType extends Timed> extends DynamicTr
27 27
}
28 28
29 29
@Override
30
- public void removeAllUpToAndIncluding(FixType fix) {
31
- getInternalFixes().removeAllLessOrEqual(fix);
30
+ public void removeAllUpToExcluding(FixType fix) {
31
+ getInternalFixes().removeAllLessThan(fix);
32 32
}
33 33
}
java/com.sap.sailing.geocoding/src/com/sap/sailing/geocoding/impl/ReverseGeocoderImpl.java
... ...
@@ -14,6 +14,7 @@ import java.util.Collections;
14 14
import java.util.Comparator;
15 15
import java.util.Iterator;
16 16
import java.util.List;
17
+import java.util.Optional;
17 18
import java.util.Random;
18 19
import java.util.TimeZone;
19 20
import java.util.logging.Level;
... ...
@@ -36,7 +37,21 @@ import com.sap.sse.common.Util;
36 37
import com.sap.sse.util.HttpUrlConnectionHelper;
37 38
38 39
public class ReverseGeocoderImpl implements ReverseGeocoder {
40
+ /**
41
+ * The system property name to get the comma separated list of geonames.org usernames; takes precedence over
42
+ * the environment variable whose name is given by {@link #USERNAMES_ENV_VARIABLE_NAME}.
43
+ */
39 44
private static final String USERNAMES_SYSTEM_PROPERTY_NAME = "geonames.org.usernames";
45
+
46
+ /**
47
+ * The environment variable name to get the comma separated list of geonames.org usernames. Not evaluated
48
+ * if the system property whose name is given by {@link #USERNAMES_SYSTEM_PROPERTY_NAME} is set.
49
+ */
50
+ private static final String USERNAMES_ENV_VARIABLE_NAME = "GEONAMES_ORG_USERNAMES";
51
+
52
+ /**
53
+ * Default username in case neither system property nor environment variable is set.
54
+ */
40 55
private static final String GEONAMES_DEFAULT_USER_NAME = "sailtracking0";
41 56
42 57
private static final String DATES = "dates";
... ...
@@ -63,7 +78,8 @@ public class ReverseGeocoderImpl implements ReverseGeocoder {
63 78
private static final Logger logger = Logger.getLogger(ReverseGeocoderImpl.class.getName());
64 79
65 80
public ReverseGeocoderImpl() {
66
- final String commaSeparatedUsernames = System.getProperty(USERNAMES_SYSTEM_PROPERTY_NAME, GEONAMES_DEFAULT_USER_NAME);
81
+ final String commaSeparatedUsernames = System.getProperty(USERNAMES_SYSTEM_PROPERTY_NAME,
82
+ Optional.ofNullable(System.getenv(USERNAMES_ENV_VARIABLE_NAME)).orElse(GEONAMES_DEFAULT_USER_NAME));
67 83
this.usernames = commaSeparatedUsernames.split(",");
68 84
}
69 85
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/whatsnew/resources/SailingAnalyticsNotes.html
... ...
@@ -8,6 +8,11 @@
8 8
<h5 class="articleSubheadline">October 2025</h5>
9 9
<ul class="bulletList">
10 10
<li>Bug fix: the "Edit Mark Passings" panel showed only up to 15 waypoints.</li>
11
+ <li>Competitors not assigned to any fleet will no longer be sorted in between a Gold
12
+ and a Silver fleet but will end up "at the bottom" of the leaderboard.</li>
13
+ <li>Bug fix: when a split-fleet series had results only entered manually as score
14
+ corrections, the progress bars for the series were not shown properly. All
15
+ fleets would be considered finished although some may not have seen results.</li>
11 16
</ul>
12 17
<h5 class="articleSubheadline">September 2025</h5>
13 18
<ul class="bulletList">
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/server/RaceContext.java
... ...
@@ -585,7 +585,8 @@ public class RaceContext {
585 585
}
586 586
}
587 587
ScoreCorrection scoreCorrection = leaderboard.getScoreCorrection();
588
- if (trackedRace == null && scoreCorrection != null && scoreCorrection.hasCorrectionForNonTrackedFleet(raceColumn)) {
588
+ // bug6168: for split fleets we need to check the fleet too
589
+ if (trackedRace == null && scoreCorrection != null && scoreCorrection.hasCorrectionForNonTrackedFleet(raceColumn, fleet)) {
589 590
return RaceViewState.FINISHED;
590 591
}
591 592
if (startTime != null) {
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/WindPanel.java
... ...
@@ -341,6 +341,7 @@ public class WindPanel extends FormPanel implements FilterablePanelProvider<Race
341 341
contentPanel.add(new Label(stringMessages.seeIgtimiTabForAccountSettings()));
342 342
final CheckBox correctByDeclination = new CheckBox(stringMessages.declinationCheckbox());
343 343
correctByDeclination.setValue(true); // by default this is desirable because the Igtimi connector reads uncorrected magnetic values
344
+ final TextBox optionalBearerTokenBox = new TextBox();
344 345
final Button importButton = new Button(stringMessages.importWindFromIgtimi());
345 346
importButton.ensureDebugId("ImportWindFromIgtimi");
346 347
final HTML resultReport = new HTML();
... ...
@@ -357,7 +358,9 @@ public class WindPanel extends FormPanel implements FilterablePanelProvider<Race
357 358
if (Window.confirm(warningMessage)) {
358 359
resultReport.setText(stringMessages.loading());
359 360
sailingServiceWrite.importWindFromIgtimi(new ArrayList<>(refreshableRaceSelectionModel.getSelectedSet()),
360
- correctByDeclination.getValue(), new AsyncCallback<Map<RegattaAndRaceIdentifier, Integer>>() {
361
+ correctByDeclination.getValue(),
362
+ Util.hasLength(optionalBearerTokenBox.getValue()) ? optionalBearerTokenBox.getValue().trim() : null,
363
+ new AsyncCallback<Map<RegattaAndRaceIdentifier, Integer>>() {
361 364
@Override
362 365
public void onFailure(Throwable caught) {
363 366
errorReporter.reportError(stringMessages.errorImportingIgtimiWind(caught.getMessage()));
... ...
@@ -383,6 +386,11 @@ public class WindPanel extends FormPanel implements FilterablePanelProvider<Race
383 386
}
384 387
});
385 388
contentPanel.add(correctByDeclination);
389
+ final HorizontalPanel tokenPanel = new HorizontalPanel();
390
+ tokenPanel.setSpacing(5);
391
+ tokenPanel.add(new Label(stringMessages.optionalBearerTokenForWindImport()));
392
+ tokenPanel.add(optionalBearerTokenBox);
393
+ contentPanel.add(tokenPanel);
386 394
contentPanel.add(importButton);
387 395
contentPanel.add(resultReport);
388 396
return igtimiWindImportRootPanel;
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceWrite.java
... ...
@@ -243,7 +243,7 @@ public interface SailingServiceWrite extends FileStorageManagementGwtService, Sa
243 243
throws NotFoundException, NotDenotableForRaceLogTrackingException;
244 244
245 245
Map<RegattaAndRaceIdentifier, Integer> importWindFromIgtimi(List<RaceDTO> selectedRaces,
246
- boolean correctByDeclination)
246
+ boolean correctByDeclination, String optionalBearerTokenOrNull)
247 247
throws IllegalStateException, Exception;
248 248
249 249
IgtimiDataAccessWindowWithSecurityDTO addIgtimiDataAccessWindow(String deviceSerialNumber, Date from, Date to);
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceWriteAsync.java
... ...
@@ -586,7 +586,7 @@ public interface SailingServiceWriteAsync extends FileStorageManagementGwtServic
586 586
void removeIgtimiDevice(String deviceSerialNumber, AsyncCallback<Void> asyncCallback);
587 587
588 588
void importWindFromIgtimi(List<RaceDTO> selectedRaces, boolean correctByDeclination,
589
- AsyncCallback<Map<RegattaAndRaceIdentifier, Integer>> asyncCallback);
589
+ String optionalBearerTokenOrNull, AsyncCallback<Map<RegattaAndRaceIdentifier, Integer>> asyncCallback);
590 590
591 591
/**
592 592
* The boolean result reflects whether a connection to the device identified by {@code serialNumber}
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java
... ...
@@ -2523,4 +2523,5 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages,
2523 2523
String selectToRaceColumn();
2524 2524
String exportTWAHistogramToCsv();
2525 2525
String exportWindSpeedHistogramToCsv();
2526
+ String optionalBearerTokenForWindImport();
2526 2527
}
... ...
\ No newline at end of file
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties
... ...
@@ -2558,4 +2558,5 @@ successfullyCopiedPairings=Successfully copied pairings
2558 2558
selectFromRaceColumn=Select race column from where to start copying pairings
2559 2559
selectToRaceColumn=Select race colunm to where to copy pairings
2560 2560
exportTWAHistogramToCsv=Export True Wind Angle histogram to CSV
2561
-exportWindSpeedHistogramToCsv=Export Wind Speed histogram to CSV
... ...
\ No newline at end of file
0
+exportWindSpeedHistogramToCsv=Export Wind Speed histogram to CSV
1
+optionalBearerTokenForWindImport=Optional bearer token for wind import
... ...
\ No newline at end of file
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties
... ...
@@ -2552,4 +2552,5 @@ successfullyCopiedPairings=Zuordnungen erfolgreich kopiert
2552 2552
selectFromRaceColumn=Spalte auswählen, ab der die Zuordnungen kopiert werden sollen
2553 2553
selectToRaceColumn=Spalte auswählen, bis zu der die Zuordnungen kopiert werden sollen
2554 2554
exportTWAHistogramToCsv=Histogramm der wahren Windwinkel in CSV exportieren
2555
-exportWindSpeedHistogramToCsv=Histogramm der wahren Windgeschwindigkeiten in CSV exportieren
... ...
\ No newline at end of file
0
+exportWindSpeedHistogramToCsv=Histogramm der wahren Windgeschwindigkeiten in CSV exportieren
1
+optionalBearerTokenForWindImport=Optionales Bearer Token für Wind-Import
... ...
\ No newline at end of file
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/Activator.java
... ...
@@ -1,5 +1,7 @@
1 1
package com.sap.sailing.gwt.ui.server;
2 2
3
+import java.util.Optional;
4
+
3 5
import org.osgi.framework.BundleActivator;
4 6
import org.osgi.framework.BundleContext;
5 7
... ...
@@ -10,11 +12,31 @@ public class Activator implements BundleActivator {
10 12
private SailingServiceImpl sailingServiceToStopWhenStopping;
11 13
private static Activator INSTANCE;
12 14
15
+ /**
16
+ * If the system property named after this constant is set, its value is used for Google Maps API authentication.
17
+ * It takes precedence over the environment variable named after {@link #GOOGLE_MAPS_LOADER_AUTHENTICATION_PARAMS_ENV_VAR_NAME}.
18
+ */
13 19
private final static String GOOGLE_MAPS_LOADER_AUTHENTICATION_PARAMS_PROPERTY_NAME = "google.maps.authenticationparams";
14 20
21
+ /**
22
+ * If the system property named after {@link #GOOGLE_MAPS_LOADER_AUTHENTICATION_PARAMS_PROPERTY_NAME} is not set,
23
+ * this environment variable is checked for Google Maps API authentication parameters.
24
+ */
25
+ private final static String GOOGLE_MAPS_LOADER_AUTHENTICATION_PARAMS_ENV_VAR_NAME = "GOOGLE_MAPS_AUTHENTICATION_PARAMS";
26
+
27
+ /**
28
+ * The system property named after this constant is expected to provide the YouTube V3 API key. Takes precedence over
29
+ * the environment variable named after {@link #YOUTUBE_V3_API_KEY_ENV_VAR_NAME}.
30
+ */
15 31
private final static String YOUTUBE_V3_API_KEY_PROPERTY_NAME = "youtube.api.key";
16 32
17 33
/**
34
+ * If the system property named after {@link #YOUTUBE_V3_API_KEY_PROPERTY_NAME} is not set, this environment variable
35
+ * is checked for the YouTube V3 API key.
36
+ */
37
+ private final static String YOUTUBE_V3_API_KEY_ENV_VAR_NAME = "YOUTUBE_V3_API_KEY";
38
+
39
+ /**
18 40
* Required by {@link GoogleMapsLoader#load(Runnable, String)} and to be provided through a system property named
19 41
* after {@link GOOGLE_MAPS_LOADER_AUTHENTICATION_PARAMS_PROPERTY_NAME}. The value would be something like
20 42
* {@code client=abcde&channel=fghij}.
... ...
@@ -34,8 +56,12 @@ public class Activator implements BundleActivator {
34 56
@Override
35 57
public void start(BundleContext context) throws Exception {
36 58
Activator.context = context;
37
- googleMapsLoaderAuthenticationParams = context.getProperty(GOOGLE_MAPS_LOADER_AUTHENTICATION_PARAMS_PROPERTY_NAME);
38
- youtubeApiKey = context.getProperty(YOUTUBE_V3_API_KEY_PROPERTY_NAME);
59
+ googleMapsLoaderAuthenticationParams = Optional
60
+ .ofNullable(context.getProperty(GOOGLE_MAPS_LOADER_AUTHENTICATION_PARAMS_PROPERTY_NAME))
61
+ .orElse(System.getenv(GOOGLE_MAPS_LOADER_AUTHENTICATION_PARAMS_ENV_VAR_NAME));
62
+ youtubeApiKey = Optional
63
+ .ofNullable(context.getProperty(YOUTUBE_V3_API_KEY_PROPERTY_NAME))
64
+ .orElse(System.getenv(YOUTUBE_V3_API_KEY_ENV_VAR_NAME));
39 65
}
40 66
41 67
@Override
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java
... ...
@@ -6098,7 +6098,17 @@ public class SailingServiceImpl extends ResultCachingProxiedRemoteServiceServlet
6098 6098
return Activator.getInstance().getGoogleMapsLoaderAuthenticationParams();
6099 6099
}
6100 6100
6101
- protected IgtimiConnection createIgtimiConnection() {
6102
- return getIgtimiConnectionFactory().getOrCreateConnection(()->getSecurityService().getCurrentUser() != null ? getSecurityService().getAccessToken(getSecurityService().getCurrentUser().getName()) : null);
6101
+ /**
6102
+ * @param optionalBearerToken
6103
+ * if present, this bearer token is used to authenticate the Igtimi connection; otherwise, we try to use
6104
+ * Igtimi default credentials provided at startup through the {@code igtimi.bearer.token} system
6105
+ * property. Only if no such system property has been set, we look for a logged-in user in the
6106
+ * {@link #getSecurityService() security service} and use its access token
6107
+ */
6108
+ protected IgtimiConnection createIgtimiConnection(Optional<String> optionalBearerToken) {
6109
+ return optionalBearerToken.map(bearerToken->getIgtimiConnectionFactory().getOrCreateConnection(bearerToken))
6110
+ .orElse(getIgtimiConnectionFactory().getOrCreateConnection(()->getSecurityService().getCurrentUser() != null
6111
+ ? getSecurityService().getAccessToken(getSecurityService().getCurrentUser().getName())
6112
+ : null));
6103 6113
}
6104 6114
}
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceWriteImpl.java
... ...
@@ -2215,7 +2215,7 @@ public class SailingServiceWriteImpl extends SailingServiceImpl implements Saili
2215 2215
2216 2216
@Override
2217 2217
public Map<RegattaAndRaceIdentifier, Integer> importWindFromIgtimi(List<RaceDTO> selectedRaces,
2218
- boolean correctByDeclination)
2218
+ boolean correctByDeclination, String optionalBearerTokenOrNull)
2219 2219
throws IllegalStateException, ClientProtocolException, IOException, org.json.simple.parser.ParseException {
2220 2220
final List<DynamicTrackedRace> trackedRaces = new ArrayList<>();
2221 2221
if (selectedRaces != null && !selectedRaces.isEmpty()) {
... ...
@@ -2238,7 +2238,7 @@ public class SailingServiceWriteImpl extends SailingServiceImpl implements Saili
2238 2238
}
2239 2239
}
2240 2240
Map<RegattaAndRaceIdentifier, Integer> numberOfWindFixesImportedPerRace = new HashMap<RegattaAndRaceIdentifier, Integer>();
2241
- final IgtimiConnection conn = createIgtimiConnection();
2241
+ final IgtimiConnection conn = createIgtimiConnection(Optional.ofNullable(optionalBearerTokenOrNull));
2242 2242
// filter account based on used permissions to read account:
2243 2243
Map<TrackedRace, Integer> resultsForAccounts = conn.importWindIntoRace(trackedRaces, correctByDeclination);
2244 2244
for (Entry<TrackedRace, Integer> resultForAccount : resultsForAccounts.entrySet()) {
java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/core/SeleniumTestInvocationProvider.java
... ...
@@ -2,6 +2,7 @@ package com.sap.sailing.selenium.core;
2 2
3 3
import java.util.Arrays;
4 4
import java.util.List;
5
+import java.util.Map.Entry;
5 6
import java.util.stream.Stream;
6 7
7 8
import org.junit.jupiter.api.extension.Extension;
... ...
@@ -33,6 +34,9 @@ public class SeleniumTestInvocationProvider implements TestTemplateInvocationCon
33 34
@Override
34 35
public Stream<TestTemplateInvocationContext> provideTestTemplateInvocationContexts(ExtensionContext context) {
35 36
TestEnvironmentConfiguration config = TestEnvironmentConfiguration.getInstance();
37
+ for (Entry<String, String> e : config.getSystemProperties().entrySet()) {
38
+ System.setProperty(e.getKey(), e.getValue());
39
+ }
36 40
return config.getDriverDefinitions().stream().map(driverDef -> {
37 41
return new SeleniumTestContext(driverDef);
38 42
});
... ...
@@ -55,4 +59,4 @@ public class SeleniumTestInvocationProvider implements TestTemplateInvocationCon
55 59
return Arrays.asList(new SeleniumTestEnvironmentInjector(driverDefinition));
56 60
}
57 61
}
58
-}
... ...
\ No newline at end of file
0
+}
java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/raceboard/SimulatorOverlayTest.java
... ...
@@ -119,7 +119,7 @@ public class SimulatorOverlayTest extends AbstractSeleniumTest {
119 119
gpxInputStream.close();
120 120
final String routeconverterWindFileName = tmpFile.getAbsolutePath();
121 121
windPanel.importWindFromRouteconverter(routeconverterWindFileName, /* waiting up to 10 min */ 15 * 60);
122
- Thread.sleep(10000); // wait for 10s to allow all wind fixes to get processed by the PolarDataService
122
+ Thread.sleep(30000); // wait for 30s to allow all wind fixes to get processed by the PolarDataService
123 123
} finally {
124 124
tmpFile.delete();
125 125
}
java/com.sap.sailing.www/release_notes_admin.html
... ...
@@ -23,6 +23,21 @@
23 23
<div class="mainContent">
24 24
<h2 class="releaseHeadline">Release Notes - Administration Console</h2>
25 25
<div class="innerContent">
26
+ <h2 class="articleSubheadline">October 2025</h2>
27
+ <ul class="bulletList">
28
+ <li>When WindBots sent buffered data, e.g., from previous days, there was a possibility that
29
+ under some circumstances this data may have interfered with live data, leading, e.g.,
30
+ to "jumping" WindBot positions. This has now been fixed.<p>
31
+ <li>For the following system properties, alternative environment variables have been introduced:
32
+ <ul>
33
+ <li><tt>AWS_S3_TEST_S3ACCESSID</tt> for <tt>aws.s3.test.s3AccessId</tt></li>
34
+ <li><tt>AWS_S3_TEST_S3ACCESSKEY</tt> for <tt>aws.s3.test.s3AccessKey</tt></li>
35
+ <li><tt>GOOGLE_MAPS_AUTHENTICATION_PARAMS</tt> for <tt>google.maps.authenticationparams</tt></li>
36
+ <li><tt>YOUTUBE_V3_API_KEY</tt> for <tt>youtube.api.key</tt></li>
37
+ <li><tt>GEONAMES_ORG_USERNAMES</tt> for <tt>geonames.org.usernames</tt></li>
38
+ </ul>
39
+ If the system property is set, it takes precedence over the environment variable.</li>
40
+ </ul>
26 41
<h2 class="articleSubheadline">September 2025</h2>
27 42
<ul class="bulletList">
28 43
<li>Added boat classes "Elan E4" and "Beneteau First 36" to the list of boat classes.<p>
java/com.sap.sse.filestorage/src/com/sap/sse/filestorage/testsupport/AmazonS3TestSupport.java
... ...
@@ -8,17 +8,20 @@ import com.sap.sse.filestorage.impl.BaseFileStorageServiceImpl;
8 8
import com.sap.sse.security.SecurityService;
9 9
10 10
/**
11
- * Provide the S3 credentials for an IAM account that has access to the "sapsailing-automatic-upload-test" bucket
12
- * in the system properties {@code aws.s3.test.s3AccessId} and {@code aws.s3.test.s3AccessKey}. For the build
13
- * script in {@code configuration/buildAndUpdateProduct.sh} this can be done by setting the {@code APP_PARAMETERS}
14
- * environment variable for the script like this: {@code APP_PARAMETERS="-Daws.s3.test.s3AccessId=... -Daws.s3.test.s3AccessKey=..."}
11
+ * Provide the S3 credentials for an IAM account that has access to the "sapsailing-automatic-upload-test" bucket in the
12
+ * system properties {@code aws.s3.test.s3AccessId} and {@code aws.s3.test.s3AccessKey}. For the build script in
13
+ * {@code configuration/buildAndUpdateProduct.sh} this can be done by setting the {@code APP_PARAMETERS} environment
14
+ * variable for the script like this:
15
+ * {@code APP_PARAMETERS="-Daws.s3.test.s3AccessId=... -Daws.s3.test.s3AccessKey=..."}. Alternatively, e.g., in order to
16
+ * avoid system property setting with the "-D" command line option to be shown in log files, you may pass the ID and key
17
+ * as environment variables {@code AWS_S3_TEST_S3ACCESSID} and {@code AWS_S3_TEST_S3ACCESSKEY}, respectively.
15 18
*
16 19
* @author Axel Uhl (d043530)
17 20
*
18 21
*/
19 22
public class AmazonS3TestSupport {
20
- public static final String s3AccessId = System.getProperty("aws.s3.test.s3AccessId");
21
- public static final String s3AccessKey = System.getProperty("aws.s3.test.s3AccessKey");
23
+ public static final String s3AccessId = System.getProperty("aws.s3.test.s3AccessId", System.getenv("AWS_S3_TEST_S3ACCESSID"));
24
+ public static final String s3AccessKey = System.getProperty("aws.s3.test.s3AccessKey", System.getenv("AWS_S3_TEST_S3ACCESSKEY"));
22 25
private static final String s3BucketName = "sapsailing-automatic-upload-test";
23 26
24 27
public static BaseFileStorageServiceImpl createService(final SecurityService securityService) throws InvalidPropertiesException, IOException {
java/com.sap.sse.landscape.aws/src/com/sap/sse/landscape/aws/impl/AwsLandscapeImpl.java
... ...
@@ -155,6 +155,7 @@ import software.amazon.awssdk.services.elasticloadbalancingv2.model.DescribeTarg
155 155
import software.amazon.awssdk.services.elasticloadbalancingv2.model.DescribeTargetGroupsResponse;
156 156
import software.amazon.awssdk.services.elasticloadbalancingv2.model.DescribeTargetHealthRequest;
157 157
import software.amazon.awssdk.services.elasticloadbalancingv2.model.DescribeTargetHealthResponse;
158
+import software.amazon.awssdk.services.elasticloadbalancingv2.model.IpAddressType;
158 159
import software.amazon.awssdk.services.elasticloadbalancingv2.model.Listener;
159 160
import software.amazon.awssdk.services.elasticloadbalancingv2.model.LoadBalancer;
160 161
import software.amazon.awssdk.services.elasticloadbalancingv2.model.LoadBalancerAttribute;
... ...
@@ -348,6 +349,7 @@ public class AwsLandscapeImpl<ShardingKey> implements AwsLandscape<ShardingKey>
348 349
final CreateLoadBalancerResponse response = client
349 350
.createLoadBalancer(CreateLoadBalancerRequest.builder()
350 351
.name(name)
352
+ .ipAddressType(IpAddressType.DUALSTACK) // IPv4 and IPv6
351 353
.subnetMappings(subnetMappings)
352 354
.securityGroups(getDefaultSecurityGroupForApplicationLoadBalancer(region).getId())
353 355
.build());
java/com.sap.sse.landscape.aws/src/com/sap/sse/landscape/aws/orchestration/AwsApplicationConfiguration.java
... ...
@@ -58,9 +58,6 @@ implements UserDataProvider {
58 58
* <li>The {@link #setMailSmtpPort(int) SMTP port} defaults to 25.</li>
59 59
* <li>The {@link #setMailSmtpHost(String) mail SMTP host} defaults to <tt>email-smtp.${region}.amazonaws.com</tt>.</li>
60 60
* <li>The {@link #setMailSmtpAuth(boolean) mail SMTP authentication} is activated by default.</li>
61
- * <li>The {@link #setMailSmtpUser(String) mail SMTP user} defaults to {@code AKIAIHCQEFAZDLIK7SUQ} which is the
62
- * project's AWS SES (Simple e-Mail Service) username. The {@link #setMailSmtpPassword(String) password}, however,
63
- * must explicitly be provided and does not default to any non-{@code null}, non-empty value.</li>
64 61
* <li>The {@link #setMemoryInMegabytes(int) memory size} allocated to the process defaults to what {@link #setMemoryTotalSizeFactor(int)}
65 62
* sets, or, if {@link #setMemoryTotalSizeFactor(int)} isn't used either, to 3/4 of the physical memory available
66 63
* on the host running the application, minus some baseline allocated to the operating system.</li>
... ...
@@ -148,7 +145,6 @@ implements UserDataProvider {
148 145
* The pattern requires the region ID as {@link String} parameter and produces an AWS SES SMTP hostname for that region.
149 146
*/
150 147
private static final String DEFAULT_SMTP_HOSTNAME_PATTERN = "email-smtp.%s.amazonaws.com";
151
- private static final String DEFAULT_SMTP_USER = "AKIAIHCQEFAZDLIK7SUQ";
152 148
private static final Integer DEFAULT_SMTP_PORT = 25;
153 149
private AwsLandscape<ShardingKey> landscape;
154 150
private AwsRegion region;
... ...
@@ -388,7 +384,9 @@ implements UserDataProvider {
388 384
userData.put(DefaultProcessConfigurationVariables.MAIL_SMTP_HOST, mailSmtpHost==null?getDefaultAwsSesMailHostForRegion():mailSmtpHost);
389 385
userData.put(DefaultProcessConfigurationVariables.MAIL_SMTP_PORT, mailSmtpPort==null?DEFAULT_SMTP_PORT.toString():mailSmtpPort.toString());
390 386
userData.put(DefaultProcessConfigurationVariables.MAIL_SMTP_AUTH, mailSmtpAuth==null?Boolean.TRUE.toString():mailSmtpAuth.toString());
391
- userData.put(DefaultProcessConfigurationVariables.MAIL_SMTP_USER, mailSmtpUser==null?DEFAULT_SMTP_USER:mailSmtpUser);
387
+ if (mailSmtpUser != null) {
388
+ userData.put(DefaultProcessConfigurationVariables.MAIL_SMTP_USER, mailSmtpUser);
389
+ }
392 390
if (mailSmtpPassword != null) {
393 391
userData.put(DefaultProcessConfigurationVariables.MAIL_SMTP_PASSWORD, mailSmtpPassword);
394 392
}
wiki/howto/onboarding.md
... ...
@@ -10,7 +10,7 @@ First of all, make sure you've looked at [http://www.amazon.de/Patterns-Elements
10 10
11 11
1. Git Account
12 12
13
- - The primary Git repository for the project is hosted on Github (see [https://github.com/SAP/sailing-analytics](https://github.com/SAP/sailing-analytics)). To clone, use ``git@github.com:SAP/sailing-analytics.git``. To gain write access you have to become member of the [sailing-analytics-team](https://github.com/orgs/SAP/teams/sailing-analytics-team) organization. For that you need to [link your Github user to the Github SAP organization](https://wiki.one.int.sap/wiki/display/ospodocs/Self-Service+for+Joining+an+SAP+GitHub+Organization). We still have a shadow repository around that, e.g., powers our Wiki at [https://wiki.sapsailing.com](https://wiki.sapsailing.com) and which lives at ``ssh://trac@sapsailing.com/home/trac/git``.
13
+ - The primary Git repository for the project is hosted on Github (see [https://github.com/SAP/sailing-analytics](https://github.com/SAP/sailing-analytics)). To clone, use ``git@github.com:SAP/sailing-analytics.git``. To gain write access you have to become member of the [sailing-analytics-team](https://github.com/orgs/SAP/teams/sailing-analytics-team) organization. For that you need to [link your Github user to the Github SAP organization](https://wiki.one.int.sap/wiki/display/ospodocs/Self-Service+for+Joining+an+SAP+GitHub+Organization). For that to work, your Github account needs to have your @sap.com e-mail address assigned and verified. We still have a shadow repository around that, e.g., powers our Wiki at [https://wiki.sapsailing.com](https://wiki.sapsailing.com) and which lives at ``ssh://trac@sapsailing.com/home/trac/git``.
14 14
15 15
- In case you'd like to get access to the external git at `ssh://trac@sapsailing.com/home/trac/git` please send your SSH public key to one of the project maintainers, requesting git access. Make sure to NOT generate the key using Putty. Putty keys don't work reliably under Linux and on Windows/Cygwin environments. Use ssh-keygen in a Cygwin or Linux or MacOS/X environment instead. For further instructions for generating an ssh-key see [GitHub](https://docs.github.com/en/authentication/connecting-to-github-with-ssh/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent).
16 16
Note: If you want to use the ssh-key in the context of our solution, it can be an RSA or ED25519 format. Example for creating a key: `ssh-keygen -t ed25519 -b 512 -C "test@test.com"`. Make sure to set a non-empty password for your key.
wiki/info/landscape/operating-system-upgrade.md
... ...
@@ -28,7 +28,7 @@ Amazon Linux 2023 uses ``dnf`` as its package manager. An operating system upgra
28 28
```
29 29
dnf --releasever=latest upgrade
30 30
```
31
-This will upgrade all packages installed as well as the kernel. When run interactively, upgrade requiring a reboot will be displayed in the update list in red color. For scripted use, consider the ``needs-restarting`` command, delivering an exit status of ``1`` if a reboot is required.
31
+This will upgrade all packages installed as well as the kernel. When run interactively, upgrade requiring a reboot will be displayed in the update list in red color. For scripted use, consider the ``needs-restarting -r`` command, delivering an exit status of ``1`` if a reboot is required.
32 32
33 33
#### Debian
34 34