.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
Gemfile.lock
... ...
@@ -70,7 +70,7 @@ GEM
70 70
faraday_middleware (1.2.1)
71 71
faraday (~> 1.0)
72 72
fastimage (2.4.0)
73
- fastlane (2.227.1)
73
+ fastlane (2.228.0)
74 74
CFPropertyList (>= 2.3, < 4.0.0)
75 75
addressable (>= 2.8, < 3.0.0)
76 76
artifactory (~> 3.0)
... ...
@@ -134,12 +134,12 @@ GEM
134 134
google-apis-core (>= 0.11.0, < 2.a)
135 135
google-apis-storage_v1 (0.31.0)
136 136
google-apis-core (>= 0.11.0, < 2.a)
137
- google-cloud-core (1.8.0)
137
+ google-cloud-core (1.7.1)
138 138
google-cloud-env (>= 1.0, < 3.a)
139 139
google-cloud-errors (~> 1.0)
140 140
google-cloud-env (1.6.0)
141 141
faraday (>= 0.17.3, < 3.0)
142
- google-cloud-errors (1.5.0)
142
+ google-cloud-errors (1.4.0)
143 143
google-cloud-storage (1.47.0)
144 144
addressable (~> 2.8)
145 145
digest-crc (~> 0.4)
... ...
@@ -176,14 +176,14 @@ GEM
176 176
optparse (0.6.0)
177 177
os (1.1.4)
178 178
plist (3.7.2)
179
- public_suffix (6.0.1)
179
+ public_suffix (5.1.1)
180 180
rake (13.2.1)
181 181
representable (3.2.0)
182 182
declarative (< 0.1.0)
183 183
trailblazer-option (>= 0.1.1, < 0.2.0)
184 184
uber (< 0.2.0)
185 185
retriable (3.1.2)
186
- rexml (3.4.1)
186
+ rexml (3.4.2)
187 187
rouge (3.28.0)
188 188
ruby2_keywords (0.0.5)
189 189
rubyzip (2.4.1)
... ...
@@ -231,4 +231,4 @@ DEPENDENCIES
231 231
json (> 0)
232 232
233 233
BUNDLED WITH
234
- 2.6.1
234
+ 2.4.21
Home.md
... ...
@@ -14,25 +14,47 @@ SAP is at the center of today’s technology revolution, developing innovations
14 14
15 15
### General
16 16
17
+* [[Onboarding|wiki/howto/onboarding]]
17 18
* [[Information about this Wiki and HowTo|wiki/info/general/wiki]]
18 19
* [[General Project Information|wiki/info/general/general-information]]
19 20
* [[Project History|wiki/info/general/project-history]]
21
+* [[Development Environment|wiki/info/landscape/development-environment]]
20 22
21
-### Landscape
23
+### Development
24
+
25
+* [[Development Environment|wiki/info/landscape/development-environment]]
26
+* [[Typical Development Scenarios|wiki/info/landscape/typical-development-scenarios]]
27
+* [[Building and Deploying|wiki/info/landscape/building-and-deploying]]
28
+* [[Continuous Integration with Hudson/Jenkins|wiki/howto/development/ci]]
29
+* [[Dispatch|wiki/howto/development/dispatch]]
30
+* [[Working with GWT UI Binder|wiki/howto/development/gwt-ui-binder]]
31
+* [[Java De(Serialization) and Circular Dependencies|wiki/howto/development/java-de-serialization-and-circular-dependencies]]
32
+* [[Create boat graphics for the 2D race viewer|wiki/howto/development/boatgraphicssvg]]
33
+* [[JMX Support|wiki/howto/development/jmx]]
34
+* [[Working with GWT Locally|wiki/howto/development/local-gwt]]
35
+* [[UI Tests with Selenium|wiki/howto/development/selenium-ui-tests]]
36
+* [[Profiling|wiki/howto/development/profiling]]
37
+* [[Working with GWT Super Dev Mode|wiki/howto/development/super-dev-mode]]
38
+* [[Training of internal Wind Estimation models|wiki/howto/windestimation]]
39
+* [[Whitelabelling|wiki/howto/whitelabelling]]
40
+* [[Secured Settings|wiki/howto/development/secured-settings]]
41
+* [[Webdesign|wiki/info/landscape/webdesign]]
22 42
23
-* Architecture and Infrastructure
43
+### Architecture and Infrastructure
24 44
* [[Workspace, Bundles, Projects|wiki/info/general/workspace-bundles-projects-structure]]
25 45
* [[Runtime Environment|wiki/info/landscape/runtime-environment]]
26 46
* [[Basic architectual principles|wiki/info/landscape/basic-architectural-principles]]
27 47
* [[User Management|wiki/info/landscape/usermanagement]]
28 48
* [[Igtimi Windbot Riot Connector|wiki/info/landscape/igtimi-riot]]
29
- * [[Development Environment|wiki/info/landscape/development-environment]]
30 49
* [[Production Environment|wiki/info/landscape/production-environment]]
31 50
* [[Internationalization (i18n)|wiki/howto/development/i18n]]
32 51
* [[AI Agent|wiki/info/landscape/ai-agent]]
33 52
* [[Malware Scanning|wiki/info/landscape/malware-scanning]]
34
-* [[RaceLog Tracking Server Architecture|wiki/info/landscape/server]]
53
+ * [[RaceLog Tracking Server Architecture|wiki/info/landscape/server]]
35 54
* Environment Overview [[PDF|wiki/info/mobile/event-tracking/architecture.pdf]] | [[SVG|wiki/info/mobile/event-tracking/architecture.svg]]
55
+
56
+### Landscape
57
+
36 58
* Amazon
37 59
* [[Amazon EC2|wiki/info/landscape/amazon-ec2]]
38 60
* [[Upgrading ARCHIVE server|wiki/info/landscape/archive-server-upgrade]]
... ...
@@ -40,6 +62,7 @@ SAP is at the center of today’s technology revolution, developing innovations
40 62
* [[Creating an EC2 image from scratch|wiki/info/landscape/creating-ec2-image-from-scratch]]
41 63
* [[Upgrading an EC2 image|wiki/info/landscape/upgrading-ec2-image]]
42 64
* [[Creating a webserver EC2 image from scratch|wiki/info/landscape/creating-ec2-image-for-webserver-from-scratch]]
65
+ * [[Upgrading Operating System Across Landscape|wiki/info/landscape/operating-system-upgrade]]
43 66
* [[EC2 mail relaying vs. Amazon Simple E-Mail Service (SES)|wiki/info/landscape/mail-relaying]]
44 67
* [[Establishing support@sapsailing.com with AWS SES, SNS, and Lambda|wiki/info/landscape/support-email]]
45 68
* [[Creating an EC2 image for a MongoDB Replica Set from scratch|wiki/info/landscape/creating-ec2-mongodb-image-from-scratch]]
... ...
@@ -50,28 +73,9 @@ SAP is at the center of today’s technology revolution, developing innovations
50 73
* [[Downloading and Archiving TracTrac Events|wiki/howto/downloading-and-archiving-tractrac-events]]
51 74
* [[Data Mining Architecture|wiki/info/landscape/data-mining-architecture]]
52 75
* [[Typical Data Mining Scenarios|wiki/info/landscape/typical-data-mining-scenarios]]
53
-* [[Webdesign|wiki/info/landscape/webdesign]]
54 76
* [[sail-insight.com website|wiki/info/landscape/sail-insight.com-website]]
55 77
* [[Docker Registry|wiki/info/landscape/docker-registry]]
56 78
57
-### Development
58
-
59
-* [[Typical Development Scenarios|wiki/info/landscape/typical-development-scenarios]]
60
-* [[Building and Deploying|wiki/info/landscape/building-and-deploying]]
61
-* [[Create boat graphics for the 2D race viewer|wiki/howto/development/boatgraphicssvg]]
62
-* [[Continuous Integration with Hudson/Jenkins|wiki/howto/development/ci]]
63
-* [[Dispatch|wiki/howto/development/dispatch]]
64
-* [[Working with GWT UI Binder|wiki/howto/development/gwt-ui-binder]]
65
-* [[Java De(Serialization) and Circular Dependencies|wiki/howto/development/java-de-serialization-and-circular-dependencies]]
66
-* [[JMX Support|wiki/howto/development/jmx]]
67
-* [[Working with GWT Locally|wiki/howto/development/local-gwt]]
68
-* [[UI Tests with Selenium|wiki/howto/development/selenium-ui-tests]]
69
-* [[Profiling|wiki/howto/development/profiling]]
70
-* [[Working with GWT Super Dev Mode|wiki/howto/development/super-dev-mode]]
71
-* [[Training of internal Wind Estimation models|wiki/howto/windestimation]]
72
-* [[Whitelabelling|wiki/howto/whitelabelling]]
73
-* [[Secured Settings|wiki/howto/development/secured-settings]]
74
-
75 79
### Mobile
76 80
77 81
* [[Mobile Development|wiki/info/mobile/mobile-development]]
... ...
@@ -154,10 +158,8 @@ SAP is at the center of today’s technology revolution, developing innovations
154 158
* [[Monitoring Apache and RabbitMQ|wiki/misc/monitoring-apache-and-rabbitmq]]
155 159
156 160
## Projects
157
-* [[Analytics on a stick|wiki/projects/analytics-on-a-stick]]
158
-* [[Consolidating User Stores (bug 4006 / 4018)|wiki/projects/consolidating-user-stores]]
159
-* [[Cloud Infrastructure Orchestration|wiki/projects/cloud-orchestrator]]
160 161
* [[Management Console for Easier Administration|wiki/howto/development/management-console]]
162
+* [[Cloud Infrastructure Orchestration|wiki/projects/cloud-orchestrator]]
161 163
162 164
## Events and Planning
163 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/createHudsonJobForBug.sh
... ...
@@ -27,9 +27,9 @@ curl -s -X GET $COPY_TEMPLATE_CONFIG_URL -u "$USERNAME:$PASSWORD" -o "$CONFIGFIL
27 27
# On macosx is gnu-sed needed
28 28
if [[ "$OSTYPE" == *"$OS_FOR_GSED"* ]]; then
29 29
echo "Using gsed"
30
- gsed -i'' -e 's|<description>..*</description>|<description>This is the CI job for \&lt;a href=\&quot;'$BUGZILLA_BASE'/show_bug.cgi?id='$BUG_ID'\&quot;\&gt;Bug '$BUG_ID'\&lt;/a\&gt;</description>|' -e 's|<disabled>true</disabled>|<disabled>false</disabled>|' "$CONFIGFILE"
30
+ gsed -i'' -e 's|<description>..*</description>|<description>This is the CI job for \&lt;a href=\&quot;'$BUGZILLA_BASE'/show_bug.cgi?id='$BUG_ID'\&quot;\&gt;Bug '$BUG_ID'\&lt;/a\&gt;. See its latest \&lt;a href=\&quot;/userContent/measurements.html?job=bug'$BUG_ID'\&quot;\&gt;quality and performance measurements here.\&lt;/a\&gt;</description>|' -e 's|<disabled>true</disabled>|<disabled>false</disabled>|' "$CONFIGFILE"
31 31
else
32
- sed -i -e 's|<description>..*</description>|<description>This is the CI job for \&lt;a href=\&quot;'$BUGZILLA_BASE'/show_bug.cgi?id='$BUG_ID'\&quot;\&gt;Bug '$BUG_ID'\&lt;/a\&gt;</description>|' -e 's|<disabled>true</disabled>|<disabled>false</disabled>|' "$CONFIGFILE"
32
+ sed -i -e 's|<description>..*</description>|<description>This is the CI job for \&lt;a href=\&quot;'$BUGZILLA_BASE'/show_bug.cgi?id='$BUG_ID'\&quot;\&gt;Bug '$BUG_ID'\&lt;/a\&gt;. See its latest \&lt;a href=\&quot;/userContent/measurements.html?job=bug'$BUG_ID'\&quot;\&gt;quality and performance measurements here.\&lt;/a\&gt;</description>|' -e 's|<disabled>true</disabled>|<disabled>false</disabled>|' "$CONFIGFILE"
33 33
fi
34 34
35 35
# On macosx is gnu-sed needed
configuration/environments_scripts/build_server/files/etc/sysconfig/hudson
... ...
@@ -35,7 +35,7 @@ HUDSON_USER="hudson"
35 35
#
36 36
# Options to pass to java when running Hudson.
37 37
#
38
-HUDSON_JAVA_OPTIONS="-Djava.awt.headless=true -Xmx2G -Dhudson.slaves.ChannelPinger.pingInterval=60 -Dhudson.slaves.ChannelPinger.pingIntervalSeconds=60 -Dhudson.slaves.ChannelPinger.pingTimeoutSeconds=60"
38
+HUDSON_JAVA_OPTIONS="-Djava.awt.headless=true -Xmx4G -Dhudson.slaves.ChannelPinger.pingInterval=60 -Dhudson.slaves.ChannelPinger.pingIntervalSeconds=60 -Dhudson.slaves.ChannelPinger.pingTimeoutSeconds=60 -XX:+UseG1GC -Xlog:gc+ergo*=trace:file=/var/log/hudson/gc_ergo.log:time:filecount=10,filesize=100000000 -Xlog:gc*:file=/var/log/hudson/gc.log:time:filecount=10,filesize=100000000 -XX:+GCHistory -XX:GCHistoryFilename=/var/log/hudson/sapjvm_gc@PID.prf"
39 39
40 40
## Type: integer(0:65535)
41 41
## Default: 8080
configuration/environments_scripts/build_server/files/home/hudson/repo/userContent/measurements.html
... ...
@@ -0,0 +1,295 @@
1
+<!doctype html>
2
+<html lang="en">
3
+<head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>Hudson CI – Measurement Graphs Overview</title>
7
+
8
+ <!-- Tailwind CSS (CDN) -->
9
+ <script src="https://cdn.tailwindcss.com"></script>
10
+
11
+ <style>
12
+ @media print {
13
+ #toolbar, .card-actions, .footer { display: none !important; }
14
+ body { background: #fff !important; }
15
+ .card { break-inside: avoid; }
16
+ }
17
+ .spinner {
18
+ border: 4px solid rgba(0,0,0,0.1);
19
+ border-top: 4px solid #4f46e5;
20
+ border-radius: 50%;
21
+ width: 32px;
22
+ height: 32px;
23
+ animation: spin 1s linear infinite;
24
+ }
25
+ @keyframes spin {
26
+ 0% { transform: rotate(0deg); }
27
+ 100% { transform: rotate(360deg); }
28
+ }
29
+ </style>
30
+</head>
31
+<body class="min-h-screen bg-gray-50 text-gray-900">
32
+ <header id="toolbar" class="sticky top-0 z-50 backdrop-blur bg-white/80 border-b">
33
+ <div class="max-w-7xl mx-auto px-4 py-3 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
34
+ <div class="flex items-center gap-3">
35
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-7 h-7 text-indigo-600"><path d="M12 2a1 1 0 0 1 .894.553l8 16A1 1 0 0 1 20 20H4a1 1 0 0 1-.894-1.447l8-16A1 1 0 0 1 12 2Zm0 3.618L6.618 18h10.764L12 5.618Z"/></svg>
36
+ <div>
37
+ <h1 id="pageTitle" class="text-xl font-semibold leading-tight">Hudson CI – Measurement Graphs</h1>
38
+ <p class="text-sm text-gray-600">At-a-glance trends from build/test history</p>
39
+ </div>
40
+ </div>
41
+ <div class="flex flex-wrap items-center gap-2">
42
+ <button id="refreshAll" class="px-3 py-2 rounded-xl bg-indigo-600 text-white text-sm font-medium shadow hover:bg-indigo-700 focus:outline-none focus:ring">Refresh all</button>
43
+ <label class="flex items-center gap-2 text-sm">
44
+ <input id="autoRefreshToggle" type="checkbox" class="rounded border-gray-300"> Auto-refresh
45
+ </label>
46
+ <select id="columns" class="px-2 py-2 rounded-xl border text-sm bg-white">
47
+ <option value="2">2 columns</option>
48
+ <option value="3" selected>3 columns</option>
49
+ <option value="4">4 columns</option>
50
+ </select>
51
+ <input id="search" type="search" placeholder="Filter by title…" class="px-3 py-2 rounded-xl border text-sm bg-white w-48" />
52
+ </div>
53
+ </div>
54
+ </header>
55
+
56
+ <main class="max-w-7xl mx-auto p-4">
57
+ <div id="grid" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 transition-all"></div>
58
+ </main>
59
+
60
+ <footer class="footer max-w-7xl mx-auto px-4 pb-8 text-sm text-gray-500">
61
+ <p>Tip: Click any chart to view full-screen. Right-click an image to open it in a new tab. This page only embeds existing Hudson graph PNGs; no data is stored.</p>
62
+ </footer>
63
+
64
+ <dialog id="viewer" class="backdrop:bg-black/60 p-0 rounded-2xl shadow-2xl w-[95vw] max-w-6xl">
65
+ <div class="flex items-center justify-between px-4 py-3 border-b">
66
+ <h3 id="viewerTitle" class="font-semibold"></h3>
67
+ <div class="flex items-center gap-2">
68
+ <a id="viewerOpenSource" href="#" target="_blank" rel="noopener" class="text-sm px-2 py-1 rounded-lg border">Open source</a>
69
+ <button id="viewerClose" class="text-sm px-2 py-1 rounded-lg bg-gray-900 text-white">Close</button>
70
+ </div>
71
+ </div>
72
+ <div class="p-2 sm:p-4">
73
+ <img id="viewerImg" alt="Measurement graph" class="w-full h-auto rounded-xl" />
74
+ </div>
75
+ </dialog>
76
+
77
+ <script>
78
+ // Determine job name from query parameter or default
79
+ const params = new URLSearchParams(window.location.search);
80
+ const jobName = params.get('job') || 'SAPSailingAnalytics-master';
81
+
82
+ // Update page title with job name
83
+ document.getElementById('pageTitle').textContent = `Hudson CI – Measurement Graphs (${jobName})`;
84
+ document.title = `Hudson CI Measurement Graphs Overview – ${jobName}`;
85
+
86
+ const testNames = [
87
+ "FlorianopolisMarkPassingTest",
88
+ "KW470MarkPassingTest",
89
+ "Kiel2013505Test",
90
+ "OBMR2012VRMarkPassingTest",
91
+ "StarIDM2013MarkPassingTest"
92
+ ];
93
+ const measurementNames = ["Extra", "Skipped", "Different", "Accurate"];
94
+ const graphs = [];
95
+ // Build the graphs dynamically
96
+ testNames.forEach(test => {
97
+ measurementNames.forEach(measure => {
98
+ graphs.push({
99
+ title: `${test} ${measure}`,
100
+ url: `https://hudson.sapsailing.com/job/${jobName}/lastCompletedBuild/testReport/junit/com.sap.sailing.domain.test.markpassing/${test}/${test}/measurementPlots/${measure}/history/graph/png`,
101
+ source: `Hudson: ${jobName} → ${test} ${measure}`
102
+ });
103
+ });
104
+ });
105
+ // Also include your Branding client script size graph as the first entry:
106
+ graphs.unshift({
107
+ title: "Branding client script size in bytes",
108
+ url: `https://hudson.sapsailing.com/job/${jobName}/lastCompletedBuild/testReport/junit/com.sap.sse.branding.impl/TestAndMeasureSizeOfSAPBranding/TestAndMeasureSizeOfSAPBranding/measurementPlots/Branding%20client%20script%20size%20in%20bytes/history/graph/png`,
109
+ source: `Hudson: ${jobName} → Branding client script size in bytes`
110
+ });
111
+ // Also include the MarkPassingCalculatorPerformanceTest.ChooserPerformance Measurement
112
+ graphs.unshift({
113
+ title: "MarkPassingCalculator Chooser Performance",
114
+ url: `https://hudson.sapsailing.com/job/${jobName}/lastCompletedBuild/testReport/junit/com.sap.sailing.domain.test.markpassing/MarkPassingCalculatorPerformanceTest/ChooserPerformance/measurementPlots/ChooserPerformance/history/graph/png`,
115
+ source: `Hudson: ${jobName} → MarkPassingCalculator Chooser Performance`
116
+ });
117
+ // Also include the MarkPassingCalculatorPerformanceTest.FinderPerformance Measurement
118
+ graphs.unshift({
119
+ title: "MarkPassingCalculator Finder Performance",
120
+ url: `https://hudson.sapsailing.com/job/${jobName}/lastCompletedBuild/testReport/junit/com.sap.sailing.domain.test.markpassing/MarkPassingCalculatorPerformanceTest/FinderPerformance/measurementPlots/FinderPerformance/history/graph/png`,
121
+ source: `Hudson: ${jobName} → MarkPassingCalculator Finder Performance`
122
+ });
123
+
124
+ const PREFS_KEY = "hudson-graphs-prefs";
125
+ const defaultPrefs = { columns: 3, autoRefresh: false };
126
+ const prefs = Object.assign({}, defaultPrefs, JSON.parse(localStorage.getItem(PREFS_KEY) || "{}"));
127
+
128
+ const grid = document.getElementById('grid');
129
+ const search = document.getElementById('search');
130
+ const columns = document.getElementById('columns');
131
+ const refreshAll = document.getElementById('refreshAll');
132
+ const autoRefreshToggle = document.getElementById('autoRefreshToggle');
133
+ const viewer = document.getElementById('viewer');
134
+ const viewerImg = document.getElementById('viewerImg');
135
+ const viewerTitle = document.getElementById('viewerTitle');
136
+ const viewerOpenSource = document.getElementById('viewerOpenSource');
137
+ const viewerClose = document.getElementById('viewerClose');
138
+
139
+ columns.value = String(prefs.columns);
140
+ autoRefreshToggle.checked = !!prefs.autoRefresh;
141
+ applyColumns();
142
+
143
+ function applyColumns() {
144
+ const cols = Number(columns.value || 3);
145
+ grid.className = `grid gap-4 transition-all grid-cols-1 sm:grid-cols-2 ${cols>=3? 'lg:grid-cols-3':''} ${cols>=4? 'xl:grid-cols-4':''}`;
146
+ savePrefs();
147
+ }
148
+ columns.addEventListener('change', applyColumns);
149
+
150
+ function savePrefs() {
151
+ localStorage.setItem(PREFS_KEY, JSON.stringify({ columns: Number(columns.value), autoRefresh: autoRefreshToggle.checked }));
152
+ }
153
+ autoRefreshToggle.addEventListener('change', savePrefs);
154
+
155
+ function render() {
156
+ grid.innerHTML = '';
157
+ const q = (search.value || '').toLowerCase();
158
+ graphs
159
+ .filter(g => !q || g.title.toLowerCase().includes(q))
160
+ .forEach((g, idx) => grid.appendChild(cardForGraph(g, idx)));
161
+ }
162
+
163
+ function cardForGraph(g, idx) {
164
+ const card = document.createElement('article');
165
+ card.className = 'card rounded-2xl bg-white border shadow-sm overflow-hidden flex flex-col';
166
+
167
+ const header = document.createElement('div');
168
+ header.className = 'px-4 pt-4 flex items-start justify-between gap-3';
169
+ const title = document.createElement('h2');
170
+ title.className = 'font-semibold leading-snug';
171
+ title.textContent = g.title;
172
+ header.appendChild(title);
173
+
174
+ const actions = document.createElement('div');
175
+ actions.className = 'card-actions flex items-center gap-2';
176
+ actions.innerHTML = `
177
+ <a href="${g.url}" target="_blank" rel="noopener" title="Open image in new tab" class="text-sm px-2 py-1 rounded-lg border">Open</a>
178
+ <button data-idx="${idx}" class="refresh-btn text-sm px-2 py-1 rounded-lg bg-gray-900 text-white">Refresh</button>
179
+ `;
180
+ header.appendChild(actions);
181
+ card.appendChild(header);
182
+
183
+ const wrap = document.createElement('div');
184
+ wrap.className = 'relative m-3 rounded-xl overflow-hidden ring-1 ring-gray-100 flex items-center justify-center min-h-[200px]';
185
+
186
+ const spinner = document.createElement('div');
187
+ spinner.className = 'spinner';
188
+ wrap.appendChild(spinner);
189
+
190
+ const img = document.createElement('img');
191
+ img.alt = g.title;
192
+ img.loading = 'lazy';
193
+ img.decoding = 'async';
194
+ img.src = g.url;
195
+ img.className = 'w-full h-auto opacity-0 transition-opacity duration-300';
196
+
197
+ img.addEventListener('load', () => {
198
+ spinner.remove();
199
+ img.classList.remove('opacity-0');
200
+ });
201
+ img.addEventListener('error', () => {
202
+ spinner.className = 'absolute inset-0 flex items-center justify-center text-sm text-red-600 bg-red-50';
203
+ spinner.textContent = 'Failed to load image';
204
+ });
205
+
206
+ img.style.cursor = 'zoom-in';
207
+ img.addEventListener('click', () => openViewer(g));
208
+
209
+ wrap.appendChild(img);
210
+ card.appendChild(wrap);
211
+
212
+ const meta = document.createElement('div');
213
+ meta.className = 'px-4 pb-4 text-sm text-gray-600 flex items-center justify-between';
214
+ const left = document.createElement('div');
215
+ left.textContent = g.source || 'Hudson';
216
+ const right = document.createElement('div');
217
+ right.innerHTML = `<time class="last-updated" datetime="${new Date().toISOString()}">Updated just now</time>`;
218
+ meta.appendChild(left);
219
+ meta.appendChild(right);
220
+ card.appendChild(meta);
221
+
222
+ return card;
223
+ }
224
+
225
+ function openViewer(g) {
226
+ viewerImg.src = g.url;
227
+ viewerTitle.textContent = g.title;
228
+ viewerOpenSource.href = g.url;
229
+ if (typeof viewer.showModal === 'function') viewer.showModal();
230
+ else viewer.setAttribute('open', '');
231
+ }
232
+
233
+ viewerClose.addEventListener('click', () => viewer.close());
234
+ viewer.addEventListener('click', (e) => { if (e.target === viewer) viewer.close(); });
235
+
236
+ function refreshImages(scope=document) {
237
+ scope.querySelectorAll('img').forEach(img => {
238
+ const spinner = document.createElement('div');
239
+ spinner.className = 'spinner absolute inset-0 m-auto';
240
+ const parent = img.parentElement;
241
+ parent.appendChild(spinner);
242
+ img.classList.add('hidden');
243
+ const src = img.src.split('?')[0];
244
+ img.src = src + '?r=' + Date.now();
245
+ img.addEventListener('load', () => {
246
+ spinner.remove();
247
+ img.classList.remove('hidden');
248
+ }, { once: true });
249
+ });
250
+ scope.querySelectorAll('time.last-updated').forEach(t => {
251
+ t.textContent = 'Updated just now';
252
+ t.setAttribute('datetime', new Date().toISOString());
253
+ });
254
+ }
255
+
256
+ refreshAll.addEventListener('click', () => refreshImages(document));
257
+
258
+ grid.addEventListener('click', (e) => {
259
+ const btn = e.target.closest('button.refresh-btn');
260
+ if (!btn) return;
261
+ const card = btn.closest('.card');
262
+ const img = card.querySelector('img');
263
+ const time = card.querySelector('time.last-updated');
264
+ const spinner = document.createElement('div');
265
+ spinner.className = 'spinner absolute inset-0 m-auto';
266
+ img.parentElement.appendChild(spinner);
267
+ img.classList.add('hidden');
268
+ const src = img.src.split('?')[0];
269
+ img.src = src + '?r=' + Date.now();
270
+ img.addEventListener('load', () => {
271
+ spinner.remove();
272
+ img.classList.remove('hidden');
273
+ }, { once: true });
274
+ time.textContent = 'Updated just now';
275
+ time.setAttribute('datetime', new Date().toISOString());
276
+ });
277
+
278
+ search.addEventListener('input', render);
279
+
280
+ let autoTimer = null;
281
+ function updateAutoTimer() {
282
+ if (autoTimer) { clearInterval(autoTimer); autoTimer = null; }
283
+ if (autoRefreshToggle.checked) {
284
+ autoTimer = setInterval(() => refreshImages(document), 5 * 60 * 1000);
285
+ }
286
+ savePrefs();
287
+ }
288
+ autoRefreshToggle.addEventListener('change', updateAutoTimer);
289
+ updateAutoTimer();
290
+
291
+ render();
292
+ </script>
293
+</body>
294
+</html>
295
+
configuration/environments_scripts/central_reverse_proxy/files/var/www/cgi-bin/github.cgi
... ...
@@ -2,23 +2,21 @@
2 2
echo "
3 3
"
4 4
BODY=$( cat )
5
-echo "${BODY}" >/tmp/github-hook-body
6 5
REF=$( echo "${BODY}" | jq -r '.ref' )
7 6
PUSHER=$( echo "${BODY}" | jq -r '.pusher.email' )
8 7
logger -t github-cgi "ref is $REF, pusher was $PUSHER"
9 8
# For testing:
10
-#if [ "${PUSHER}" = "axel.uhl@sap.com" -a "${REF}" = "refs/heads/translation" ]; then
11
-tmsatsls+github.com_service-tip-git@sap.com
12
-# The filter in case this is to be used for github.tools.sap:
9
+#if [ "${PUSHER}" = "your-email@example.com" -a "${REF}" = "refs/heads/translation" ]; then
10
+# The filter in case this is to be used for alternative Github at github.tools.sap:
13 11
#if [ "${PUSHER}" = "tmsatsls+github.tools.sap_service-tip-git@sap.com" -a "${REF}" = "refs/heads/translation" ]; then
14 12
# The filter for github.com:
15 13
if [ "${PUSHER}" = "tmsatsls+github.com_service-tip-git@sap.com" -a "${REF}" = "refs/heads/translation" ]; then
16 14
echo "Identified a push to refs/heads/translation by ${PUSHER}."
17
- echo "Fetching translation branch from github.tools.sap and pushing it to ssh://trac@sapsailing.com/home/trac/git"
18
- logger -t github-cgi "fetching translation branch from github.tools.sap and pushing it to ssh://trac@sapsailing.com/home/trac/git"
15
+ echo "Fetching translation branch from github and pushing it to ssh://trac@sapsailing.com/home/trac/git"
16
+ logger -t github-cgi "fetching translation branch from github and pushing it to ssh://trac@sapsailing.com/home/trac/git"
19 17
cd /home/wiki/gitwiki
20 18
sudo -u wiki git fetch github translation:translation 2>&1
21 19
sudo -u wiki git push origin translation:translation 2>&1
22 20
else
23
- echo "Either pusher was not tmsatsls+github.tools.sap_service-tip-git@sap.com or ref was not refs/heads/translation. Not triggering."
21
+ echo "Either pusher was not tmsatsls+github.com_service-tip-git@sap.com or ref was not refs/heads/translation. Not triggering."
24 22
fi
configuration/environments_scripts/central_reverse_proxy/setup-central-reverse-proxy.sh
... ...
@@ -66,12 +66,14 @@ gem install gollum -v 5.3.2
66 66
gem update --system 3.5.7
67 67
cd /home
68 68
# copy bugzilla
69
-scp -o StrictHostKeyChecking=no root@sapsailing.com:/var/www/static/bugzilla-5.0.4.tar.gz /usr/local/src
69
+scp -o StrictHostKeyChecking=no root@sapsailing.com:/var/www/static/bugzilla-5.2.tar.gz /usr/local/src
70 70
cd /usr/local/src
71
-tar -xzvf bugzilla-5.0.4.tar.gz
72
-mv bugzilla-5.0.4 /usr/share/bugzilla
71
+tar -xzvf bugzilla-5.2.tar.gz
72
+mv bugzilla-5.2 /usr/share/bugzilla
73 73
cd /usr/share/bugzilla/
74
+mkdir data
74 75
scp -o StrictHostKeyChecking=no root@sapsailing.com:/usr/share/bugzilla/localconfig .
76
+scp -o StrictHostKeyChecking=no root@sapsailing.com:/usr/share/bugzilla/data/params.json ./data/
75 77
echo "Bugzilla has been copied. Now setting up bugzilla modules."
76 78
echo "This can take 5 minutes or so. The output is muted (but sent to log.txt) to prevent excessive warnings and clutter in the terminal."
77 79
SECONDEOF
... ...
@@ -79,6 +81,8 @@ terminationCheck "$?"
79 81
ssh -A "root@${IP}" "bash -s" << BUGZILLAEOF &>log.txt
80 82
cd /usr/share/bugzilla/
81 83
# essentials bugzilla
84
+/usr/bin/perl -MCPAN -e 'install App::cpanminus'
85
+cpanm --notest SOAP::Lite
82 86
/usr/bin/perl install-module.pl DateTime
83 87
/usr/bin/perl install-module.pl DateTime::TimeZone
84 88
/usr/bin/perl install-module.pl Email::Sender
... ...
@@ -91,7 +95,9 @@ cd /usr/share/bugzilla/
91 95
/usr/bin/perl install-module.pl Email::Address
92 96
/usr/bin/perl install-module.pl autodie
93 97
/usr/bin/perl install-module.pl Class::XSAccessor
98
+/usr/bin/perl install-module.pl DBIx::Connector
94 99
# nice to have for buzilla
100
+/usr/bin/perl install-module.pl Encode::Detect
95 101
/usr/bin/perl install-module.pl Date::Parse
96 102
/usr/bin/perl install-module.pl Email::Send
97 103
/usr/bin/perl install-module.pl DBI
... ...
@@ -109,6 +115,15 @@ cd /usr/share/bugzilla/
109 115
/usr/bin/perl install-module.pl File::Copy::Recursive
110 116
/usr/bin/perl install-module.pl MIME::Base64
111 117
/usr/bin/perl install-module.pl Authen::SASL
118
+/usr/bin/perl install-module.pl XML::Twig
119
+/usr/bin/perl install-module.pl Net::LDAP
120
+/usr/bin/perl install-module.pl Net::SMTP::SSL
121
+/usr/bin/perl install-module.pl XMLRPC::Lite
122
+/usr/bin/perl install-module.pl Test::Taint
123
+/usr/bin/perl install-module.pl HTML::Scrubber
124
+/usr/bin/perl install-module.pl Email::Reply
125
+/usr/bin/perl install-module.pl HTML::FormatText::WithLinks
126
+/usr/bin/perl install-module.pl Cache::Memcached
112 127
BUGZILLAEOF
113 128
terminationCheck "$?"
114 129
read -n 1 -p "Bugzilla installation complete, when ready press a key to continue." key_pressed
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.amazon.aws.aws-java-api/.gitignore
... ...
@@ -3,3 +3,4 @@
3 3
/build.properties
4 4
/.classpath
5 5
/META-INF/
6
+/target/
java/com.sap.sailing.aiagent/META-INF/MANIFEST.MF
... ...
@@ -10,9 +10,7 @@ Automatic-Module-Name: com.sap.sailing.aiagent
10 10
Import-Package: com.sap.sailing.aiagent.interfaces,
11 11
com.sap.sailing.domain.abstractlog.race,
12 12
com.sap.sailing.domain.abstractlog.regatta,
13
- com.sap.sailing.domain.leaderboard,
14
- com.sap.sailing.domain.tracking,
15
- com.sap.sailing.domain.tracking.impl,
13
+ com.sap.sailing.domain.shared.tracking,
16 14
com.sap.sse.common,
17 15
com.sap.sse.concurrent,
18 16
com.sap.sse.security.shared,
java/com.sap.sailing.aiagent/src/com/sap/sailing/aiagent/impl/AIAgentImpl.java
... ...
@@ -46,7 +46,7 @@ import com.sap.sse.shared.util.WeakValueCache;
46 46
public class AIAgentImpl implements AIAgent {
47 47
private static final Logger logger = Logger.getLogger(AIAgentImpl.class.getName());
48 48
49
- private static final String DEFAULT_MODEL_NAME = "gpt-4o-mini";
49
+ private static final String DEFAULT_MODEL_NAME = "o4-mini";
50 50
51 51
private static final String SAP_AI_CORE_TAG = "SAP AI Core on %s";
52 52
... ...
@@ -182,7 +182,7 @@ public class AIAgentImpl implements AIAgent {
182 182
chatSession
183 183
.addSystemPrompt(systemPrompt)
184 184
.addPrompt(prompt)
185
- .setTemperature(0.2)
185
+ .setTemperature(1.0)
186 186
.submit(response->{
187 187
try {
188 188
getRacingEventService().getTaggingService().addTag(leaderboardName, raceColumnName, fleetName,
java/com.sap.sailing.aiagent/src/com/sap/sailing/aiagent/impl/RaceListener.java
... ...
@@ -27,7 +27,7 @@ import com.sap.sailing.domain.common.tracking.GPSFix;
27 27
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
28 28
import com.sap.sailing.domain.common.tracking.SensorFix;
29 29
import com.sap.sailing.domain.leaderboard.Leaderboard;
30
-import com.sap.sailing.domain.tracking.AddResult;
30
+import com.sap.sailing.domain.shared.tracking.AddResult;
31 31
import com.sap.sailing.domain.tracking.DynamicSensorFixTrack;
32 32
import com.sap.sailing.domain.tracking.MarkPassing;
33 33
import com.sap.sailing.domain.tracking.RaceChangeListener;
java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/util/actions/startanalysis/StartAnalysisDTOFactory.java
... ...
@@ -22,7 +22,7 @@ import com.sap.sailing.domain.common.NoWindException;
22 22
import com.sap.sailing.domain.common.Position;
23 23
import com.sap.sailing.domain.common.Wind;
24 24
import com.sap.sailing.domain.common.racelog.RacingProcedureType;
25
-import com.sap.sailing.domain.tracking.LineDetails;
25
+import com.sap.sailing.domain.shared.tracking.LineDetails;
26 26
import com.sap.sailing.domain.tracking.MarkPassing;
27 27
import com.sap.sailing.domain.tracking.TrackedLeg;
28 28
import com.sap.sailing.domain.tracking.TrackedRace;
java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/util/actions/startlineadvantage/precalculation/StartlineAdvantageCalculationDataRetriever.java
... ...
@@ -15,8 +15,8 @@ import com.sap.sailing.domain.common.confidence.BearingWithConfidence;
15 15
import com.sap.sailing.domain.common.polars.NotEnoughDataHasBeenAddedException;
16 16
import com.sap.sailing.domain.common.tracking.GPSFix;
17 17
import com.sap.sailing.domain.polars.PolarDataService;
18
+import com.sap.sailing.domain.shared.tracking.LineDetails;
18 19
import com.sap.sailing.domain.tracking.GPSFixTrack;
19
-import com.sap.sailing.domain.tracking.LineDetails;
20 20
import com.sap.sailing.domain.tracking.TrackedRace;
21 21
import com.sap.sse.common.Speed;
22 22
import com.sap.sse.common.TimePoint;
java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/RaceOfCompetitorWithContext.java
... ...
@@ -29,15 +29,15 @@ import com.sap.sailing.domain.common.Tack;
29 29
import com.sap.sailing.domain.common.tracking.GPSFix;
30 30
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
31 31
import com.sap.sailing.domain.leaderboard.Leaderboard;
32
+import com.sap.sailing.domain.shared.tracking.LineDetails;
33
+import com.sap.sailing.domain.shared.tracking.impl.TimedComparator;
32 34
import com.sap.sailing.domain.tracking.GPSFixTrack;
33
-import com.sap.sailing.domain.tracking.LineDetails;
34 35
import com.sap.sailing.domain.tracking.Maneuver;
35 36
import com.sap.sailing.domain.tracking.MarkPassing;
36 37
import com.sap.sailing.domain.tracking.TrackedLeg;
37 38
import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor;
38 39
import com.sap.sailing.domain.tracking.TrackedRace;
39 40
import com.sap.sailing.domain.tracking.WindPositionMode;
40
-import com.sap.sailing.domain.tracking.impl.TimedComparator;
41 41
import com.sap.sse.common.Bearing;
42 42
import com.sap.sse.common.Distance;
43 43
import com.sap.sse.common.Duration;
java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/TrackedRaceWithContext.java
... ...
@@ -34,8 +34,8 @@ import com.sap.sailing.domain.common.NoWindException;
34 34
import com.sap.sailing.domain.common.Position;
35 35
import com.sap.sailing.domain.common.WindSource;
36 36
import com.sap.sailing.domain.common.WindSourceType;
37
-import com.sap.sailing.domain.tracking.LineDetails;
38
-import com.sap.sailing.domain.tracking.Track;
37
+import com.sap.sailing.domain.shared.tracking.LineDetails;
38
+import com.sap.sailing.domain.shared.tracking.Track;
39 39
import com.sap.sailing.domain.tracking.TrackedLeg;
40 40
import com.sap.sailing.domain.tracking.TrackedRace;
41 41
import com.sap.sailing.geocoding.ReverseGeocoder;
java/com.sap.sailing.declination.test/src/com/sap/sailing/declination/test/NOAADeclinationImportTest.java
... ...
@@ -1,9 +1,11 @@
1 1
package com.sap.sailing.declination.test;
2 2
3 3
import org.junit.jupiter.api.BeforeEach;
4
+import org.junit.jupiter.api.Disabled;
4 5
5 6
import com.sap.sailing.declination.impl.NOAAImporterForTesting;
6 7
8
+@Disabled("US Government Shutdown around 2025-10-01")
7 9
public class NOAADeclinationImportTest extends DeclinationImportTest<NOAAImporterForTesting> {
8 10
@BeforeEach
9 11
public void setUp() {
java/com.sap.sailing.declination.test/src/com/sap/sailing/declination/test/NOAADeclinationServiceTest.java
... ...
@@ -1,9 +1,11 @@
1 1
package com.sap.sailing.declination.test;
2 2
3 3
import org.junit.jupiter.api.BeforeEach;
4
+import org.junit.jupiter.api.Disabled;
4 5
5 6
import com.sap.sailing.declination.impl.NOAAImporter;
6 7
8
+@Disabled("US Government Shutdown around 2025-10-01")
7 9
public class NOAADeclinationServiceTest extends DeclinationServiceTest<NOAAImporter> {
8 10
@Override
9 11
@BeforeEach
java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/BoatClassMasterdata.java
... ...
@@ -31,6 +31,7 @@ public enum BoatClassMasterdata {
31 31
BAVARIA_CRUISER_46 ("Bavaria Cruiser 46", true, 14.27, 4.35, BoatHullType.MONOHULL, true, "B46", "B 46", "BAVARIACRUISER46"),
32 32
BB10M ("BB 10m", true, 10.00, 2.30, BoatHullType.MONOHULL, true, "Dansk BB10M klub"),
33 33
BENETEAU_FIRST_35 ("Benetau First 35", true, 10.66, 3.636, BoatHullType.MONOHULL, true, "First 35"),
34
+ BENETEAU_FIRST_36 ("Benetau First 36", true, 11.98, 3.80, BoatHullType.MONOHULL, true, "First 36"),
34 35
BENETEAU_FIRST_45 ("Benetau First 45", true, 13.68, 4.202, BoatHullType.MONOHULL, true, "First 45"),
35 36
BRASSFAHRT_I ("Brassfahrt I", true, 12.00, 3.50, BoatHullType.MONOHULL, true, "Brassfahrt 1"),
36 37
BRASSFAHRT_II ("Brassfahrt II", true, 12.00, 3.50, BoatHullType.MONOHULL, true, "Brassfahrt 2"),
... ...
@@ -49,6 +50,7 @@ public enum BoatClassMasterdata {
49 50
DELPHIA_24 ("Delphia 24", true, 7.70, 2.50, BoatHullType.MONOHULL, true, "Delphia 24 One Design", "Delphia 24 OD"),
50 51
DYAS("Dyas", true, 7.15, 1.95, BoatHullType.MONOHULL, true),
51 52
ELAN350("Elan 350", true, 10.6, 3.5, BoatHullType.MONOHULL, true, "Elan 350 Performance"),
53
+ ELAN_E4("Elan E4", true, 10.6, 3.5, BoatHullType.MONOHULL, true, "Elan E4"),
52 54
EXTREME_40 ("Extreme 40", false, 12.2, 6.60, BoatHullType.CATAMARAN, true, "Extreme-40", "Extreme40", "ESS40", "ess"),
53 55
D_35 ("D35", false, 10.81, 6.89, BoatHullType.CATAMARAN, false),
54 56
ELLIOTT_6M ("Elliott 6m", true, 6.0, 2.35, BoatHullType.MONOHULL, true, "Elliott6m"),
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.test/src/com/sap/sailing/domain/igtimiadapter/test/IgtimiFixTrackTest.java
... ...
@@ -23,8 +23,8 @@ import com.sap.sailing.domain.igtimiadapter.datatypes.AWS;
23 23
import com.sap.sailing.domain.igtimiadapter.datatypes.Fix;
24 24
import com.sap.sailing.domain.igtimiadapter.datatypes.HDGM;
25 25
import com.sap.sailing.domain.igtimiadapter.datatypes.Type;
26
+import com.sap.sailing.domain.shared.tracking.Track;
26 27
import com.sap.sailing.domain.tracking.DynamicTrack;
27
-import com.sap.sailing.domain.tracking.Track;
28 28
import com.sap.sse.common.TimePoint;
29 29
import com.sap.sse.common.Util;
30 30
import com.sap.sse.common.impl.DegreeBearingImpl;
java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/IgtimiConnection.java
... ...
@@ -13,9 +13,9 @@ import org.json.simple.parser.ParseException;
13 13
import com.igtimi.IgtimiStream.Msg;
14 14
import com.sap.sailing.domain.igtimiadapter.datatypes.Fix;
15 15
import com.sap.sailing.domain.igtimiadapter.datatypes.Type;
16
+import com.sap.sailing.domain.shared.tracking.Track;
16 17
import com.sap.sailing.domain.tracking.DynamicTrack;
17 18
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
18
-import com.sap.sailing.domain.tracking.Track;
19 19
import com.sap.sailing.domain.tracking.TrackedRace;
20 20
import com.sap.sse.common.TimePoint;
21 21
import com.sap.sse.security.shared.HasPermissions;
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.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/AbstractGPSFixStoreTest.java
... ...
@@ -45,8 +45,8 @@ import com.sap.sailing.domain.racelog.tracking.test.mock.MockSmartphoneImeiServi
45 45
import com.sap.sailing.domain.racelogtracking.impl.SmartphoneImeiIdentifierImpl;
46 46
import com.sap.sailing.domain.ranking.OneDesignRankingMetric;
47 47
import com.sap.sailing.domain.regattalog.impl.EmptyRegattaLogStore;
48
+import com.sap.sailing.domain.shared.tracking.Track;
48 49
import com.sap.sailing.domain.tracking.DynamicTrackedRegatta;
49
-import com.sap.sailing.domain.tracking.Track;
50 50
import com.sap.sailing.domain.tracking.impl.DynamicTrackedRaceImpl;
51 51
import com.sap.sailing.domain.tracking.impl.DynamicTrackedRegattaImpl;
52 52
import com.sap.sailing.domain.tracking.impl.EmptyWindStore;
java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/CreateAndTrackWithRaceLogTest.java
... ...
@@ -68,10 +68,10 @@ import com.sap.sailing.domain.racelogtracking.impl.SmartphoneImeiIdentifierImpl;
68 68
import com.sap.sailing.domain.racelogtracking.impl.fixtracker.RaceLogFixTrackerManager;
69 69
import com.sap.sailing.domain.racelogtracking.test.RaceLogTrackingTestHelper;
70 70
import com.sap.sailing.domain.ranking.OneDesignRankingMetric;
71
+import com.sap.sailing.domain.shared.tracking.Track;
71 72
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
72 73
import com.sap.sailing.domain.tracking.RaceHandle;
73 74
import com.sap.sailing.domain.tracking.RaceTrackingHandler.DefaultRaceTrackingHandler;
74
-import com.sap.sailing.domain.tracking.Track;
75 75
import com.sap.sailing.domain.tracking.TrackedRace;
76 76
import com.sap.sailing.domain.tracking.impl.AbstractRaceChangeListener;
77 77
import com.sap.sailing.server.impl.RacingEventServiceImpl;
java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/SensorFixStoreAndLoadTest.java
... ...
@@ -88,13 +88,13 @@ import com.sap.sailing.domain.racelogtracking.impl.SmartphoneImeiIdentifierImpl;
88 88
import com.sap.sailing.domain.racelogtracking.impl.fixtracker.FixLoaderAndTracker;
89 89
import com.sap.sailing.domain.ranking.OneDesignRankingMetric;
90 90
import com.sap.sailing.domain.regattalog.impl.EmptyRegattaLogStore;
91
+import com.sap.sailing.domain.shared.tracking.Track;
91 92
import com.sap.sailing.domain.tracking.BravoFixTrack;
92 93
import com.sap.sailing.domain.tracking.DynamicGPSFixTrack;
93 94
import com.sap.sailing.domain.tracking.DynamicSensorFixTrack;
94 95
import com.sap.sailing.domain.tracking.DynamicTrack;
95 96
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
96 97
import com.sap.sailing.domain.tracking.DynamicTrackedRegatta;
97
-import com.sap.sailing.domain.tracking.Track;
98 98
import com.sap.sailing.domain.tracking.TrackedRaceStatus;
99 99
import com.sap.sailing.domain.tracking.impl.AbstractRaceChangeListener;
100 100
import com.sap.sailing.domain.tracking.impl.DynamicTrackedRaceImpl;
java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/TrackedRaceLoadsFixesTest.java
... ...
@@ -38,7 +38,7 @@ import com.sap.sailing.domain.common.tracking.GPSFixMoving;
38 38
import com.sap.sailing.domain.racelogtracking.impl.SmartphoneImeiIdentifierImpl;
39 39
import com.sap.sailing.domain.racelogtracking.impl.fixtracker.FixLoaderAndTracker;
40 40
import com.sap.sailing.domain.racelogtracking.test.AbstractGPSFixStoreTest;
41
-import com.sap.sailing.domain.tracking.AddResult;
41
+import com.sap.sailing.domain.shared.tracking.AddResult;
42 42
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
43 43
import com.sap.sailing.domain.tracking.GPSTrackListener;
44 44
import com.sap.sailing.domain.tracking.impl.DynamicTrackedRaceImpl;
java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogRaceTracker.java
... ...
@@ -64,6 +64,7 @@ import com.sap.sailing.domain.markpassinghash.MarkPassingRaceFingerprintRegistry
64 64
import com.sap.sailing.domain.racelog.RaceLogAndTrackedRaceResolver;
65 65
import com.sap.sailing.domain.racelogtracking.RaceLogTrackingAdapter;
66 66
import com.sap.sailing.domain.regattalike.IsRegattaLike;
67
+import com.sap.sailing.domain.shared.tracking.impl.TrackingConnectorInfoImpl;
67 68
import com.sap.sailing.domain.tracking.AbstractRaceTrackerBaseImpl;
68 69
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
69 70
import com.sap.sailing.domain.tracking.DynamicTrackedRegatta;
... ...
@@ -73,7 +74,6 @@ import com.sap.sailing.domain.tracking.TrackedRace;
73 74
import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
74 75
import com.sap.sailing.domain.tracking.WindStore;
75 76
import com.sap.sailing.domain.tracking.WindTrack;
76
-import com.sap.sailing.domain.tracking.impl.TrackingConnectorInfoImpl;
77 77
import com.sap.sse.common.Util;
78 78
import com.sap.sse.common.Util.Pair;
79 79
import com.sap.sse.common.impl.MillisecondsTimePoint;
java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/fixtracker/FixLoaderAndTracker.java
... ...
@@ -44,19 +44,19 @@ import com.sap.sailing.domain.racelog.tracking.SensorFixMapper;
44 44
import com.sap.sailing.domain.racelog.tracking.SensorFixStore;
45 45
import com.sap.sailing.domain.racelogsensortracking.SensorFixMapperFactory;
46 46
import com.sap.sailing.domain.racelogtracking.DeviceMappingWithRegattaLogEvent;
47
+import com.sap.sailing.domain.shared.tracking.Track;
48
+import com.sap.sailing.domain.shared.tracking.impl.TimedComparator;
47 49
import com.sap.sailing.domain.tracking.DynamicGPSFixTrack;
48 50
import com.sap.sailing.domain.tracking.DynamicSensorFixTrack;
49 51
import com.sap.sailing.domain.tracking.DynamicTrack;
50 52
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
51 53
import com.sap.sailing.domain.tracking.Maneuver;
52 54
import com.sap.sailing.domain.tracking.RaceChangeListener;
53
-import com.sap.sailing.domain.tracking.Track;
54 55
import com.sap.sailing.domain.tracking.TrackedRace;
55 56
import com.sap.sailing.domain.tracking.TrackingDataLoader;
56 57
import com.sap.sailing.domain.tracking.impl.AbstractRaceChangeListener;
57 58
import com.sap.sailing.domain.tracking.impl.DynamicGPSFixMovingTrackImpl;
58 59
import com.sap.sailing.domain.tracking.impl.OutlierFilter;
59
-import com.sap.sailing.domain.tracking.impl.TimedComparator;
60 60
import com.sap.sailing.domain.tracking.impl.TrackedRaceStatusImpl;
61 61
import com.sap.sse.common.Duration;
62 62
import com.sap.sse.common.MultiTimeRange;
java/com.sap.sailing.domain.shared.android/META-INF/MANIFEST.MF
... ...
@@ -55,9 +55,9 @@ Export-Package: com.sap.sailing.domain.abstractlog,
55 55
com.sap.sailing.domain.racelogtracking,
56 56
com.sap.sailing.domain.racelogtracking.impl,
57 57
com.sap.sailing.domain.resultimport,
58
+ com.sap.sailing.domain.shared.tracking,
59
+ com.sap.sailing.domain.shared.tracking.impl,
58 60
com.sap.sailing.domain.statistics,
59
- com.sap.sailing.domain.statistics.impl,
60
- com.sap.sailing.domain.tracking,
61
- com.sap.sailing.domain.tracking.impl
61
+ com.sap.sailing.domain.statistics.impl
62 62
Automatic-Module-Name: com.sap.sailing.domain.shared.android
63 63
Import-Package: com.sap.sse.security.shared
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/AbstractLog.java
... ...
@@ -7,7 +7,7 @@ import java.util.NavigableSet;
7 7
import java.util.UUID;
8 8
9 9
import com.sap.sailing.domain.common.abstractlog.NotRevokableException;
10
-import com.sap.sailing.domain.tracking.Track;
10
+import com.sap.sailing.domain.shared.tracking.Track;
11 11
import com.sap.sse.common.TimePoint;
12 12
import com.sap.sse.common.WithID;
13 13
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/impl/AbstractLogImpl.java
... ...
@@ -24,9 +24,9 @@ import com.sap.sailing.domain.abstractlog.race.RaceLogEvent;
24 24
import com.sap.sailing.domain.abstractlog.race.impl.RaceLogEventComparator;
25 25
import com.sap.sailing.domain.abstractlog.race.impl.RaceLogImpl;
26 26
import com.sap.sailing.domain.common.abstractlog.NotRevokableException;
27
-import com.sap.sailing.domain.tracking.Track;
28
-import com.sap.sailing.domain.tracking.impl.PartialNavigableSetView;
29
-import com.sap.sailing.domain.tracking.impl.TrackImpl;
27
+import com.sap.sailing.domain.shared.tracking.Track;
28
+import com.sap.sailing.domain.shared.tracking.impl.PartialNavigableSetView;
29
+import com.sap.sailing.domain.shared.tracking.impl.TrackImpl;
30 30
import com.sap.sse.common.Timed;
31 31
import com.sap.sse.common.Util;
32 32
import com.sap.sse.shared.util.impl.ArrayListNavigableSet;
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/impl/LogEventComparator.java
... ...
@@ -6,7 +6,7 @@ import java.util.Comparator;
6 6
import com.sap.sailing.domain.abstractlog.AbstractLogEvent;
7 7
import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor;
8 8
import com.sap.sailing.domain.abstractlog.race.RaceLogEvent;
9
-import com.sap.sailing.domain.tracking.impl.TimedComparator;
9
+import com.sap.sailing.domain.shared.tracking.impl.TimedComparator;
10 10
import com.sap.sse.common.Timed;
11 11
import com.sap.sse.common.Util;
12 12
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/analyzing/impl/RaceLogAnalyzer.java
... ...
@@ -7,7 +7,7 @@ import com.sap.sailing.domain.abstractlog.impl.AbstractLogImpl;
7 7
import com.sap.sailing.domain.abstractlog.race.RaceLog;
8 8
import com.sap.sailing.domain.abstractlog.race.RaceLogEvent;
9 9
import com.sap.sailing.domain.abstractlog.race.RaceLogEventVisitor;
10
-import com.sap.sailing.domain.tracking.Track;
10
+import com.sap.sailing.domain.shared.tracking.Track;
11 11
12 12
public abstract class RaceLogAnalyzer<ResultType> extends BaseLogAnalyzer
13 13
<RaceLog, RaceLogEvent, RaceLogEventVisitor, ResultType> {
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/impl/NoAddingRaceLogWrapper.java
... ...
@@ -16,7 +16,7 @@ import com.sap.sailing.domain.abstractlog.race.RaceLog;
16 16
import com.sap.sailing.domain.abstractlog.race.RaceLogEvent;
17 17
import com.sap.sailing.domain.abstractlog.race.RaceLogEventVisitor;
18 18
import com.sap.sailing.domain.common.abstractlog.NotRevokableException;
19
-import com.sap.sailing.domain.tracking.impl.TimeRangeCache;
19
+import com.sap.sailing.domain.shared.tracking.impl.TimeRangeCache;
20 20
import com.sap.sse.common.Duration;
21 21
import com.sap.sse.common.TimePoint;
22 22
import com.sap.sse.common.scalablevalue.ScalableValue;
... ...
@@ -273,8 +273,8 @@ public class NoAddingRaceLogWrapper implements RaceLog {
273 273
274 274
@Override
275 275
public <T> T getValueSum(TimePoint from, TimePoint to, T nullElement,
276
- com.sap.sailing.domain.tracking.Track.Adder<T> adder, TimeRangeCache<T> cache,
277
- com.sap.sailing.domain.tracking.Track.TimeRangeValueCalculator<T> valueCalculator) {
276
+ com.sap.sailing.domain.shared.tracking.Track.Adder<T> adder, TimeRangeCache<T> cache,
277
+ com.sap.sailing.domain.shared.tracking.Track.TimeRangeValueCalculator<T> valueCalculator) {
278 278
return innerRaceLog.getValueSum(from, to, nullElement, adder, cache, valueCalculator);
279 279
}
280 280
}
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/impl/RaceLogImpl.java
... ...
@@ -8,8 +8,8 @@ import com.sap.sailing.domain.abstractlog.impl.AbstractLogImpl;
8 8
import com.sap.sailing.domain.abstractlog.race.RaceLog;
9 9
import com.sap.sailing.domain.abstractlog.race.RaceLogEvent;
10 10
import com.sap.sailing.domain.abstractlog.race.RaceLogEventVisitor;
11
-import com.sap.sailing.domain.tracking.Track;
12
-import com.sap.sailing.domain.tracking.impl.TrackImpl;
11
+import com.sap.sailing.domain.shared.tracking.Track;
12
+import com.sap.sailing.domain.shared.tracking.impl.TrackImpl;
13 13
14 14
/**
15 15
* {@link Track} implementation for {@link RaceLogEvent}s.
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/EventBase.java
... ...
@@ -5,7 +5,7 @@ import java.util.Locale;
5 5
import java.util.Map;
6 6
import java.util.Set;
7 7
8
-import com.sap.sailing.domain.tracking.TrackingConnectorInfo;
8
+import com.sap.sailing.domain.shared.tracking.TrackingConnectorInfo;
9 9
import com.sap.sse.common.NamedWithID;
10 10
import com.sap.sse.common.Renamable;
11 11
import com.sap.sse.common.TimePoint;
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/StrippedEventImpl.java
... ...
@@ -6,7 +6,7 @@ import java.util.UUID;
6 6
import com.sap.sailing.domain.base.EventBase;
7 7
import com.sap.sailing.domain.base.LeaderboardGroupBase;
8 8
import com.sap.sailing.domain.base.Venue;
9
-import com.sap.sailing.domain.tracking.TrackingConnectorInfo;
9
+import com.sap.sailing.domain.shared.tracking.TrackingConnectorInfo;
10 10
import com.sap.sse.common.TimePoint;
11 11
12 12
/**
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/shared/tracking/AddResult.java
... ...
@@ -0,0 +1,14 @@
1
+package com.sap.sailing.domain.shared.tracking;
2
+
3
+import javax.swing.plaf.basic.BasicSliderUI.TrackListener;
4
+
5
+/**
6
+ * The result of trying to add a fix to a {@link Track}. Used when notifying {@link TrackListener}s.
7
+ * This allows listeners, in particular, to distinguish between the add and replace scenario.
8
+ *
9
+ * @author Axel Uhl (D043530)
10
+ *
11
+ */
12
+public enum AddResult {
13
+ NOT_ADDED, ADDED, REPLACED;
14
+}
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/shared/tracking/FixAcceptancePredicate.java
... ...
@@ -0,0 +1,14 @@
1
+package com.sap.sailing.domain.shared.tracking;
2
+
3
+/**
4
+ * A predicate for a fix, for use in
5
+ * {@link Track#getInterpolatedValue(com.sap.sse.common.TimePoint, com.sap.sse.common.Util.Function)}, used, e.g., to
6
+ * provide a rule for when a fix shall be accepted during the search for surrounding fixes.
7
+ *
8
+ * @author Axel Uhl (d043530)
9
+ *
10
+ * @param <FixType>
11
+ */
12
+public interface FixAcceptancePredicate<FixType> {
13
+ boolean isAcceptFix(FixType fix);
14
+}
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/shared/tracking/LineDetails.java
... ...
@@ -0,0 +1,57 @@
1
+package com.sap.sailing.domain.shared.tracking;
2
+
3
+import com.sap.sailing.domain.base.Mark;
4
+import com.sap.sailing.domain.base.Waypoint;
5
+import com.sap.sailing.domain.common.NauticalSide;
6
+import com.sap.sailing.domain.common.Position;
7
+import com.sap.sse.common.Bearing;
8
+import com.sap.sse.common.Distance;
9
+import com.sap.sse.common.TimePoint;
10
+
11
+/**
12
+ * For a line such as a start or a finish line, tells the line's length at a given time, which side is
13
+ * {@link NauticalSide#PORT port} and which is {@link NauticalSide#STARBOARD starboard} when approaching the line,
14
+ * and---if wind information is available---its angle to a true wind direction and the advantageous side in approaching
15
+ * direction as well as how much the advantageous side is ahead. The wind-dependent information
16
+ * will all be <code>null</code> if no wind data is available.
17
+ *
18
+ * @author Axel Uhl (d043530)
19
+ *
20
+ */
21
+public interface LineDetails {
22
+ TimePoint getTimePoint();
23
+
24
+ Waypoint getWaypoint();
25
+
26
+ Distance getLength();
27
+
28
+ Bearing getAngleDifferenceFromPortToStarboardWhenApproachingLineToTrueWind();
29
+
30
+ NauticalSide getAdvantageousSideWhileApproachingLine();
31
+
32
+ Mark getStarboardMarkWhileApproachingLine();
33
+
34
+ Mark getPortMarkWhileApproachingLine();
35
+
36
+ Distance getAdvantage();
37
+
38
+ Position getPortMarkPosition();
39
+
40
+ Position getStarboardMarkPosition();
41
+
42
+ default Mark getAdvantageousMark() {
43
+ return getAdvantageousSideWhileApproachingLine() == NauticalSide.PORT ? getPortMarkWhileApproachingLine() : getStarboardMarkWhileApproachingLine();
44
+ }
45
+
46
+ default Position getAdvantageousMarkPosition() {
47
+ return getAdvantageousSideWhileApproachingLine() == NauticalSide.PORT ? getPortMarkPosition() : getStarboardMarkPosition();
48
+ }
49
+
50
+ default Bearing getBearingFromStarboardToPortWhenApproachingLine() {
51
+ return getStarboardMarkPosition().getBearingGreatCircle(getPortMarkPosition());
52
+ }
53
+
54
+ default Bearing getBearingFromPortToStarboardWhenApproachingLine() {
55
+ return getPortMarkPosition().getBearingGreatCircle(getStarboardMarkPosition());
56
+ }
57
+}
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/shared/tracking/MappedTrack.java
... ...
@@ -0,0 +1,20 @@
1
+package com.sap.sailing.domain.shared.tracking;
2
+
3
+import com.sap.sse.common.Timed;
4
+
5
+/**
6
+ * {@link Track} specialization, which is mapped to a specific type of items.
7
+ *
8
+ * @param <ItemType>
9
+ * the type of item this track is mapped to
10
+ * @param <FixType>
11
+ * the type of fix that is contained in this track
12
+ */
13
+public interface MappedTrack<ItemType, FixType extends Timed> extends Track<FixType> {
14
+
15
+ /**
16
+ * @return the item this track is mapped to.
17
+ */
18
+ ItemType getTrackedItem();
19
+
20
+}
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/shared/tracking/Track.java
... ...
@@ -0,0 +1,259 @@
1
+package com.sap.sailing.domain.shared.tracking;
2
+
3
+import java.io.Serializable;
4
+import java.util.Iterator;
5
+import java.util.concurrent.locks.ReadWriteLock;
6
+import java.util.function.Function;
7
+
8
+import com.sap.sailing.domain.common.tracking.GPSFixMoving;
9
+import com.sap.sailing.domain.shared.tracking.impl.TimeRangeCache;
10
+import com.sap.sse.common.Duration;
11
+import com.sap.sse.common.TimePoint;
12
+import com.sap.sse.common.Timed;
13
+import com.sap.sse.common.scalablevalue.ScalableValue;
14
+
15
+/**
16
+ * A track records {@link Timed} items for an object of type <code>ItemType</code>. It allows clients to ask for a value
17
+ * close to a given {@link TimePoint}. The track manages a time-based set of raw fixes. An implementation may have an
18
+ * understanding of how to eliminate outliers. For example, if a track implementation knows it's tracking boats, it may
19
+ * consider fixes that the boat cannot possibly have reached due to its speed and direction change limitations as
20
+ * outliers. The set of fixes with outliers filtered out can be obtained using {@link #getFixes} whereas
21
+ * {@link #getRawFixes()} returns the unfiltered, raw fixes. If an implementation has no idea what an outlier is,
22
+ * both methods will return the same fix sequence.<p>
23
+ *
24
+ * With tracks, concurrency is an important issue. Threads may want to modify a track while other threads may want to
25
+ * read from it. Several methods such as {@link #getLastFixAtOrBefore(TimePoint)} return a single fix and can manage
26
+ * concurrency internally. However, those methods returning a collection of fixes, such as {@link #getFixes()} or an
27
+ * iterator over a collection of fixes, such as {@link #getFixesIterator(TimePoint, boolean)}, need special treatment.
28
+ * Until we internalize such iterations (see bug 824, http://bugzilla.sapsailing.com/bugzilla/show_bug.cgi?id=824),
29
+ * callers need to manage a read lock which is part of a {@link ReadWriteLock} managed by this track. Callers do so
30
+ * by calling {@link #lockForRead} and {@link #unlockAfterRead}.
31
+ *
32
+ * @author Axel Uhl (d043530)
33
+ */
34
+public interface Track<FixType extends Timed> extends Serializable {
35
+ /**
36
+ * An adding function to be used together with {@link Track#getValueSum(TimePoint, TimePoint, Object, Adder, TimeRangeCache, TimeRangeValueCalculator)}.
37
+ *
38
+ * @author Axel Uhl (D043530)
39
+ *
40
+ * @param <T>
41
+ */
42
+ static interface Adder<T> {
43
+ /**
44
+ * Adds two elements of type {@code T}. Neither argument must be {@code null}.
45
+ */
46
+ T add(T t1, T t2);
47
+ }
48
+
49
+ static interface TimeRangeValueCalculator<T> {
50
+ /**
51
+ * Calculates a value for fixes across a time range. When the method is called,
52
+ * a read lock will previously have been {@link Track#lockForRead obtained} before,
53
+ * so an implementing class does not need to worry about acquiring the lock.
54
+ */
55
+ T calculate(TimePoint from, TimePoint to);
56
+ }
57
+
58
+ /**
59
+ * Locks this track for reading by the calling thread. If the thread already holds the lock for this track,
60
+ * the hold count will be incremented. Make sure to call {@link #unlockAfterRead()} in a <code>finally</code>
61
+ * block to release the lock under all possible circumstances. Failure to do so will inevitably lead to
62
+ * deadlocks!
63
+ */
64
+ void lockForRead();
65
+
66
+ /**
67
+ * Decrements the hold count for this track's read lock for the calling thread. If it goes to zero, the lock will be
68
+ * released and other readers or a writer can obtain the lock. Make sure to call this method in a
69
+ * <code>finally</code> block for each {@link #lockForRead()} invocation.
70
+ */
71
+ void unlockAfterRead();
72
+
73
+ /**
74
+ * Callers must have called {@link #lockForRead()} before calling this method. This will be checked, and an exception
75
+ * will be thrown in case the caller has failed to do so.
76
+ *
77
+ * @return the smoothened fixes
78
+ */
79
+ Iterable<FixType> getFixes();
80
+
81
+ /**
82
+ * Callers must have called {@link #lockForRead()} before calling this method. This will be checked, and an exception
83
+ * will be thrown in case the caller has failed to do so.
84
+ *
85
+ * @return The smoothened fixes between from and to.
86
+ */
87
+ Iterable<FixType> getFixes(TimePoint from, boolean fromInclusive, TimePoint to, boolean toInclusive);
88
+
89
+ /**
90
+ * Callers must have called {@link #lockForRead()} before calling this method. This will be checked, and an exception
91
+ * will be thrown in case the caller has failed to do so.
92
+ */
93
+ Iterable<FixType> getRawFixes();
94
+
95
+ /**
96
+ * Returns <code>null</code> if no such fix exists.
97
+ */
98
+ FixType getLastFixAtOrBefore(TimePoint timePoint);
99
+
100
+ /**
101
+ * Returns <code>null</code> if no such fix exists.
102
+ */
103
+ FixType getLastFixBefore(TimePoint timePoint);
104
+
105
+ /**
106
+ * Returns <code>null</code> if no such fix exists.
107
+ */
108
+ FixType getLastRawFixAtOrBefore(TimePoint timePoint);
109
+
110
+ /**
111
+ * Returns <code>null</code> if no such fix exists.
112
+ */
113
+ FixType getFirstFixAtOrAfter(TimePoint timePoint);
114
+
115
+ /**
116
+ * Returns <code>null</code> if no such fix exists.
117
+ */
118
+ FixType getFirstRawFixAtOrAfter(TimePoint timePoint);
119
+
120
+ /**
121
+ * Returns <code>null</code> if no such fix exists.
122
+ */
123
+ FixType getLastRawFixBefore(TimePoint timePoint);
124
+
125
+ /**
126
+ * Returns <code>null</code> if no such fix exists.
127
+ */
128
+ FixType getFirstRawFixAfter(TimePoint timePoint);
129
+
130
+ /**
131
+ * Returns <code>null</code> if no such fix exists.
132
+ */
133
+ FixType getFirstFixAfter(TimePoint timePoint);
134
+
135
+ /**
136
+ * The first fix in this track or <code>null</code> if the track is empty. The fix returned may
137
+ * be an outlier that is not returned by calls operating on the smoothened version of the track.
138
+ */
139
+ FixType getFirstRawFix();
140
+
141
+ /**
142
+ * The last fix in this track or <code>null</code> if the track is empty. The fix returned may
143
+ * be an outlier that is not returned by calls operating on the smoothened version of the track.
144
+ */
145
+ FixType getLastRawFix();
146
+
147
+ /**
148
+ * Interpolates an aspect of the fixes in this track for a given {@code timePoint}. If {@code timePoint} matches
149
+ * exactly a fix in this track, that fix is used. If this track is empty, {@code null} is returned. If the
150
+ * {@code timePoint} is after the last fix of this track, the last fix is used; if before the first fix, the first
151
+ * fix is used.
152
+ * <p>
153
+ *
154
+ * The fix(es) are converted to {@link ScalableValue}s using the {@code converter} which gives callers a choice
155
+ * which aspect of the fixes to project and interpolate. If more than one value results because two fixes (one
156
+ * before, one after) are used, linear interpolation based on the fixes' time points takes place.
157
+ * <p>
158
+ *
159
+ * Example: for a track of {@link GPSFixMoving} fixes the course over ground shall be determined for a given time
160
+ * point. The call would look like this:
161
+ * {@code getInterpolatedValue(timePoint, f->new ScalableBearing(f.getSpeed().getBearing()))}
162
+ *
163
+ * @return the projected interpolated value, typed by what the {@link ScalableValue#divide(double)} method returns.
164
+ */
165
+ <InternalType, ValueType> ValueType getInterpolatedValue(TimePoint timePoint,
166
+ Function<FixType, ScalableValue<InternalType, ValueType>> converter);
167
+
168
+ /**
169
+ * Returns an iterator starting at the first fix after <code>startingAt</code> (or "at or after" in case
170
+ * <code>inclusive</code> is <code>true</code>). The fixes returned by the iterator exclude outliers (see
171
+ * also {@link #getFixes()} and returns the remaining fixes without any smoothening or dampening applied.
172
+ *
173
+ * Callers must have called {@link #lockForRead()} before calling this method. This will be checked, and an exception
174
+ * will be thrown in case the caller has failed to do so.
175
+ */
176
+ Iterator<FixType> getFixesIterator(TimePoint startingAt, boolean inclusive);
177
+
178
+ /**
179
+ * Returns an iterator starting at the first fix after <code>startingAt</code> (or "at or after" in case
180
+ * <code>inclusive</code> is <code>true</code>) and that ends at the <code>endingAt</code> time point or just before
181
+ * in case <code>endingAtIncluive</code> is false. The fixes returned by the iterator are the smoothened fixes (see
182
+ * also {@link #getFixes()}, without any smoothening or dampening applied.
183
+ *
184
+ * Callers must have called {@link #lockForRead()} before calling this method. This will be checked, and an
185
+ * exception will be thrown in case the caller has failed to do so.
186
+ *
187
+ * @param startingAt
188
+ * if <code>null</code>, starts with the first fix available
189
+ * @param endingAt
190
+ * if <code>null</code>., ends with the last fix available
191
+ */
192
+ Iterator<FixType> getFixesIterator(TimePoint startingAt, boolean startingAtInclusive, TimePoint endingAt, boolean endingAtInclusive);
193
+
194
+ /**
195
+ * Returns an iterator starting at the first raw fix after <code>startingAt</code> (or "at or after" in case
196
+ * <code>inclusive</code> is <code>true</code>). The fixes returned by the iterator are the raw fixes (see also
197
+ * {@link #getRawFixes()}, without any smoothening or dampening applied.
198
+ *
199
+ * Callers must have called {@link #lockForRead()} before calling this method. This will be checked, and an exception
200
+ * will be thrown in case the caller has failed to do so.
201
+ */
202
+ Iterator<FixType> getRawFixesIterator(TimePoint startingAt, boolean inclusive);
203
+
204
+ /**
205
+ * Returns an iterator starting at the first raw fix after <code>startingAt</code> (or "at or after" in case
206
+ * <code>startingAtInclusive</code> is <code>true</code>) and ending at the <code>endingAt</code> time point or just before
207
+ * in case <code>endingAtIncluive</code> is false. The fixes returned by the iterator are the raw fixes (see also
208
+ * {@link #getRawFixes()}, without any smoothening or dampening applied.
209
+ *
210
+ * Callers must have called {@link #lockForRead()} before calling this method. This will be checked, and an exception
211
+ * will be thrown in case the caller has failed to do so.
212
+ */
213
+ Iterator<FixType> getRawFixesIterator(TimePoint startingAt, boolean startingAtInclusive, TimePoint endingAt, boolean endingAtInclusive);
214
+
215
+ /**
216
+ * Returns a descending iterator starting at the first fix before <code>startingAt</code> (or "at or before" in case
217
+ * <code>inclusive</code> is <code>true</code>). The fixes returned by the iterator are the smoothened fixes (see
218
+ * also {@link #getFixes()}, without any smoothening or dampening applied.
219
+ *
220
+ * Callers must have called {@link #lockForRead()} before calling this method. This will be checked, and an exception
221
+ * will be thrown in case the caller has failed to do so.
222
+ */
223
+ Iterator<FixType> getFixesDescendingIterator(TimePoint startingAt, boolean inclusive);
224
+
225
+ /**
226
+ * Returns a descending iterator starting at the first raw fix before <code>startingAt</code> (or "at or before" in case
227
+ * <code>inclusive</code> is <code>true</code>). The fixes returned by the iterator are the raw fixes (see also
228
+ * {@link #getRawFixes()}, without any smoothening or dampening applied.
229
+ *
230
+ * Callers must have called {@link #lockForRead()} before calling this method. This will be checked, and an exception
231
+ * will be thrown in case the caller has failed to do so.
232
+ */
233
+ Iterator<FixType> getRawFixesDescendingIterator(TimePoint startingAt, boolean inclusive);
234
+
235
+ /**
236
+ * @return the average duration between two fixes (outliers removed) in this track or <code>null</code> if there is not
237
+ * more than one fix in the track
238
+ */
239
+ Duration getAverageIntervalBetweenFixes();
240
+
241
+ /**
242
+ * @return the average duration between two fixes (outliers <em>not</em> removed) in this track or <code>null</code> if there is not
243
+ * more than one raw fix in the track
244
+ */
245
+ Duration getAverageIntervalBetweenRawFixes();
246
+
247
+ <T> T getValueSum(TimePoint from, TimePoint to, T nullElement, Adder<T> adder, TimeRangeCache<T> cache, TimeRangeValueCalculator<T> valueCalculator);
248
+
249
+ /**
250
+ * @return the number of raw fixes contained in the Track.
251
+ */
252
+ int size();
253
+
254
+ /**
255
+ * Tells whether the collection of {@link #getRawFixes() raw fixes} (no outliers removed) is empty
256
+ */
257
+ boolean isEmpty();
258
+
259
+}
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/shared/tracking/TrackingConnectorInfo.java
... ...
@@ -0,0 +1,28 @@
1
+
2
+package com.sap.sailing.domain.shared.tracking;
3
+
4
+import java.io.Serializable;
5
+
6
+/**
7
+ * Identifies the tracking connector that was used to create a TrackedRace.
8
+ * Further the Connector can provide a webUrl, that leads to an event web page.
9
+ */
10
+public interface TrackingConnectorInfo extends Serializable {
11
+
12
+ /**
13
+ * gets the name associated with the tracking technology used for the Race
14
+ */
15
+ String getTrackingConnectorName();
16
+
17
+ /**
18
+ * gets a {@link String} representation of the default web-URL associated with the tracking technology used for the Race.
19
+ * may be {@code null} if there is none provided in the adapter.
20
+ */
21
+ String getTrackingConnectorDefaultUrl();
22
+
23
+ /**
24
+ * gets a {@link String} representation of the web-URL associated with the Event.
25
+ * may be {@code null} if the API of the respective Tracking-Service does not provide a URL.
26
+ */
27
+ String getWebUrl();
28
+}
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/shared/tracking/impl/LineDetailsImpl.java
... ...
@@ -0,0 +1,91 @@
1
+package com.sap.sailing.domain.shared.tracking.impl;
2
+
3
+import com.sap.sailing.domain.base.Mark;
4
+import com.sap.sailing.domain.base.Waypoint;
5
+import com.sap.sailing.domain.common.NauticalSide;
6
+import com.sap.sailing.domain.common.Position;
7
+import com.sap.sailing.domain.shared.tracking.LineDetails;
8
+import com.sap.sse.common.Bearing;
9
+import com.sap.sse.common.Distance;
10
+import com.sap.sse.common.TimePoint;
11
+
12
+public class LineDetailsImpl implements LineDetails {
13
+ private final TimePoint timePoint;
14
+ private final Waypoint waypoint;
15
+ private final Distance length;
16
+ private final Bearing angleDifferenceFromPortToStarboardWhenApproachingLineToTrueWind;
17
+ private final NauticalSide advantageousSidewhileApproachingLine;
18
+ private final Distance advantage;
19
+ private final Mark portMarkWhileApproachingLine;
20
+ private final Mark starboardMarkWhileApproachingLine;
21
+ private final Position portMarkPosition;
22
+ private final Position starboardMarkPosition;
23
+
24
+ public LineDetailsImpl(TimePoint timePoint, Waypoint waypoint, Distance length,
25
+ Bearing angleDifferenceFromPortToStarboardWhenApproachingLineToTrueWind, NauticalSide advantageousSideWhileApproachingLine,
26
+ Distance advantage, Mark portMarkWhileApproachingLine, Mark starboardMarkWhileApproachingLine,
27
+ Position portMarkPosition, Position starboardMarkPosition) {
28
+ super();
29
+ this.timePoint = timePoint;
30
+ this.waypoint = waypoint;
31
+ this.length = length;
32
+ this.angleDifferenceFromPortToStarboardWhenApproachingLineToTrueWind = angleDifferenceFromPortToStarboardWhenApproachingLineToTrueWind;
33
+ this.advantageousSidewhileApproachingLine = advantageousSideWhileApproachingLine;
34
+ this.advantage = advantage;
35
+ this.portMarkWhileApproachingLine = portMarkWhileApproachingLine;
36
+ this.starboardMarkWhileApproachingLine = starboardMarkWhileApproachingLine;
37
+ this.portMarkPosition = portMarkPosition;
38
+ this.starboardMarkPosition = starboardMarkPosition;
39
+ }
40
+
41
+ @Override
42
+ public TimePoint getTimePoint() {
43
+ return timePoint;
44
+ }
45
+
46
+ @Override
47
+ public Waypoint getWaypoint() {
48
+ return waypoint;
49
+ }
50
+
51
+ @Override
52
+ public Distance getLength() {
53
+ return length;
54
+ }
55
+
56
+ @Override
57
+ public Bearing getAngleDifferenceFromPortToStarboardWhenApproachingLineToTrueWind() {
58
+ return angleDifferenceFromPortToStarboardWhenApproachingLineToTrueWind;
59
+ }
60
+
61
+ @Override
62
+ public NauticalSide getAdvantageousSideWhileApproachingLine() {
63
+ return advantageousSidewhileApproachingLine;
64
+ }
65
+
66
+ @Override
67
+ public Distance getAdvantage() {
68
+ return advantage;
69
+ }
70
+
71
+ @Override
72
+ public Mark getStarboardMarkWhileApproachingLine() {
73
+ return starboardMarkWhileApproachingLine;
74
+ }
75
+
76
+ @Override
77
+ public Mark getPortMarkWhileApproachingLine() {
78
+ return portMarkWhileApproachingLine;
79
+ }
80
+
81
+ @Override
82
+ public Position getPortMarkPosition() {
83
+ return portMarkPosition;
84
+ }
85
+
86
+ @Override
87
+ public Position getStarboardMarkPosition() {
88
+ return starboardMarkPosition;
89
+ }
90
+
91
+}
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/shared/tracking/impl/MappedTrackImpl.java
... ...
@@ -0,0 +1,39 @@
1
+package com.sap.sailing.domain.shared.tracking.impl;
2
+
3
+import com.sap.sailing.domain.shared.tracking.MappedTrack;
4
+import com.sap.sse.common.Timed;
5
+import com.sap.sse.shared.util.impl.ArrayListNavigableSet;
6
+
7
+/**
8
+ * Default implementation of {@link MappedTrack} interface.
9
+ *
10
+ * @param <ItemType>
11
+ * the type of item this track is mapped to
12
+ * @param <FixType>
13
+ * the type of fix that is contained in this track
14
+ */
15
+public class MappedTrackImpl<ItemType, FixType extends Timed> extends TrackImpl<FixType>
16
+ implements MappedTrack<ItemType, FixType> {
17
+
18
+ private static final long serialVersionUID = 6165693342087329096L;
19
+
20
+ private final ItemType trackedItem;
21
+
22
+ /** @see TrackImpl#TrackImpl(String) */
23
+ public MappedTrackImpl(ItemType trackedItem, String nameForReadWriteLock) {
24
+ super(nameForReadWriteLock);
25
+ this.trackedItem = trackedItem;
26
+ }
27
+
28
+ /** @see TrackImpl#TrackImpl(ArrayListNavigableSet, String) */
29
+ protected MappedTrackImpl(ItemType trackedItem, ArrayListNavigableSet<Timed> fixes, String nameForReadWriteLock) {
30
+ super(fixes, nameForReadWriteLock);
31
+ this.trackedItem = trackedItem;
32
+ }
33
+
34
+ @Override
35
+ public ItemType getTrackedItem() {
36
+ return trackedItem;
37
+ }
38
+
39
+}
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/shared/tracking/impl/PartialNavigableSetView.java
... ...
@@ -0,0 +1,387 @@
1
+package com.sap.sailing.domain.shared.tracking.impl;
2
+
3
+import java.util.ArrayList;
4
+import java.util.Collection;
5
+import java.util.Comparator;
6
+import java.util.Iterator;
7
+import java.util.List;
8
+import java.util.NavigableSet;
9
+import java.util.NoSuchElementException;
10
+import java.util.SortedSet;
11
+import java.util.TreeSet;
12
+
13
+/**
14
+ * A view on a {@link NavigableSet} which suppresses some entries based on some configurable rule.
15
+ * The {@link #size()} operation is expensive because it requires a full scan. {@link #isEmpty()} is
16
+ * much cheaper because it suffices to find one element passing the filter rule. The filtering rule
17
+ * has to be expressed by subclsses implementing the {@link #isValid(Object)} method.
18
+ *
19
+ * @author Axel Uhl (d043530)
20
+ *
21
+ * @param <E>
22
+ */
23
+public abstract class PartialNavigableSetView<E> implements NavigableSet<E> {
24
+ private final NavigableSet<E> set;
25
+
26
+ private class PartialNavigableSetViewWithSameValidityAsEnclosing extends PartialNavigableSetView<E> {
27
+ public PartialNavigableSetViewWithSameValidityAsEnclosing(NavigableSet<E> set) {
28
+ super(set);
29
+ }
30
+
31
+ @Override
32
+ protected boolean isValid(E e) {
33
+ return PartialNavigableSetView.this.isValid(e);
34
+ }
35
+ }
36
+
37
+ private class FilteringIterator implements Iterator<E> {
38
+ /**
39
+ * The iterator is always kept one step "ahead" in order to know whether there really is a next element. The
40
+ * next valid element is fetched and stored in {@link #nextValid} and {@link #hasNext} is set to
41
+ * <code>true</code>.
42
+ */
43
+ private Iterator<E> iter;
44
+
45
+ private E nextValid;
46
+
47
+ private boolean hasNext;
48
+
49
+ private boolean hasLastNext;
50
+
51
+ private E lastNext;
52
+
53
+ public FilteringIterator() {
54
+ iter = getSet().iterator();
55
+ hasLastNext = false;
56
+ advance();
57
+ }
58
+
59
+ private void advance() {
60
+ if (iter.hasNext()) {
61
+ E next = iter.next();
62
+ while (!isValid(next) && iter.hasNext()) {
63
+ next = iter.next();
64
+ }
65
+ if (isValid(next)) {
66
+ nextValid = next;
67
+ hasNext = true;
68
+ } else {
69
+ hasNext = false;
70
+ }
71
+ } else {
72
+ hasNext = false;
73
+ }
74
+ }
75
+
76
+ @Override
77
+ public boolean hasNext() {
78
+ return hasNext;
79
+ }
80
+
81
+ @Override
82
+ public E next() {
83
+ if (hasNext) {
84
+ E result = nextValid;
85
+ advance();
86
+ hasLastNext = true;
87
+ lastNext = result;
88
+ return result;
89
+ } else {
90
+ throw new NoSuchElementException();
91
+ }
92
+ }
93
+
94
+ @Override
95
+ public void remove() {
96
+ if (!hasLastNext) {
97
+ throw new IllegalStateException("next() was not called before remove()");
98
+ } else {
99
+ PartialNavigableSetView.this.remove(lastNext);
100
+ hasLastNext = false;
101
+ }
102
+ }
103
+ }
104
+
105
+ public PartialNavigableSetView(NavigableSet<E> set) {
106
+ this.set = set;
107
+ }
108
+
109
+ /**
110
+ * Subclasses need to implement this method. For elements to be eliminated from the view represented by this
111
+ * object, return <code>false</code> for such an element being passed to this method.
112
+ */
113
+ abstract protected boolean isValid(E e);
114
+
115
+ @Override
116
+ public Comparator<? super E> comparator() {
117
+ return getSet().comparator();
118
+ }
119
+
120
+ public NavigableSet<E> descendingSet() {
121
+ return new PartialNavigableSetViewWithSameValidityAsEnclosing(getSet().descendingSet());
122
+ }
123
+
124
+ @Override
125
+ public Iterator<E> descendingIterator() {
126
+ return descendingSet().iterator();
127
+ }
128
+
129
+ @Override
130
+ public E first() {
131
+ E first = getSet().first();
132
+ while (first != null && !isValid(first)) {
133
+ first = getSet().higher(first);
134
+ }
135
+ if (first == null) {
136
+ throw new NoSuchElementException();
137
+ } else {
138
+ return first;
139
+ }
140
+ }
141
+
142
+ @Override
143
+ public E last() {
144
+ E last = getSet().last();
145
+ while (last != null && !isValid(last)) {
146
+ last = getSet().lower(last);
147
+ }
148
+ if (last == null) {
149
+ throw new NoSuchElementException();
150
+ } else {
151
+ return last;
152
+ }
153
+ }
154
+
155
+ @Override
156
+ public int size() {
157
+ int size = 0;
158
+ for (E e : getSet()) {
159
+ if (isValid(e)) {
160
+ size++;
161
+ }
162
+ }
163
+ return size;
164
+ }
165
+
166
+ @Override
167
+ public boolean isEmpty() {
168
+ for (E e : getSet()) {
169
+ if (isValid(e)) {
170
+ return false;
171
+ }
172
+ }
173
+ return true;
174
+ }
175
+
176
+ @SuppressWarnings("unchecked")
177
+ @Override
178
+ public boolean contains(Object o) {
179
+ return getSet().contains(o) && isValid((E) o);
180
+ }
181
+
182
+ @Override
183
+ public Object[] toArray() {
184
+ List<E> l = new ArrayList<E>();
185
+ for (E e : getSet()) {
186
+ if (isValid(e)) {
187
+ l.add(e);
188
+ }
189
+ }
190
+ return l.toArray();
191
+ }
192
+
193
+ @SuppressWarnings("unchecked")
194
+ @Override
195
+ public <T> T[] toArray(T[] a) {
196
+ List<T> l = new ArrayList<T>();
197
+ for (E e : getSet()) {
198
+ if (isValid(e)) {
199
+ l.add((T) e);
200
+ }
201
+ }
202
+ return l.toArray(a);
203
+
204
+ }
205
+
206
+ @Override
207
+ public boolean add(E e) {
208
+ return getSet().add(e);
209
+ }
210
+
211
+ @Override
212
+ public boolean remove(Object o) {
213
+ return getSet().remove(o);
214
+ }
215
+
216
+ @SuppressWarnings("unchecked")
217
+ @Override
218
+ public boolean containsAll(Collection<?> c) {
219
+ for (Object o : c) {
220
+ if (!isValid((E) o) || !getSet().contains(o)) {
221
+ return false;
222
+ }
223
+ }
224
+ return true;
225
+ }
226
+
227
+ @Override
228
+ public boolean addAll(Collection<? extends E> c) {
229
+ return getSet().addAll(c);
230
+ }
231
+
232
+ @Override
233
+ public boolean retainAll(Collection<?> c) {
234
+ return getSet().retainAll(c);
235
+ }
236
+
237
+ @Override
238
+ public boolean removeAll(Collection<?> c) {
239
+ return getSet().removeAll(c);
240
+ }
241
+
242
+ @Override
243
+ public void clear() {
244
+ getSet().clear();
245
+ }
246
+
247
+ @Override
248
+ public E lower(E e) {
249
+ E result = getSet().lower(e);
250
+ while (result != null && !isValid(result)) {
251
+ result = getSet().lower(result);
252
+ }
253
+ return result;
254
+ }
255
+
256
+ /**
257
+ * goes one left on the raw, unfiltered set and therefore may return fixes that have {@link #isValid(Object)}==
258
+ * <code>false</code>
259
+ */
260
+ protected E lowerInternal(E e) {
261
+ return getSet().lower(e);
262
+ }
263
+
264
+ /**
265
+ * goes one right on the raw, unfiltered set and therefore may return fixes that have {@link #isValid(Object)}==
266
+ * <code>false</code>
267
+ */
268
+ protected E higherInternal(E e) {
269
+ return getSet().higher(e);
270
+ }
271
+
272
+ @Override
273
+ public E floor(E e) {
274
+ E result = getSet().floor(e);
275
+ while (result != null && !isValid(result)) {
276
+ result = getSet().lower(result);
277
+ }
278
+ return result;
279
+ }
280
+
281
+ @Override
282
+ public E ceiling(E e) {
283
+ E result = getSet().ceiling(e);
284
+ while (result != null && !isValid(result)) {
285
+ result = getSet().higher(result);
286
+ }
287
+ return result;
288
+ }
289
+
290
+ @Override
291
+ public E higher(E e) {
292
+ E result = getSet().higher(e);
293
+ while (result != null && !isValid(result)) {
294
+ result = getSet().higher(result);
295
+ }
296
+ return result;
297
+ }
298
+
299
+ /**
300
+ * Removes all raw fixes that have {@link #isValid(Object)}==<code>false</code> and the first element to have
301
+ * {@link #isValid(Object)}==<code>true</code>. This latter element is returned. If no such element exists,
302
+ * <code>null</code> is returned. It is hence possible that invalid raw fixes are removed but stil <code>null</code>
303
+ * is returned.
304
+ */
305
+ @Override
306
+ public E pollFirst() {
307
+ E result = getSet().first();
308
+ while (result != null && !isValid(result)) {
309
+ getSet().remove(result);
310
+ result = getSet().first();
311
+ }
312
+ return result;
313
+ }
314
+
315
+ @Override
316
+ public E pollLast() {
317
+ E result = getSet().last();
318
+ while (result != null && !isValid(result)) {
319
+ getSet().remove(result);
320
+ result = getSet().last();
321
+ }
322
+ return result;
323
+ }
324
+
325
+ @Override
326
+ public NavigableSet<E> subSet(E fromElement, boolean fromInclusive, E toElement, boolean toInclusive) {
327
+ return new PartialNavigableSetViewWithSameValidityAsEnclosing(getSet().subSet(fromElement, fromInclusive, toElement, toInclusive));
328
+ }
329
+
330
+ @Override
331
+ public NavigableSet<E> headSet(E toElement, boolean inclusive) {
332
+ return new PartialNavigableSetViewWithSameValidityAsEnclosing(getSet().headSet(toElement, inclusive));
333
+ }
334
+
335
+ @Override
336
+ public NavigableSet<E> tailSet(E fromElement, boolean inclusive) {
337
+ return new PartialNavigableSetViewWithSameValidityAsEnclosing(getSet().tailSet(fromElement, inclusive));
338
+ }
339
+
340
+ @Override
341
+ public NavigableSet<E> subSet(E fromElement, E toElement) {
342
+ SortedSet<E> subSet = set.subSet(fromElement, toElement);
343
+ if (subSet instanceof NavigableSet<?>) {
344
+ return new PartialNavigableSetViewWithSameValidityAsEnclosing((NavigableSet<E>) subSet);
345
+ } else {
346
+ TreeSet<E> result = new TreeSet<E>(subSet);
347
+ return new PartialNavigableSetViewWithSameValidityAsEnclosing(result);
348
+ }
349
+ }
350
+
351
+ @Override
352
+ public NavigableSet<E> headSet(E toElement) {
353
+ SortedSet<E> headSet = set.headSet(toElement);
354
+ if (headSet instanceof NavigableSet<?>) {
355
+ return new PartialNavigableSetViewWithSameValidityAsEnclosing((NavigableSet<E>) headSet);
356
+ } else {
357
+ TreeSet<E> result = new TreeSet<E>(headSet);
358
+ return new PartialNavigableSetViewWithSameValidityAsEnclosing(result);
359
+ }
360
+ }
361
+
362
+ @Override
363
+ public NavigableSet<E> tailSet(E fromElement) {
364
+ SortedSet<E> tailSet = set.tailSet(fromElement);
365
+ if (tailSet instanceof NavigableSet<?>) {
366
+ return new PartialNavigableSetViewWithSameValidityAsEnclosing((NavigableSet<E>) tailSet);
367
+ } else {
368
+ TreeSet<E> result = new TreeSet<E>(tailSet);
369
+ return new PartialNavigableSetViewWithSameValidityAsEnclosing(result);
370
+ }
371
+ }
372
+
373
+
374
+ @Override
375
+ public Iterator<E> iterator() {
376
+ return new FilteringIterator();
377
+ }
378
+
379
+ @Override
380
+ public String toString() {
381
+ return new ArrayList<E>(this).toString();
382
+ }
383
+
384
+ protected NavigableSet<E> getSet() {
385
+ return set;
386
+ }
387
+}
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/shared/tracking/impl/TimeRangeCache.java
... ...
@@ -0,0 +1,221 @@
1
+package com.sap.sailing.domain.shared.tracking.impl;
2
+
3
+import java.util.Comparator;
4
+import java.util.Iterator;
5
+import java.util.LinkedHashMap;
6
+import java.util.Map.Entry;
7
+import java.util.NavigableSet;
8
+
9
+import com.sap.sse.common.TimePoint;
10
+import com.sap.sse.common.Util;
11
+import com.sap.sse.common.Util.Pair;
12
+import com.sap.sse.concurrent.LockUtil;
13
+import com.sap.sse.concurrent.NamedReentrantReadWriteLock;
14
+import com.sap.sse.shared.util.impl.ArrayListNavigableSet;
15
+
16
+/**
17
+ * This cache looks "backwards." It contains pairs whose first component represents a <code>to</code> parameter used in
18
+ * a calculation for a time range. It is ordered by this component. The second component is a navigable, ordered set of
19
+ * pairs where the first pair component represents a <code>from</code> parameter used in the calculation's time range
20
+ * and the second pair component represents the result of the calculation for this parameter combination.
21
+ * <p>
22
+ *
23
+ * For implementation efficiency in combination with using an {@link ArrayListNavigableSet} for the values and in order
24
+ * to be able to efficiently extend a cache entry for a single <code>to</code> fix, the navigable sets containing the
25
+ * <code>from</code> fixes and results are ordered such that earlier fixes come later in the set. This way, extending
26
+ * the cache entry for a <code>to</code> fix to an earlier <code>from</code> fix only requires appending to the set.
27
+ * <p>
28
+ *
29
+ * <b>Invalidation</b>: When a new fix is added to the track, all cache entries for fixes at or later than the new fix's
30
+ * time point are removed from this cache. Additionally, the fix insertion may have an impact on the
31
+ * {@link #getEarliestFromAndResultAtOrAfterFrom(TimePoint, TimePoint) previous fix's} validity (track smoothing) and
32
+ * therefore on its selection for result aggregation. Therefore, if fix addition turned the previous fix invalid, the
33
+ * cache entries for the time points at or after the previous fix also need to be removed.
34
+ * <p>
35
+ *
36
+ * <b>Cache use</b>: When a result across a time range is to be computed the calculating method should first look for a
37
+ * cache entry for the <code>to</code> parameter. If one is found, the earliest entry in the navigable set for the
38
+ * navigable set of <code>from</code> and result values that is at or after the requested <code>from</code> time point
39
+ * is determined. If such an entry exists, the result is remembered and the algorithm is repeated recursively, using the
40
+ * <code>from</code> value found in the cache as the new <code>to</code> value, and the <code>from</code> value
41
+ * originally passed to the calculating method as <code>from</code> again. If no entry is found in the cache entry for
42
+ * <code>to</code> that is at or after the requested <code>from</code> time, the result has to be computed "from
43
+ * scratch."
44
+ * <p>
45
+ *
46
+ * If a cache entry for <code>to</code> is not found, the latest cache entry before it is looked up. If one is found,
47
+ * the result for the time range between the <code>to</code> time point requested and the <code>to</code> time point
48
+ * found in the cache is computed by iterating the smoothened fixes for this interval. If none is found, the result is
49
+ * computed by iterating backwards all the way to <code>from</code>.
50
+ * <p>
51
+ *
52
+ * Once the calculating method has computed its value, it should {@link #cache(TimePoint, TimePoint, Object) add} the
53
+ * result to the cache.
54
+ *
55
+ * @author Axel Uhl (D043530)
56
+ */
57
+public class TimeRangeCache<T> {
58
+ public static final int MAX_SIZE = 100;
59
+
60
+ private final NavigableSet<Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, T>>>> timeRangeCache;
61
+
62
+ /**
63
+ * The cache is to have limited size. Eviction shall happen based on a least-recently-used strategy. Usage is
64
+ * defined as having been returned by {@link #getEarliestFromAndResultAtOrAfterFrom(TimePoint, TimePoint)} or
65
+ * having been added by {@link #cache(TimePoint, TimePoint, Object)}.
66
+ * <p>
67
+ *
68
+ * When an eldest entry is asked to be expunged from this map and the map has more than {@link #MAX_SIZE} elements,
69
+ * the expunging will be admitted, and the entry is removed from the {@link #timeRangeCache} core structure. Reading
70
+ * and writing this structure must happen under the {@link #lock write lock} because also reading the linked hash
71
+ * map that counts access as "use" has a modifying effect on its internal structures.
72
+ * <p>
73
+ *
74
+ * The key pairs are from/to pairs. Note that this is in some sense "the opposite direction" compared to the
75
+ * alignment of the {@link #timeRangeCache} structure which has as its outer keys the "to" time point.<p>
76
+ *
77
+ * Read access is to be <code>synchronized<code> using this field's mutex; write access only happens under the
78
+ * {@link #lock write lock} and therefore will have no contenders.
79
+ */
80
+ private final LinkedHashMap<Util.Pair<TimePoint, TimePoint>, Void> lruCache;
81
+
82
+ private final NamedReentrantReadWriteLock lock;
83
+
84
+ private static final Comparator<Util.Pair<TimePoint, ?>> timePointInPairComparator = new Comparator<Util.Pair<TimePoint, ?>>() {
85
+ @Override
86
+ public int compare(Util.Pair<TimePoint, ?> o1, Util.Pair<TimePoint, ?> o2) {
87
+ return o1.getA().compareTo(o2.getA());
88
+ }
89
+ };
90
+
91
+ public TimeRangeCache(String nameForLockLogging) {
92
+ lock = new NamedReentrantReadWriteLock("lock for TimeRangeCache for "+nameForLockLogging, /* fair */ true);
93
+ this.timeRangeCache = new ArrayListNavigableSet<Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, T>>>>(timePointInPairComparator);
94
+ this.lruCache = new LinkedHashMap<Util.Pair<TimePoint, TimePoint>, Void>(/* initial capacity */ 10, /* load factor */ 0.75f,
95
+ /* access-based ordering */ true) {
96
+ private static final long serialVersionUID = -6568235517111733193L;
97
+
98
+ @Override
99
+ protected boolean removeEldestEntry(Entry<Pair<TimePoint, TimePoint>, Void> eldest) {
100
+ final boolean expunge = size() > MAX_SIZE;
101
+ if (expunge) {
102
+ removeCacheEntry(eldest.getKey().getA(), eldest.getKey().getB());
103
+ }
104
+ return expunge;
105
+ }
106
+ };
107
+ }
108
+
109
+ public int size() {
110
+ return lruCache.size();
111
+ }
112
+
113
+ private void removeCacheEntry(TimePoint from, TimePoint to) {
114
+ assert lock.getWriteHoldCount() == 1; // we can be sure we are alone here; this only happens when adding a new entry, holding the write lock
115
+ Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, T>>> entryForTo = timeRangeCache.floor(createDummy(to));
116
+ if (entryForTo.getA().equals(to)) {
117
+ Pair<TimePoint, T> entryForFrom = entryForTo.getB().ceiling(new Util.Pair<TimePoint, T>(from, null));
118
+ if (entryForFrom.getA().equals(from)) {
119
+ entryForTo.getB().remove(entryForFrom);
120
+ if (entryForTo.getB().isEmpty()) {
121
+ timeRangeCache.remove(entryForTo);
122
+ }
123
+ }
124
+ }
125
+ }
126
+
127
+ /**
128
+ * Looks up the entry closest to but no later than <code>to</code>. If not found, <code>null</code> is returned. If
129
+ * found, the earliest pair of from/result that is at or after <code>from</code> will be returned, together with
130
+ * the <code>to</code> value of the entry. If there is no entry that is at or after <code>from</code>,
131
+ * <code>null</code> is returned.
132
+ */
133
+ public Util.Pair<TimePoint, Util.Pair<TimePoint, T>> getEarliestFromAndResultAtOrAfterFrom(TimePoint from, TimePoint to) {
134
+ LockUtil.lockForRead(lock);
135
+ try {
136
+ Util.Pair<TimePoint, Util.Pair<TimePoint, T>> result = null;
137
+ Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, T>>> entryForTo = timeRangeCache.floor(createDummy(to));
138
+ if (entryForTo != null) {
139
+ final Util.Pair<TimePoint, T> fromCeiling = entryForTo.getB().ceiling(new Util.Pair<TimePoint, T>(from, null));
140
+ if (fromCeiling != null) {
141
+ result = new Util.Pair<TimePoint, Util.Pair<TimePoint, T>>(entryForTo.getA(), fromCeiling);
142
+ }
143
+ }
144
+ // no writer can be active because we're holding the read lock; read access on the lruCache is synchronized using
145
+ // the lruCache's mutex; this is necessary because we're using access-based LRU pinging where even getting an entry
146
+ // modifies the internal parts of the data structure which is not thread safe.
147
+ synchronized (lruCache) { // ping the "perfect match" although it may not even have existed in the cache
148
+ lruCache.get(new Util.Pair<TimePoint, TimePoint>(from, to));
149
+ }
150
+ return result;
151
+ } finally {
152
+ LockUtil.unlockAfterRead(lock);
153
+ }
154
+ }
155
+
156
+ /**
157
+ * Removes all cache entries that have a <code>to</code> time point that is at or after <code>timePoint</code>.
158
+ */
159
+ public void invalidateAllAtOrLaterThan(TimePoint timePoint) {
160
+ LockUtil.lockForWrite(lock);
161
+ try {
162
+ Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, T>>> dummy = createDummy(timePoint);
163
+ NavigableSet<Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, T>>>> toRemove = timeRangeCache.tailSet(dummy, /* inclusive */ true);
164
+ for (Iterator<Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, T>>>> i=toRemove.iterator(); i.hasNext(); ) {
165
+ Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, T>>> entryToRemove = i.next();
166
+ assert entryToRemove.getA().compareTo(timePoint) >= 0;
167
+ for (Pair<TimePoint, T> fromAndResult : entryToRemove.getB()) {
168
+ lruCache.remove(new Util.Pair<>(fromAndResult.getA(), entryToRemove.getA()));
169
+ }
170
+ i.remove();
171
+ }
172
+ } finally {
173
+ LockUtil.unlockAfterWrite(lock);
174
+ }
175
+ }
176
+
177
+ private NavigableSet<Util.Pair<TimePoint, T>> getEntryForTo(TimePoint to) {
178
+ NavigableSet<Util.Pair<TimePoint, T>> result = null;
179
+ Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, T>>> dummyForTo = createDummy(to);
180
+ Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, T>>> entryForTo = timeRangeCache.floor(dummyForTo);
181
+ if (entryForTo != null && entryForTo.getA().equals(to)) {
182
+ result = entryForTo.getB();
183
+ }
184
+ return result;
185
+ }
186
+
187
+ public void cache(TimePoint from, TimePoint to, T result) {
188
+ LockUtil.lockForWrite(lock);
189
+ try {
190
+ NavigableSet<Util.Pair<TimePoint, T>> entryForTo = getEntryForTo(to);
191
+ if (entryForTo == null) {
192
+ entryForTo = new ArrayListNavigableSet<Util.Pair<TimePoint, T>>(timePointInPairComparator);
193
+ Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, T>>> pairForTo = new Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, T>>>(
194
+ to, entryForTo);
195
+ timeRangeCache.add(pairForTo);
196
+ }
197
+ entryForTo.add(new Util.Pair<TimePoint, T>(from, result));
198
+ lruCache.put(new Util.Pair<TimePoint, TimePoint>(from, to), null);
199
+ } finally {
200
+ LockUtil.unlockAfterWrite(lock);
201
+ }
202
+ }
203
+
204
+ private Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, T>>> createDummy(TimePoint to) {
205
+ return new Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, T>>>(to, null);
206
+ }
207
+
208
+ /**
209
+ * Removes all contents from this cache
210
+ */
211
+ public void clear() {
212
+ LockUtil.lockForWrite(lock);
213
+ try {
214
+ timeRangeCache.clear();
215
+ lruCache.clear();
216
+ } finally {
217
+ LockUtil.unlockAfterWrite(lock);
218
+ }
219
+
220
+ }
221
+}
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/shared/tracking/impl/TimedComparator.java
... ...
@@ -0,0 +1,17 @@
1
+package com.sap.sailing.domain.shared.tracking.impl;
2
+
3
+import java.io.Serializable;
4
+import java.util.Comparator;
5
+
6
+import com.sap.sse.common.Timed;
7
+
8
+public class TimedComparator implements Comparator<Timed>, Serializable {
9
+ private static final long serialVersionUID = 1604511471599854988L;
10
+ public static final Comparator<Timed> INSTANCE = new TimedComparator();
11
+
12
+ @Override
13
+ public int compare(Timed o1, Timed o2) {
14
+ return o1.getTimePoint().compareTo(o2.getTimePoint());
15
+ }
16
+}
17
+
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/shared/tracking/impl/TrackImpl.java
... ...
@@ -0,0 +1,549 @@
1
+package com.sap.sailing.domain.shared.tracking.impl;
2
+
3
+import java.io.IOException;
4
+import java.io.ObjectOutputStream;
5
+import java.util.ConcurrentModificationException;
6
+import java.util.Iterator;
7
+import java.util.NavigableSet;
8
+import java.util.function.Function;
9
+
10
+import com.sap.sailing.domain.shared.tracking.AddResult;
11
+import com.sap.sailing.domain.shared.tracking.FixAcceptancePredicate;
12
+import com.sap.sailing.domain.shared.tracking.Track;
13
+import com.sap.sse.common.Duration;
14
+import com.sap.sse.common.TimePoint;
15
+import com.sap.sse.common.Timed;
16
+import com.sap.sse.common.Util;
17
+import com.sap.sse.common.Util.Pair;
18
+import com.sap.sse.common.scalablevalue.ScalableValue;
19
+import com.sap.sse.concurrent.LockUtil;
20
+import com.sap.sse.concurrent.NamedReentrantReadWriteLock;
21
+import com.sap.sse.shared.util.impl.ArrayListNavigableSet;
22
+import com.sap.sse.shared.util.impl.UnmodifiableNavigableSet;
23
+
24
+public class TrackImpl<FixType extends Timed> implements Track<FixType> {
25
+ private static final long serialVersionUID = -4075853657857657528L;
26
+ /**
27
+ * The fixes, ordered by their time points
28
+ */
29
+ private final ArrayListNavigableSet<Timed> fixes;
30
+
31
+ private final NamedReentrantReadWriteLock readWriteLock;
32
+
33
+ protected static class DummyTimed implements Timed {
34
+ private static final long serialVersionUID = 6047311973718918856L;
35
+ private final TimePoint timePoint;
36
+ public DummyTimed(TimePoint timePoint) {
37
+ super();
38
+ this.timePoint = timePoint;
39
+ }
40
+ @Override
41
+ public TimePoint getTimePoint() {
42
+ return timePoint;
43
+ }
44
+ @Override
45
+ public String toString() {
46
+ return timePoint.toString();
47
+ }
48
+ }
49
+
50
+ public TrackImpl(String nameForReadWriteLock) {
51
+ this(new ArrayListNavigableSet<Timed>(TimedComparator.INSTANCE), nameForReadWriteLock);
52
+ }
53
+
54
+ protected TrackImpl(ArrayListNavigableSet<Timed> fixes, String nameForReadWriteLock) {
55
+ this.readWriteLock = new NamedReentrantReadWriteLock(nameForReadWriteLock, /* fair */ false);
56
+ this.fixes = fixes;
57
+ }
58
+
59
+ /**
60
+ * Synchronize the serialization such that no fixes are added while serializing
61
+ */
62
+ private void writeObject(ObjectOutputStream s) throws IOException {
63
+ lockForRead();
64
+ try {
65
+ s.defaultWriteObject();
66
+ } finally {
67
+ unlockAfterRead();
68
+ }
69
+ }
70
+
71
+ @Override
72
+ public void lockForRead() {
73
+ LockUtil.lockForRead(readWriteLock);
74
+ }
75
+
76
+ @Override
77
+ public void unlockAfterRead() {
78
+ LockUtil.unlockAfterRead(readWriteLock);
79
+ }
80
+
81
+ protected void lockForWrite() {
82
+ LockUtil.lockForWrite(readWriteLock);
83
+ }
84
+
85
+ protected void unlockAfterWrite() {
86
+ LockUtil.unlockAfterWrite(readWriteLock);
87
+ }
88
+
89
+ /**
90
+ * Callers that want to iterate over the collection returned need to use {@link #lockForRead()} and {@link #unlockAfterRead()}
91
+ * to avoid {@link ConcurrentModificationException}s. Should they modify the structure returned, they have to use
92
+ * {@link #lockForWrite()} and {@link #unlockAfterWrite()}, respectively.
93
+ */
94
+ protected NavigableSet<FixType> getInternalRawFixes() {
95
+ @SuppressWarnings("unchecked")
96
+ NavigableSet<FixType> result = (NavigableSet<FixType>) fixes;
97
+ return result;
98
+ }
99
+
100
+ /**
101
+ * asserts that the calling thread holds at least one of read and write lock
102
+ */
103
+ protected void assertReadLock() {
104
+ if (readWriteLock.getReadHoldCount() < 1 && readWriteLock.getWriteHoldCount() < 1) {
105
+ throw new IllegalStateException("Caller must obtain read lock using lockForRead() before calling this method");
106
+ }
107
+ }
108
+
109
+ protected void assertWriteLock() {
110
+ if (readWriteLock.getWriteHoldCount() < 1) {
111
+ throw new IllegalStateException("Caller must obtain write lock using lockForWrite() before calling this method");
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Callers that want to iterate over the collection returned need to use {@link #lockForRead()} and
117
+ * {@link #unlockAfterRead()} to avoid {@link ConcurrentModificationException}s.
118
+ *
119
+ * @return the smoothened fixes ordered by their time points; this implementation simply delegates to
120
+ * {@link #getInternalRawFixes()} because for only {@link Timed} fixes we can't know how to remove outliers.
121
+ * Subclasses that constrain the <code>FixType</code> may provide smoothening implementations.
122
+ */
123
+ protected NavigableSet<FixType> getInternalFixes() {
124
+ NavigableSet<FixType> result = getInternalRawFixes();
125
+ return result;
126
+ }
127
+
128
+ /**
129
+ * Iterates the fixes with outliers getting skipped, in the order of their time points.
130
+ * Relies on {@link #getInternalFixes()} to void the track view from outliers.
131
+ */
132
+ @Override
133
+ public NavigableSet<FixType> getFixes() {
134
+ assertReadLock();
135
+ return new UnmodifiableNavigableSet<FixType>(getInternalFixes());
136
+ }
137
+
138
+ @Override
139
+ public Iterable<FixType> getFixes(TimePoint from, boolean fromInclusive, TimePoint to, boolean toInclusive) {
140
+ return getFixes().subSet(getDummyFix(from), fromInclusive, getDummyFix(to), toInclusive);
141
+ }
142
+
143
+ /**
144
+ * Iterates over the raw sequence of fixes, all potential outliers included
145
+ */
146
+ @Override
147
+ public NavigableSet<FixType> getRawFixes() {
148
+ assertReadLock();
149
+ return new UnmodifiableNavigableSet<FixType>(getInternalRawFixes());
150
+ }
151
+
152
+ @Override
153
+ public FixType getLastFixAtOrBefore(TimePoint timePoint) {
154
+ return getLastFixAtOrBefore(timePoint, /* fixAcceptancePredicate == null means accept all */ null);
155
+ }
156
+
157
+ private FixType getLastFixAtOrBefore(TimePoint timePoint, FixAcceptancePredicate<FixType> fixAcceptancePredicate) {
158
+ lockForRead();
159
+ try {
160
+ final NavigableSet<FixType> headSet = getInternalFixes().headSet(getDummyFix(timePoint), /* inclusive */ true);
161
+ for (final Iterator<FixType> i=headSet.descendingIterator(); i.hasNext(); ) {
162
+ final FixType next = i.next();
163
+ if (fixAcceptancePredicate == null || fixAcceptancePredicate.isAcceptFix(next)) {
164
+ return next;
165
+ }
166
+ }
167
+ return null;
168
+ } finally {
169
+ unlockAfterRead();
170
+ }
171
+ }
172
+
173
+ @Override
174
+ public FixType getLastFixBefore(TimePoint timePoint) {
175
+ lockForRead();
176
+ try {
177
+ return (FixType) getInternalFixes().lower(getDummyFix(timePoint));
178
+ } finally {
179
+ unlockAfterRead();
180
+ }
181
+ }
182
+
183
+ @Override
184
+ public FixType getLastRawFixAtOrBefore(TimePoint timePoint) {
185
+ lockForRead();
186
+ try {
187
+ return (FixType) getInternalRawFixes().floor(getDummyFix(timePoint));
188
+ } finally {
189
+ unlockAfterRead();
190
+ }
191
+ }
192
+
193
+ @Override
194
+ public FixType getFirstRawFixAtOrAfter(TimePoint timePoint) {
195
+ lockForRead();
196
+ try {
197
+ return (FixType) getInternalRawFixes().ceiling(getDummyFix(timePoint));
198
+ } finally {
199
+ unlockAfterRead();
200
+ }
201
+ }
202
+
203
+ @Override
204
+ public FixType getFirstFixAtOrAfter(TimePoint timePoint) {
205
+ return getFirstFixAtOrAfter(timePoint, /* fixAcceptancePredicate==null means accept all fixes */ null);
206
+ }
207
+
208
+ private FixType getFirstFixAtOrAfter(TimePoint timePoint, FixAcceptancePredicate<FixType> fixAcceptancePredicate) {
209
+ lockForRead();
210
+ try {
211
+ final NavigableSet<FixType> tailSet = getInternalFixes().tailSet(getDummyFix(timePoint), /* inclusive */ true);
212
+ for (final FixType next : tailSet) {
213
+ if (fixAcceptancePredicate == null || fixAcceptancePredicate.isAcceptFix(next)) {
214
+ return next;
215
+ }
216
+ }
217
+ return null;
218
+ } finally {
219
+ unlockAfterRead();
220
+ }
221
+ }
222
+
223
+ @Override
224
+ public FixType getLastRawFixBefore(TimePoint timePoint) {
225
+ lockForRead();
226
+ try {
227
+ return (FixType) getInternalRawFixes().lower(getDummyFix(timePoint));
228
+ } finally {
229
+ unlockAfterRead();
230
+ }
231
+ }
232
+
233
+ @Override
234
+ public FixType getFirstFixAfter(TimePoint timePoint) {
235
+ lockForRead();
236
+ try {
237
+ return (FixType) getInternalFixes().higher(getDummyFix(timePoint));
238
+ } finally {
239
+ unlockAfterRead();
240
+ }
241
+ }
242
+
243
+ @Override
244
+ public FixType getFirstRawFixAfter(TimePoint timePoint) {
245
+ lockForRead();
246
+ try {
247
+ return (FixType) getInternalRawFixes().higher(getDummyFix(timePoint));
248
+ } finally {
249
+ unlockAfterRead();
250
+ }
251
+ }
252
+
253
+ @Override
254
+ public FixType getFirstRawFix() {
255
+ lockForRead();
256
+ try {
257
+ if (getInternalFixes().isEmpty()) {
258
+ return null;
259
+ } else {
260
+ return (FixType) getInternalFixes().first();
261
+ }
262
+ } finally {
263
+ unlockAfterRead();
264
+ }
265
+ }
266
+
267
+ @Override
268
+ public FixType getLastRawFix() {
269
+ lockForRead();
270
+ try {
271
+ if (getInternalRawFixes().isEmpty()) {
272
+ return null;
273
+ } else {
274
+ return (FixType) getInternalRawFixes().last();
275
+ }
276
+ } finally {
277
+ unlockAfterRead();
278
+ }
279
+ }
280
+
281
+ /**
282
+ * @param fixAcceptancePredicate
283
+ * if not {@code null}, adjacent fixes will be skipped as long as this predicate does not
284
+ * {@link FixAcceptancePredicate#isAcceptFix(Object) accept} the fix. This can, e.g., be used to skip
285
+ * fixes that don't have values in a dimension required. If {@code null}, the next fixes left and right
286
+ * (including the exact {@code timePoint} if a fix exists there) will be used without further check.
287
+ */
288
+ private Pair<FixType, FixType> getSurroundingFixes(TimePoint timePoint, FixAcceptancePredicate<FixType> fixAcceptancePredicate) {
289
+ FixType left = getLastFixAtOrBefore(timePoint, fixAcceptancePredicate);
290
+ FixType right = getFirstFixAtOrAfter(timePoint, fixAcceptancePredicate);
291
+ com.sap.sse.common.Util.Pair<FixType, FixType> result = new com.sap.sse.common.Util.Pair<>(left, right);
292
+ return result;
293
+ }
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
+ */
300
+ private <V, T> T timeBasedAverage(TimePoint timePoint, ScalableValue<V, T> value1, TimePoint timePoint1, ScalableValue<V, T> value2, TimePoint timePoint2) {
301
+ final T acc;
302
+ if (timePoint1.equals(timePoint2)) {
303
+ acc = value1.add(value2).divide(2);
304
+ } else {
305
+ long timeDiff1 = Math.abs(timePoint1.asMillis() - timePoint.asMillis());
306
+ long timeDiff2 = Math.abs(timePoint2.asMillis() - timePoint.asMillis());
307
+ acc = value1.multiply(timeDiff2).add(value2.multiply(timeDiff1)).divide(timeDiff1 + timeDiff2);
308
+ }
309
+ return acc;
310
+ }
311
+
312
+ @Override
313
+ public <InternalType, ValueType> ValueType getInterpolatedValue(TimePoint timePoint,
314
+ Function<FixType, ScalableValue<InternalType, ValueType>> converter) {
315
+ return getInterpolatedValue(timePoint, converter, /* fixAcceptancePredicate==null means accept all */ null);
316
+ }
317
+
318
+ protected <InternalType, ValueType> ValueType getInterpolatedValue(TimePoint timePoint,
319
+ Function<FixType, ScalableValue<InternalType, ValueType>> converter, FixAcceptancePredicate<FixType> fixAcceptancePredicate) {
320
+ final ValueType result;
321
+ Pair<FixType, FixType> fixPair = getSurroundingFixes(timePoint, fixAcceptancePredicate);
322
+ if (fixPair.getA() == null) {
323
+ if (fixPair.getB() == null) {
324
+ result = null;
325
+ } else {
326
+ result = converter.apply(fixPair.getB()).divide(1);
327
+ }
328
+ } else {
329
+ if (fixPair.getB() == null || fixPair.getA() == fixPair.getB()) {
330
+ result = converter.apply(fixPair.getA()).divide(1);
331
+ } else {
332
+ result = timeBasedAverage(timePoint,
333
+ converter.apply(fixPair.getA()), fixPair.getA().getTimePoint(),
334
+ converter.apply(fixPair.getB()), fixPair.getB().getTimePoint());
335
+ }
336
+ }
337
+ return result;
338
+ }
339
+
340
+ @Override
341
+ public Iterator<FixType> getFixesIterator(TimePoint startingAt, boolean inclusive) {
342
+ assertReadLock();
343
+ return getTimeConstrainedFixesIterator(getInternalFixes(), startingAt, inclusive, /* endingAt */ null, /* endingAtInclusive */ false);
344
+ }
345
+
346
+ @Override
347
+ public Iterator<FixType> getFixesIterator(TimePoint startingAt, boolean startingAtInclusive, TimePoint endingAt,
348
+ boolean endingAtInclusive) {
349
+ assertReadLock();
350
+ return getTimeConstrainedFixesIterator(getInternalFixes(), startingAt, startingAtInclusive, endingAt, endingAtInclusive);
351
+ }
352
+
353
+ @Override
354
+ public Iterator<FixType> getFixesDescendingIterator(TimePoint startingAt, boolean inclusive) {
355
+ assertReadLock();
356
+ Iterator<FixType> result = (Iterator<FixType>) getInternalFixes().headSet(
357
+ getDummyFix(startingAt), inclusive).descendingIterator();
358
+ return result;
359
+ }
360
+
361
+ /**
362
+ * Creates a dummy fix that conforms to <code>FixType</code>. This in particular means that subclasses
363
+ * instantiating <code>FixType</code> with a specific class need to redefine this method so as to return
364
+ * a dummy fix complying with their instantiation type used for <code>FixType</code>. Otherwise, a
365
+ * {@link ClassCastException} may result upon certain operations performed with the fix returned by
366
+ * this method.
367
+ */
368
+ protected FixType getDummyFix(TimePoint timePoint) {
369
+ @SuppressWarnings("unchecked")
370
+ FixType result = (FixType) new DummyTimed(timePoint);
371
+ return result;
372
+ }
373
+
374
+ @Override
375
+ public Iterator<FixType> getRawFixesIterator(TimePoint startingAt, boolean inclusive) {
376
+ assertReadLock();
377
+ return getTimeConstrainedFixesIterator(getInternalRawFixes(), startingAt, inclusive, /* endingAt */ null, /* endingAtInclusive */ false);
378
+ }
379
+
380
+ private Iterator<FixType> getTimeConstrainedFixesIterator(NavigableSet<FixType> set, TimePoint startingAt, boolean startingAtInclusive,
381
+ TimePoint endingAt, boolean endingAtInclusive) {
382
+ assertReadLock();
383
+ if (startingAt != null && endingAt != null) {
384
+ set = set.subSet(getDummyFix(startingAt), startingAtInclusive, getDummyFix(endingAt), endingAtInclusive);
385
+ } else if (endingAt != null) {
386
+ set = set.headSet(getDummyFix(endingAt), endingAtInclusive);
387
+ } else if (startingAt != null) {
388
+ set = set.tailSet(getDummyFix(startingAt), startingAtInclusive);
389
+ }
390
+ Iterator<FixType> result = set.iterator();
391
+ return result;
392
+ }
393
+
394
+ @Override
395
+ public Iterator<FixType> getRawFixesIterator(TimePoint startingAt, boolean startingAtInclusive,
396
+ TimePoint endingAt, boolean endingAtInclusive) {
397
+ assertReadLock();
398
+ return getTimeConstrainedFixesIterator(getInternalRawFixes(), startingAt, startingAtInclusive, endingAt, endingAtInclusive);
399
+ }
400
+
401
+ @Override
402
+ public Iterator<FixType> getRawFixesDescendingIterator(TimePoint startingAt, boolean inclusive) {
403
+ assertReadLock();
404
+ Iterator<FixType> result = (Iterator<FixType>) getInternalRawFixes().headSet(
405
+ getDummyFix(startingAt), inclusive).descendingIterator();
406
+ return result;
407
+ }
408
+
409
+ protected boolean add(FixType fix) {
410
+ return add(fix, /* replace */ false);
411
+ }
412
+
413
+ /**
414
+ * @return {@code true} if the fix was added or replaced; {@code false} in case no change was performed
415
+ */
416
+ protected boolean add(FixType fix, boolean replace) {
417
+ lockForWrite();
418
+ try {
419
+ final AddResult addResult = addWithoutLocking(fix, replace);
420
+ return addResult == AddResult.ADDED || addResult == AddResult.REPLACED;
421
+ } finally {
422
+ unlockAfterWrite();
423
+ }
424
+ }
425
+
426
+ /**
427
+ * The caller must ensure to hold the write lock for this track when calling this method.
428
+ *
429
+ * @param replace
430
+ * whether or not to replace an existing fix in the track that is equal to {@link #fix} as defined by the
431
+ * comparator used for the {@link #fixes} set. By default this is a comparator only comparing the
432
+ * fixes' time stamps. Subclasses may use different comparator implementations.
433
+ */
434
+ protected AddResult addWithoutLocking(FixType fix, boolean replace) {
435
+ final AddResult result;
436
+ final boolean added = getInternalRawFixes().add(fix);
437
+ if (!added && replace) {
438
+ getInternalRawFixes().remove(fix);
439
+ result = getInternalRawFixes().add(fix) ? AddResult.REPLACED : AddResult.NOT_ADDED;
440
+ } else {
441
+ result = added ? AddResult.ADDED : AddResult.NOT_ADDED;
442
+ }
443
+ return result;
444
+ }
445
+
446
+ @Override
447
+ public Duration getAverageIntervalBetweenFixes() {
448
+ lockForRead();
449
+ try {
450
+ final Duration result;
451
+ final int size = getRawFixes().size();
452
+ if (size > 1) {
453
+ result = getRawFixes().first().getTimePoint().until(getRawFixes().last().getTimePoint()).divide(size-1);
454
+ } else {
455
+ result = null;
456
+ }
457
+ return result;
458
+ } finally {
459
+ unlockAfterRead();
460
+ }
461
+ }
462
+
463
+ @Override
464
+ public Duration getAverageIntervalBetweenRawFixes() {
465
+ lockForRead();
466
+ try {
467
+ final Duration result;
468
+ final int size = getRawFixes().size();
469
+ if (size > 1) {
470
+ result = getRawFixes().first().getTimePoint().until(getRawFixes().last().getTimePoint()).divide(size-1);
471
+ } else {
472
+ result = null;
473
+ }
474
+ return result;
475
+ } finally {
476
+ unlockAfterRead();
477
+ }
478
+ }
479
+
480
+ @Override
481
+ public <T> T getValueSum(TimePoint from, TimePoint to, T nullElement, Adder<T> adder, TimeRangeCache<T> cache, TimeRangeValueCalculator<T> valueCalculator) {
482
+ return getValueSumRecursively(from, to, /* recursionLevel */ 0, nullElement, adder, cache, valueCalculator);
483
+ }
484
+
485
+ private <T> T getValueSumRecursively(TimePoint from, TimePoint to, int recursionDepth, T nullElement,
486
+ Adder<T> adder, TimeRangeCache<T> cache, TimeRangeValueCalculator<T> valueCalculator) {
487
+ T result;
488
+ if (!from.before(to)) {
489
+ result = nullElement;
490
+ } else {
491
+ boolean perfectCacheHit = false;
492
+ lockForRead();
493
+ try {
494
+ Util.Pair<TimePoint, Util.Pair<TimePoint, T>> bestCacheEntry = cache.getEarliestFromAndResultAtOrAfterFrom(from, to);
495
+ if (bestCacheEntry != null) {
496
+ perfectCacheHit = true; // potentially a cache hit; but if it doesn't span the full interval, it's not perfect; see below
497
+ // compute the missing stretches between best cache entry's "from" and our "from" and the cache
498
+ // entry's "to" and our "to"
499
+ T valueFromFromToBeginningOfCacheEntry = nullElement;
500
+ T valueFromEndOfCacheEntryToTo = nullElement;
501
+ if (!bestCacheEntry.getB().getA().equals(from)) {
502
+ assert bestCacheEntry.getB().getA().after(from);
503
+ perfectCacheHit = false;
504
+ valueFromFromToBeginningOfCacheEntry = getValueSumRecursively(from, bestCacheEntry
505
+ .getB().getA(), recursionDepth + 1, nullElement, adder, cache, valueCalculator);
506
+ }
507
+ if (!bestCacheEntry.getA().equals(to)) {
508
+ assert bestCacheEntry.getA().before(to);
509
+ perfectCacheHit = false;
510
+ valueFromEndOfCacheEntryToTo = getValueSumRecursively(bestCacheEntry.getA(), to,
511
+ recursionDepth + 1, nullElement, adder, cache, valueCalculator);
512
+ }
513
+ if (valueFromEndOfCacheEntryToTo == null || bestCacheEntry.getB().getB() == null) {
514
+ result = null;
515
+ } else {
516
+ result = adder.add(adder.add(valueFromFromToBeginningOfCacheEntry, bestCacheEntry.getB().getB()),
517
+ valueFromEndOfCacheEntryToTo);
518
+ }
519
+ } else {
520
+ if (from.compareTo(to) < 0) {
521
+ result = valueCalculator.calculate(from, to);
522
+ } else {
523
+ result = nullElement;
524
+ }
525
+ }
526
+ // run the cache update while still holding the read lock; this avoids bug4629 where a cache invalidation
527
+ // caused by fix insertions can come after the result calculation and before the cache update
528
+ if (!perfectCacheHit && recursionDepth == 0) {
529
+ cache.cache(from, to, result);
530
+ }
531
+ } finally {
532
+ unlockAfterRead();
533
+ }
534
+ }
535
+ return result;
536
+ }
537
+
538
+
539
+
540
+ @Override
541
+ public int size() {
542
+ return fixes.size();
543
+ }
544
+
545
+ @Override
546
+ public boolean isEmpty() {
547
+ return fixes.isEmpty();
548
+ }
549
+}
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/shared/tracking/impl/TrackingConnectorInfoImpl.java
... ...
@@ -0,0 +1,66 @@
1
+package com.sap.sailing.domain.shared.tracking.impl;
2
+
3
+import com.sap.sailing.domain.shared.tracking.TrackingConnectorInfo;
4
+
5
+public class TrackingConnectorInfoImpl implements TrackingConnectorInfo {
6
+ private static final long serialVersionUID = 7970268841592389145L;
7
+ private final String trackingConnectorName;
8
+ private final String TtackingConnectorDefaultUrl;
9
+ private final String webUrl;
10
+
11
+ public TrackingConnectorInfoImpl(String trackingConnectorName, String trackingConnectorDefaultUrl, String webUrl) {
12
+ super();
13
+ this.trackingConnectorName = trackingConnectorName;
14
+ TtackingConnectorDefaultUrl = trackingConnectorDefaultUrl;
15
+ this.webUrl = webUrl;
16
+ }
17
+
18
+ public String getTrackingConnectorDefaultUrl() {
19
+ return TtackingConnectorDefaultUrl;
20
+ }
21
+
22
+ public String getTrackingConnectorName() {
23
+ return trackingConnectorName;
24
+ }
25
+
26
+ public String getWebUrl() {
27
+ return webUrl;
28
+ }
29
+
30
+ @Override
31
+ public int hashCode() {
32
+ final int prime = 31;
33
+ int result = 1;
34
+ result = prime * result + ((TtackingConnectorDefaultUrl == null) ? 0 : TtackingConnectorDefaultUrl.hashCode());
35
+ result = prime * result + ((trackingConnectorName == null) ? 0 : trackingConnectorName.hashCode());
36
+ result = prime * result + ((webUrl == null) ? 0 : webUrl.hashCode());
37
+ return result;
38
+ }
39
+
40
+ @Override
41
+ public boolean equals(Object obj) {
42
+ if (this == obj)
43
+ return true;
44
+ if (obj == null)
45
+ return false;
46
+ if (getClass() != obj.getClass())
47
+ return false;
48
+ TrackingConnectorInfoImpl other = (TrackingConnectorInfoImpl) obj;
49
+ if (TtackingConnectorDefaultUrl == null) {
50
+ if (other.TtackingConnectorDefaultUrl != null)
51
+ return false;
52
+ } else if (!TtackingConnectorDefaultUrl.equals(other.TtackingConnectorDefaultUrl))
53
+ return false;
54
+ if (trackingConnectorName == null) {
55
+ if (other.trackingConnectorName != null)
56
+ return false;
57
+ } else if (!trackingConnectorName.equals(other.trackingConnectorName))
58
+ return false;
59
+ if (webUrl == null) {
60
+ if (other.webUrl != null)
61
+ return false;
62
+ } else if (!webUrl.equals(other.webUrl))
63
+ return false;
64
+ return true;
65
+ }
66
+}
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/AddResult.java
... ...
@@ -1,14 +0,0 @@
1
-package com.sap.sailing.domain.tracking;
2
-
3
-import javax.swing.plaf.basic.BasicSliderUI.TrackListener;
4
-
5
-/**
6
- * The result of trying to add a fix to a {@link Track}. Used when notifying {@link TrackListener}s.
7
- * This allows listeners, in particular, to distinguish between the add and replace scenario.
8
- *
9
- * @author Axel Uhl (D043530)
10
- *
11
- */
12
-public enum AddResult {
13
- NOT_ADDED, ADDED, REPLACED;
14
-}
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/FixAcceptancePredicate.java
... ...
@@ -1,14 +0,0 @@
1
-package com.sap.sailing.domain.tracking;
2
-
3
-/**
4
- * A predicate for a fix, for use in
5
- * {@link Track#getInterpolatedValue(com.sap.sse.common.TimePoint, com.sap.sse.common.Util.Function)}, used, e.g., to
6
- * provide a rule for when a fix shall be accepted during the search for surrounding fixes.
7
- *
8
- * @author Axel Uhl (d043530)
9
- *
10
- * @param <FixType>
11
- */
12
-public interface FixAcceptancePredicate<FixType> {
13
- boolean isAcceptFix(FixType fix);
14
-}
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/LineDetails.java
... ...
@@ -1,57 +0,0 @@
1
-package com.sap.sailing.domain.tracking;
2
-
3
-import com.sap.sailing.domain.base.Mark;
4
-import com.sap.sailing.domain.base.Waypoint;
5
-import com.sap.sailing.domain.common.NauticalSide;
6
-import com.sap.sailing.domain.common.Position;
7
-import com.sap.sse.common.Bearing;
8
-import com.sap.sse.common.Distance;
9
-import com.sap.sse.common.TimePoint;
10
-
11
-/**
12
- * For a line such as a start or a finish line, tells the line's length at a given time, which side is
13
- * {@link NauticalSide#PORT port} and which is {@link NauticalSide#STARBOARD starboard} when approaching the line,
14
- * and---if wind information is available---its angle to a true wind direction and the advantageous side in approaching
15
- * direction as well as how much the advantageous side is ahead. The wind-dependent information
16
- * will all be <code>null</code> if no wind data is available.
17
- *
18
- * @author Axel Uhl (d043530)
19
- *
20
- */
21
-public interface LineDetails {
22
- TimePoint getTimePoint();
23
-
24
- Waypoint getWaypoint();
25
-
26
- Distance getLength();
27
-
28
- Bearing getAngleDifferenceFromPortToStarboardWhenApproachingLineToTrueWind();
29
-
30
- NauticalSide getAdvantageousSideWhileApproachingLine();
31
-
32
- Mark getStarboardMarkWhileApproachingLine();
33
-
34
- Mark getPortMarkWhileApproachingLine();
35
-
36
- Distance getAdvantage();
37
-
38
- Position getPortMarkPosition();
39
-
40
- Position getStarboardMarkPosition();
41
-
42
- default Mark getAdvantageousMark() {
43
- return getAdvantageousSideWhileApproachingLine() == NauticalSide.PORT ? getPortMarkWhileApproachingLine() : getStarboardMarkWhileApproachingLine();
44
- }
45
-
46
- default Position getAdvantageousMarkPosition() {
47
- return getAdvantageousSideWhileApproachingLine() == NauticalSide.PORT ? getPortMarkPosition() : getStarboardMarkPosition();
48
- }
49
-
50
- default Bearing getBearingFromStarboardToPortWhenApproachingLine() {
51
- return getStarboardMarkPosition().getBearingGreatCircle(getPortMarkPosition());
52
- }
53
-
54
- default Bearing getBearingFromPortToStarboardWhenApproachingLine() {
55
- return getPortMarkPosition().getBearingGreatCircle(getStarboardMarkPosition());
56
- }
57
-}
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/MappedTrack.java
... ...
@@ -1,20 +0,0 @@
1
-package com.sap.sailing.domain.tracking;
2
-
3
-import com.sap.sse.common.Timed;
4
-
5
-/**
6
- * {@link Track} specialization, which is mapped to a specific type of items.
7
- *
8
- * @param <ItemType>
9
- * the type of item this track is mapped to
10
- * @param <FixType>
11
- * the type of fix that is contained in this track
12
- */
13
-public interface MappedTrack<ItemType, FixType extends Timed> extends Track<FixType> {
14
-
15
- /**
16
- * @return the item this track is mapped to.
17
- */
18
- ItemType getTrackedItem();
19
-
20
-}
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/Track.java
... ...
@@ -1,259 +0,0 @@
1
-package com.sap.sailing.domain.tracking;
2
-
3
-import java.io.Serializable;
4
-import java.util.Iterator;
5
-import java.util.concurrent.locks.ReadWriteLock;
6
-import java.util.function.Function;
7
-
8
-import com.sap.sailing.domain.common.tracking.GPSFixMoving;
9
-import com.sap.sailing.domain.tracking.impl.TimeRangeCache;
10
-import com.sap.sse.common.Duration;
11
-import com.sap.sse.common.TimePoint;
12
-import com.sap.sse.common.Timed;
13
-import com.sap.sse.common.scalablevalue.ScalableValue;
14
-
15
-/**
16
- * A track records {@link Timed} items for an object of type <code>ItemType</code>. It allows clients to ask for a value
17
- * close to a given {@link TimePoint}. The track manages a time-based set of raw fixes. An implementation may have an
18
- * understanding of how to eliminate outliers. For example, if a track implementation knows it's tracking boats, it may
19
- * consider fixes that the boat cannot possibly have reached due to its speed and direction change limitations as
20
- * outliers. The set of fixes with outliers filtered out can be obtained using {@link #getFixes} whereas
21
- * {@link #getRawFixes()} returns the unfiltered, raw fixes. If an implementation has no idea what an outlier is,
22
- * both methods will return the same fix sequence.<p>
23
- *
24
- * With tracks, concurrency is an important issue. Threads may want to modify a track while other threads may want to
25
- * read from it. Several methods such as {@link #getLastFixAtOrBefore(TimePoint)} return a single fix and can manage
26
- * concurrency internally. However, those methods returning a collection of fixes, such as {@link #getFixes()} or an
27
- * iterator over a collection of fixes, such as {@link #getFixesIterator(TimePoint, boolean)}, need special treatment.
28
- * Until we internalize such iterations (see bug 824, http://bugzilla.sapsailing.com/bugzilla/show_bug.cgi?id=824),
29
- * callers need to manage a read lock which is part of a {@link ReadWriteLock} managed by this track. Callers do so
30
- * by calling {@link #lockForRead} and {@link #unlockAfterRead}.
31
- *
32
- * @author Axel Uhl (d043530)
33
- */
34
-public interface Track<FixType extends Timed> extends Serializable {
35
- /**
36
- * An adding function to be used together with {@link Track#getValueSum(TimePoint, TimePoint, Object, Adder, TimeRangeCache, TimeRangeValueCalculator)}.
37
- *
38
- * @author Axel Uhl (D043530)
39
- *
40
- * @param <T>
41
- */
42
- static interface Adder<T> {
43
- /**
44
- * Adds two elements of type {@code T}. Neither argument must be {@code null}.
45
- */
46
- T add(T t1, T t2);
47
- }
48
-
49
- static interface TimeRangeValueCalculator<T> {
50
- /**
51
- * Calculates a value for fixes across a time range. When the method is called,
52
- * a read lock will previously have been {@link Track#lockForRead obtained} before,
53
- * so an implementing class does not need to worry about acquiring the lock.
54
- */
55
- T calculate(TimePoint from, TimePoint to);
56
- }
57
-
58
- /**
59
- * Locks this track for reading by the calling thread. If the thread already holds the lock for this track,
60
- * the hold count will be incremented. Make sure to call {@link #unlockAfterRead()} in a <code>finally</code>
61
- * block to release the lock under all possible circumstances. Failure to do so will inevitably lead to
62
- * deadlocks!
63
- */
64
- void lockForRead();
65
-
66
- /**
67
- * Decrements the hold count for this track's read lock for the calling thread. If it goes to zero, the lock will be
68
- * released and other readers or a writer can obtain the lock. Make sure to call this method in a
69
- * <code>finally</code> block for each {@link #lockForRead()} invocation.
70
- */
71
- void unlockAfterRead();
72
-
73
- /**
74
- * Callers must have called {@link #lockForRead()} before calling this method. This will be checked, and an exception
75
- * will be thrown in case the caller has failed to do so.
76
- *
77
- * @return the smoothened fixes
78
- */
79
- Iterable<FixType> getFixes();
80
-
81
- /**
82
- * Callers must have called {@link #lockForRead()} before calling this method. This will be checked, and an exception
83
- * will be thrown in case the caller has failed to do so.
84
- *
85
- * @return The smoothened fixes between from and to.
86
- */
87
- Iterable<FixType> getFixes(TimePoint from, boolean fromInclusive, TimePoint to, boolean toInclusive);
88
-
89
- /**
90
- * Callers must have called {@link #lockForRead()} before calling this method. This will be checked, and an exception
91
- * will be thrown in case the caller has failed to do so.
92
- */
93
- Iterable<FixType> getRawFixes();
94
-
95
- /**
96
- * Returns <code>null</code> if no such fix exists.
97
- */
98
- FixType getLastFixAtOrBefore(TimePoint timePoint);
99
-
100
- /**
101
- * Returns <code>null</code> if no such fix exists.
102
- */
103
- FixType getLastFixBefore(TimePoint timePoint);
104
-
105
- /**
106
- * Returns <code>null</code> if no such fix exists.
107
- */
108
- FixType getLastRawFixAtOrBefore(TimePoint timePoint);
109
-
110
- /**
111
- * Returns <code>null</code> if no such fix exists.
112
- */
113
- FixType getFirstFixAtOrAfter(TimePoint timePoint);
114
-
115
- /**
116
- * Returns <code>null</code> if no such fix exists.
117
- */
118
- FixType getFirstRawFixAtOrAfter(TimePoint timePoint);
119
-
120
- /**
121
- * Returns <code>null</code> if no such fix exists.
122
- */
123
- FixType getLastRawFixBefore(TimePoint timePoint);
124
-
125
- /**
126
- * Returns <code>null</code> if no such fix exists.
127
- */
128
- FixType getFirstRawFixAfter(TimePoint timePoint);
129
-
130
- /**
131
- * Returns <code>null</code> if no such fix exists.
132
- */
133
- FixType getFirstFixAfter(TimePoint timePoint);
134
-
135
- /**
136
- * The first fix in this track or <code>null</code> if the track is empty. The fix returned may
137
- * be an outlier that is not returned by calls operating on the smoothened version of the track.
138
- */
139
- FixType getFirstRawFix();
140
-
141
- /**
142
- * The last fix in this track or <code>null</code> if the track is empty. The fix returned may
143
- * be an outlier that is not returned by calls operating on the smoothened version of the track.
144
- */
145
- FixType getLastRawFix();
146
-
147
- /**
148
- * Interpolates an aspect of the fixes in this track for a given {@code timePoint}. If {@code timePoint} matches
149
- * exactly a fix in this track, that fix is used. If this track is empty, {@code null} is returned. If the
150
- * {@code timePoint} is after the last fix of this track, the last fix is used; if before the first fix, the first
151
- * fix is used.
152
- * <p>
153
- *
154
- * The fix(es) are converted to {@link ScalableValue}s using the {@code converter} which gives callers a choice
155
- * which aspect of the fixes to project and interpolate. If more than one value results because two fixes (one
156
- * before, one after) are used, linear interpolation based on the fixes' time points takes place.
157
- * <p>
158
- *
159
- * Example: for a track of {@link GPSFixMoving} fixes the course over ground shall be determined for a given time
160
- * point. The call would look like this:
161
- * {@code getInterpolatedValue(timePoint, f->new ScalableBearing(f.getSpeed().getBearing()))}
162
- *
163
- * @return the projected interpolated value, typed by what the {@link ScalableValue#divide(double)} method returns.
164
- */
165
- <InternalType, ValueType> ValueType getInterpolatedValue(TimePoint timePoint,
166
- Function<FixType, ScalableValue<InternalType, ValueType>> converter);
167
-
168
- /**
169
- * Returns an iterator starting at the first fix after <code>startingAt</code> (or "at or after" in case
170
- * <code>inclusive</code> is <code>true</code>). The fixes returned by the iterator exclude outliers (see
171
- * also {@link #getFixes()} and returns the remaining fixes without any smoothening or dampening applied.
172
- *
173
- * Callers must have called {@link #lockForRead()} before calling this method. This will be checked, and an exception
174
- * will be thrown in case the caller has failed to do so.
175
- */
176
- Iterator<FixType> getFixesIterator(TimePoint startingAt, boolean inclusive);
177
-
178
- /**
179
- * Returns an iterator starting at the first fix after <code>startingAt</code> (or "at or after" in case
180
- * <code>inclusive</code> is <code>true</code>) and that ends at the <code>endingAt</code> time point or just before
181
- * in case <code>endingAtIncluive</code> is false. The fixes returned by the iterator are the smoothened fixes (see
182
- * also {@link #getFixes()}, without any smoothening or dampening applied.
183
- *
184
- * Callers must have called {@link #lockForRead()} before calling this method. This will be checked, and an
185
- * exception will be thrown in case the caller has failed to do so.
186
- *
187
- * @param startingAt
188
- * if <code>null</code>, starts with the first fix available
189
- * @param endingAt
190
- * if <code>null</code>., ends with the last fix available
191
- */
192
- Iterator<FixType> getFixesIterator(TimePoint startingAt, boolean startingAtInclusive, TimePoint endingAt, boolean endingAtInclusive);
193
-
194
- /**
195
- * Returns an iterator starting at the first raw fix after <code>startingAt</code> (or "at or after" in case
196
- * <code>inclusive</code> is <code>true</code>). The fixes returned by the iterator are the raw fixes (see also
197
- * {@link #getRawFixes()}, without any smoothening or dampening applied.
198
- *
199
- * Callers must have called {@link #lockForRead()} before calling this method. This will be checked, and an exception
200
- * will be thrown in case the caller has failed to do so.
201
- */
202
- Iterator<FixType> getRawFixesIterator(TimePoint startingAt, boolean inclusive);
203
-
204
- /**
205
- * Returns an iterator starting at the first raw fix after <code>startingAt</code> (or "at or after" in case
206
- * <code>startingAtInclusive</code> is <code>true</code>) and ending at the <code>endingAt</code> time point or just before
207
- * in case <code>endingAtIncluive</code> is false. The fixes returned by the iterator are the raw fixes (see also
208
- * {@link #getRawFixes()}, without any smoothening or dampening applied.
209
- *
210
- * Callers must have called {@link #lockForRead()} before calling this method. This will be checked, and an exception
211
- * will be thrown in case the caller has failed to do so.
212
- */
213
- Iterator<FixType> getRawFixesIterator(TimePoint startingAt, boolean startingAtInclusive, TimePoint endingAt, boolean endingAtInclusive);
214
-
215
- /**
216
- * Returns a descending iterator starting at the first fix before <code>startingAt</code> (or "at or before" in case
217
- * <code>inclusive</code> is <code>true</code>). The fixes returned by the iterator are the smoothened fixes (see
218
- * also {@link #getFixes()}, without any smoothening or dampening applied.
219
- *
220
- * Callers must have called {@link #lockForRead()} before calling this method. This will be checked, and an exception
221
- * will be thrown in case the caller has failed to do so.
222
- */
223
- Iterator<FixType> getFixesDescendingIterator(TimePoint startingAt, boolean inclusive);
224
-
225
- /**
226
- * Returns a descending iterator starting at the first raw fix before <code>startingAt</code> (or "at or before" in case
227
- * <code>inclusive</code> is <code>true</code>). The fixes returned by the iterator are the raw fixes (see also
228
- * {@link #getRawFixes()}, without any smoothening or dampening applied.
229
- *
230
- * Callers must have called {@link #lockForRead()} before calling this method. This will be checked, and an exception
231
- * will be thrown in case the caller has failed to do so.
232
- */
233
- Iterator<FixType> getRawFixesDescendingIterator(TimePoint startingAt, boolean inclusive);
234
-
235
- /**
236
- * @return the average duration between two fixes (outliers removed) in this track or <code>null</code> if there is not
237
- * more than one fix in the track
238
- */
239
- Duration getAverageIntervalBetweenFixes();
240
-
241
- /**
242
- * @return the average duration between two fixes (outliers <em>not</em> removed) in this track or <code>null</code> if there is not
243
- * more than one raw fix in the track
244
- */
245
- Duration getAverageIntervalBetweenRawFixes();
246
-
247
- <T> T getValueSum(TimePoint from, TimePoint to, T nullElement, Adder<T> adder, TimeRangeCache<T> cache, TimeRangeValueCalculator<T> valueCalculator);
248
-
249
- /**
250
- * @return the number of raw fixes contained in the Track.
251
- */
252
- int size();
253
-
254
- /**
255
- * Tells whether the collection of {@link #getRawFixes() raw fixes} (no outliers removed) is empty
256
- */
257
- boolean isEmpty();
258
-
259
-}
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/TrackingConnectorInfo.java
... ...
@@ -1,28 +0,0 @@
1
-
2
-package com.sap.sailing.domain.tracking;
3
-
4
-import java.io.Serializable;
5
-
6
-/**
7
- * Identifies the tracking connector that was used to create a TrackedRace.
8
- * Further the Connector can provide a webUrl, that leads to an event web page.
9
- */
10
-public interface TrackingConnectorInfo extends Serializable {
11
-
12
- /**
13
- * gets the name associated with the tracking technology used for the Race
14
- */
15
- String getTrackingConnectorName();
16
-
17
- /**
18
- * gets a {@link String} representation of the default web-URL associated with the tracking technology used for the Race.
19
- * may be {@code null} if there is none provided in the adapter.
20
- */
21
- String getTrackingConnectorDefaultUrl();
22
-
23
- /**
24
- * gets a {@link String} representation of the web-URL associated with the Event.
25
- * may be {@code null} if the API of the respective Tracking-Service does not provide a URL.
26
- */
27
- String getWebUrl();
28
-}
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/impl/LineDetailsImpl.java
... ...
@@ -1,91 +0,0 @@
1
-package com.sap.sailing.domain.tracking.impl;
2
-
3
-import com.sap.sailing.domain.base.Mark;
4
-import com.sap.sailing.domain.base.Waypoint;
5
-import com.sap.sailing.domain.common.NauticalSide;
6
-import com.sap.sailing.domain.common.Position;
7
-import com.sap.sailing.domain.tracking.LineDetails;
8
-import com.sap.sse.common.Bearing;
9
-import com.sap.sse.common.Distance;
10
-import com.sap.sse.common.TimePoint;
11
-
12
-public class LineDetailsImpl implements LineDetails {
13
- private final TimePoint timePoint;
14
- private final Waypoint waypoint;
15
- private final Distance length;
16
- private final Bearing angleDifferenceFromPortToStarboardWhenApproachingLineToTrueWind;
17
- private final NauticalSide advantageousSidewhileApproachingLine;
18
- private final Distance advantage;
19
- private final Mark portMarkWhileApproachingLine;
20
- private final Mark starboardMarkWhileApproachingLine;
21
- private final Position portMarkPosition;
22
- private final Position starboardMarkPosition;
23
-
24
- public LineDetailsImpl(TimePoint timePoint, Waypoint waypoint, Distance length,
25
- Bearing angleDifferenceFromPortToStarboardWhenApproachingLineToTrueWind, NauticalSide advantageousSideWhileApproachingLine,
26
- Distance advantage, Mark portMarkWhileApproachingLine, Mark starboardMarkWhileApproachingLine,
27
- Position portMarkPosition, Position starboardMarkPosition) {
28
- super();
29
- this.timePoint = timePoint;
30
- this.waypoint = waypoint;
31
- this.length = length;
32
- this.angleDifferenceFromPortToStarboardWhenApproachingLineToTrueWind = angleDifferenceFromPortToStarboardWhenApproachingLineToTrueWind;
33
- this.advantageousSidewhileApproachingLine = advantageousSideWhileApproachingLine;
34
- this.advantage = advantage;
35
- this.portMarkWhileApproachingLine = portMarkWhileApproachingLine;
36
- this.starboardMarkWhileApproachingLine = starboardMarkWhileApproachingLine;
37
- this.portMarkPosition = portMarkPosition;
38
- this.starboardMarkPosition = starboardMarkPosition;
39
- }
40
-
41
- @Override
42
- public TimePoint getTimePoint() {
43
- return timePoint;
44
- }
45
-
46
- @Override
47
- public Waypoint getWaypoint() {
48
- return waypoint;
49
- }
50
-
51
- @Override
52
- public Distance getLength() {
53
- return length;
54
- }
55
-
56
- @Override
57
- public Bearing getAngleDifferenceFromPortToStarboardWhenApproachingLineToTrueWind() {
58
- return angleDifferenceFromPortToStarboardWhenApproachingLineToTrueWind;
59
- }
60
-
61
- @Override
62
- public NauticalSide getAdvantageousSideWhileApproachingLine() {
63
- return advantageousSidewhileApproachingLine;
64
- }
65
-
66
- @Override
67
- public Distance getAdvantage() {
68
- return advantage;
69
- }
70
-
71
- @Override
72
- public Mark getStarboardMarkWhileApproachingLine() {
73
- return starboardMarkWhileApproachingLine;
74
- }
75
-
76
- @Override
77
- public Mark getPortMarkWhileApproachingLine() {
78
- return portMarkWhileApproachingLine;
79
- }
80
-
81
- @Override
82
- public Position getPortMarkPosition() {
83
- return portMarkPosition;
84
- }
85
-
86
- @Override
87
- public Position getStarboardMarkPosition() {
88
- return starboardMarkPosition;
89
- }
90
-
91
-}
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/impl/MappedTrackImpl.java
... ...
@@ -1,39 +0,0 @@
1
-package com.sap.sailing.domain.tracking.impl;
2
-
3
-import com.sap.sailing.domain.tracking.MappedTrack;
4
-import com.sap.sse.common.Timed;
5
-import com.sap.sse.shared.util.impl.ArrayListNavigableSet;
6
-
7
-/**
8
- * Default implementation of {@link MappedTrack} interface.
9
- *
10
- * @param <ItemType>
11
- * the type of item this track is mapped to
12
- * @param <FixType>
13
- * the type of fix that is contained in this track
14
- */
15
-public class MappedTrackImpl<ItemType, FixType extends Timed> extends TrackImpl<FixType>
16
- implements MappedTrack<ItemType, FixType> {
17
-
18
- private static final long serialVersionUID = 6165693342087329096L;
19
-
20
- private final ItemType trackedItem;
21
-
22
- /** @see TrackImpl#TrackImpl(String) */
23
- public MappedTrackImpl(ItemType trackedItem, String nameForReadWriteLock) {
24
- super(nameForReadWriteLock);
25
- this.trackedItem = trackedItem;
26
- }
27
-
28
- /** @see TrackImpl#TrackImpl(ArrayListNavigableSet, String) */
29
- protected MappedTrackImpl(ItemType trackedItem, ArrayListNavigableSet<Timed> fixes, String nameForReadWriteLock) {
30
- super(fixes, nameForReadWriteLock);
31
- this.trackedItem = trackedItem;
32
- }
33
-
34
- @Override
35
- public ItemType getTrackedItem() {
36
- return trackedItem;
37
- }
38
-
39
-}
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/impl/PartialNavigableSetView.java
... ...
@@ -1,387 +0,0 @@
1
-package com.sap.sailing.domain.tracking.impl;
2
-
3
-import java.util.ArrayList;
4
-import java.util.Collection;
5
-import java.util.Comparator;
6
-import java.util.Iterator;
7
-import java.util.List;
8
-import java.util.NavigableSet;
9
-import java.util.NoSuchElementException;
10
-import java.util.SortedSet;
11
-import java.util.TreeSet;
12
-
13
-/**
14
- * A view on a {@link NavigableSet} which suppresses some entries based on some configurable rule.
15
- * The {@link #size()} operation is expensive because it requires a full scan. {@link #isEmpty()} is
16
- * much cheaper because it suffices to find one element passing the filter rule. The filtering rule
17
- * has to be expressed by subclsses implementing the {@link #isValid(Object)} method.
18
- *
19
- * @author Axel Uhl (d043530)
20
- *
21
- * @param <E>
22
- */
23
-public abstract class PartialNavigableSetView<E> implements NavigableSet<E> {
24
- private final NavigableSet<E> set;
25
-
26
- private class PartialNavigableSetViewWithSameValidityAsEnclosing extends PartialNavigableSetView<E> {
27
- public PartialNavigableSetViewWithSameValidityAsEnclosing(NavigableSet<E> set) {
28
- super(set);
29
- }
30
-
31
- @Override
32
- protected boolean isValid(E e) {
33
- return PartialNavigableSetView.this.isValid(e);
34
- }
35
- }
36
-
37
- private class FilteringIterator implements Iterator<E> {
38
- /**
39
- * The iterator is always kept one step "ahead" in order to know whether there really is a next element. The
40
- * next valid element is fetched and stored in {@link #nextValid} and {@link #hasNext} is set to
41
- * <code>true</code>.
42
- */
43
- private Iterator<E> iter;
44
-
45
- private E nextValid;
46
-
47
- private boolean hasNext;
48
-
49
- private boolean hasLastNext;
50
-
51
- private E lastNext;
52
-
53
- public FilteringIterator() {
54
- iter = getSet().iterator();
55
- hasLastNext = false;
56
- advance();
57
- }
58
-
59
- private void advance() {
60
- if (iter.hasNext()) {
61
- E next = iter.next();
62
- while (!isValid(next) && iter.hasNext()) {
63
- next = iter.next();
64
- }
65
- if (isValid(next)) {
66
- nextValid = next;
67
- hasNext = true;
68
- } else {
69
- hasNext = false;
70
- }
71
- } else {
72
- hasNext = false;
73
- }
74
- }
75
-
76
- @Override
77
- public boolean hasNext() {
78
- return hasNext;
79
- }
80
-
81
- @Override
82
- public E next() {
83
- if (hasNext) {
84
- E result = nextValid;
85
- advance();
86
- hasLastNext = true;
87
- lastNext = result;
88
- return result;
89
- } else {
90
- throw new NoSuchElementException();
91
- }
92
- }
93
-
94
- @Override
95
- public void remove() {
96
- if (!hasLastNext) {
97
- throw new IllegalStateException("next() was not called before remove()");
98
- } else {
99
- PartialNavigableSetView.this.remove(lastNext);
100
- hasLastNext = false;
101
- }
102
- }
103
- }
104
-
105
- public PartialNavigableSetView(NavigableSet<E> set) {
106
- this.set = set;
107
- }
108
-
109
- /**
110
- * Subclasses need to implement this method. For elements to be eliminated from the view represented by this
111
- * object, return <code>false</code> for such an element being passed to this method.
112
- */
113
- abstract protected boolean isValid(E e);
114
-
115
- @Override
116
- public Comparator<? super E> comparator() {
117
- return getSet().comparator();
118
- }
119
-
120
- public NavigableSet<E> descendingSet() {
121
- return new PartialNavigableSetViewWithSameValidityAsEnclosing(getSet().descendingSet());
122
- }
123
-
124
- @Override
125
- public Iterator<E> descendingIterator() {
126
- return descendingSet().iterator();
127
- }
128
-
129
- @Override
130
- public E first() {
131
- E first = getSet().first();
132
- while (first != null && !isValid(first)) {
133
- first = getSet().higher(first);
134
- }
135
- if (first == null) {
136
- throw new NoSuchElementException();
137
- } else {
138
- return first;
139
- }
140
- }
141
-
142
- @Override
143
- public E last() {
144
- E last = getSet().last();
145
- while (last != null && !isValid(last)) {
146
- last = getSet().lower(last);
147
- }
148
- if (last == null) {
149
- throw new NoSuchElementException();
150
- } else {
151
- return last;
152
- }
153
- }
154
-
155
- @Override
156
- public int size() {
157
- int size = 0;
158
- for (E e : getSet()) {
159
- if (isValid(e)) {
160
- size++;
161
- }
162
- }
163
- return size;
164
- }
165
-
166
- @Override
167
- public boolean isEmpty() {
168
- for (E e : getSet()) {
169
- if (isValid(e)) {
170
- return false;
171
- }
172
- }
173
- return true;
174
- }
175
-
176
- @SuppressWarnings("unchecked")
177
- @Override
178
- public boolean contains(Object o) {
179
- return getSet().contains(o) && isValid((E) o);
180
- }
181
-
182
- @Override
183
- public Object[] toArray() {
184
- List<E> l = new ArrayList<E>();
185
- for (E e : getSet()) {
186
- if (isValid(e)) {
187
- l.add(e);
188
- }
189
- }
190
- return l.toArray();
191
- }
192
-
193
- @SuppressWarnings("unchecked")
194
- @Override
195
- public <T> T[] toArray(T[] a) {
196
- List<T> l = new ArrayList<T>();
197
- for (E e : getSet()) {
198
- if (isValid(e)) {
199
- l.add((T) e);
200
- }
201
- }
202
- return l.toArray(a);
203
-
204
- }
205
-
206
- @Override
207
- public boolean add(E e) {
208
- return getSet().add(e);
209
- }
210
-
211
- @Override
212
- public boolean remove(Object o) {
213
- return getSet().remove(o);
214
- }
215
-
216
- @SuppressWarnings("unchecked")
217
- @Override
218
- public boolean containsAll(Collection<?> c) {
219
- for (Object o : c) {
220
- if (!isValid((E) o) || !getSet().contains(o)) {
221
- return false;
222
- }
223
- }
224
- return true;
225
- }
226
-
227
- @Override
228
- public boolean addAll(Collection<? extends E> c) {
229
- return getSet().addAll(c);
230
- }
231
-
232
- @Override
233
- public boolean retainAll(Collection<?> c) {
234
- return getSet().retainAll(c);
235
- }
236
-
237
- @Override
238
- public boolean removeAll(Collection<?> c) {
239
- return getSet().removeAll(c);
240
- }
241
-
242
- @Override
243
- public void clear() {
244
- getSet().clear();
245
- }
246
-
247
- @Override
248
- public E lower(E e) {
249
- E result = getSet().lower(e);
250
- while (result != null && !isValid(result)) {
251
- result = getSet().lower(result);
252
- }
253
- return result;
254
- }
255
-
256
- /**
257
- * goes one left on the raw, unfiltered set and therefore may return fixes that have {@link #isValid(Object)}==
258
- * <code>false</code>
259
- */
260
- protected E lowerInternal(E e) {
261
- return getSet().lower(e);
262
- }
263
-
264
- /**
265
- * goes one right on the raw, unfiltered set and therefore may return fixes that have {@link #isValid(Object)}==
266
- * <code>false</code>
267
- */
268
- protected E higherInternal(E e) {
269
- return getSet().higher(e);
270
- }
271
-
272
- @Override
273
- public E floor(E e) {
274
- E result = getSet().floor(e);
275
- while (result != null && !isValid(result)) {
276
- result = getSet().lower(result);
277
- }
278
- return result;
279
- }
280
-
281
- @Override
282
- public E ceiling(E e) {
283
- E result = getSet().ceiling(e);
284
- while (result != null && !isValid(result)) {
285
- result = getSet().higher(result);
286
- }
287
- return result;
288
- }
289
-
290
- @Override
291
- public E higher(E e) {
292
- E result = getSet().higher(e);
293
- while (result != null && !isValid(result)) {
294
- result = getSet().higher(result);
295
- }
296
- return result;
297
- }
298
-
299
- /**
300
- * Removes all raw fixes that have {@link #isValid(Object)}==<code>false</code> and the first element to have
301
- * {@link #isValid(Object)}==<code>true</code>. This latter element is returned. If no such element exists,
302
- * <code>null</code> is returned. It is hence possible that invalid raw fixes are removed but stil <code>null</code>
303
- * is returned.
304
- */
305
- @Override
306
- public E pollFirst() {
307
- E result = getSet().first();
308
- while (result != null && !isValid(result)) {
309
- getSet().remove(result);
310
- result = getSet().first();
311
- }
312
- return result;
313
- }
314
-
315
- @Override
316
- public E pollLast() {
317
- E result = getSet().last();
318
- while (result != null && !isValid(result)) {
319
- getSet().remove(result);
320
- result = getSet().last();
321
- }
322
- return result;
323
- }
324
-
325
- @Override
326
- public NavigableSet<E> subSet(E fromElement, boolean fromInclusive, E toElement, boolean toInclusive) {
327
- return new PartialNavigableSetViewWithSameValidityAsEnclosing(getSet().subSet(fromElement, fromInclusive, toElement, toInclusive));
328
- }
329
-
330
- @Override
331
- public NavigableSet<E> headSet(E toElement, boolean inclusive) {
332
- return new PartialNavigableSetViewWithSameValidityAsEnclosing(getSet().headSet(toElement, inclusive));
333
- }
334
-
335
- @Override
336
- public NavigableSet<E> tailSet(E fromElement, boolean inclusive) {
337
- return new PartialNavigableSetViewWithSameValidityAsEnclosing(getSet().tailSet(fromElement, inclusive));
338
- }
339
-
340
- @Override
341
- public NavigableSet<E> subSet(E fromElement, E toElement) {
342
- SortedSet<E> subSet = set.subSet(fromElement, toElement);
343
- if (subSet instanceof NavigableSet<?>) {
344
- return new PartialNavigableSetViewWithSameValidityAsEnclosing((NavigableSet<E>) subSet);
345
- } else {
346
- TreeSet<E> result = new TreeSet<E>(subSet);
347
- return new PartialNavigableSetViewWithSameValidityAsEnclosing(result);
348
- }
349
- }
350
-
351
- @Override
352
- public NavigableSet<E> headSet(E toElement) {
353
- SortedSet<E> headSet = set.headSet(toElement);
354
- if (headSet instanceof NavigableSet<?>) {
355
- return new PartialNavigableSetViewWithSameValidityAsEnclosing((NavigableSet<E>) headSet);
356
- } else {
357
- TreeSet<E> result = new TreeSet<E>(headSet);
358
- return new PartialNavigableSetViewWithSameValidityAsEnclosing(result);
359
- }
360
- }
361
-
362
- @Override
363
- public NavigableSet<E> tailSet(E fromElement) {
364
- SortedSet<E> tailSet = set.tailSet(fromElement);
365
- if (tailSet instanceof NavigableSet<?>) {
366
- return new PartialNavigableSetViewWithSameValidityAsEnclosing((NavigableSet<E>) tailSet);
367
- } else {
368
- TreeSet<E> result = new TreeSet<E>(tailSet);
369
- return new PartialNavigableSetViewWithSameValidityAsEnclosing(result);
370
- }
371
- }
372
-
373
-
374
- @Override
375
- public Iterator<E> iterator() {
376
- return new FilteringIterator();
377
- }
378
-
379
- @Override
380
- public String toString() {
381
- return new ArrayList<E>(this).toString();
382
- }
383
-
384
- protected NavigableSet<E> getSet() {
385
- return set;
386
- }
387
-}
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/impl/TimeRangeCache.java
... ...
@@ -1,221 +0,0 @@
1
-package com.sap.sailing.domain.tracking.impl;
2
-
3
-import java.util.Comparator;
4
-import java.util.Iterator;
5
-import java.util.LinkedHashMap;
6
-import java.util.Map.Entry;
7
-import java.util.NavigableSet;
8
-
9
-import com.sap.sse.common.TimePoint;
10
-import com.sap.sse.common.Util;
11
-import com.sap.sse.common.Util.Pair;
12
-import com.sap.sse.concurrent.LockUtil;
13
-import com.sap.sse.concurrent.NamedReentrantReadWriteLock;
14
-import com.sap.sse.shared.util.impl.ArrayListNavigableSet;
15
-
16
-/**
17
- * This cache looks "backwards." It contains pairs whose first component represents a <code>to</code> parameter used in
18
- * a calculation for a time range. It is ordered by this component. The second component is a navigable, ordered set of
19
- * pairs where the first pair component represents a <code>from</code> parameter used in the calculation's time range
20
- * and the second pair component represents the result of the calculation for this parameter combination.
21
- * <p>
22
- *
23
- * For implementation efficiency in combination with using an {@link ArrayListNavigableSet} for the values and in order
24
- * to be able to efficiently extend a cache entry for a single <code>to</code> fix, the navigable sets containing the
25
- * <code>from</code> fixes and results are ordered such that earlier fixes come later in the set. This way, extending
26
- * the cache entry for a <code>to</code> fix to an earlier <code>from</code> fix only requires appending to the set.
27
- * <p>
28
- *
29
- * <b>Invalidation</b>: When a new fix is added to the track, all cache entries for fixes at or later than the new fix's
30
- * time point are removed from this cache. Additionally, the fix insertion may have an impact on the
31
- * {@link #getEarliestFromAndResultAtOrAfterFrom(TimePoint, TimePoint) previous fix's} validity (track smoothing) and
32
- * therefore on its selection for result aggregation. Therefore, if fix addition turned the previous fix invalid, the
33
- * cache entries for the time points at or after the previous fix also need to be removed.
34
- * <p>
35
- *
36
- * <b>Cache use</b>: When a result across a time range is to be computed the calculating method should first look for a
37
- * cache entry for the <code>to</code> parameter. If one is found, the earliest entry in the navigable set for the
38
- * navigable set of <code>from</code> and result values that is at or after the requested <code>from</code> time point
39
- * is determined. If such an entry exists, the result is remembered and the algorithm is repeated recursively, using the
40
- * <code>from</code> value found in the cache as the new <code>to</code> value, and the <code>from</code> value
41
- * originally passed to the calculating method as <code>from</code> again. If no entry is found in the cache entry for
42
- * <code>to</code> that is at or after the requested <code>from</code> time, the result has to be computed "from
43
- * scratch."
44
- * <p>
45
- *
46
- * If a cache entry for <code>to</code> is not found, the latest cache entry before it is looked up. If one is found,
47
- * the result for the time range between the <code>to</code> time point requested and the <code>to</code> time point
48
- * found in the cache is computed by iterating the smoothened fixes for this interval. If none is found, the result is
49
- * computed by iterating backwards all the way to <code>from</code>.
50
- * <p>
51
- *
52
- * Once the calculating method has computed its value, it should {@link #cache(TimePoint, TimePoint, Object) add} the
53
- * result to the cache.
54
- *
55
- * @author Axel Uhl (D043530)
56
- */
57
-public class TimeRangeCache<T> {
58
- public static final int MAX_SIZE = 100;
59
-
60
- private final NavigableSet<Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, T>>>> timeRangeCache;
61
-
62
- /**
63
- * The cache is to have limited size. Eviction shall happen based on a least-recently-used strategy. Usage is
64
- * defined as having been returned by {@link #getEarliestFromAndResultAtOrAfterFrom(TimePoint, TimePoint)} or
65
- * having been added by {@link #cache(TimePoint, TimePoint, Object)}.
66
- * <p>
67
- *
68
- * When an eldest entry is asked to be expunged from this map and the map has more than {@link #MAX_SIZE} elements,
69
- * the expunging will be admitted, and the entry is removed from the {@link #timeRangeCache} core structure. Reading
70
- * and writing this structure must happen under the {@link #lock write lock} because also reading the linked hash
71
- * map that counts access as "use" has a modifying effect on its internal structures.
72
- * <p>
73
- *
74
- * The key pairs are from/to pairs. Note that this is in some sense "the opposite direction" compared to the
75
- * alignment of the {@link #timeRangeCache} structure which has as its outer keys the "to" time point.<p>
76
- *
77
- * Read access is to be <code>synchronized<code> using this field's mutex; write access only happens under the
78
- * {@link #lock write lock} and therefore will have no contenders.
79
- */
80
- private final LinkedHashMap<Util.Pair<TimePoint, TimePoint>, Void> lruCache;
81
-
82
- private final NamedReentrantReadWriteLock lock;
83
-
84
- private static final Comparator<Util.Pair<TimePoint, ?>> timePointInPairComparator = new Comparator<Util.Pair<TimePoint, ?>>() {
85
- @Override
86
- public int compare(Util.Pair<TimePoint, ?> o1, Util.Pair<TimePoint, ?> o2) {
87
- return o1.getA().compareTo(o2.getA());
88
- }
89
- };
90
-
91
- public TimeRangeCache(String nameForLockLogging) {
92
- lock = new NamedReentrantReadWriteLock("lock for TimeRangeCache for "+nameForLockLogging, /* fair */ true);
93
- this.timeRangeCache = new ArrayListNavigableSet<Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, T>>>>(timePointInPairComparator);
94
- this.lruCache = new LinkedHashMap<Util.Pair<TimePoint, TimePoint>, Void>(/* initial capacity */ 10, /* load factor */ 0.75f,
95
- /* access-based ordering */ true) {
96
- private static final long serialVersionUID = -6568235517111733193L;
97
-
98
- @Override
99
- protected boolean removeEldestEntry(Entry<Pair<TimePoint, TimePoint>, Void> eldest) {
100
- final boolean expunge = size() > MAX_SIZE;
101
- if (expunge) {
102
- removeCacheEntry(eldest.getKey().getA(), eldest.getKey().getB());
103
- }
104
- return expunge;
105
- }
106
- };
107
- }
108
-
109
- public int size() {
110
- return lruCache.size();
111
- }
112
-
113
- private void removeCacheEntry(TimePoint from, TimePoint to) {
114
- assert lock.getWriteHoldCount() == 1; // we can be sure we are alone here; this only happens when adding a new entry, holding the write lock
115
- Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, T>>> entryForTo = timeRangeCache.floor(createDummy(to));
116
- if (entryForTo.getA().equals(to)) {
117
- Pair<TimePoint, T> entryForFrom = entryForTo.getB().ceiling(new Util.Pair<TimePoint, T>(from, null));
118
- if (entryForFrom.getA().equals(from)) {
119
- entryForTo.getB().remove(entryForFrom);
120
- if (entryForTo.getB().isEmpty()) {
121
- timeRangeCache.remove(entryForTo);
122
- }
123
- }
124
- }
125
- }
126
-
127
- /**
128
- * Looks up the entry closest to but no later than <code>to</code>. If not found, <code>null</code> is returned. If
129
- * found, the earliest pair of from/result that is at or after <code>from</code> will be returned, together with
130
- * the <code>to</code> value of the entry. If there is no entry that is at or after <code>from</code>,
131
- * <code>null</code> is returned.
132
- */
133
- public Util.Pair<TimePoint, Util.Pair<TimePoint, T>> getEarliestFromAndResultAtOrAfterFrom(TimePoint from, TimePoint to) {
134
- LockUtil.lockForRead(lock);
135
- try {
136
- Util.Pair<TimePoint, Util.Pair<TimePoint, T>> result = null;
137
- Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, T>>> entryForTo = timeRangeCache.floor(createDummy(to));
138
- if (entryForTo != null) {
139
- final Util.Pair<TimePoint, T> fromCeiling = entryForTo.getB().ceiling(new Util.Pair<TimePoint, T>(from, null));
140
- if (fromCeiling != null) {
141
- result = new Util.Pair<TimePoint, Util.Pair<TimePoint, T>>(entryForTo.getA(), fromCeiling);
142
- }
143
- }
144
- // no writer can be active because we're holding the read lock; read access on the lruCache is synchronized using
145
- // the lruCache's mutex; this is necessary because we're using access-based LRU pinging where even getting an entry
146
- // modifies the internal parts of the data structure which is not thread safe.
147
- synchronized (lruCache) { // ping the "perfect match" although it may not even have existed in the cache
148
- lruCache.get(new Util.Pair<TimePoint, TimePoint>(from, to));
149
- }
150
- return result;
151
- } finally {
152
- LockUtil.unlockAfterRead(lock);
153
- }
154
- }
155
-
156
- /**
157
- * Removes all cache entries that have a <code>to</code> time point that is at or after <code>timePoint</code>.
158
- */
159
- public void invalidateAllAtOrLaterThan(TimePoint timePoint) {
160
- LockUtil.lockForWrite(lock);
161
- try {
162
- Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, T>>> dummy = createDummy(timePoint);
163
- NavigableSet<Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, T>>>> toRemove = timeRangeCache.tailSet(dummy, /* inclusive */ true);
164
- for (Iterator<Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, T>>>> i=toRemove.iterator(); i.hasNext(); ) {
165
- Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, T>>> entryToRemove = i.next();
166
- assert entryToRemove.getA().compareTo(timePoint) >= 0;
167
- for (Pair<TimePoint, T> fromAndResult : entryToRemove.getB()) {
168
- lruCache.remove(new Util.Pair<>(fromAndResult.getA(), entryToRemove.getA()));
169
- }
170
- i.remove();
171
- }
172
- } finally {
173
- LockUtil.unlockAfterWrite(lock);
174
- }
175
- }
176
-
177
- private NavigableSet<Util.Pair<TimePoint, T>> getEntryForTo(TimePoint to) {
178
- NavigableSet<Util.Pair<TimePoint, T>> result = null;
179
- Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, T>>> dummyForTo = createDummy(to);
180
- Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, T>>> entryForTo = timeRangeCache.floor(dummyForTo);
181
- if (entryForTo != null && entryForTo.getA().equals(to)) {
182
- result = entryForTo.getB();
183
- }
184
- return result;
185
- }
186
-
187
- public void cache(TimePoint from, TimePoint to, T result) {
188
- LockUtil.lockForWrite(lock);
189
- try {
190
- NavigableSet<Util.Pair<TimePoint, T>> entryForTo = getEntryForTo(to);
191
- if (entryForTo == null) {
192
- entryForTo = new ArrayListNavigableSet<Util.Pair<TimePoint, T>>(timePointInPairComparator);
193
- Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, T>>> pairForTo = new Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, T>>>(
194
- to, entryForTo);
195
- timeRangeCache.add(pairForTo);
196
- }
197
- entryForTo.add(new Util.Pair<TimePoint, T>(from, result));
198
- lruCache.put(new Util.Pair<TimePoint, TimePoint>(from, to), null);
199
- } finally {
200
- LockUtil.unlockAfterWrite(lock);
201
- }
202
- }
203
-
204
- private Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, T>>> createDummy(TimePoint to) {
205
- return new Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, T>>>(to, null);
206
- }
207
-
208
- /**
209
- * Removes all contents from this cache
210
- */
211
- public void clear() {
212
- LockUtil.lockForWrite(lock);
213
- try {
214
- timeRangeCache.clear();
215
- lruCache.clear();
216
- } finally {
217
- LockUtil.unlockAfterWrite(lock);
218
- }
219
-
220
- }
221
-}
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/impl/TimedComparator.java
... ...
@@ -1,17 +0,0 @@
1
-package com.sap.sailing.domain.tracking.impl;
2
-
3
-import java.io.Serializable;
4
-import java.util.Comparator;
5
-
6
-import com.sap.sse.common.Timed;
7
-
8
-public class TimedComparator implements Comparator<Timed>, Serializable {
9
- private static final long serialVersionUID = 1604511471599854988L;
10
- public static final Comparator<Timed> INSTANCE = new TimedComparator();
11
-
12
- @Override
13
- public int compare(Timed o1, Timed o2) {
14
- return o1.getTimePoint().compareTo(o2.getTimePoint());
15
- }
16
-}
17
-
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/impl/TrackImpl.java
... ...
@@ -1,544 +0,0 @@
1
-package com.sap.sailing.domain.tracking.impl;
2
-
3
-import java.io.IOException;
4
-import java.io.ObjectOutputStream;
5
-import java.util.ConcurrentModificationException;
6
-import java.util.Iterator;
7
-import java.util.NavigableSet;
8
-import java.util.function.Function;
9
-
10
-import com.sap.sailing.domain.tracking.AddResult;
11
-import com.sap.sailing.domain.tracking.FixAcceptancePredicate;
12
-import com.sap.sailing.domain.tracking.Track;
13
-import com.sap.sse.common.Duration;
14
-import com.sap.sse.common.TimePoint;
15
-import com.sap.sse.common.Timed;
16
-import com.sap.sse.common.Util;
17
-import com.sap.sse.common.Util.Pair;
18
-import com.sap.sse.common.scalablevalue.ScalableValue;
19
-import com.sap.sse.concurrent.LockUtil;
20
-import com.sap.sse.concurrent.NamedReentrantReadWriteLock;
21
-import com.sap.sse.shared.util.impl.ArrayListNavigableSet;
22
-import com.sap.sse.shared.util.impl.UnmodifiableNavigableSet;
23
-
24
-public class TrackImpl<FixType extends Timed> implements Track<FixType> {
25
- private static final long serialVersionUID = -4075853657857657528L;
26
- /**
27
- * The fixes, ordered by their time points
28
- */
29
- private final ArrayListNavigableSet<Timed> fixes;
30
-
31
- private final NamedReentrantReadWriteLock readWriteLock;
32
-
33
- protected static class DummyTimed implements Timed {
34
- private static final long serialVersionUID = 6047311973718918856L;
35
- private final TimePoint timePoint;
36
- public DummyTimed(TimePoint timePoint) {
37
- super();
38
- this.timePoint = timePoint;
39
- }
40
- @Override
41
- public TimePoint getTimePoint() {
42
- return timePoint;
43
- }
44
- @Override
45
- public String toString() {
46
- return timePoint.toString();
47
- }
48
- }
49
-
50
- public TrackImpl(String nameForReadWriteLock) {
51
- this(new ArrayListNavigableSet<Timed>(TimedComparator.INSTANCE), nameForReadWriteLock);
52
- }
53
-
54
- protected TrackImpl(ArrayListNavigableSet<Timed> fixes, String nameForReadWriteLock) {
55
- this.readWriteLock = new NamedReentrantReadWriteLock(nameForReadWriteLock, /* fair */ false);
56
- this.fixes = fixes;
57
- }
58
-
59
- /**
60
- * Synchronize the serialization such that no fixes are added while serializing
61
- */
62
- private void writeObject(ObjectOutputStream s) throws IOException {
63
- lockForRead();
64
- try {
65
- s.defaultWriteObject();
66
- } finally {
67
- unlockAfterRead();
68
- }
69
- }
70
-
71
- @Override
72
- public void lockForRead() {
73
- LockUtil.lockForRead(readWriteLock);
74
- }
75
-
76
- @Override
77
- public void unlockAfterRead() {
78
- LockUtil.unlockAfterRead(readWriteLock);
79
- }
80
-
81
- protected void lockForWrite() {
82
- LockUtil.lockForWrite(readWriteLock);
83
- }
84
-
85
- protected void unlockAfterWrite() {
86
- LockUtil.unlockAfterWrite(readWriteLock);
87
- }
88
-
89
- /**
90
- * Callers that want to iterate over the collection returned need to use {@link #lockForRead()} and {@link #unlockAfterRead()}
91
- * to avoid {@link ConcurrentModificationException}s. Should they modify the structure returned, they have to use
92
- * {@link #lockForWrite()} and {@link #unlockAfterWrite()}, respectively.
93
- */
94
- protected NavigableSet<FixType> getInternalRawFixes() {
95
- @SuppressWarnings("unchecked")
96
- NavigableSet<FixType> result = (NavigableSet<FixType>) fixes;
97
- return result;
98
- }
99
-
100
- /**
101
- * asserts that the calling thread holds at least one of read and write lock
102
- */
103
- protected void assertReadLock() {
104
- if (readWriteLock.getReadHoldCount() < 1 && readWriteLock.getWriteHoldCount() < 1) {
105
- throw new IllegalStateException("Caller must obtain read lock using lockForRead() before calling this method");
106
- }
107
- }
108
-
109
- protected void assertWriteLock() {
110
- if (readWriteLock.getWriteHoldCount() < 1) {
111
- throw new IllegalStateException("Caller must obtain write lock using lockForWrite() before calling this method");
112
- }
113
- }
114
-
115
- /**
116
- * Callers that want to iterate over the collection returned need to use {@link #lockForRead()} and
117
- * {@link #unlockAfterRead()} to avoid {@link ConcurrentModificationException}s.
118
- *
119
- * @return the smoothened fixes ordered by their time points; this implementation simply delegates to
120
- * {@link #getInternalRawFixes()} because for only {@link Timed} fixes we can't know how to remove outliers.
121
- * Subclasses that constrain the <code>FixType</code> may provide smoothening implementations.
122
- */
123
- protected NavigableSet<FixType> getInternalFixes() {
124
- NavigableSet<FixType> result = getInternalRawFixes();
125
- return result;
126
- }
127
-
128
- /**
129
- * Iterates the fixes with outliers getting skipped, in the order of their time points.
130
- * Relies on {@link #getInternalFixes()} to void the track view from outliers.
131
- */
132
- @Override
133
- public NavigableSet<FixType> getFixes() {
134
- assertReadLock();
135
- return new UnmodifiableNavigableSet<FixType>(getInternalFixes());
136
- }
137
-
138
- @Override
139
- public Iterable<FixType> getFixes(TimePoint from, boolean fromInclusive, TimePoint to, boolean toInclusive) {
140
- return getFixes().subSet(getDummyFix(from), fromInclusive, getDummyFix(to), toInclusive);
141
- }
142
-
143
- /**
144
- * Iterates over the raw sequence of fixes, all potential outliers included
145
- */
146
- @Override
147
- public NavigableSet<FixType> getRawFixes() {
148
- assertReadLock();
149
- return new UnmodifiableNavigableSet<FixType>(getInternalRawFixes());
150
- }
151
-
152
- @Override
153
- public FixType getLastFixAtOrBefore(TimePoint timePoint) {
154
- return getLastFixAtOrBefore(timePoint, /* fixAcceptancePredicate == null means accept all */ null);
155
- }
156
-
157
- private FixType getLastFixAtOrBefore(TimePoint timePoint, FixAcceptancePredicate<FixType> fixAcceptancePredicate) {
158
- lockForRead();
159
- try {
160
- final NavigableSet<FixType> headSet = getInternalFixes().headSet(getDummyFix(timePoint), /* inclusive */ true);
161
- for (final Iterator<FixType> i=headSet.descendingIterator(); i.hasNext(); ) {
162
- final FixType next = i.next();
163
- if (fixAcceptancePredicate == null || fixAcceptancePredicate.isAcceptFix(next)) {
164
- return next;
165
- }
166
- }
167
- return null;
168
- } finally {
169
- unlockAfterRead();
170
- }
171
- }
172
-
173
- @Override
174
- public FixType getLastFixBefore(TimePoint timePoint) {
175
- lockForRead();
176
- try {
177
- return (FixType) getInternalFixes().lower(getDummyFix(timePoint));
178
- } finally {
179
- unlockAfterRead();
180
- }
181
- }
182
-
183
- @Override
184
- public FixType getLastRawFixAtOrBefore(TimePoint timePoint) {
185
- lockForRead();
186
- try {
187
- return (FixType) getInternalRawFixes().floor(getDummyFix(timePoint));
188
- } finally {
189
- unlockAfterRead();
190
- }
191
- }
192
-
193
- @Override
194
- public FixType getFirstRawFixAtOrAfter(TimePoint timePoint) {
195
- lockForRead();
196
- try {
197
- return (FixType) getInternalRawFixes().ceiling(getDummyFix(timePoint));
198
- } finally {
199
- unlockAfterRead();
200
- }
201
- }
202
-
203
- @Override
204
- public FixType getFirstFixAtOrAfter(TimePoint timePoint) {
205
- return getFirstFixAtOrAfter(timePoint, /* fixAcceptancePredicate==null means accept all fixes */ null);
206
- }
207
-
208
- private FixType getFirstFixAtOrAfter(TimePoint timePoint, FixAcceptancePredicate<FixType> fixAcceptancePredicate) {
209
- lockForRead();
210
- try {
211
- final NavigableSet<FixType> tailSet = getInternalFixes().tailSet(getDummyFix(timePoint), /* inclusive */ true);
212
- for (final FixType next : tailSet) {
213
- if (fixAcceptancePredicate == null || fixAcceptancePredicate.isAcceptFix(next)) {
214
- return next;
215
- }
216
- }
217
- return null;
218
- } finally {
219
- unlockAfterRead();
220
- }
221
- }
222
-
223
- @Override
224
- public FixType getLastRawFixBefore(TimePoint timePoint) {
225
- lockForRead();
226
- try {
227
- return (FixType) getInternalRawFixes().lower(getDummyFix(timePoint));
228
- } finally {
229
- unlockAfterRead();
230
- }
231
- }
232
-
233
- @Override
234
- public FixType getFirstFixAfter(TimePoint timePoint) {
235
- lockForRead();
236
- try {
237
- return (FixType) getInternalFixes().higher(getDummyFix(timePoint));
238
- } finally {
239
- unlockAfterRead();
240
- }
241
- }
242
-
243
- @Override
244
- public FixType getFirstRawFixAfter(TimePoint timePoint) {
245
- lockForRead();
246
- try {
247
- return (FixType) getInternalRawFixes().higher(getDummyFix(timePoint));
248
- } finally {
249
- unlockAfterRead();
250
- }
251
- }
252
-
253
- @Override
254
- public FixType getFirstRawFix() {
255
- lockForRead();
256
- try {
257
- if (getInternalFixes().isEmpty()) {
258
- return null;
259
- } else {
260
- return (FixType) getInternalFixes().first();
261
- }
262
- } finally {
263
- unlockAfterRead();
264
- }
265
- }
266
-
267
- @Override
268
- public FixType getLastRawFix() {
269
- lockForRead();
270
- try {
271
- if (getInternalRawFixes().isEmpty()) {
272
- return null;
273
- } else {
274
- return (FixType) getInternalRawFixes().last();
275
- }
276
- } finally {
277
- unlockAfterRead();
278
- }
279
- }
280
-
281
- /**
282
- * @param fixAcceptancePredicate
283
- * if not {@code null}, adjacent fixes will be skipped as long as this predicate does not
284
- * {@link FixAcceptancePredicate#isAcceptFix(Object) accept} the fix. This can, e.g., be used to skip
285
- * fixes that don't have values in a dimension required. If {@code null}, the next fixes left and right
286
- * (including the exact {@code timePoint} if a fix exists there) will be used without further check.
287
- */
288
- private Pair<FixType, FixType> getSurroundingFixes(TimePoint timePoint, FixAcceptancePredicate<FixType> fixAcceptancePredicate) {
289
- FixType left = getLastFixAtOrBefore(timePoint, fixAcceptancePredicate);
290
- FixType right = getFirstFixAtOrAfter(timePoint, fixAcceptancePredicate);
291
- com.sap.sse.common.Util.Pair<FixType, FixType> result = new com.sap.sse.common.Util.Pair<>(left, right);
292
- return result;
293
- }
294
-
295
- private <V, T> T timeBasedAverage(TimePoint timePoint, ScalableValue<V, T> value1, TimePoint timePoint1, ScalableValue<V, T> value2, TimePoint timePoint2) {
296
- final T acc;
297
- if (timePoint1.equals(timePoint2)) {
298
- acc = value1.add(value2).divide(2);
299
- } else {
300
- long timeDiff1 = Math.abs(timePoint1.asMillis() - timePoint.asMillis());
301
- long timeDiff2 = Math.abs(timePoint2.asMillis() - timePoint.asMillis());
302
- acc = value1.multiply(timeDiff2).add(value2.multiply(timeDiff1)).divide(timeDiff1 + timeDiff2);
303
- }
304
- return acc;
305
- }
306
-
307
- @Override
308
- public <InternalType, ValueType> ValueType getInterpolatedValue(TimePoint timePoint,
309
- Function<FixType, ScalableValue<InternalType, ValueType>> converter) {
310
- return getInterpolatedValue(timePoint, converter, /* fixAcceptancePredicate==null means accept all */ null);
311
- }
312
-
313
- protected <InternalType, ValueType> ValueType getInterpolatedValue(TimePoint timePoint,
314
- Function<FixType, ScalableValue<InternalType, ValueType>> converter, FixAcceptancePredicate<FixType> fixAcceptancePredicate) {
315
- final ValueType result;
316
- Pair<FixType, FixType> fixPair = getSurroundingFixes(timePoint, fixAcceptancePredicate);
317
- if (fixPair.getA() == null) {
318
- if (fixPair.getB() == null) {
319
- result = null;
320
- } else {
321
- result = converter.apply(fixPair.getB()).divide(1);
322
- }
323
- } else {
324
- if (fixPair.getB() == null || fixPair.getA() == fixPair.getB()) {
325
- result = converter.apply(fixPair.getA()).divide(1);
326
- } else {
327
- result = timeBasedAverage(timePoint,
328
- converter.apply(fixPair.getA()), fixPair.getA().getTimePoint(),
329
- converter.apply(fixPair.getB()), fixPair.getB().getTimePoint());
330
- }
331
- }
332
- return result;
333
- }
334
-
335
- @Override
336
- public Iterator<FixType> getFixesIterator(TimePoint startingAt, boolean inclusive) {
337
- assertReadLock();
338
- return getTimeConstrainedFixesIterator(getInternalFixes(), startingAt, inclusive, /* endingAt */ null, /* endingAtInclusive */ false);
339
- }
340
-
341
- @Override
342
- public Iterator<FixType> getFixesIterator(TimePoint startingAt, boolean startingAtInclusive, TimePoint endingAt,
343
- boolean endingAtInclusive) {
344
- assertReadLock();
345
- return getTimeConstrainedFixesIterator(getInternalFixes(), startingAt, startingAtInclusive, endingAt, endingAtInclusive);
346
- }
347
-
348
- @Override
349
- public Iterator<FixType> getFixesDescendingIterator(TimePoint startingAt, boolean inclusive) {
350
- assertReadLock();
351
- Iterator<FixType> result = (Iterator<FixType>) getInternalFixes().headSet(
352
- getDummyFix(startingAt), inclusive).descendingIterator();
353
- return result;
354
- }
355
-
356
- /**
357
- * Creates a dummy fix that conforms to <code>FixType</code>. This in particular means that subclasses
358
- * instantiating <code>FixType</code> with a specific class need to redefine this method so as to return
359
- * a dummy fix complying with their instantiation type used for <code>FixType</code>. Otherwise, a
360
- * {@link ClassCastException} may result upon certain operations performed with the fix returned by
361
- * this method.
362
- */
363
- protected FixType getDummyFix(TimePoint timePoint) {
364
- @SuppressWarnings("unchecked")
365
- FixType result = (FixType) new DummyTimed(timePoint);
366
- return result;
367
- }
368
-
369
- @Override
370
- public Iterator<FixType> getRawFixesIterator(TimePoint startingAt, boolean inclusive) {
371
- assertReadLock();
372
- return getTimeConstrainedFixesIterator(getInternalRawFixes(), startingAt, inclusive, /* endingAt */ null, /* endingAtInclusive */ false);
373
- }
374
-
375
- private Iterator<FixType> getTimeConstrainedFixesIterator(NavigableSet<FixType> set, TimePoint startingAt, boolean startingAtInclusive,
376
- TimePoint endingAt, boolean endingAtInclusive) {
377
- assertReadLock();
378
- if (startingAt != null && endingAt != null) {
379
- set = set.subSet(getDummyFix(startingAt), startingAtInclusive, getDummyFix(endingAt), endingAtInclusive);
380
- } else if (endingAt != null) {
381
- set = set.headSet(getDummyFix(endingAt), endingAtInclusive);
382
- } else if (startingAt != null) {
383
- set = set.tailSet(getDummyFix(startingAt), startingAtInclusive);
384
- }
385
- Iterator<FixType> result = set.iterator();
386
- return result;
387
- }
388
-
389
- @Override
390
- public Iterator<FixType> getRawFixesIterator(TimePoint startingAt, boolean startingAtInclusive,
391
- TimePoint endingAt, boolean endingAtInclusive) {
392
- assertReadLock();
393
- return getTimeConstrainedFixesIterator(getInternalRawFixes(), startingAt, startingAtInclusive, endingAt, endingAtInclusive);
394
- }
395
-
396
- @Override
397
- public Iterator<FixType> getRawFixesDescendingIterator(TimePoint startingAt, boolean inclusive) {
398
- assertReadLock();
399
- Iterator<FixType> result = (Iterator<FixType>) getInternalRawFixes().headSet(
400
- getDummyFix(startingAt), inclusive).descendingIterator();
401
- return result;
402
- }
403
-
404
- protected boolean add(FixType fix) {
405
- return add(fix, /* replace */ false);
406
- }
407
-
408
- /**
409
- * @return {@code true} if the fix was added or replaced; {@code false} in case no change was performed
410
- */
411
- protected boolean add(FixType fix, boolean replace) {
412
- lockForWrite();
413
- try {
414
- final AddResult addResult = addWithoutLocking(fix, replace);
415
- return addResult == AddResult.ADDED || addResult == AddResult.REPLACED;
416
- } finally {
417
- unlockAfterWrite();
418
- }
419
- }
420
-
421
- /**
422
- * The caller must ensure to hold the write lock for this track when calling this method.
423
- *
424
- * @param replace
425
- * whether or not to replace an existing fix in the track that is equal to {@link #fix} as defined by the
426
- * comparator used for the {@link #fixes} set. By default this is a comparator only comparing the
427
- * fixes' time stamps. Subclasses may use different comparator implementations.
428
- */
429
- protected AddResult addWithoutLocking(FixType fix, boolean replace) {
430
- final AddResult result;
431
- final boolean added = getInternalRawFixes().add(fix);
432
- if (!added && replace) {
433
- getInternalRawFixes().remove(fix);
434
- result = getInternalRawFixes().add(fix) ? AddResult.REPLACED : AddResult.NOT_ADDED;
435
- } else {
436
- result = added ? AddResult.ADDED : AddResult.NOT_ADDED;
437
- }
438
- return result;
439
- }
440
-
441
- @Override
442
- public Duration getAverageIntervalBetweenFixes() {
443
- lockForRead();
444
- try {
445
- final Duration result;
446
- final int size = getRawFixes().size();
447
- if (size > 1) {
448
- result = getRawFixes().first().getTimePoint().until(getRawFixes().last().getTimePoint()).divide(size-1);
449
- } else {
450
- result = null;
451
- }
452
- return result;
453
- } finally {
454
- unlockAfterRead();
455
- }
456
- }
457
-
458
- @Override
459
- public Duration getAverageIntervalBetweenRawFixes() {
460
- lockForRead();
461
- try {
462
- final Duration result;
463
- final int size = getRawFixes().size();
464
- if (size > 1) {
465
- result = getRawFixes().first().getTimePoint().until(getRawFixes().last().getTimePoint()).divide(size-1);
466
- } else {
467
- result = null;
468
- }
469
- return result;
470
- } finally {
471
- unlockAfterRead();
472
- }
473
- }
474
-
475
- @Override
476
- public <T> T getValueSum(TimePoint from, TimePoint to, T nullElement, Adder<T> adder, TimeRangeCache<T> cache, TimeRangeValueCalculator<T> valueCalculator) {
477
- return getValueSumRecursively(from, to, /* recursionLevel */ 0, nullElement, adder, cache, valueCalculator);
478
- }
479
-
480
- private <T> T getValueSumRecursively(TimePoint from, TimePoint to, int recursionDepth, T nullElement,
481
- Adder<T> adder, TimeRangeCache<T> cache, TimeRangeValueCalculator<T> valueCalculator) {
482
- T result;
483
- if (!from.before(to)) {
484
- result = nullElement;
485
- } else {
486
- boolean perfectCacheHit = false;
487
- lockForRead();
488
- try {
489
- Util.Pair<TimePoint, Util.Pair<TimePoint, T>> bestCacheEntry = cache.getEarliestFromAndResultAtOrAfterFrom(from, to);
490
- if (bestCacheEntry != null) {
491
- perfectCacheHit = true; // potentially a cache hit; but if it doesn't span the full interval, it's not perfect; see below
492
- // compute the missing stretches between best cache entry's "from" and our "from" and the cache
493
- // entry's "to" and our "to"
494
- T valueFromFromToBeginningOfCacheEntry = nullElement;
495
- T valueFromEndOfCacheEntryToTo = nullElement;
496
- if (!bestCacheEntry.getB().getA().equals(from)) {
497
- assert bestCacheEntry.getB().getA().after(from);
498
- perfectCacheHit = false;
499
- valueFromFromToBeginningOfCacheEntry = getValueSumRecursively(from, bestCacheEntry
500
- .getB().getA(), recursionDepth + 1, nullElement, adder, cache, valueCalculator);
501
- }
502
- if (!bestCacheEntry.getA().equals(to)) {
503
- assert bestCacheEntry.getA().before(to);
504
- perfectCacheHit = false;
505
- valueFromEndOfCacheEntryToTo = getValueSumRecursively(bestCacheEntry.getA(), to,
506
- recursionDepth + 1, nullElement, adder, cache, valueCalculator);
507
- }
508
- if (valueFromEndOfCacheEntryToTo == null || bestCacheEntry.getB().getB() == null) {
509
- result = null;
510
- } else {
511
- result = adder.add(adder.add(valueFromFromToBeginningOfCacheEntry, bestCacheEntry.getB().getB()),
512
- valueFromEndOfCacheEntryToTo);
513
- }
514
- } else {
515
- if (from.compareTo(to) < 0) {
516
- result = valueCalculator.calculate(from, to);
517
- } else {
518
- result = nullElement;
519
- }
520
- }
521
- // run the cache update while still holding the read lock; this avoids bug4629 where a cache invalidation
522
- // caused by fix insertions can come after the result calculation and before the cache update
523
- if (!perfectCacheHit && recursionDepth == 0) {
524
- cache.cache(from, to, result);
525
- }
526
- } finally {
527
- unlockAfterRead();
528
- }
529
- }
530
- return result;
531
- }
532
-
533
-
534
-
535
- @Override
536
- public int size() {
537
- return fixes.size();
538
- }
539
-
540
- @Override
541
- public boolean isEmpty() {
542
- return fixes.isEmpty();
543
- }
544
-}
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/impl/TrackingConnectorInfoImpl.java
... ...
@@ -1,66 +0,0 @@
1
-package com.sap.sailing.domain.tracking.impl;
2
-
3
-import com.sap.sailing.domain.tracking.TrackingConnectorInfo;
4
-
5
-public class TrackingConnectorInfoImpl implements TrackingConnectorInfo {
6
- private static final long serialVersionUID = 7970268841592389145L;
7
- private final String trackingConnectorName;
8
- private final String TtackingConnectorDefaultUrl;
9
- private final String webUrl;
10
-
11
- public TrackingConnectorInfoImpl(String trackingConnectorName, String trackingConnectorDefaultUrl, String webUrl) {
12
- super();
13
- this.trackingConnectorName = trackingConnectorName;
14
- TtackingConnectorDefaultUrl = trackingConnectorDefaultUrl;
15
- this.webUrl = webUrl;
16
- }
17
-
18
- public String getTrackingConnectorDefaultUrl() {
19
- return TtackingConnectorDefaultUrl;
20
- }
21
-
22
- public String getTrackingConnectorName() {
23
- return trackingConnectorName;
24
- }
25
-
26
- public String getWebUrl() {
27
- return webUrl;
28
- }
29
-
30
- @Override
31
- public int hashCode() {
32
- final int prime = 31;
33
- int result = 1;
34
- result = prime * result + ((TtackingConnectorDefaultUrl == null) ? 0 : TtackingConnectorDefaultUrl.hashCode());
35
- result = prime * result + ((trackingConnectorName == null) ? 0 : trackingConnectorName.hashCode());
36
- result = prime * result + ((webUrl == null) ? 0 : webUrl.hashCode());
37
- return result;
38
- }
39
-
40
- @Override
41
- public boolean equals(Object obj) {
42
- if (this == obj)
43
- return true;
44
- if (obj == null)
45
- return false;
46
- if (getClass() != obj.getClass())
47
- return false;
48
- TrackingConnectorInfoImpl other = (TrackingConnectorInfoImpl) obj;
49
- if (TtackingConnectorDefaultUrl == null) {
50
- if (other.TtackingConnectorDefaultUrl != null)
51
- return false;
52
- } else if (!TtackingConnectorDefaultUrl.equals(other.TtackingConnectorDefaultUrl))
53
- return false;
54
- if (trackingConnectorName == null) {
55
- if (other.trackingConnectorName != null)
56
- return false;
57
- } else if (!trackingConnectorName.equals(other.trackingConnectorName))
58
- return false;
59
- if (webUrl == null) {
60
- if (other.webUrl != null)
61
- return false;
62
- } else if (!webUrl.equals(other.webUrl))
63
- return false;
64
- return true;
65
- }
66
-}
java/com.sap.sailing.domain.swisstimingadapter.test/src/com/sap/sailing/domain/swisstimingadapter/test/ui/EditCAM.java
... ...
@@ -56,9 +56,7 @@ public class EditCAM extends javax.swing.JDialog {
56 56
*/
57 57
58 58
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
59
- @SuppressWarnings("serial")
60 59
private void initComponents() {
61
-
62 60
jLabel1 = new javax.swing.JLabel();
63 61
jRaceID = new javax.swing.JTextField();
64 62
jSeparator1 = new javax.swing.JSeparator();
... ...
@@ -73,47 +71,35 @@ public class EditCAM extends javax.swing.JDialog {
73 71
jButton3 = new javax.swing.JButton();
74 72
jScrollPane1 = new javax.swing.JScrollPane();
75 73
jMarkList = new javax.swing.JList<ClockAtMarkElement>();
76
-
77 74
setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
78
-
79 75
jLabel1.setText("Race ID:");
80
-
81 76
jRaceID.setText("jTextField1");
82
-
83 77
jLabel2.setText("Mark index:");
84
-
85 78
jMarkIndex.setText("0");
86
-
87 79
jLabel3.setText("Mark time:");
88
-
89 80
jMarkTime.setText("12:34:17");
90
-
91 81
jLabel4.setText("Sail number:");
92
-
93 82
jSailNumber.setText("w123");
94
-
95 83
jButton1.setText("Ok");
96 84
jButton1.addActionListener(new java.awt.event.ActionListener() {
97 85
public void actionPerformed(java.awt.event.ActionEvent evt) {
98 86
jButton1ActionPerformed(evt);
99 87
}
100 88
});
101
-
102 89
jButton2.setText("Remove");
103 90
jButton2.addActionListener(new java.awt.event.ActionListener() {
104 91
public void actionPerformed(java.awt.event.ActionEvent evt) {
105 92
jButton2ActionPerformed(evt);
106 93
}
107 94
});
108
-
109 95
jButton3.setText("Add");
110 96
jButton3.addActionListener(new java.awt.event.ActionListener() {
111 97
public void actionPerformed(java.awt.event.ActionEvent evt) {
112 98
jButton3ActionPerformed(evt);
113 99
}
114 100
});
115
-
116 101
jMarkList.setModel(new javax.swing.AbstractListModel<ClockAtMarkElement>() {
102
+ private static final long serialVersionUID = 1L;
117 103
ClockAtMarkElement[] clockAtMarkElements = { new ClockAtMarkElement(1, new Date(), "Item 1"),
118 104
new ClockAtMarkElement(1, new Date(), "Item 2"), new ClockAtMarkElement(1, new Date(), "Item 3"),
119 105
new ClockAtMarkElement(1, new Date(), "Item 4"), new ClockAtMarkElement(1, new Date(), "Item 5") };
java/com.sap.sailing.domain.swisstimingadapter.test/src/com/sap/sailing/domain/swisstimingadapter/test/ui/EditCCG.java
... ...
@@ -51,9 +51,7 @@ public class EditCCG extends javax.swing.JDialog {
51 51
*/
52 52
53 53
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
54
- @SuppressWarnings("serial")
55 54
private void initComponents() {
56
-
57 55
jBuoyType = new javax.swing.ButtonGroup();
58 56
jLabel1 = new javax.swing.JLabel();
59 57
jRaceID = new javax.swing.JTextField();
... ...
@@ -130,6 +128,7 @@ public class EditCCG extends javax.swing.JDialog {
130 128
});
131 129
132 130
jMarkList.setModel(new javax.swing.AbstractListModel<CCGMessage>() {
131
+ private static final long serialVersionUID = 1L;
133 132
CCGMessage[] ccgMessages = { new CCGMessage("Item 1", new ArrayList<Mark>()),
134 133
new CCGMessage("Item 2", new ArrayList<Mark>()),
135 134
new CCGMessage("Item 3", new ArrayList<Mark>()),
java/com.sap.sailing.domain.swisstimingadapter.test/src/com/sap/sailing/domain/swisstimingadapter/test/ui/EditSTL.java
... ...
@@ -49,9 +49,7 @@ public class EditSTL extends javax.swing.JDialog {
49 49
*/
50 50
51 51
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
52
- @SuppressWarnings("serial")
53 52
private void initComponents() {
54
-
55 53
jLabel1 = new javax.swing.JLabel();
56 54
jRaceID = new javax.swing.JTextField();
57 55
jSeparator1 = new javax.swing.JSeparator();
... ...
@@ -66,48 +64,36 @@ public class EditSTL extends javax.swing.JDialog {
66 64
jButton2 = new javax.swing.JButton();
67 65
jScrollPane1 = new javax.swing.JScrollPane();
68 66
jCompetitorList = new javax.swing.JList<Competitor>();
69
-
70 67
setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
71
-
72 68
jLabel1.setText("Race ID:");
73
-
74 69
jRaceID.setText("jTextField1");
75
-
76 70
jLabel2.setText("Sail Number:");
77
-
78 71
jSailNumber.setText("jTextField1");
79
-
80 72
jLabel3.setText("NOC:");
81
-
82 73
jNOC.setModel(new javax.swing.DefaultComboBoxModel<String>(new String[] { "AUS", "FRA", "GBR", "GER", "ITA", "USA" }));
83 74
jNOC.setSelectedIndex(2);
84
-
85 75
jLabel4.setText("Name:");
86
-
87 76
jName.setText("jTextField1");
88
-
89 77
jButton1.setText("Add");
90 78
jButton1.addActionListener(new java.awt.event.ActionListener() {
91 79
public void actionPerformed(java.awt.event.ActionEvent evt) {
92 80
jButton1ActionPerformed(evt);
93 81
}
94 82
});
95
-
96 83
jRemove.setText("Remove");
97 84
jRemove.addActionListener(new java.awt.event.ActionListener() {
98 85
public void actionPerformed(java.awt.event.ActionEvent evt) {
99 86
jRemoveActionPerformed(evt);
100 87
}
101 88
});
102
-
103 89
jButton2.setText("Ok");
104 90
jButton2.addActionListener(new java.awt.event.ActionListener() {
105 91
public void actionPerformed(java.awt.event.ActionEvent evt) {
106 92
jButton2ActionPerformed(evt);
107 93
}
108 94
});
109
-
110 95
jCompetitorList.setModel(new javax.swing.AbstractListModel<Competitor>() {
96
+ private static final long serialVersionUID = 1L;
111 97
Competitor[] competitors = { new CompetitorWithoutID("Item 1", "DEU", "Item 1"),
112 98
new CompetitorWithoutID("Item 2", "DEU", "Item 2"),
113 99
new CompetitorWithoutID("Item 3", "DEU", "Item 3"),
... ...
@@ -117,7 +103,6 @@ public class EditSTL extends javax.swing.JDialog {
117 103
public Competitor getElementAt(int i) { return competitors[i]; }
118 104
});
119 105
jScrollPane1.setViewportView(jCompetitorList);
120
-
121 106
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
122 107
getContentPane().setLayout(layout);
123 108
layout.setHorizontalGroup(
java/com.sap.sailing.domain.swisstimingadapter.test/src/com/sap/sailing/domain/swisstimingadapter/test/ui/EditTMD.java
... ...
@@ -54,9 +54,7 @@ public class EditTMD extends javax.swing.JDialog {
54 54
*/
55 55
56 56
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
57
- @SuppressWarnings("serial")
58 57
private void initComponents() {
59
-
60 58
jLabel1 = new javax.swing.JLabel();
61 59
jRaceId = new javax.swing.JTextField();
62 60
jLabel2 = new javax.swing.JLabel();
... ...
@@ -73,51 +71,37 @@ public class EditTMD extends javax.swing.JDialog {
73 71
jButton3 = new javax.swing.JButton();
74 72
jScrollPane1 = new javax.swing.JScrollPane();
75 73
jTmdData = new javax.swing.JList<TimingDataElement>();
76
-
77 74
setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
78
-
79 75
jLabel1.setText("Race ID:");
80
-
81 76
jRaceId.setText("jTextField1");
82
-
83 77
jLabel2.setText("Sail number:");
84
-
85 78
jSailNumber.setText("jTextField2");
86
-
87 79
jLabel3.setText("Mark index:");
88
-
89 80
jMarkIndex.setText("1");
90
-
91 81
jLabel4.setText("Rank:");
92
-
93 82
jRank.setText("1");
94
-
95 83
jLabel5.setText("Time since start:");
96
-
97 84
jTimeSinceStart.setText("00:12:14");
98
-
99 85
jButton1.setText("Remove");
100 86
jButton1.addActionListener(new java.awt.event.ActionListener() {
101 87
public void actionPerformed(java.awt.event.ActionEvent evt) {
102 88
jButton1ActionPerformed(evt);
103 89
}
104 90
});
105
-
106 91
jButton2.setText("Add");
107 92
jButton2.addActionListener(new java.awt.event.ActionListener() {
108 93
public void actionPerformed(java.awt.event.ActionEvent evt) {
109 94
jButton2ActionPerformed(evt);
110 95
}
111 96
});
112
-
113 97
jButton3.setText("Ok");
114 98
jButton3.addActionListener(new java.awt.event.ActionListener() {
115 99
public void actionPerformed(java.awt.event.ActionEvent evt) {
116 100
jButton3ActionPerformed(evt);
117 101
}
118 102
});
119
-
120 103
jTmdData.setModel(new javax.swing.AbstractListModel<TimingDataElement>() {
104
+ private static final long serialVersionUID = 1L;
121 105
TimingDataElement[] strings = { new TimingDataElement(1, 1, new Date()),
122 106
new TimingDataElement(2, 2, new Date()),
123 107
new TimingDataElement(3, 3, new Date()),
... ...
@@ -127,7 +111,6 @@ public class EditTMD extends javax.swing.JDialog {
127 111
public TimingDataElement getElementAt(int i) { return strings[i]; }
128 112
});
129 113
jScrollPane1.setViewportView(jTmdData);
130
-
131 114
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
132 115
getContentPane().setLayout(layout);
133 116
layout.setHorizontalGroup(
java/com.sap.sailing.domain.swisstimingadapter.test/src/com/sap/sailing/domain/swisstimingadapter/test/ui/SwissTimingRaceEditor.java
... ...
@@ -70,10 +70,8 @@ public class SwissTimingRaceEditor extends javax.swing.JFrame {
70 70
* WARNING: Do NOT modify this code. The content of this method is
71 71
* always regenerated by the Form Editor.
72 72
*/
73
- @SuppressWarnings({ "serial" })
74 73
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
75 74
private void initComponents() {
76
-
77 75
jScrollPane1 = new javax.swing.JScrollPane();
78 76
jCommandList = new javax.swing.JList<Object>();
79 77
jAdd = new javax.swing.JButton();
... ...
@@ -86,54 +84,46 @@ public class SwissTimingRaceEditor extends javax.swing.JFrame {
86 84
jImport = new javax.swing.JMenuItem();
87 85
jExport = new javax.swing.JMenuItem();
88 86
jMenuItem3 = new javax.swing.JMenuItem();
89
-
90 87
setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
91 88
setTitle("SwissTiming-Sample-Race Editor");
92
-
93 89
jCommandList.setModel(new javax.swing.AbstractListModel<Object>() {
90
+ private static final long serialVersionUID = 1L;
94 91
String[] strings = { "Item 1", "Item 2", "Item 3", "Item 4", "Item 5" };
95 92
public int getSize() { return strings.length; }
96 93
public Object getElementAt(int i) { return strings[i]; }
97 94
});
98 95
jScrollPane1.setViewportView(jCommandList);
99
-
100 96
jAdd.setText("Add");
101 97
jAdd.addActionListener(new java.awt.event.ActionListener() {
102 98
public void actionPerformed(java.awt.event.ActionEvent evt) {
103 99
jAddActionPerformed(evt);
104 100
}
105 101
});
106
-
107 102
jRemove.setText("Remove");
108 103
jRemove.addActionListener(new java.awt.event.ActionListener() {
109 104
public void actionPerformed(java.awt.event.ActionEvent evt) {
110 105
jRemoveActionPerformed(evt);
111 106
}
112 107
});
113
-
114 108
jEdit.setText("Edit");
115 109
jEdit.addActionListener(new java.awt.event.ActionListener() {
116 110
public void actionPerformed(java.awt.event.ActionEvent evt) {
117 111
jEditActionPerformed(evt);
118 112
}
119 113
});
120
-
121 114
jMoveUp.setText("Up");
122 115
jMoveUp.addActionListener(new java.awt.event.ActionListener() {
123 116
public void actionPerformed(java.awt.event.ActionEvent evt) {
124 117
jMoveUpActionPerformed(evt);
125 118
}
126 119
});
127
-
128 120
jMoveDown.setText("Down");
129 121
jMoveDown.addActionListener(new java.awt.event.ActionListener() {
130 122
public void actionPerformed(java.awt.event.ActionEvent evt) {
131 123
jMoveDownActionPerformed(evt);
132 124
}
133 125
});
134
-
135 126
jMenu1.setText("File");
136
-
137 127
jImport.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_I, java.awt.event.InputEvent.CTRL_DOWN_MASK));
138 128
jImport.setText("Import");
139 129
jImport.addActionListener(new java.awt.event.ActionListener() {
... ...
@@ -142,7 +132,6 @@ public class SwissTimingRaceEditor extends javax.swing.JFrame {
142 132
}
143 133
});
144 134
jMenu1.add(jImport);
145
-
146 135
jExport.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_S, java.awt.event.InputEvent.CTRL_DOWN_MASK));
147 136
jExport.setText("Export");
148 137
jExport.addActionListener(new java.awt.event.ActionListener() {
... ...
@@ -151,7 +140,6 @@ public class SwissTimingRaceEditor extends javax.swing.JFrame {
151 140
}
152 141
});
153 142
jMenu1.add(jExport);
154
-
155 143
jMenuItem3.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_ESCAPE, 0));
156 144
jMenuItem3.setText("Exit");
157 145
jMenuItem3.addActionListener(new java.awt.event.ActionListener() {
... ...
@@ -160,11 +148,8 @@ public class SwissTimingRaceEditor extends javax.swing.JFrame {
160 148
}
161 149
});
162 150
jMenu1.add(jMenuItem3);
163
-
164 151
jMenuBar1.add(jMenu1);
165
-
166 152
setJMenuBar(jMenuBar1);
167
-
168 153
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
169 154
getContentPane().setLayout(layout);
170 155
layout.setHorizontalGroup(
java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingRaceTrackerImpl.java
... ...
@@ -41,6 +41,7 @@ import com.sap.sailing.domain.markpassinghash.MarkPassingRaceFingerprintRegistry
41 41
import com.sap.sailing.domain.racelog.RaceLogAndTrackedRaceResolver;
42 42
import com.sap.sailing.domain.racelog.RaceLogStore;
43 43
import com.sap.sailing.domain.regattalog.RegattaLogStore;
44
+import com.sap.sailing.domain.shared.tracking.impl.TrackingConnectorInfoImpl;
44 45
import com.sap.sailing.domain.swisstimingadapter.Course;
45 46
import com.sap.sailing.domain.swisstimingadapter.DomainFactory;
46 47
import com.sap.sailing.domain.swisstimingadapter.Fix;
... ...
@@ -70,7 +71,6 @@ import com.sap.sailing.domain.tracking.TrackingDataLoader;
70 71
import com.sap.sailing.domain.tracking.WindStore;
71 72
import com.sap.sailing.domain.tracking.WindTrack;
72 73
import com.sap.sailing.domain.tracking.impl.TrackedRaceStatusImpl;
73
-import com.sap.sailing.domain.tracking.impl.TrackingConnectorInfoImpl;
74 74
import com.sap.sailing.domain.tracking.impl.UpdateHandler;
75 75
import com.sap.sse.common.Distance;
76 76
import com.sap.sse.common.TimePoint;
java/com.sap.sailing.domain.swisstimingreplayadapter/src/com/sap/sailing/domain/swisstimingreplayadapter/impl/SwissTimingReplayToDomainAdapter.java
... ...
@@ -41,6 +41,7 @@ import com.sap.sailing.domain.markpassinghash.MarkPassingRaceFingerprintRegistry
41 41
import com.sap.sailing.domain.racelog.RaceLogAndTrackedRaceResolver;
42 42
import com.sap.sailing.domain.racelog.RaceLogStore;
43 43
import com.sap.sailing.domain.regattalog.RegattaLogStore;
44
+import com.sap.sailing.domain.shared.tracking.impl.TrackingConnectorInfoImpl;
44 45
import com.sap.sailing.domain.swisstimingadapter.DomainFactory;
45 46
import com.sap.sailing.domain.swisstimingadapter.SwissTimingAdapter;
46 47
import com.sap.sailing.domain.swisstimingreplayadapter.CompetitorStatus;
... ...
@@ -60,7 +61,6 @@ import com.sap.sailing.domain.tracking.WindTrack;
60 61
import com.sap.sailing.domain.tracking.impl.EmptyWindStore;
61 62
import com.sap.sailing.domain.tracking.impl.MarkPassingImpl;
62 63
import com.sap.sailing.domain.tracking.impl.TrackedRaceStatusImpl;
63
-import com.sap.sailing.domain.tracking.impl.TrackingConnectorInfoImpl;
64 64
import com.sap.sse.common.Bearing;
65 65
import com.sap.sse.common.TimePoint;
66 66
import com.sap.sse.common.Util;
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/markpassingcalculation/impl/AbstractCandidateFilterTestSupport.java
... ...
@@ -12,7 +12,7 @@ import org.junit.jupiter.api.BeforeEach;
12 12
13 13
import com.sap.sailing.domain.base.Waypoint;
14 14
import com.sap.sailing.domain.markpassingcalculation.Candidate;
15
-import com.sap.sailing.domain.tracking.impl.TimedComparator;
15
+import com.sap.sailing.domain.shared.tracking.impl.TimedComparator;
16 16
import com.sap.sse.common.Duration;
17 17
import com.sap.sse.common.TimePoint;
18 18
import com.sap.sse.common.Util;
java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/BravoFixTrackFoiledDistanceCacheTest.java
... ...
@@ -25,11 +25,11 @@ import com.sap.sailing.domain.common.tracking.GPSFixMoving;
25 25
import com.sap.sailing.domain.common.tracking.impl.BravoExtendedFixImpl;
26 26
import com.sap.sailing.domain.common.tracking.impl.DoubleVectorFixImpl;
27 27
import com.sap.sailing.domain.common.tracking.impl.GPSFixMovingImpl;
28
+import com.sap.sailing.domain.shared.tracking.impl.TimeRangeCache;
28 29
import com.sap.sailing.domain.tracking.BravoFixTrack;
29 30
import com.sap.sailing.domain.tracking.DynamicBravoFixTrack;
30 31
import com.sap.sailing.domain.tracking.impl.BravoFixTrackImpl;
31 32
import com.sap.sailing.domain.tracking.impl.DynamicGPSFixMovingTrackImpl;
32
-import com.sap.sailing.domain.tracking.impl.TimeRangeCache;
33 33
import com.sap.sse.common.Distance;
34 34
import com.sap.sse.common.TimePoint;
35 35
import com.sap.sse.common.impl.DegreeBearingImpl;
java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/FetchTracksAndStoreLocallyTest.java
... ...
@@ -15,7 +15,7 @@ import com.sap.sailing.domain.base.Boat;
15 15
import com.sap.sailing.domain.base.Competitor;
16 16
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
17 17
import com.sap.sailing.domain.common.tracking.impl.GPSFixMovingImpl;
18
-import com.sap.sailing.domain.tracking.AddResult;
18
+import com.sap.sailing.domain.shared.tracking.AddResult;
19 19
import com.sap.sailing.domain.tracking.DynamicGPSFixTrack;
20 20
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
21 21
import com.sap.sailing.domain.tracking.DynamicTrackedRegatta;
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
... ...
@@ -74,12 +74,12 @@ import com.sap.sailing.domain.leaderboard.meta.LeaderboardGroupMetaLeaderboard;
74 74
import com.sap.sailing.domain.racelog.impl.EmptyRaceLogStore;
75 75
import com.sap.sailing.domain.ranking.OneDesignRankingMetric;
76 76
import com.sap.sailing.domain.regattalog.impl.EmptyRegattaLogStore;
77
+import com.sap.sailing.domain.shared.tracking.impl.TimedComparator;
77 78
import com.sap.sailing.domain.test.mock.MockedTrackedRaceWithStartTimeAndRanks;
78 79
import com.sap.sailing.domain.tracking.MarkPassing;
79 80
import com.sap.sailing.domain.tracking.TrackedRace;
80 81
import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
81 82
import com.sap.sailing.domain.tracking.impl.MarkPassingImpl;
82
-import com.sap.sailing.domain.tracking.impl.TimedComparator;
83 83
import com.sap.sse.common.Duration;
84 84
import com.sap.sse.common.TimePoint;
85 85
import com.sap.sse.common.Util;
... ...
@@ -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/LeaderboardScoringAndRankingTestBase.java
... ...
@@ -32,12 +32,12 @@ import com.sap.sailing.domain.leaderboard.ScoringScheme;
32 32
import com.sap.sailing.domain.leaderboard.impl.RegattaLeaderboardImpl;
33 33
import com.sap.sailing.domain.leaderboard.impl.ThresholdBasedResultDiscardingRuleImpl;
34 34
import com.sap.sailing.domain.ranking.OneDesignRankingMetric;
35
+import com.sap.sailing.domain.shared.tracking.impl.TimedComparator;
35 36
import com.sap.sailing.domain.test.mock.MockedTrackedRaceWithStartTimeAndRanks;
36 37
import com.sap.sailing.domain.test.mock.MockedTrackedRaceWithStartTimeAndZeroRanks;
37 38
import com.sap.sailing.domain.tracking.MarkPassing;
38 39
import com.sap.sailing.domain.tracking.TrackedRace;
39 40
import com.sap.sailing.domain.tracking.impl.MarkPassingImpl;
40
-import com.sap.sailing.domain.tracking.impl.TimedComparator;
41 41
import com.sap.sse.common.Duration;
42 42
import com.sap.sse.common.TimePoint;
43 43
import com.sap.sse.common.impl.MillisecondsTimePoint;
java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LineAnalysisTest.java
... ...
@@ -14,9 +14,9 @@ import com.sap.sailing.domain.common.impl.MeterDistance;
14 14
import com.sap.sailing.domain.common.tracking.GPSFix;
15 15
import com.sap.sailing.domain.common.tracking.impl.GPSFixImpl;
16 16
import com.sap.sailing.domain.common.tracking.impl.GPSFixMovingImpl;
17
+import com.sap.sailing.domain.shared.tracking.LineDetails;
17 18
import com.sap.sailing.domain.tracking.DynamicGPSFixTrack;
18 19
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
19
-import com.sap.sailing.domain.tracking.LineDetails;
20 20
import com.sap.sse.common.Bearing;
21 21
import com.sap.sse.common.Distance;
22 22
import com.sap.sse.common.TimePoint;
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/PartialNavigableSetViewTest.java
... ...
@@ -12,7 +12,7 @@ import java.util.TreeSet;
12 12
import org.junit.jupiter.api.BeforeEach;
13 13
import org.junit.jupiter.api.Test;
14 14
15
-import com.sap.sailing.domain.tracking.impl.PartialNavigableSetView;
15
+import com.sap.sailing.domain.shared.tracking.impl.PartialNavigableSetView;
16 16
17 17
public class PartialNavigableSetViewTest {
18 18
private PartialNavigableSetView<Integer> fullSet;
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/ReceiveTrackingDataTest.java
... ...
@@ -20,7 +20,7 @@ import com.sap.sailing.domain.leaderboard.LeaderboardGroupResolver;
20 20
import com.sap.sailing.domain.racelog.RaceLogAndTrackedRaceResolver;
21 21
import com.sap.sailing.domain.racelog.impl.EmptyRaceLogStore;
22 22
import com.sap.sailing.domain.regattalog.impl.EmptyRegattaLogStore;
23
-import com.sap.sailing.domain.tracking.AddResult;
23
+import com.sap.sailing.domain.shared.tracking.AddResult;
24 24
import com.sap.sailing.domain.tracking.DynamicRaceDefinitionSet;
25 25
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
26 26
import com.sap.sailing.domain.tracking.DynamicTrackedRegatta;
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.test/src/com/sap/sailing/domain/test/StarbordSideOfStartLineRecognitionTest.java
... ...
@@ -26,8 +26,8 @@ import com.sap.sailing.domain.common.tracking.GPSFix;
26 26
import com.sap.sailing.domain.common.tracking.impl.GPSFixImpl;
27 27
import com.sap.sailing.domain.racelog.RaceLogAndTrackedRaceResolver;
28 28
import com.sap.sailing.domain.ranking.OneDesignRankingMetric;
29
+import com.sap.sailing.domain.shared.tracking.LineDetails;
29 30
import com.sap.sailing.domain.tracking.DynamicGPSFixTrack;
30
-import com.sap.sailing.domain.tracking.LineDetails;
31 31
import com.sap.sailing.domain.tracking.MarkPositionAtTimePointCache;
32 32
import com.sap.sailing.domain.tracking.impl.DynamicGPSFixTrackImpl;
33 33
import com.sap.sailing.domain.tracking.impl.DynamicTrackedRaceImpl;
java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TrackTest.java
... ...
@@ -44,14 +44,14 @@ import com.sap.sailing.domain.common.tracking.impl.CompactionNotPossibleExceptio
44 44
import com.sap.sailing.domain.common.tracking.impl.GPSFixImpl;
45 45
import com.sap.sailing.domain.common.tracking.impl.GPSFixMovingImpl;
46 46
import com.sap.sailing.domain.common.tracking.impl.VeryCompactGPSFixMovingImpl;
47
+import com.sap.sailing.domain.shared.tracking.impl.TimeRangeCache;
48
+import com.sap.sailing.domain.shared.tracking.impl.TrackImpl;
47 49
import com.sap.sailing.domain.tracking.DynamicGPSFixTrack;
48 50
import com.sap.sailing.domain.tracking.GPSFixTrack;
49 51
import com.sap.sailing.domain.tracking.impl.DynamicGPSFixMovingTrackImpl;
50 52
import com.sap.sailing.domain.tracking.impl.DynamicGPSFixTrackImpl;
51 53
import com.sap.sailing.domain.tracking.impl.GPSFixTrackImpl;
52 54
import com.sap.sailing.domain.tracking.impl.MaxSpeedCache;
53
-import com.sap.sailing.domain.tracking.impl.TimeRangeCache;
54
-import com.sap.sailing.domain.tracking.impl.TrackImpl;
55 55
import com.sap.sse.common.AbstractBearing;
56 56
import com.sap.sse.common.Bearing;
57 57
import com.sap.sse.common.Distance;
... ...
@@ -156,7 +156,6 @@ public class TrackTest {
156 156
* {@link Timed} objects in ascending order, this method compares those results to the ordinary explicit calls
157 157
* to {@link GPSFixTrack#getEstimatedPosition(TimePoint, boolean)}.
158 158
*/
159
- @SuppressWarnings("serial")
160 159
@Test
161 160
public void testGetEstimatedPositionSingleVsIteratedWithSmallerSteps() {
162 161
TimePoint start = gpsFix1.getTimePoint().minus((gpsFix5.getTimePoint().asMillis()-gpsFix1.getTimePoint().asMillis())/2);
... ...
@@ -164,13 +163,15 @@ public class TrackTest {
164 163
List<Timed> timeds = new ArrayList<>();
165 164
for (TimePoint t = start; !t.after(end); t = t.plus((gpsFix5.getTimePoint().asMillis()-gpsFix1.getTimePoint().asMillis())/10)) {
166 165
final TimePoint finalT = t;
167
- timeds.add(new Timed() {public TimePoint getTimePoint() { return finalT; }});
166
+ timeds.add(new Timed() {
167
+ private static final long serialVersionUID = 7038806820707652754L;
168
+ public TimePoint getTimePoint() { return finalT; }
169
+ });
168 170
}
169 171
assertEqualEstimatedPositionsSingleVsIterated(timeds, /* extrapolate */ true);
170 172
assertEqualEstimatedPositionsSingleVsIterated(timeds, /* extrapolate */ false);
171 173
}
172 174
173
- @SuppressWarnings("serial")
174 175
@Test
175 176
public void testGetEstimatedPositionSingleVsIteratedWithLargerSteps() {
176 177
TimePoint start = gpsFix1.getTimePoint().minus((gpsFix5.getTimePoint().asMillis()-gpsFix1.getTimePoint().asMillis())/2);
... ...
@@ -178,7 +179,10 @@ public class TrackTest {
178 179
List<Timed> timeds = new ArrayList<>();
179 180
for (TimePoint t = start; !t.after(end); t = t.plus(gpsFix5.getTimePoint().asMillis()-gpsFix1.getTimePoint().asMillis())) {
180 181
final TimePoint finalT = t;
181
- timeds.add(new Timed() {public TimePoint getTimePoint() { return finalT; }});
182
+ timeds.add(new Timed() {
183
+ private static final long serialVersionUID = -6329517520161330872L;
184
+ public TimePoint getTimePoint() { return finalT; }
185
+ });
182 186
}
183 187
assertEqualEstimatedPositionsSingleVsIterated(timeds, /* extrapolate */ true);
184 188
assertEqualEstimatedPositionsSingleVsIterated(timeds, /* extrapolate */ false);
java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRace.java
... ...
@@ -64,6 +64,8 @@ import com.sap.sailing.domain.ranking.RankingMetricConstructor;
64 64
import com.sap.sailing.domain.regattalike.IsRegattaLike;
65 65
import com.sap.sailing.domain.regattalike.RegattaLikeIdentifier;
66 66
import com.sap.sailing.domain.regattalike.RegattaLikeListener;
67
+import com.sap.sailing.domain.shared.tracking.LineDetails;
68
+import com.sap.sailing.domain.shared.tracking.TrackingConnectorInfo;
67 69
import com.sap.sailing.domain.tracking.CourseDesignChangedListener;
68 70
import com.sap.sailing.domain.tracking.DynamicGPSFixTrack;
69 71
import com.sap.sailing.domain.tracking.DynamicRaceDefinitionSet;
... ...
@@ -71,7 +73,6 @@ import com.sap.sailing.domain.tracking.DynamicSensorFixTrack;
71 73
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
72 74
import com.sap.sailing.domain.tracking.DynamicTrackedRegatta;
73 75
import com.sap.sailing.domain.tracking.GPSFixTrack;
74
-import com.sap.sailing.domain.tracking.LineDetails;
75 76
import com.sap.sailing.domain.tracking.Maneuver;
76 77
import com.sap.sailing.domain.tracking.MarkPassing;
77 78
import com.sap.sailing.domain.tracking.MarkPositionAtTimePointCache;
... ...
@@ -86,7 +87,6 @@ import com.sap.sailing.domain.tracking.TrackedLeg;
86 87
import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor;
87 88
import com.sap.sailing.domain.tracking.TrackedRace;
88 89
import com.sap.sailing.domain.tracking.TrackedRaceStatus;
89
-import com.sap.sailing.domain.tracking.TrackingConnectorInfo;
90 90
import com.sap.sailing.domain.tracking.TrackingDataLoader;
91 91
import com.sap.sailing.domain.tracking.WindLegTypeAndLegBearingAndORCPerformanceCurveCache;
92 92
import com.sap.sailing.domain.tracking.WindPositionMode;
java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRaceWithStartTimeAndRanks.java
... ...
@@ -53,9 +53,10 @@ import com.sap.sailing.domain.leaderboard.impl.RankAndRankComparable;
53 53
import com.sap.sailing.domain.polars.PolarDataService;
54 54
import com.sap.sailing.domain.ranking.RankingMetric;
55 55
import com.sap.sailing.domain.ranking.RankingMetric.RankingInfo;
56
+import com.sap.sailing.domain.shared.tracking.LineDetails;
57
+import com.sap.sailing.domain.shared.tracking.TrackingConnectorInfo;
56 58
import com.sap.sailing.domain.tracking.CourseDesignChangedListener;
57 59
import com.sap.sailing.domain.tracking.GPSFixTrack;
58
-import com.sap.sailing.domain.tracking.LineDetails;
59 60
import com.sap.sailing.domain.tracking.Maneuver;
60 61
import com.sap.sailing.domain.tracking.MarkPassing;
61 62
import com.sap.sailing.domain.tracking.MarkPositionAtTimePointCache;
... ...
@@ -69,7 +70,6 @@ import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor;
69 70
import com.sap.sailing.domain.tracking.TrackedRace;
70 71
import com.sap.sailing.domain.tracking.TrackedRaceStatus;
71 72
import com.sap.sailing.domain.tracking.TrackedRegatta;
72
-import com.sap.sailing.domain.tracking.TrackingConnectorInfo;
73 73
import com.sap.sailing.domain.tracking.WindLegTypeAndLegBearingAndORCPerformanceCurveCache;
74 74
import com.sap.sailing.domain.tracking.WindPositionMode;
75 75
import com.sap.sailing.domain.tracking.WindStore;
java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/DomainFactoryImpl.java
... ...
@@ -69,6 +69,8 @@ import com.sap.sailing.domain.racelog.RaceLogStore;
69 69
import com.sap.sailing.domain.ranking.RankingMetricConstructor;
70 70
import com.sap.sailing.domain.ranking.RankingMetricsFactory;
71 71
import com.sap.sailing.domain.regattalog.RegattaLogStore;
72
+import com.sap.sailing.domain.shared.tracking.TrackingConnectorInfo;
73
+import com.sap.sailing.domain.shared.tracking.impl.TrackingConnectorInfoImpl;
72 74
import com.sap.sailing.domain.tracking.DynamicRaceDefinitionSet;
73 75
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
74 76
import com.sap.sailing.domain.tracking.DynamicTrackedRegatta;
... ...
@@ -78,13 +80,11 @@ import com.sap.sailing.domain.tracking.RaceTrackingConnectivityParameters;
78 80
import com.sap.sailing.domain.tracking.RaceTrackingHandler;
79 81
import com.sap.sailing.domain.tracking.TrackedRegatta;
80 82
import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
81
-import com.sap.sailing.domain.tracking.TrackingConnectorInfo;
82 83
import com.sap.sailing.domain.tracking.WindStore;
83 84
import com.sap.sailing.domain.tracking.WindTrack;
84 85
import com.sap.sailing.domain.tracking.impl.FinishTimeUpdateHandler;
85 86
import com.sap.sailing.domain.tracking.impl.RaceAbortedHandler;
86 87
import com.sap.sailing.domain.tracking.impl.StartTimeUpdateHandler;
87
-import com.sap.sailing.domain.tracking.impl.TrackingConnectorInfoImpl;
88 88
import com.sap.sailing.domain.tractracadapter.DomainFactory;
89 89
import com.sap.sailing.domain.tractracadapter.JSONService;
90 90
import com.sap.sailing.domain.tractracadapter.MetadataParser;
java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/RaceCourseReceiver.java
... ...
@@ -23,13 +23,13 @@ import com.sap.sailing.domain.common.PassingInstruction;
23 23
import com.sap.sailing.domain.leaderboard.LeaderboardGroupResolver;
24 24
import com.sap.sailing.domain.markpassinghash.MarkPassingRaceFingerprintRegistry;
25 25
import com.sap.sailing.domain.racelog.RaceLogAndTrackedRaceResolver;
26
+import com.sap.sailing.domain.shared.tracking.impl.TrackingConnectorInfoImpl;
26 27
import com.sap.sailing.domain.tracking.DynamicRaceDefinitionSet;
27 28
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
28 29
import com.sap.sailing.domain.tracking.DynamicTrackedRegatta;
29 30
import com.sap.sailing.domain.tracking.RaceTrackingHandler;
30 31
import com.sap.sailing.domain.tracking.TrackedRace;
31 32
import com.sap.sailing.domain.tracking.WindStore;
32
-import com.sap.sailing.domain.tracking.impl.TrackingConnectorInfoImpl;
33 33
import com.sap.sailing.domain.tractracadapter.DomainFactory;
34 34
import com.sap.sailing.domain.tractracadapter.TracTracAdapter;
35 35
import com.sap.sse.common.TimePoint;
java/com.sap.sailing.domain.windfinderadapter/META-INF/MANIFEST.MF
... ...
@@ -7,7 +7,8 @@ Bundle-Version: 1.0.0.qualifier
7 7
Bundle-Activator: com.sap.sailing.domain.windfinderadapter.impl.Activator
8 8
Bundle-Vendor: SAP
9 9
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
10
-Import-Package: com.sap.sse.security,
10
+Import-Package: com.sap.sailing.domain.shared.tracking,
11
+ com.sap.sse.security,
11 12
org.osgi.framework;version="1.3.0",
12 13
org.osgi.util.tracker;version="1.5.1"
13 14
Bundle-ActivationPolicy: lazy
java/com.sap.sailing.domain.yellowbrickadapter/src/com/sap/sailing/domain/yellowbrickadapter/impl/YellowBrickRaceTrackerImpl.java
... ...
@@ -47,6 +47,7 @@ import com.sap.sailing.domain.leaderboard.LeaderboardGroupResolver;
47 47
import com.sap.sailing.domain.racelog.RaceLogAndTrackedRaceResolver;
48 48
import com.sap.sailing.domain.racelog.RaceLogStore;
49 49
import com.sap.sailing.domain.regattalog.RegattaLogStore;
50
+import com.sap.sailing.domain.shared.tracking.impl.TrackingConnectorInfoImpl;
50 51
import com.sap.sailing.domain.tracking.AbstractRaceTrackerImpl;
51 52
import com.sap.sailing.domain.tracking.DynamicRaceDefinitionSet;
52 53
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
... ...
@@ -60,7 +61,6 @@ import com.sap.sailing.domain.tracking.WindStore;
60 61
import com.sap.sailing.domain.tracking.WindTrack;
61 62
import com.sap.sailing.domain.tracking.impl.AbstractRaceChangeListener;
62 63
import com.sap.sailing.domain.tracking.impl.TrackedRaceStatusImpl;
63
-import com.sap.sailing.domain.tracking.impl.TrackingConnectorInfoImpl;
64 64
import com.sap.sailing.domain.yellowbrickadapter.YellowBrickRace;
65 65
import com.sap.sailing.domain.yellowbrickadapter.YellowBrickRaceTrackingConnectivityParams;
66 66
import com.sap.sailing.domain.yellowbrickadapter.YellowBrickTrackingAdapter;
java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/EventImpl.java
... ...
@@ -24,8 +24,8 @@ import com.sap.sailing.domain.common.WindSourceType;
24 24
import com.sap.sailing.domain.common.scalablevalue.impl.ScalablePosition;
25 25
import com.sap.sailing.domain.leaderboard.Leaderboard;
26 26
import com.sap.sailing.domain.leaderboard.LeaderboardGroup;
27
+import com.sap.sailing.domain.shared.tracking.TrackingConnectorInfo;
27 28
import com.sap.sailing.domain.tracking.TrackedRace;
28
-import com.sap.sailing.domain.tracking.TrackingConnectorInfo;
29 29
import com.sap.sailing.geocoding.ReverseGeocoder;
30 30
import com.sap.sse.common.TimePoint;
31 31
import com.sap.sse.common.Util;
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/maneuverdetection/impl/IncrementalApproximatedFixesCalculatorImpl.java
... ...
@@ -10,10 +10,10 @@ import java.util.NavigableSet;
10 10
import com.sap.sailing.domain.base.Competitor;
11 11
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
12 12
import com.sap.sailing.domain.maneuverdetection.IncrementalApproximatedFixesCalculator;
13
+import com.sap.sailing.domain.shared.tracking.impl.TimedComparator;
13 14
import com.sap.sailing.domain.tracking.GPSFixTrack;
14 15
import com.sap.sailing.domain.tracking.MarkPassing;
15 16
import com.sap.sailing.domain.tracking.TrackedRace;
16
-import com.sap.sailing.domain.tracking.impl.TimedComparator;
17 17
import com.sap.sse.common.Duration;
18 18
import com.sap.sse.common.TimePoint;
19 19
import com.sap.sse.common.Util;
java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/MarkPassingCalculator.java
... ...
@@ -30,9 +30,9 @@ import com.sap.sailing.domain.markpassingcalculation.impl.CandidateFinderImpl;
30 30
import com.sap.sailing.domain.markpassinghash.MarkPassingRaceFingerprint;
31 31
import com.sap.sailing.domain.markpassinghash.MarkPassingRaceFingerprintFactory;
32 32
import com.sap.sailing.domain.markpassinghash.MarkPassingRaceFingerprintRegistry;
33
+import com.sap.sailing.domain.shared.tracking.impl.TimedComparator;
33 34
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
34 35
import com.sap.sailing.domain.tracking.MarkPassing;
35
-import com.sap.sailing.domain.tracking.impl.TimedComparator;
36 36
import com.sap.sse.common.TimePoint;
37 37
import com.sap.sse.common.Util;
38 38
import com.sap.sse.common.Util.Pair;
... ...
@@ -589,7 +589,7 @@ public class MarkPassingCalculator {
589 589
// creation matches that of this mark passing calculator's race; load instead of compute
590 590
updateMarkPassingsFromRegistry();
591 591
queue.clear();
592
- stop(); // ensures an end marker is written to queue to the queue.take() call in Listen.run() will always get unblocked after the queue.clear() above
592
+ stop(); // ensures an end marker is written to queue so the queue.take() call in Listen.run() will always get unblocked after the queue.clear() above
593 593
suspended = false;
594 594
} else {
595 595
suspended = false;
java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/MarkPassingUpdateListener.java
... ...
@@ -10,7 +10,7 @@ import com.sap.sailing.domain.base.Mark;
10 10
import com.sap.sailing.domain.base.Waypoint;
11 11
import com.sap.sailing.domain.common.tracking.GPSFix;
12 12
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
13
-import com.sap.sailing.domain.tracking.AddResult;
13
+import com.sap.sailing.domain.shared.tracking.AddResult;
14 14
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
15 15
import com.sap.sailing.domain.tracking.impl.AbstractRaceChangeListener;
16 16
import com.sap.sse.common.TimePoint;
java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateChooserImpl.java
... ...
@@ -29,13 +29,13 @@ import com.sap.sailing.domain.common.tracking.GPSFixMoving;
29 29
import com.sap.sailing.domain.markpassingcalculation.Candidate;
30 30
import com.sap.sailing.domain.markpassingcalculation.CandidateChooser;
31 31
import com.sap.sailing.domain.markpassingcalculation.MarkPassingCalculator;
32
+import com.sap.sailing.domain.shared.tracking.impl.TimedComparator;
32 33
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
33 34
import com.sap.sailing.domain.tracking.GPSFixTrack;
34 35
import com.sap.sailing.domain.tracking.MarkPassing;
35 36
import com.sap.sailing.domain.tracking.TrackedLeg;
36 37
import com.sap.sailing.domain.tracking.TrackedRace;
37 38
import com.sap.sailing.domain.tracking.impl.MarkPassingImpl;
38
-import com.sap.sailing.domain.tracking.impl.TimedComparator;
39 39
import com.sap.sse.common.Distance;
40 40
import com.sap.sse.common.Duration;
41 41
import com.sap.sse.common.Speed;
java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateFinderImpl.java
... ...
@@ -35,13 +35,13 @@ import com.sap.sailing.domain.common.tracking.GPSFix;
35 35
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
36 36
import com.sap.sailing.domain.markpassingcalculation.Candidate;
37 37
import com.sap.sailing.domain.markpassingcalculation.CandidateFinder;
38
+import com.sap.sailing.domain.shared.tracking.impl.TimedComparator;
38 39
import com.sap.sailing.domain.tracking.DynamicGPSFixTrack;
39 40
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
40 41
import com.sap.sailing.domain.tracking.GPSFixTrack;
41 42
import com.sap.sailing.domain.tracking.MarkPositionAtTimePointCache;
42 43
import com.sap.sailing.domain.tracking.TrackedRace;
43 44
import com.sap.sailing.domain.tracking.impl.MarkPositionAtTimePointCacheImpl;
44
-import com.sap.sailing.domain.tracking.impl.TimedComparator;
45 45
import com.sap.sse.common.Bearing;
46 46
import com.sap.sse.common.Distance;
47 47
import com.sap.sse.common.Duration;
java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/WaypointPositionAndDistanceCache.java
... ...
@@ -21,7 +21,7 @@ import com.sap.sailing.domain.base.Mark;
21 21
import com.sap.sailing.domain.base.Waypoint;
22 22
import com.sap.sailing.domain.common.Position;
23 23
import com.sap.sailing.domain.common.tracking.GPSFix;
24
-import com.sap.sailing.domain.tracking.AddResult;
24
+import com.sap.sailing.domain.shared.tracking.AddResult;
25 25
import com.sap.sailing.domain.tracking.GPSFixTrack;
26 26
import com.sap.sailing.domain.tracking.TrackedRace;
27 27
import com.sap.sailing.domain.tracking.impl.AbstractRaceChangeListener;
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/DummyTrackedRace.java
... ...
@@ -43,6 +43,8 @@ import com.sap.sailing.domain.leaderboard.impl.RankAndRankComparable;
43 43
import com.sap.sailing.domain.polars.PolarDataService;
44 44
import com.sap.sailing.domain.ranking.RankingMetric;
45 45
import com.sap.sailing.domain.ranking.RankingMetric.RankingInfo;
46
+import com.sap.sailing.domain.shared.tracking.LineDetails;
47
+import com.sap.sailing.domain.shared.tracking.TrackingConnectorInfo;
46 48
import com.sap.sailing.domain.tracking.impl.DynamicTrackedRegattaImpl;
47 49
import com.sap.sailing.domain.tracking.impl.EmptyWindStore;
48 50
import com.sap.sailing.domain.windestimation.IncrementalWindEstimation;
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/DynamicMappedTrack.java
... ...
@@ -1,5 +1,6 @@
1 1
package com.sap.sailing.domain.tracking;
2 2
3
+import com.sap.sailing.domain.shared.tracking.MappedTrack;
3 4
import com.sap.sse.common.Timed;
4 5
5 6
/**
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/DynamicTrack.java
... ...
@@ -1,5 +1,6 @@
1 1
package com.sap.sailing.domain.tracking;
2 2
3
+import com.sap.sailing.domain.shared.tracking.Track;
3 4
import com.sap.sse.common.Timed;
4 5
5 6
public interface DynamicTrack<FixType extends Timed> extends Track<FixType> {
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/GPSFixTrack.java
... ...
@@ -10,6 +10,7 @@ import com.sap.sailing.domain.common.confidence.Weigher;
10 10
import com.sap.sailing.domain.common.impl.KnotSpeedImpl;
11 11
import com.sap.sailing.domain.common.tracking.GPSFix;
12 12
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
13
+import com.sap.sailing.domain.shared.tracking.MappedTrack;
13 14
import com.sap.sse.common.Distance;
14 15
import com.sap.sse.common.Duration;
15 16
import com.sap.sse.common.Speed;
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/GPSTrackListener.java
... ...
@@ -1,6 +1,7 @@
1 1
package com.sap.sailing.domain.tracking;
2 2
3 3
import com.sap.sailing.domain.common.tracking.GPSFix;
4
+import com.sap.sailing.domain.shared.tracking.AddResult;
4 5
5 6
public interface GPSTrackListener<ItemType, FixType extends GPSFix> extends TrackListener {
6 7
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/RaceChangeListener.java
... ...
@@ -17,6 +17,7 @@ import com.sap.sailing.domain.common.WindSource;
17 17
import com.sap.sailing.domain.common.tracking.GPSFix;
18 18
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
19 19
import com.sap.sailing.domain.common.tracking.SensorFix;
20
+import com.sap.sailing.domain.shared.tracking.AddResult;
20 21
import com.sap.sse.common.TimePoint;
21 22
22 23
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/RaceTrackingHandler.java
... ...
@@ -20,6 +20,7 @@ import com.sap.sailing.domain.base.impl.DynamicTeam;
20 20
import com.sap.sailing.domain.base.impl.RaceDefinitionImpl;
21 21
import com.sap.sailing.domain.markpassinghash.MarkPassingRaceFingerprintRegistry;
22 22
import com.sap.sailing.domain.racelog.RaceLogAndTrackedRaceResolver;
23
+import com.sap.sailing.domain.shared.tracking.TrackingConnectorInfo;
23 24
import com.sap.sse.common.Color;
24 25
import com.sap.sse.common.Duration;
25 26
import com.sap.sse.util.ThreadLocalTransporter;
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/SensorFixTrack.java
... ...
@@ -3,6 +3,8 @@ package com.sap.sailing.domain.tracking;
3 3
import java.io.Serializable;
4 4
5 5
import com.sap.sailing.domain.common.tracking.SensorFix;
6
+import com.sap.sailing.domain.shared.tracking.MappedTrack;
7
+import com.sap.sailing.domain.shared.tracking.Track;
6 8
import com.sap.sse.common.WithID;
7 9
8 10
/**
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/SensorFixTrackListener.java
... ...
@@ -1,6 +1,7 @@
1 1
package com.sap.sailing.domain.tracking;
2 2
3 3
import com.sap.sailing.domain.common.tracking.SensorFix;
4
+import com.sap.sailing.domain.shared.tracking.AddResult;
4 5
5 6
/**
6 7
* Listener to observe a {@link com.sap.sailing.domain.tracking.SensorFixTrack} to get informed whenever a new fix is
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRace.java
... ...
@@ -69,6 +69,8 @@ import com.sap.sailing.domain.racelog.RaceLogAndTrackedRaceResolver;
69 69
import com.sap.sailing.domain.racelog.tracking.SensorFixStore;
70 70
import com.sap.sailing.domain.ranking.RankingMetric;
71 71
import com.sap.sailing.domain.ranking.RankingMetric.RankingInfo;
72
+import com.sap.sailing.domain.shared.tracking.LineDetails;
73
+import com.sap.sailing.domain.shared.tracking.TrackingConnectorInfo;
72 74
import com.sap.sailing.domain.tracking.impl.NonCachingMarkPositionAtTimePointCache;
73 75
import com.sap.sailing.domain.tracking.impl.TrackedRaceImpl;
74 76
import com.sap.sailing.domain.windestimation.IncrementalWindEstimation;
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRegatta.java
... ...
@@ -12,6 +12,7 @@ import com.sap.sailing.domain.base.impl.TrackedRaces;
12 12
import com.sap.sailing.domain.common.NoWindException;
13 13
import com.sap.sailing.domain.markpassinghash.MarkPassingRaceFingerprintRegistry;
14 14
import com.sap.sailing.domain.racelog.RaceLogAndTrackedRaceResolver;
15
+import com.sap.sailing.domain.shared.tracking.TrackingConnectorInfo;
15 16
import com.sap.sse.common.TimePoint;
16 17
import com.sap.sse.metering.HasCPUMeter;
17 18
import com.sap.sse.util.ThreadLocalTransporter;
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/AbstractRaceChangeListener.java
... ...
@@ -12,7 +12,7 @@ import com.sap.sailing.domain.common.WindSource;
12 12
import com.sap.sailing.domain.common.tracking.GPSFix;
13 13
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
14 14
import com.sap.sailing.domain.common.tracking.SensorFix;
15
-import com.sap.sailing.domain.tracking.AddResult;
15
+import com.sap.sailing.domain.shared.tracking.AddResult;
16 16
import com.sap.sailing.domain.tracking.DynamicSensorFixTrack;
17 17
import com.sap.sailing.domain.tracking.MarkPassing;
18 18
import com.sap.sailing.domain.tracking.RaceChangeListener;
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/BravoFixTrackImpl.java
... ...
@@ -10,12 +10,13 @@ import com.sap.sailing.domain.common.scalablevalue.impl.ScalableDistance;
10 10
import com.sap.sailing.domain.common.tracking.BravoExtendedFix;
11 11
import com.sap.sailing.domain.common.tracking.BravoFix;
12 12
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
13
-import com.sap.sailing.domain.tracking.AddResult;
13
+import com.sap.sailing.domain.shared.tracking.AddResult;
14
+import com.sap.sailing.domain.shared.tracking.Track;
15
+import com.sap.sailing.domain.shared.tracking.impl.TimeRangeCache;
14 16
import com.sap.sailing.domain.tracking.BravoFixTrack;
15 17
import com.sap.sailing.domain.tracking.DynamicBravoFixTrack;
16 18
import com.sap.sailing.domain.tracking.GPSFixTrack;
17 19
import com.sap.sailing.domain.tracking.GPSTrackListener;
18
-import com.sap.sailing.domain.tracking.Track;
19 20
import com.sap.sailing.domain.tracking.TrackedRace;
20 21
import com.sap.sse.common.Bearing;
21 22
import com.sap.sse.common.Distance;
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/CourseChangeBasedTrackApproximation.java
... ...
@@ -17,7 +17,8 @@ import com.sap.sailing.domain.base.Competitor;
17 17
import com.sap.sailing.domain.common.SpeedWithBearing;
18 18
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
19 19
import com.sap.sailing.domain.common.tracking.impl.GPSFixMovingImpl;
20
-import com.sap.sailing.domain.tracking.AddResult;
20
+import com.sap.sailing.domain.shared.tracking.AddResult;
21
+import com.sap.sailing.domain.shared.tracking.impl.TimedComparator;
21 22
import com.sap.sailing.domain.tracking.GPSFixTrack;
22 23
import com.sap.sailing.domain.tracking.GPSTrackListener;
23 24
import com.sap.sse.common.Duration;
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/CrossTrackErrorCache.java
... ...
@@ -19,11 +19,12 @@ import com.sap.sailing.domain.common.NoWindException;
19 19
import com.sap.sailing.domain.common.impl.MeterDistance;
20 20
import com.sap.sailing.domain.common.tracking.GPSFix;
21 21
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
22
-import com.sap.sailing.domain.tracking.AddResult;
22
+import com.sap.sailing.domain.shared.tracking.AddResult;
23
+import com.sap.sailing.domain.shared.tracking.Track;
24
+import com.sap.sailing.domain.shared.tracking.impl.TrackImpl;
23 25
import com.sap.sailing.domain.tracking.GPSFixTrack;
24 26
import com.sap.sailing.domain.tracking.MarkPassing;
25 27
import com.sap.sailing.domain.tracking.RaceChangeListener;
26
-import com.sap.sailing.domain.tracking.Track;
27 28
import com.sap.sailing.domain.tracking.TrackedLeg;
28 29
import com.sap.sailing.domain.tracking.TrackedRace;
29 30
import com.sap.sse.common.Distance;
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicMappedTrackImpl.java
... ...
@@ -1,5 +1,6 @@
1 1
package com.sap.sailing.domain.tracking.impl;
2 2
3
+import com.sap.sailing.domain.shared.tracking.impl.MappedTrackImpl;
3 4
import com.sap.sailing.domain.tracking.DynamicMappedTrack;
4 5
import com.sap.sailing.domain.tracking.DynamicTrack;
5 6
import com.sap.sse.common.Timed;
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackImpl.java
... ...
@@ -1,5 +1,6 @@
1 1
package com.sap.sailing.domain.tracking.impl;
2 2
3
+import com.sap.sailing.domain.shared.tracking.impl.TrackImpl;
3 4
import com.sap.sailing.domain.tracking.DynamicTrack;
4 5
import com.sap.sse.common.Timed;
5 6
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.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceImpl.java
... ...
@@ -48,7 +48,8 @@ import com.sap.sailing.domain.markpassingcalculation.MarkPassingCalculator;
48 48
import com.sap.sailing.domain.markpassinghash.MarkPassingRaceFingerprintRegistry;
49 49
import com.sap.sailing.domain.racelog.RaceLogAndTrackedRaceResolver;
50 50
import com.sap.sailing.domain.ranking.RankingMetricConstructor;
51
-import com.sap.sailing.domain.tracking.AddResult;
51
+import com.sap.sailing.domain.shared.tracking.AddResult;
52
+import com.sap.sailing.domain.shared.tracking.TrackingConnectorInfo;
52 53
import com.sap.sailing.domain.tracking.CourseDesignChangedListener;
53 54
import com.sap.sailing.domain.tracking.DynamicGPSFixTrack;
54 55
import com.sap.sailing.domain.tracking.DynamicSensorFixTrack;
... ...
@@ -65,7 +66,6 @@ import com.sap.sailing.domain.tracking.TrackFactory;
65 66
import com.sap.sailing.domain.tracking.TrackedLeg;
66 67
import com.sap.sailing.domain.tracking.TrackedRaceStatus;
67 68
import com.sap.sailing.domain.tracking.TrackedRegatta;
68
-import com.sap.sailing.domain.tracking.TrackingConnectorInfo;
69 69
import com.sap.sailing.domain.tracking.TrackingDataLoader;
70 70
import com.sap.sailing.domain.tracking.WindStore;
71 71
import com.sap.sailing.domain.tracking.WindTrack;
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/GPSFixTrackImpl.java
... ...
@@ -33,12 +33,15 @@ import com.sap.sailing.domain.common.tracking.GPSFix;
33 33
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
34 34
import com.sap.sailing.domain.common.tracking.WithValidityCache;
35 35
import com.sap.sailing.domain.common.tracking.impl.CompactPositionHelper;
36
-import com.sap.sailing.domain.tracking.AddResult;
36
+import com.sap.sailing.domain.shared.tracking.AddResult;
37
+import com.sap.sailing.domain.shared.tracking.Track;
38
+import com.sap.sailing.domain.shared.tracking.impl.MappedTrackImpl;
39
+import com.sap.sailing.domain.shared.tracking.impl.PartialNavigableSetView;
40
+import com.sap.sailing.domain.shared.tracking.impl.TimeRangeCache;
37 41
import com.sap.sailing.domain.tracking.GPSFixTrack;
38 42
import com.sap.sailing.domain.tracking.GPSTrackListener;
39 43
import com.sap.sailing.domain.tracking.SpeedWithBearingStep;
40 44
import com.sap.sailing.domain.tracking.SpeedWithBearingStepsIterable;
41
-import com.sap.sailing.domain.tracking.Track;
42 45
import com.sap.sailing.domain.tracking.TrackedRace;
43 46
import com.sap.sse.common.Bearing;
44 47
import com.sap.sse.common.Distance;
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/MaxSpeedCache.java
... ...
@@ -9,7 +9,7 @@ import java.util.NavigableSet;
9 9
10 10
import com.sap.sailing.domain.common.tracking.GPSFix;
11 11
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
12
-import com.sap.sailing.domain.tracking.AddResult;
12
+import com.sap.sailing.domain.shared.tracking.AddResult;
13 13
import com.sap.sailing.domain.tracking.GPSFixTrack;
14 14
import com.sap.sailing.domain.tracking.GPSTrackListener;
15 15
import com.sap.sse.common.Speed;
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/SensorFixTrackImpl.java
... ...
@@ -4,7 +4,7 @@ import java.io.Serializable;
4 4
import java.util.function.Consumer;
5 5
6 6
import com.sap.sailing.domain.common.tracking.SensorFix;
7
-import com.sap.sailing.domain.tracking.AddResult;
7
+import com.sap.sailing.domain.shared.tracking.AddResult;
8 8
import com.sap.sailing.domain.tracking.DynamicSensorFixTrack;
9 9
import com.sap.sailing.domain.tracking.SensorFixTrack;
10 10
import com.sap.sailing.domain.tracking.SensorFixTrackListener;
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackBasedEstimationWindTrackImpl.java
... ...
@@ -27,7 +27,8 @@ import com.sap.sailing.domain.common.impl.WindImpl;
27 27
import com.sap.sailing.domain.common.tracking.GPSFix;
28 28
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
29 29
import com.sap.sailing.domain.common.tracking.SensorFix;
30
-import com.sap.sailing.domain.tracking.AddResult;
30
+import com.sap.sailing.domain.shared.tracking.AddResult;
31
+import com.sap.sailing.domain.shared.tracking.impl.TrackImpl;
31 32
import com.sap.sailing.domain.tracking.DynamicSensorFixTrack;
32 33
import com.sap.sailing.domain.tracking.MarkPassing;
33 34
import com.sap.sailing.domain.tracking.TrackedRace;
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java
... ...
@@ -141,13 +141,17 @@ import com.sap.sailing.domain.racelog.RaceLogAndTrackedRaceResolver;
141 141
import com.sap.sailing.domain.ranking.OneDesignRankingMetric;
142 142
import com.sap.sailing.domain.ranking.RankingMetric;
143 143
import com.sap.sailing.domain.ranking.RankingMetric.RankingInfo;
144
+import com.sap.sailing.domain.shared.tracking.AddResult;
145
+import com.sap.sailing.domain.shared.tracking.LineDetails;
146
+import com.sap.sailing.domain.shared.tracking.Track;
147
+import com.sap.sailing.domain.shared.tracking.TrackingConnectorInfo;
148
+import com.sap.sailing.domain.shared.tracking.impl.LineDetailsImpl;
149
+import com.sap.sailing.domain.shared.tracking.impl.TimedComparator;
144 150
import com.sap.sailing.domain.ranking.RankingMetricConstructor;
145
-import com.sap.sailing.domain.tracking.AddResult;
146 151
import com.sap.sailing.domain.tracking.BravoFixTrack;
147 152
import com.sap.sailing.domain.tracking.DynamicSensorFixTrack;
148 153
import com.sap.sailing.domain.tracking.GPSFixTrack;
149 154
import com.sap.sailing.domain.tracking.GPSTrackListener;
150
-import com.sap.sailing.domain.tracking.LineDetails;
151 155
import com.sap.sailing.domain.tracking.Maneuver;
152 156
import com.sap.sailing.domain.tracking.MarkPassing;
153 157
import com.sap.sailing.domain.tracking.MarkPositionAtTimePointCache;
... ...
@@ -155,7 +159,6 @@ import com.sap.sailing.domain.tracking.RaceChangeListener;
155 159
import com.sap.sailing.domain.tracking.RaceExecutionOrderProvider;
156 160
import com.sap.sailing.domain.tracking.RaceListener;
157 161
import com.sap.sailing.domain.tracking.SensorFixTrack;
158
-import com.sap.sailing.domain.tracking.Track;
159 162
import com.sap.sailing.domain.tracking.TrackFactory;
160 163
import com.sap.sailing.domain.tracking.TrackedLeg;
161 164
import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor;
... ...
@@ -163,7 +166,6 @@ import com.sap.sailing.domain.tracking.TrackedRace;
163 166
import com.sap.sailing.domain.tracking.TrackedRaceStatus;
164 167
import com.sap.sailing.domain.tracking.TrackedRaceWithWindEssentials;
165 168
import com.sap.sailing.domain.tracking.TrackedRegatta;
166
-import com.sap.sailing.domain.tracking.TrackingConnectorInfo;
167 169
import com.sap.sailing.domain.tracking.WindLegTypeAndLegBearingAndORCPerformanceCurveCache;
168 170
import com.sap.sailing.domain.tracking.WindPositionMode;
169 171
import com.sap.sailing.domain.tracking.WindStore;
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRegattaImpl.java
... ...
@@ -24,12 +24,12 @@ import com.sap.sailing.domain.base.Sideline;
24 24
import com.sap.sailing.domain.common.NoWindException;
25 25
import com.sap.sailing.domain.markpassinghash.MarkPassingRaceFingerprintRegistry;
26 26
import com.sap.sailing.domain.racelog.RaceLogAndTrackedRaceResolver;
27
+import com.sap.sailing.domain.shared.tracking.TrackingConnectorInfo;
27 28
import com.sap.sailing.domain.tracking.DynamicRaceDefinitionSet;
28 29
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
29 30
import com.sap.sailing.domain.tracking.RaceListener;
30 31
import com.sap.sailing.domain.tracking.TrackedRace;
31 32
import com.sap.sailing.domain.tracking.TrackedRegatta;
32
-import com.sap.sailing.domain.tracking.TrackingConnectorInfo;
33 33
import com.sap.sailing.domain.tracking.WindStore;
34 34
import com.sap.sse.common.TimePoint;
35 35
import com.sap.sse.common.Util;
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/VirtualWindTrackImpl.java
... ...
@@ -4,6 +4,7 @@ import java.util.NavigableSet;
4 4
5 5
import com.sap.sailing.domain.common.Position;
6 6
import com.sap.sailing.domain.common.Wind;
7
+import com.sap.sailing.domain.shared.tracking.impl.PartialNavigableSetView;
7 8
import com.sap.sailing.domain.tracking.TrackedRace;
8 9
import com.sap.sailing.domain.tracking.WindWithConfidence;
9 10
import com.sap.sse.common.TimePoint;
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/WindTrackImpl.java
... ...
@@ -23,6 +23,7 @@ import com.sap.sailing.domain.common.tracking.impl.PreciseCompactWindImpl;
23 23
import com.sap.sailing.domain.common.tracking.impl.VeryCompactWindImpl;
24 24
import com.sap.sailing.domain.confidence.ConfidenceBasedWindAverager;
25 25
import com.sap.sailing.domain.confidence.ConfidenceFactory;
26
+import com.sap.sailing.domain.shared.tracking.impl.TrackImpl;
26 27
import com.sap.sailing.domain.tracking.WindListener;
27 28
import com.sap.sailing.domain.tracking.WindTrack;
28 29
import com.sap.sailing.domain.tracking.WindWithConfidence;
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/Home.css
... ...
@@ -1 +1 @@
1
-/* Not used yet because all CssResources are comming from the design templates. */
1
+/* Not used yet because all CssResources are coming from the design templates. */
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/BoatClassImageResolver.java
... ...
@@ -46,6 +46,7 @@ public class BoatClassImageResolver {
46 46
boatClassIconsMap.put(BoatClassMasterdata.BAVARIA_CRUISER_46.getDisplayName(), imageResources.BavariaCruiser46Icon());
47 47
boatClassIconsMap.put(BoatClassMasterdata.BB10M.getDisplayName(), imageResources.BB10MIcon());
48 48
boatClassIconsMap.put(BoatClassMasterdata.BENETEAU_FIRST_35.getDisplayName(), imageResources.BeneteauFirst35Icon());
49
+ boatClassIconsMap.put(BoatClassMasterdata.BENETEAU_FIRST_36.getDisplayName(), imageResources.BeneteauFirst36Icon());
49 50
boatClassIconsMap.put(BoatClassMasterdata.BENETEAU_FIRST_45.getDisplayName(), imageResources.BeneteauFirst45Icon());
50 51
boatClassIconsMap.put(BoatClassMasterdata.BRASSFAHRT_I.getDisplayName(), imageResources.Brassfahrt1Icon());
51 52
boatClassIconsMap.put(BoatClassMasterdata.BRASSFAHRT_II.getDisplayName(), imageResources.Brassfahrt2Icon());
... ...
@@ -63,6 +64,7 @@ public class BoatClassImageResolver {
63 64
boatClassIconsMap.put(BoatClassMasterdata.DRAGON_INT.getDisplayName(), imageResources.DragonIcon());
64 65
boatClassIconsMap.put(BoatClassMasterdata.DYAS.getDisplayName(), imageResources.DyasIcon());
65 66
boatClassIconsMap.put(BoatClassMasterdata.ELAN350.getDisplayName(), imageResources.Elan350Icon());
67
+ boatClassIconsMap.put(BoatClassMasterdata.ELAN_E4.getDisplayName(), imageResources.ElanE4Icon());
66 68
boatClassIconsMap.put(BoatClassMasterdata.ELLIOTT_6M.getDisplayName(), imageResources.Elliott6mIcon());
67 69
boatClassIconsMap.put(BoatClassMasterdata.EUROPE_INT.getDisplayName(), imageResources.EuropeIcon());
68 70
boatClassIconsMap.put(BoatClassMasterdata.EXTREME_40.getDisplayName(), imageResources.Extreme40Icon());
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/BoatClassImageResources.java
... ...
@@ -96,6 +96,10 @@ public interface BoatClassImageResources extends ClientBundle {
96 96
@ImageOptions(preventInlining = true)
97 97
ImageResource BeneteauFirst35Icon();
98 98
99
+ @Source("com/sap/sailing/gwt/ui/client/images/boatclass/BENETEAU_FIRST_36.png")
100
+ @ImageOptions(preventInlining = true)
101
+ ImageResource BeneteauFirst36Icon();
102
+
99 103
@Source("com/sap/sailing/gwt/ui/client/images/boatclass/BENETEAU_FIRST_45.png")
100 104
@ImageOptions(preventInlining = true)
101 105
ImageResource BeneteauFirst45Icon();
... ...
@@ -152,6 +156,10 @@ public interface BoatClassImageResources extends ClientBundle {
152 156
@ImageOptions(preventInlining = true)
153 157
ImageResource Elan350Icon();
154 158
159
+ @Source("com/sap/sailing/gwt/ui/client/images/boatclass/ELAN_E4.png")
160
+ @ImageOptions(preventInlining = true)
161
+ ImageResource ElanE4Icon();
162
+
155 163
@Source("com/sap/sailing/gwt/ui/client/images/boatclass/EUROPE_INT.png")
156 164
@ImageOptions(preventInlining = true)
157 165
ImageResource EuropeIcon();
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/main.gss
... ...
@@ -2,7 +2,6 @@
2 2
* The main styling styles
3 3
*/
4 4
*,
5
-*:before,
6 5
*:after {
7 6
-moz-box-sizing: border-box;
8 7
box-sizing: border-box;
... ...
@@ -176,130 +175,68 @@ figcaption {
176 175
}
177 176
.label {
178 177
display: inline-block;
178
+ position: relative;
179 179
font-size: 0.8rem;
180 180
font-weight: 600;
181 181
color: #fff;
182
- padding: 1px 0.666666666666667em;
183
- position: relative;
184
- margin-left: 0.833333333333333em;
185
- height: 1.666666666666667em;
186
- line-height: 1.666666666666667em;
182
+ height: 1.6666666667em;
183
+ line-height: 1.6666666667em;
184
+ padding: 0 0.6666666667em 0 1.5em;
185
+ -webkit-clip-path: polygon(0 50%, 0.8333333333em 0, 100% 0, 100% 100%, 0.8333333333em 100%);
186
+ clip-path: polygon(0 50%, 0.8333333333em 0, 100% 0, 100% 100%, 0.8333333333em 100%);
187 187
}
188 188
.mobile .label {
189
- font-size: 0.666666666666667rem;
190
- margin-left: 1em;
189
+ font-size: 0.6667rem;
191 190
height: 1.6em;
192 191
line-height: 1.6em;
193
-}
194
-.label:before {
195
- content: '';
196
- position: absolute;
197
- top: 0;
198
- left: -0.833333333333333em;
199
- width: 0;
200
- height: 0;
201
- border-top: 0.833333333333333em solid transparent;
202
- border-bottom: 0.833333333333333em solid transparent;
203
- border-right: 0.833333333333333em solid transparent;
204
-}
205
-.mobile .label:before {
206
- left: -0.8em;
207
- border-top: 0.8em solid transparent;
208
- border-bottom: 0.8em solid transparent;
209
- border-right: 0.8em solid transparent;
192
+ padding-left: 1.4em; /* 0.8em tip + 0.6em inner */
193
+ -webkit-clip-path: polygon(0 50%, 0.8em 0, 100% 0, 100% 100%, 0.8em 100%);
194
+ clip-path: polygon(0 50%, 0.8em 0, 100% 0, 100% 100%, 0.8em 100%);
195
+ transform: translateZ(0);
196
+ backface-visibility: hidden;
210 197
}
211 198
.labellarge {
212 199
height: 2em;
213
- line-height: 1.75em;
214
- margin-left: 1em;
200
+ line-height: 2em;
201
+ padding-left: 1.6666666667em;
202
+ -webkit-clip-path: polygon(0 50%, 1em 0, 100% 0, 100% 100%, 1em 100%);
203
+ clip-path: polygon(0 50%, 1em 0, 100% 0, 100% 100%, 1em 100%);
215 204
}
216 205
.mobile .labellarge {
217 206
height: 2.4em;
218
- line-height: 2.1em;
219
- margin-left: 1.2em;
220
-}
221
-.labellarge:before {
222
- left: -1em;
223
- border-top-width: 1em;
224
- border-bottom-width: 1em;
225
- border-right-width: 1em;
226
-}
227
-.mobile .labellarge:before {
228
- left: -1.2em;
229
- border-top-width: 1.2em;
230
- border-bottom-width: 1.2em;
231
- border-right-width: 1.2em;
207
+ line-height: 2.4em;
208
+ padding-left: 1.8em; /* 1.2em tip + 0.6em inner */
209
+ -webkit-clip-path: polygon(0 50%, 1.2em 0, 100% 0, 100% 100%, 1.2em 100%);
210
+ clip-path: polygon(0 50%, 1.2em 0, 100% 0, 100% 100%, 1.2em 100%);
211
+ transform: translateZ(0);
212
+ backface-visibility: hidden;
232 213
}
233 214
.label[data-labeltype='live'] {
234 215
background-color: #e94a1b;
235 216
vertical-align: middle;
236 217
margin-top: -0.416666666666667em;
237 218
}
238
-.label[data-labeltype='live']:before {
239
- border-right-color: #e94a1b;
240
-}
241 219
.label[data-labeltype='finished'] {
242 220
background-color: #8ab54e;
243 221
vertical-align: middle;
244 222
margin-top: -0.416666666666667em;
245 223
}
246
-.label[data-labeltype='finished']:before {
247
- border-right-color: #8ab54e;
248
-}
249 224
.label[data-labeltype='next'] {
250 225
background-color: #efaa00;
251 226
vertical-align: middle;
252 227
margin-top: -0.416666666666667em;
253 228
}
254
-.label[data-labeltype='next']:before {
255
- border-right-color: #efaa00;
256
-}
257 229
.label[data-labeltype='progress'] {
258 230
background-color: #efaa00;
259 231
vertical-align: middle;
260 232
margin-top: -0.416666666666667em;
261 233
}
262
-.label[data-labeltype='progress']:before {
263
- border-right-color: #efaa00;
264
-}
265 234
.label[data-labeltype='upcoming'] {
266 235
color: #f6f9fc;
267 236
background-color: #666;
268 237
vertical-align: middle;
269 238
margin-top: -0.416666666666667em;
270 239
}
271
-.label[data-labeltype='upcoming']:before {
272
- border-right-color: #666;
273
-}
274
-.label[data-labeltype='new'],
275
-.label[data-labeltype='updated'] {
276
- background-color: #efaa00;
277
- text-transform: uppercase;
278
- vertical-align: middle;
279
-}
280
-.label[data-labeltype='new']:before,
281
-.label[data-labeltype='updated']:before {
282
- border-right-color: #efaa00;
283
-}
284
-.label[data-labeltype='ess'] {
285
- background-color: #a71724;
286
-}
287
-.label[data-labeltype='ess']:before {
288
- border-right-color: #a71724;
289
-}
290
-.label[data-labeltype='worldcup'] {
291
- background-color: #c79242;
292
-}
293
-.label[data-labeltype='worldcup']:before {
294
- border-right-color: #c79242;
295
-}
296
-.label[data-labeltype='bundesliga'] {
297
- background-color: #005fa7;
298
-}
299
-.label[data-labeltype='bundesliga']:before {
300
- border-right-color: #005fa7;
301
-}
302
-
303 240
.label[data-labeltype='active'],
304 241
.label[data-labeltype='cancelled'],
305 242
.label[data-labeltype='intrial'],
... ...
@@ -310,27 +247,15 @@ figcaption {
310 247
.label[data-labeltype='active'] {
311 248
background-color: #8ab54e;
312 249
}
313
-.label[data-labeltype='active']:before {
314
- border-right-color: #8ab54e;
315
-}
316 250
.label[data-labeltype='cancelled'] {
317 251
background-color: #005fa7;
318 252
}
319
-.label[data-labeltype='cancelled']:before {
320
- border-right-color: #005fa7;
321
-}
322 253
.label[data-labeltype='intrial'] {
323 254
background-color: #efaa00;
324 255
}
325
-.label[data-labeltype='intrial']:before {
326
- border-right-color: #efaa00;
327
-}
328 256
.label[data-labeltype='unknown'] {
329 257
background-color: #e94a1b;
330 258
}
331
-.label[data-labeltype='unknown']:before {
332
- border-right-color: #e94a1b;
333
-}
334 259
335 260
.morelink {
336 261
color: #008fcc;
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/event/LabelType.java
... ...
@@ -33,41 +33,6 @@ public enum LabelType {
33 33
return StringMessages.INSTANCE.upcoming();
34 34
}
35 35
},
36
- NEW_("new") {
37
- @Override
38
- public String getLabel() {
39
-// return StringMessages.INSTANCE.;
40
- return null;
41
- }
42
- },
43
- UPDATED("updated") {
44
- @Override
45
- public String getLabel() {
46
-// return StringMessages.INSTANCE.;
47
- return null;
48
- }
49
- },
50
- ESS("ess") {
51
- @Override
52
- public String getLabel() {
53
-// return StringMessages.INSTANCE.;
54
- return null;
55
- }
56
- },
57
- WORLDCUP("worldcup") {
58
- @Override
59
- public String getLabel() {
60
-// return StringMessages.INSTANCE.;
61
- return null;
62
- }
63
- },
64
- BUNDESLIGA("bundesliga") {
65
- @Override
66
- public String getLabel() {
67
-// return StringMessages.INSTANCE.;
68
- return null;
69
- }
70
- },
71 36
ACTIVE("active") {
72 37
@Override
73 38
public String getLabel() {
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/events/TabletAndDesktopEventsView.java
... ...
@@ -14,6 +14,7 @@ import com.sap.sailing.gwt.home.shared.places.start.StartPlace;
14 14
import com.sap.sailing.gwt.ui.client.StringMessages;
15 15
import com.sap.sse.gwt.client.breadcrumb.BreadcrumbPane;
16 16
import com.sap.sse.gwt.client.media.TakedownNoticeService;
17
+import com.sap.sse.gwt.resources.CommonControlsCSS;
17 18
18 19
public class TabletAndDesktopEventsView extends AbstractEventsView {
19 20
private static EventsPageViewUiBinder uiBinder = GWT.create(EventsPageViewUiBinder.class);
... ...
@@ -33,9 +34,7 @@ public class TabletAndDesktopEventsView extends AbstractEventsView {
33 34
this.navigator = navigator;
34 35
recentEventsWidget = new EventsOverviewRecent(navigator);
35 36
upcomingEventsWidget = new EventsOverviewUpcoming(navigator);
36
-
37 37
initWidget(uiBinder.createAndBindUi(this));
38
-
39 38
initBreadCrumbs();
40 39
}
41 40
... ...
@@ -57,6 +56,12 @@ public class TabletAndDesktopEventsView extends AbstractEventsView {
57 56
});
58 57
}
59 58
59
+ @Override
60
+ protected void onLoad() {
61
+ super.onLoad();
62
+ CommonControlsCSS.ensureInjected();
63
+ }
64
+
60 65
@Override
61 66
protected void updateEventsUI(TakedownNoticeService takedownNoticeService) {
62 67
recentEventsWidget.updateEvents(eventListView.getRecentEvents(), takedownNoticeService);
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/whatsnew/resources/SailingAnalyticsNotes.html
... ...
@@ -5,6 +5,23 @@
5 5
<div id="mainContent">
6 6
<h4 class="articleHeadline">What's New - SAP Sailing Analytics</h4>
7 7
<div class="innerContent">
8
+ <h5 class="articleSubheadline">October 2025</h5>
9
+ <ul class="bulletList">
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>
16
+ <li>The source code of the Sailing Analytics including its companion apps (Race Manager App,
17
+ Buoy Pinger App, Sail Insight) has been published under the Apache 2.0 license on
18
+ GitHub. See <a href="https://github.com/SAP/sailing-analytics">https://github.com/SAP/sailing-analytics</a>.
19
+ Get engaged!</li>
20
+ </ul>
21
+ <h5 class="articleSubheadline">September 2025</h5>
22
+ <ul class="bulletList">
23
+ <li>Added CSV export for polar data histograms in Data Mining.</li>
24
+ </ul>
8 25
<h5 class="articleSubheadline">June 2025</h5>
9 26
<ul class="bulletList">
10 27
<li>Bug fix: the refresh interval in the Races/Tracking list ran into an overflow when races
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/MobileEntryPoint.java
... ...
@@ -35,12 +35,9 @@ public class MobileEntryPoint extends AbstractMvpEntryPoint<StringMessages, Mobi
35 35
@Override
36 36
public void doOnModuleLoad() {
37 37
Document.get().getBody().addClassName(SharedResources.INSTANCE.mainCss().mobile());
38
-
39 38
CommonControlsCSS.ensureInjected();
40
-
41 39
ServerConfigurationServiceAsync serverConfigService = GWT.create(ServerConfigurationService.class);
42 40
EntryPointHelper.registerASyncService((ServiceDefTarget) serverConfigService, RemoteServiceMappingConstants.serverConfigurationServiceRemotePath);
43
-
44 41
serverConfigService.isStandaloneServer(new AsyncCallback<Boolean>() {
45 42
@Override
46 43
public void onSuccess(Boolean result) {
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/home/shared/app/HomePlacesNavigator.java
... ...
@@ -42,7 +42,6 @@ public class HomePlacesNavigator extends AbstractPlaceNavigator {
42 42
return createGlobalPlaceNavigation(new EventsPlace());
43 43
}
44 44
45
-
46 45
public PlaceNavigation<MoreLoginInformationPlace> getMoreLoginInfo() {
47 46
return createGlobalPlaceNavigation(new MoreLoginInformationPlace());
48 47
}
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
... ...
@@ -2565,6 +2565,9 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages,
2565 2565
String unableToLoadIpsBlockedForBearerTokenAbuse();
2566 2566
String ipAddress();
2567 2567
String filterIpAddresses();
2568
+ String exportTWAHistogramToCsv();
2569
+ String exportWindSpeedHistogramToCsv();
2570
+ String optionalBearerTokenForWindImport();
2568 2571
String unlock();
2569 2572
String ipsLockedForUserCreationAbuse();
2570 2573
String unableToLoadIpsBlockedForUserCreationAbuse();
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties
... ...
@@ -2603,6 +2603,9 @@ selectToRaceColumn=Select race colunm to where to copy pairings
2603 2603
unableToLoadIpsBlockedForBearerTokenAbuse=Unable to load IPs blocked for bearer token abuse
2604 2604
ipAddress=IP Address
2605 2605
filterIpAddresses=Filter IP Addresses
2606
+exportTWAHistogramToCsv=Export True Wind Angle histogram to CSV
2607
+exportWindSpeedHistogramToCsv=Export Wind Speed histogram to CSV
2608
+optionalBearerTokenForWindImport=Optional bearer token for wind import
2606 2609
unlock=Unlock
2607 2610
ipsLockedForUserCreationAbuse=IPs Locked for User Creation Abuse
2608 2611
unableToLoadIpsBlockedForUserCreationAbuse=Unable to load IPs Blocked for User Creation Abuse
... ...
\ No newline at end of file
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_cs.properties
... ...
@@ -243,6 +243,7 @@ raceIsKnownToStartUpwind=Rozjížďky začínající úsekem proti větru
243 243
events=Události
244 244
pairingList=Rozpis
245 245
pairingLists=Rozpisy
246
+copyPairingListFromOtherLeaderboard=Kopírovat rozpis z jiné výsledkové tabule
246 247
pairingListCreationInfo=Nejprve se vypočítá předloha, která obsahuje pouze čísla závodníků a kterou lze použít i pro jiné výsledkové tabule. Není-li počet závodníků beze zbytku dělitelný počtem lodí, do rozpisu se zadají prázdné zástupné znaky. \ Pokud se počet závodníků nerovná počtu registrovaných závodníků v aktuální výsledkové tabuli, rozpis nebude možné použít pro závodní logy. \ Počet rozjezdů, na jehož základě se tvoří rozpis, je definován počtem rozjezdů ve výsledkové tabuli bez finálových rozjížděk. \ Používají-li se opakované rozjezdy, lze přiřazení do rozjezdů opakovat (např. 15 rozjezdů se třemi opakováními: 5 přiřazení do jedinečných rozjezdů, přičemž se každý bude třikrát opakovat). \ Koeficient změny lodě rovnající se 0 povede k náležitému rozdělení závodníků mezi lodě, zatímco maximální hodnota (=počet rozjezdů) povede k nižšímu počtu změn lodí zvýšením pravděpodobnosti, že závodník popluje na stejné lodi dvě po sobě jdoucí rozjížďky v rozjezdu.
247 248
setCompetitors=Nastavte počet závodníků:
248 249
setBoatChangeFactor=Nastavte koeficient změny lodě:
... ...
@@ -2576,7 +2577,9 @@ hasNoAIAgentCredentials=Žádné přihlašovací údaje agenta AI nejsou dostupn
2576 2577
placeholderAICoreCredentialsAsJSON=Poskytněte přihlašovací údaje AI Core jako dokument JSON
2577 2578
credentials=Přihlašovací údaje
2578 2579
updateCredentials=Aktualizovat přihlašovací údaje
2580
+resetCredentials=Resetovat přihlašovací údaje
2579 2581
successfullyUpdatedAIAgentCredentials=Přihlašovací údaje agenta AI byly úspěšně aktualizovány
2582
+successfullyResetAIAgentCredentials=Přihlašovací údaje agenta AI byly úspěšně resetovány
2580 2583
errorUpdatingAIAgentCredentials=Chyba při aktualizaci přihlašovacích údajů agenta AI: {0}
2581 2584
corsAndCSPFilterConfiguration=Konfigurace filtru „frame-ancestors“ CORS a CSP
2582 2585
corsAndCSPFilterConfigurationHint=Používejte opatrně! Můžete použít zástupný znak, abyste požadavkům CORS povolili rozhraní API serveru a integraci vlastních stránek, např. IFrames odkudkoliv, nebo můžete zadat skupinu povolených původních bází URL jako https://www.example.org
... ...
@@ -2585,3 +2588,8 @@ corsAllowedOrigins=Povolené původy
2585 2588
successfullyUpdatedCORSAllowedOrigins=Povolené původy CORS byly úspěšně aktualizovány
2586 2589
commandLogOutput=Výstup protokolu příkazu
2587 2590
errorLoadingLeaderboard=Chyba při načítání výsledkové tabule {0}: {1}
2591
+selectRaceColumnsWhosePairingsToCopy=Sloupce rozjížďky, jejichž propojení chcete zkopírovat
2592
+errorCopyingPairings=Chyba při kopírování propojení: {0}
2593
+successfullyCopiedPairings=Úspěšně zkopírovaná propojení
2594
+selectFromRaceColumn=Vyberte sloupec rozjížďky, od kterého chcete zahájit kopírování propojení
2595
+selectToRaceColumn=Vyberte sloupec rozjížďky, do kterého chcete zkopírovat propojení
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_da.properties
... ...
@@ -243,6 +243,7 @@ raceIsKnownToStartUpwind=Kapsejlads starter med ben på bidevind
243 243
events=Begivenheder
244 244
pairingList=Parliste
245 245
pairingLists=Parlister
246
+copyPairingListFromOtherLeaderboard=Kopier parlister fra anden rangliste
246 247
pairingListCreationInfo=for det første beregnes en skabelon, der kun indeholder deltagernumrene, og som kan anvendes til andre ranglister. Hvis antallet af deltagere ikke kan divideres med antallet af både uden rest, indsættes tomme pladsholdere i parlisten. \ Hvis antallet af deltagere derudover ikke er lig med antallet af tilmeldte deltagere i den faktiske rangliste, kan parlisten ikke anvendes på kapsejladsprotokoller. \ Antallet af løb, der bruges til at generere en parliste, defineres af antallet af løb i ranglisten, der ikke er medaljesejladser. \ Når gentagne løb bruges, kan tildelinger af løb gentages (fx 15 løb med 3 gentagne løb: 5 unikke tildelinger af løb, og hver af dem gentages 3 gange). \ En bådændringsfaktor lig med 0 medfører en veldistribueret tildeligen af deltagere til både, hvorimod maksimumværdien (antal løb) medfører færre bådændringer ved at øge chancen for, at en deltager sejler med den samme båd i mere end 2 fortløbende kapsejladser på tværs af en løbsgrænse.
247 248
setCompetitors=Sæt antallet af deltagere:
248 249
setBoatChangeFactor=Sæt bådændringsfaktor:
... ...
@@ -2576,7 +2577,9 @@ hasNoAIAgentCredentials=Ingen legitimationsoplysninger for AI-agenten er tilgæn
2576 2577
placeholderAICoreCredentialsAsJSON=Angiv AI Core-legitimationsoplysninger som JSON-dokument
2577 2578
credentials=Legitimationsoplysninger
2578 2579
updateCredentials=Opdater legitimationsoplysninger
2580
+resetCredentials=Nulstil legitimationsoplysninger
2579 2581
successfullyUpdatedAIAgentCredentials=AI-agentens legitimationsoplysninger blev opdateret
2582
+successfullyResetAIAgentCredentials=AI-agentens legitimationsoplysninger blev nulstillet
2580 2583
errorUpdatingAIAgentCredentials=Fejl ved opdatering af AI-agentens legitimationsoplysninger: {0}
2581 2584
corsAndCSPFilterConfiguration=Konfiguration af filteret "frame-ancestors" for CORS og CSP
2582 2585
corsAndCSPFilterConfigurationHint=Brug med forsigtighed. Du kan bruge et jokertegn til at tillade CORS-anmodninger til denne servers API og integration af dens sider i fx iFrames alle steder fra, eller du kan angive en række basis-URL''er for tilladte oprindelser, som i https://www.example.org
... ...
@@ -2585,3 +2588,8 @@ corsAllowedOrigins=Tilladte oprindelser
2585 2588
successfullyUpdatedCORSAllowedOrigins=Tilladte oprindelser for CORS blev opdateret
2586 2589
commandLogOutput=Logoutput for kommando
2587 2590
errorLoadingLeaderboard=Fejl ved indlæsning af rangliste {0}: {1}
2591
+selectRaceColumnsWhosePairingsToCopy=Kapsejladskolonner, hvis par skal kopieres
2592
+errorCopyingPairings=Fejl ved kopiering af par: {0}
2593
+successfullyCopiedPairings=Par blev kopieret
2594
+selectFromRaceColumn=Vælg kapsejladskolonne, hvorfra kopiering af par skal startes
2595
+selectToRaceColumn=Vælg kapsejladskolonne, hvortil kopiering af par skal startes
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties
... ...
@@ -2596,6 +2596,9 @@ selectToRaceColumn=Spalte auswählen, bis zu der die Zuordnungen kopiert werden
2596 2596
unableToLoadIpsBlockedForBearerTokenAbuse=Aufgrund von Missbrauch des Inhabertokens blockierte IPs können nicht geladen werden
2597 2597
ipsLockedForBearerTokenAbuse=IPs wegen Missbrauchs von Bearer-Token gesperrt
2598 2598
ipAddress=IP-Addresse
2599
+exportTWAHistogramToCsv=Histogramm der wahren Windwinkel in CSV exportieren
2600
+exportWindSpeedHistogramToCsv=Histogramm der wahren Windgeschwindigkeiten in CSV exportieren
2601
+optionalBearerTokenForWindImport=Optionales Bearer Token für Wind-Import
2599 2602
filterIpAddresses=IP-Adressen filtern
2600 2603
unlock=Entsperren
2601 2604
ipsLockedForUserCreationAbuse=Wegen Missbrauchs bei der Benutzererstellung gesperrte IPs
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_es.properties
... ...
@@ -243,6 +243,7 @@ raceIsKnownToStartUpwind=La prueba empieza con ceñida
243 243
events=Eventos
244 244
pairingList=Lista de emparejamientos
245 245
pairingLists=Listas de emparejamientos
246
+copyPairingListFromOtherLeaderboard=Copiar la lista de emparejamientos de otra clasificación
246 247
pairingListCreationInfo=Inicialmente se calculará una plantilla que contenga solo los números de los competidores y que pueda aplicarse a otras clasificaciones. Si el recuento de competidores no es divisible por recuento de embarcaciones sin remanente, se introducirán marcadores de posición en blanco en la lista de emparejamientos. \ Además, si el recuento de competidores difiere del recuento de competidores registrado en la clasificación real, la lista de emparejamientos no podrá aplicarse a los registros de la prueba. \ El recuento de flights, que se utiliza para generar una lista de emparejamientos, está definido por el número de flights de la clasificación que no son pruebas de medalla. \ Cuando se utilizan repeticiones de flight, se pueden repetir asignaciones de flights (por ejemplo, 15 flights con 3 repeticiones de flight: 5 asignaciones de flight únicas que se repetirán 3 veces cada una de ellas). \ Un factor de cambio de embarcación equivalente a 0 da como resultado una asignación bien distribuida de competidores y embarcaciones, considerando que el máximo (número de flights) provoca menos cambios de embarcación aumentando las posibilidades de que un competidor navegue con la misma embarcación en dos pruebas contiguas a través de un límite de flights.
247 248
setCompetitors=Fije el recuento de competidores:
248 249
setBoatChangeFactor=Fije el factor de cambio de embarcación:
... ...
@@ -2576,7 +2577,9 @@ hasNoAIAgentCredentials=Credenciales del agente de IA no disponibles
2576 2577
placeholderAICoreCredentialsAsJSON=Proporcionar credenciales del núcleo de IA como documento JSON
2577 2578
credentials=Credenciales
2578 2579
updateCredentials=Actualizar credenciales
2580
+resetCredentials=Restablecer credenciales
2579 2581
successfullyUpdatedAIAgentCredentials=Credenciales del agente de IA actualizadas correctamente
2582
+successfullyResetAIAgentCredentials=Credenciales del agente de IA restablecidas correctamente
2580 2583
errorUpdatingAIAgentCredentials=Error al actualizar las credenciales del agente de IA: {0}
2581 2584
corsAndCSPFilterConfiguration=Configuración de filtro "predecesores del marco" CORS y CSP
2582 2585
corsAndCSPFilterConfigurationHint=Use con precaución. Puede utilizar un comodín para permitir las solicitudes CORS para la API de este servidor y la integración de sus páginas en, por ejemplo, iFrames de cualquier lugar, o puede especificar un conjunto de URLs base de origen permitidas, como en https://www.example.org
... ...
@@ -2585,3 +2588,8 @@ corsAllowedOrigins=Orígenes permitidos
2585 2588
successfullyUpdatedCORSAllowedOrigins=Orígenes permitidos CORS actualizados correctamente
2586 2589
commandLogOutput=Salida de log de comandos
2587 2590
errorLoadingLeaderboard=Error al cargar la tabla de clasificación {0}: {1}
2591
+selectRaceColumnsWhosePairingsToCopy=Columna Prueba cuyos emparejamientos se deben copiar
2592
+errorCopyingPairings=Error al copiar emparejamientos: {0}
2593
+successfullyCopiedPairings=Emparejamientos copiados correctamente
2594
+selectFromRaceColumn=Seleccione la columna Prueba desde la que se inicia la copia de emparejamientos
2595
+selectToRaceColumn=Seleccione la columna Prueba desde la que se copian los emparejamientos
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_fr.properties
... ...
@@ -243,6 +243,7 @@ raceIsKnownToStartUpwind=La course a commencé avec une portion de parcours au v
243 243
events=Manifestations
244 244
pairingList=Liste d''appariement
245 245
pairingLists=Listes d''appariement
246
+copyPairingListFromOtherLeaderboard=Copier la liste d''appariements de l''autre palmarès
246 247
pairingListCreationInfo=Tout d''abord, un modèle contenant uniquement le nombre de concurrents sera calculé. Il pourra être appliqué à d''autres palmarès. Si la division du nombre de concurrents par le nombre de bateaux ne permet pas d''obtenir un nombre entier, des espaces réservés vides seront ajoutés à la liste d''appariement. \ En outre, si le nombre de concurrents ne correspond pas au nombre de concurrents inscrits au palmarès actuel, la liste d''appariement ne pourra pas être appliquée aux journaux de course. \ Le nombre de flights (matchs) utilisé pour générer une liste d''appariement est défini par tous les flights du palmarès, hors courses médaillées. \ En cas de répétition des flights, les affectations de flights peuvent être répétées (par exemple, 15 flights avec 3 répétitions : 5 affectations de flights uniques, et chacun sera répété 3 fois). \ Un facteur de changement de bateau équivalent à 0 permet une répartition correcte des concurrents sur les bateaux, alors que le nombre de flights maximum implique moins de changements de bateaux en augmentant les chances qu''un concurrent navigue sur un même bateau pour deux courses consécutives trans-flight.
247 248
setCompetitors=Veuillez définir le nombre de concurrents :
248 249
setBoatChangeFactor=Veuillez définir le facteur de changement de bateau :
... ...
@@ -2576,7 +2577,9 @@ hasNoAIAgentCredentials=Aucun identifiant d''agent d''IA disponible.
2576 2577
placeholderAICoreCredentialsAsJSON=Fournir des identifiants AI Core sous la forme d''un document JSON
2577 2578
credentials=Identifiants
2578 2579
updateCredentials=Mettre à jour les identifiants
2580
+resetCredentials=Réinitialiser les identifiants
2579 2581
successfullyUpdatedAIAgentCredentials=Identifiants d''agent d''IA mis à jour
2582
+successfullyResetAIAgentCredentials=Identifiants d''agent d''IA réinitialisés
2580 2583
errorUpdatingAIAgentCredentials=Erreur lors de la mise à jour des identifiants d''agent d''IA : {0}
2581 2584
corsAndCSPFilterConfiguration=Configuration des filtres CORS et CSP "frame-ancestors"
2582 2585
corsAndCSPFilterConfigurationHint=Attention ! Vous pouvez utiliser un caractère générique pour autoriser les demandes CORS vers l''API de ce serveur et l''intégration de ses pages (par ex. dans IFrame) depuis tout emplacement, ou vous pouvez indiquer un ensemble d''URL de base pour des origines autorisées, par ex. https://www.example.org.
... ...
@@ -2585,3 +2588,8 @@ corsAllowedOrigins=Origines autorisées
2585 2588
successfullyUpdatedCORSAllowedOrigins=Origines autorisées CORS mises à jour
2586 2589
commandLogOutput=Édition du journal des commandes
2587 2590
errorLoadingLeaderboard=Erreur lors du chargement du palmarès {0} : {1}
2591
+selectRaceColumnsWhosePairingsToCopy=Colonnes de la course avec appariements à copier
2592
+errorCopyingPairings=Erreur lors de la copie des appariements : {0}
2593
+successfullyCopiedPairings=Appariements copiés
2594
+selectFromRaceColumn=Sélectionnez la colonne de la course dans laquelle commencer à copier des appariements.
2595
+selectToRaceColumn=Sélectionnez la colonne de la course dans laquelle commencer à coller des appariements.
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_it.properties
... ...
@@ -243,6 +243,7 @@ raceIsKnownToStartUpwind=La gara inizia con una tratta sopravento
243 243
events=Eventi
244 244
pairingList=Pairing list
245 245
pairingLists=Pairing lists
246
+copyPairingListFromOtherLeaderboard=Copia pairing lista dall''altra classifica
246 247
pairingListCreationInfo=Innanzitutto verrà calcolato un modello contenente solo i numeri dei concorrenti che potrà essere applicato ad altre classifiche. Se il numero di concorrenti non è divisibile per il numero di barche senza promemoria, nella pairing list verranno inseriti segnaposti vuoti. \ Inoltre, se il numero dei concorrenti è diverso dal numero di concorrenti registrati nella classifica effettiva, la pairing list non potrà applicarsi ai registri della gara. \ Il numero di voli utilizzato per generare una pairing list è definito dal numero di voli nella classifica di gare diverse dalla medal race. \ Quando si utilizzano ripetizioni di volo, è possibile ripetere assegnazioni di volo (ad esempio 15 voli con 3 ripetizioni: 5 assegnazioni univoche di voli, ciascuna delle quali verrà ripetuta 3 volte). \ Un fattore di cambio barca uguale a 0 porta a un''assegnazione ben distribuita di concorrenti nelle barche, mentre il massimo (numero di voli) porta a un numero inferiore di cambi barca aumentando le possibilità che un concorrente gareggi nella stessa barca per due gare contigue oltre un confine del volo.
247 248
setCompetitors=Impostare il numero di concorrenti:
248 249
setBoatChangeFactor=Impostare il fattore di cambio barca:
... ...
@@ -2576,7 +2577,9 @@ hasNoAIAgentCredentials=Credenziali Agente AI non disponibili
2576 2577
placeholderAICoreCredentialsAsJSON=Fornire le credenziali AI Core come documento JSON
2577 2578
credentials=Credenziali
2578 2579
updateCredentials=Aggiorna credenziali
2580
+resetCredentials=Resetta credenziali
2579 2581
successfullyUpdatedAIAgentCredentials=Credenziali Agente AI aggiornate correttamente
2582
+successfullyResetAIAgentCredentials=Credenziali Agente AI resettate correttamente
2580 2583
errorUpdatingAIAgentCredentials=Errore nell''aggiornamento delle credenziali Agente AI: {0}
2581 2584
corsAndCSPFilterConfiguration=Configurazione filtro "frame-ancestors" CORS e CSP
2582 2585
corsAndCSPFilterConfigurationHint=Usare con prudenza. È possibile utilizzare un carattere per ricerca generica per consentire richieste CORS all''API di questo server e l''integrazione delle sue pagine, ad esempio in IFrames, da qualsiasi punto. In alternativa, è possibile specificare un insieme di URL base di origini consentite, quale https://www.esempio.org
... ...
@@ -2585,3 +2588,8 @@ corsAllowedOrigins=Origini consentite
2585 2588
successfullyUpdatedCORSAllowedOrigins=Origini CORS consentite aggiornate correttamente
2586 2589
commandLogOutput=Output del registro di comando
2587 2590
errorLoadingLeaderboard=Errore di caricamento della classifica {0}: {1}
2591
+selectRaceColumnsWhosePairingsToCopy=Colonne della gara di cui copiare gli accoppiamenti
2592
+errorCopyingPairings=Errore di copia degli accoppiamenti: {0}
2593
+successfullyCopiedPairings=Accoppiamenti copiati correttamente
2594
+selectFromRaceColumn=Seleziona la colonna della gara da cui iniziare a copiare gli accoppiamenti
2595
+selectToRaceColumn=Seleziona la colonna della gara in cui copiare gli accoppiamenti
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ja.properties
... ...
@@ -243,6 +243,7 @@ raceIsKnownToStartUpwind=アップウィンドレグでレーススタート
243 243
events=イベント
244 244
pairingList=対戦表
245 245
pairingLists=対戦表
246
+copyPairingListFromOtherLeaderboard=他のリーダーボードから対戦表をコピー
246 247
pairingListCreationInfo=まず最初に、競技者番号のみが含まれていて、他のリーダーボードに適用できるテンプレートがすべて計算されます。競技者カウントが艇カウントで割り切れない場合、空のプレースホルダーが対戦表に入力されます。 \ また、競技者カウントが実際のリーダーボードにある登録済の競技者カウントと等しくない場合、対戦表をレースログに適用することはできません。 \ 対戦表を生成するために使用されるフライトのカウントは、リーダーボードにあるメダルレースでないフライトの回数によって設定されます。 \ フライト繰返の使用時に、フライト割当を繰り返すことができます (例: 3 つのフライト繰返がある 15 回のフライトでは、別々の 5 つのフライト割当でそれぞれが 3 回繰り返されます)。 \ 艇変更係数が 0 に等しければ、艇への競技者の割当がうまく配分されることにつながり、一方、最大 (フライト回数) になると、フライトの境界を超えて隣接している 2 つのレースで競技者が同一艇で帆走する可能性が高くなることで艇変更がより少なくなります。
247 248
setCompetitors=競技者カウントを設定してください:
248 249
setBoatChangeFactor=艇変更係数を設定してください:
... ...
@@ -2576,7 +2577,9 @@ hasNoAIAgentCredentials=AI エージェント認証情報が利用可能であ
2576 2577
placeholderAICoreCredentialsAsJSON=AI Core 認証情報を JSON 文書として指定してください
2577 2578
credentials=認証情報
2578 2579
updateCredentials=認証情報の更新
2580
+resetCredentials=認証情報のリセット
2579 2581
successfullyUpdatedAIAgentCredentials=AI エージェント認証情報が更新されました
2582
+successfullyResetAIAgentCredentials=AI エージェント認証情報がリセットされました
2580 2583
errorUpdatingAIAgentCredentials=AI エージェント認証情報の更新でエラーが発生しました: {0}
2581 2584
corsAndCSPFilterConfiguration=CORS および CSP "frame-ancestors" フィルタ設定
2582 2585
corsAndCSPFilterConfigurationHint=注意深く使用してください。このサーバの API への CORS 要求を許可するワイルドカードを使用してそのページをたとえばどこからでも iFrame に埋め込むか、https://www.example.org でのように許可済みソースベース URL のセットを指定することができます。
... ...
@@ -2585,3 +2588,8 @@ corsAllowedOrigins=許可済みソース
2585 2588
successfullyUpdatedCORSAllowedOrigins=CORS 許可済みソースが更新されました
2586 2589
commandLogOutput=コマンドログ出力
2587 2590
errorLoadingLeaderboard=リーダーボード {0} のロードでエラーが発生: {1}
2591
+selectRaceColumnsWhosePairingsToCopy=対戦組み合わせをコピーするレース列
2592
+errorCopyingPairings=対戦組み合わせのコピーでエラーが発生: {0}
2593
+successfullyCopiedPairings=対戦組み合わせを正常にコピー済み
2594
+selectFromRaceColumn=対戦組み合わせのコピーを開始する元のレース列を選択
2595
+selectToRaceColumn=対戦組み合わせのコピー先のレース列を選択
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_pt.properties
... ...
@@ -243,6 +243,7 @@ raceIsKnownToStartUpwind=A corrida começa com perna de contravento
243 243
events=Eventos
244 244
pairingList=Lista de pares
245 245
pairingLists=Listas de pares
246
+copyPairingListFromOtherLeaderboard=Copiar lista de pares de outro painel de classificação
246 247
pairingListCreationInfo=Primeiro será calculado um modelo que contém somente os números dos competidores e pode ser aplicado a outros painéis de classificação. Se contagem de competidores não for divisível pela contagem de barcos sem restante, os caracteres de preenchimento em branco serão inseridos na lista de pares. \ Além disso, se a contagem de competidores for diferente da contagem de competidores registrados no painel de classificação real, a lista de pares não pode ser aplicada aos registros das corridas. \ A contagem de voos, que é utilizada para gerar uma lista de pares, é definida pelo número de voos no painel de classificação que não sejam corridas para medalha. \ Ao utilizar repetições de voo, as atribuições de voo podem ser repetidas (por exemplo, 15 voos com 3 repetições de voo: 5 atribuições de voo únicas e cada uma será repetida 3 vezes). \ Um fator de mudança de barco igual a 0 leva a uma atribuição bem distribuída de competidores a barcos, sendo que o (número de voos) máximo leva a menos mudanças de barco aumentando as hipóteses de um competidor navegar no mesmo barco em duas corridas contínuas além do limite do voo.
247 248
setCompetitors=Defina a contagem dos competidores:
248 249
setBoatChangeFactor=Defina o fator de mudança de barco:
... ...
@@ -2576,7 +2577,9 @@ hasNoAIAgentCredentials=Nenhuma credencial do agente de IA disponível
2576 2577
placeholderAICoreCredentialsAsJSON=Fornecer credenciais de SAP AI Core como documento JSON
2577 2578
credentials=Credenciais
2578 2579
updateCredentials=Atualizar credenciais
2580
+resetCredentials=Reiniciar credenciais
2579 2581
successfullyUpdatedAIAgentCredentials=Credenciais de agente de IA atualizadas com êxito
2582
+successfullyResetAIAgentCredentials=Credenciais de agente de IA reinicializadas com êxito
2580 2583
errorUpdatingAIAgentCredentials=Erro ao atualizar credenciais de agente de IA: {0}
2581 2584
corsAndCSPFilterConfiguration=Configuração de filtro CORS e CSP "frame-ancestors"
2582 2585
corsAndCSPFilterConfigurationHint=Utilizar com cuidado! Você pode utilizar um curinga para permitir solicitações CORS à API deste servidor e a incorporação das suas páginas em, por exemplo, IFrames de qualquer lugar, ou pode indicar um conjunto de URLs base de origem permitidos, como em https://www.example.org
... ...
@@ -2585,3 +2588,8 @@ corsAllowedOrigins=Origens permitidas
2585 2588
successfullyUpdatedCORSAllowedOrigins=Origens permitidas CORS atualizadas com êxito
2586 2589
commandLogOutput=Saída do log de comando
2587 2590
errorLoadingLeaderboard=Erro ao carregar o painel de classificação {0}: {1}
2591
+selectRaceColumnsWhosePairingsToCopy=Colunas da corrida cujos pares devem ser copiados
2592
+errorCopyingPairings=Erro ao copiar pares: {0}
2593
+successfullyCopiedPairings=Pares copiados com êxito
2594
+selectFromRaceColumn=Selecionar coluna da corrida a partir da qual deve ser iniciada a cópia dos pares
2595
+selectToRaceColumn=Selecionar coluna da corrida para a qual deve ser efetuada a cópia dos pares
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties
... ...
@@ -243,6 +243,7 @@ raceIsKnownToStartUpwind=Гонка начинается против ветра
243 243
events=События
244 244
pairingList=Список пар
245 245
pairingLists=Списки пар
246
+copyPairingListFromOtherLeaderboard=Скопировать список пар из другой таблицы лидеров
246 247
pairingListCreationInfo=Первым будет рассчитан шаблон, который содержит только номера участников и может быть применен к другим таблицам лидеров. Если число участников не делится на число лодок без остатка, в списке пар будут пустые места. \ Если число участников не равно числу зарегистрированных участников в фактической таблице лидеров, список пар не может быть применен к журналам гонки. \ Для генерирования списка пар используется число всех флайтов таблицы лидеров, не являющихся гонками на медали. \ Если используются повторы флайтов, то присвоения флайтов могут повторяться (например, 15 флайтов с 3 повторами: 5 уникальных присвоений флайтов, каждый из которых повторяется 3 раза). \ Результатом нулевого коэффициента смены лодки является хорошее распределение участников по лодкам, то есть максимум (число флайтов) ведет к меньшему числу смен лодок, и это увеличивает шансы, что участник будет плыть на одной и той же лодке в двух смежных гонках до окончания флайта.
247 248
setCompetitors=Укажите число участников:
248 249
setBoatChangeFactor=Укажите коэффициент смены лодки:
... ...
@@ -2576,7 +2577,9 @@ hasNoAIAgentCredentials=Учетных данных агента ИИ нет
2576 2577
placeholderAICoreCredentialsAsJSON=Предоставьте учетные данные ядра ИИ в качестве документа JSON
2577 2578
credentials=Учетные данные
2578 2579
updateCredentials=Обновить учетные данные
2580
+resetCredentials=Сбросить учетные данные
2579 2581
successfullyUpdatedAIAgentCredentials=Учетные данные агента ИИ обновлены
2582
+successfullyResetAIAgentCredentials=Учетные данные агента ИИ сброшены
2580 2583
errorUpdatingAIAgentCredentials=Ошибка при обновлении учетных данных агента ИИ: {0}
2581 2584
corsAndCSPFilterConfiguration=Конфигурация фильтра "frame-ancestors" CORS и CSP
2582 2585
corsAndCSPFilterConfigurationHint=Используйте с осторожностью! Подстановочный знак можно использовать, чтобы разрешить запросы CORS для этого серверного API и вложения страниц, например, IFrames из любого места, или можно указать набор базовых URL разрешенных происхождений, как в https://www.example.org
... ...
@@ -2585,3 +2588,8 @@ corsAllowedOrigins=Разрешенные происхождения
2585 2588
successfullyUpdatedCORSAllowedOrigins=Разрешенные происхождения CORS обновлены
2586 2589
commandLogOutput=Вывод журнала команд
2587 2590
errorLoadingLeaderboard=Ошибка при загрузке таблицы лидеров {0}: {1}
2591
+selectRaceColumnsWhosePairingsToCopy=Столбцы гонки, пары которой требуется скопировать
2592
+errorCopyingPairings=Ошибка при копировании пар: {0}
2593
+successfullyCopiedPairings=Успешно скопированные пары
2594
+selectFromRaceColumn=Выберите столбец гонки, с которого требуется начать копирование пар
2595
+selectToRaceColumn=Выберите столбец гонки, в который требуется копировать пары
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_sl.properties
... ...
@@ -243,6 +243,7 @@ raceIsKnownToStartUpwind=Plov se začne s stranico proti vetru
243 243
events=Dogodki
244 244
pairingList=Seznam parov
245 245
pairingLists=Seznami parov
246
+copyPairingListFromOtherLeaderboard=Kopiranje seznama parov z druge lestvice vodilnih
246 247
pairingListCreationInfo=Najprej bo izračunana predloga, ki vsebuje samo številke tekmovalcev in jo je mogoče uporabiti za druge lestvice vodilnih. Če število tekmovalcev ni deljivo s številom jadrnic brez ostanka, bodo na seznam parov dodane prazne označbe mesta. \ Če število tekmovalcev ni enako številu registriranih tekmovalcev na aktualni lestvici vodilnih, seznama parov ni mogoče uporabiti za dnevnike plovov. \ Število jadranj, uporabljeno za generiranje seznama parov, je opredeljeno kot število jadranj na lestvici vodilnih, ki niso plovi za kolajne. \ Pri uporabi ponovitev jadranj je mogoče ponoviti dodelitve jadranj (npr. 15 jadranj s 3 ponovitvami jadranj: 5 enkratnih dodelitev jadranj, kjer se vsaka ponovi 3-krat). \ Faktor menjave jadrnice, ki je enak 0, pomeni enakomerno porazdeljeno dodelitev tekmovalcev jadrnicam, največje (število jadranj) pa pomeni manj menjav jadrnic, zato je večja verjetnost, da bo tekmovalec z isto jadrnico jadral dva plova zapored znotraj meja jadranja.
247 248
setCompetitors=Nastavite število tekmovalcev:
248 249
setBoatChangeFactor=Nastavite faktor menjave jadrnic:
... ...
@@ -2576,7 +2577,9 @@ hasNoAIAgentCredentials=Poverilnice agenta UI niso na voljo
2576 2577
placeholderAICoreCredentialsAsJSON=Zagotavljanje poverilnic za AI Core kot dokument JSON
2577 2578
credentials=Poverilnice
2578 2579
updateCredentials=Posodobitev poverilnic
2580
+resetCredentials=Ponastavitev poverilnic
2579 2581
successfullyUpdatedAIAgentCredentials=Uspešno posodobljene poverilnice agenta UI
2582
+successfullyResetAIAgentCredentials=Uspešno ponastavljene poverilnice agenta UI
2580 2583
errorUpdatingAIAgentCredentials=Napaka pri posodobitvi poverilnic agenta UV: {0}
2581 2584
corsAndCSPFilterConfiguration=Konfiguracija filtra CORS in CSP "frame-ancestors"
2582 2585
corsAndCSPFilterConfigurationHint=Uporabite previdno! Uporabite lahko nadomestni znak, ki omogoča zahteve CORS v API-ju tega strežnika in vdelovanje strani v npr. IFrames od koderkoli ali pa določite niz dovoljenih URL-jev osnovnega porekla, kot v https://www.example.org
... ...
@@ -2585,3 +2588,8 @@ corsAllowedOrigins=Dovoljena porekla
2585 2588
successfullyUpdatedCORSAllowedOrigins=Uspešno posodobljeni CORS - dovoljeno poreklo
2586 2589
commandLogOutput=Izdaja dnevnika ukazov
2587 2590
errorLoadingLeaderboard=Napaka pri prenosu lestvice vodilnih {0}: {1}
2591
+selectRaceColumnsWhosePairingsToCopy=Stolpci plovov, katerih pare želite kopirati
2592
+errorCopyingPairings=Napaka pri kopiranju parov: {0}
2593
+successfullyCopiedPairings=Uspešno kopirani pari
2594
+selectFromRaceColumn=Izberite stolpec plova, iz katerega želite začeti kopirati pare
2595
+selectToRaceColumn=Izberite stolpec plova, v katerega želite začeti kopirati pare
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh.properties
... ...
@@ -243,6 +243,7 @@ raceIsKnownToStartUpwind=比赛轮次从迎风航段开始
243 243
events=活动
244 244
pairingList=配对清单
245 245
pairingLists=配对清单
246
+copyPairingListFromOtherLeaderboard=从其他积分榜复制配对清单
246 247
pairingListCreationInfo=首先,将计算仅包含参赛队数并且可以应用到其他积分榜的模板。如果参赛队数量无法被船只数量除尽,则在配对清单中输入空占位符。\ 此外,如果参赛队数量不等于实际积分榜中注册的参赛队数量,配对清单无法应用到比赛轮次日志。\ 用于生成配对清单的航程数由积分榜中不是奖牌轮的航程数定义。\ 使用航程重复时,可以重复航程分配(例如,有 3 个航程重复的 15 个航程:5 个独特的航程分配,每个重复 3 次)。\ 船只更换系数等于 0 导致向船只平均分配参赛队,而最大值(航程数)通过提高参赛队在两个连续的比赛轮次驾驶同一船只驶过航程边界的机会来减少换船次数。
247 248
setCompetitors=请设置参赛队数量:
248 249
setBoatChangeFactor=请设置船只更换系数:
... ...
@@ -2576,7 +2577,9 @@ hasNoAIAgentCredentials=AI 代理凭据不可用
2576 2577
placeholderAICoreCredentialsAsJSON=提供 AI Core 凭据作为 JSON 文档
2577 2578
credentials=凭据
2578 2579
updateCredentials=更新凭据
2580
+resetCredentials=重置凭据
2579 2581
successfullyUpdatedAIAgentCredentials=已成功更新 AI 代理凭据
2582
+successfullyResetAIAgentCredentials=已成功重置 AI 代理凭据
2580 2583
errorUpdatingAIAgentCredentials=更新 AI 代理凭据时出错:{0}
2581 2584
corsAndCSPFilterConfiguration=CORS 和 CSP "frame-ancestors" 筛选器配置
2582 2585
corsAndCSPFilterConfigurationHint=仔细使用!您可以使用通配符来允许 CORS 申请到该服务器的 API,并从任何位置嵌入其页面,例如,IFrame,或者您可以指定一组允许的来源基础 URL,如 https://www.example.org 中所示
... ...
@@ -2585,3 +2588,8 @@ corsAllowedOrigins=允许的来源
2585 2588
successfullyUpdatedCORSAllowedOrigins=已成功更新 CORS 允许的来源
2586 2589
commandLogOutput=命令日志输出
2587 2590
errorLoadingLeaderboard=加载积分榜 {0} 出错:{1}
2591
+selectRaceColumnsWhosePairingsToCopy=要复制配对的比赛轮次列
2592
+errorCopyingPairings=复制配对出错:{0}
2593
+successfullyCopiedPairings=已成功复制配对
2594
+selectFromRaceColumn=选择要从哪一个比赛轮次列开始复制配对
2595
+selectToRaceColumn=选择要将配对复制到哪一个比赛轮次列
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/EditMarkPassingsPanel.java
... ...
@@ -130,7 +130,7 @@ public class EditMarkPassingsPanel extends AbstractCompositeComponent<AbstractSe
130 130
enableButtons();
131 131
}
132 132
});
133
- wayPointSelectionTable = new BaseCelltable<Util.Pair<Integer, Date>>();
133
+ wayPointSelectionTable = new BaseCelltable<Util.Pair<Integer, Date>>(/* pageSize */10000);
134 134
wayPointSelectionTable.addColumn(new Column<Util.Pair<Integer, Date>, SafeHtml>(new AnchorCell()) {
135 135
@Override
136 136
public SafeHtml getValue(final Util.Pair<Integer, Date> object) {
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/BoatClassVectorGraphicsResolver.java
... ...
@@ -46,15 +46,15 @@ public class BoatClassVectorGraphicsResolver {
46 46
BoatClassMasterdata.HOBIE_WILD_CAT, BoatClassMasterdata.HOBIE_16, BoatClassMasterdata.HOBIE_TIGER,
47 47
BoatClassMasterdata.A_CAT, BoatClassMasterdata.TORNADO, BoatClassMasterdata.FLYING_PHANTOM, BoatClassMasterdata.ORC_MULTIHULL);
48 48
BoatClassVectorGraphics keelBoatWithGennaker = new KeelBoatWithGennakerVectorGraphics(BoatClassMasterdata.ELAN350,
49
- BoatClassMasterdata.FIRST_CLASS_7_5,
49
+ BoatClassMasterdata.ELAN_E4, BoatClassMasterdata.FIRST_CLASS_7_5,
50 50
BoatClassMasterdata.J70, BoatClassMasterdata.J80, BoatClassMasterdata.J92, BoatClassMasterdata.J92S,
51 51
BoatClassMasterdata.B_ONE, BoatClassMasterdata.IRC, BoatClassMasterdata.LASER_SB3, BoatClassMasterdata.LONGTZE,
52 52
BoatClassMasterdata.RS_FEVA, BoatClassMasterdata.RS_TERA, BoatClassMasterdata.RS_VENTURE,
53 53
BoatClassMasterdata.RS_VAREO,
54 54
BoatClassMasterdata.RS100, BoatClassMasterdata.RS21, BoatClassMasterdata.TP52,
55 55
BoatClassMasterdata.CLUB_SWAN_50, BoatClassMasterdata.BAVARIA_CRUISER_41S, BoatClassMasterdata.BAVARIA_CRUISER_45,
56
- BoatClassMasterdata.BAVARIA_CRUISER_46, BoatClassMasterdata.BENETEAU_FIRST_35, BoatClassMasterdata.BENETEAU_FIRST_45,
57
- BoatClassMasterdata.SPAEKHUGGER, BoatClassMasterdata.SCAN_KAP_99,
56
+ BoatClassMasterdata.BAVARIA_CRUISER_46, BoatClassMasterdata.BENETEAU_FIRST_35, BoatClassMasterdata.BENETEAU_FIRST_36,
57
+ BoatClassMasterdata.BENETEAU_FIRST_45, BoatClassMasterdata.SPAEKHUGGER, BoatClassMasterdata.SCAN_KAP_99,
58 58
BoatClassMasterdata.BB10M, BoatClassMasterdata.WAYFARER, BoatClassMasterdata.X_332,
59 59
BoatClassMasterdata.BRASSFAHRT_I, BoatClassMasterdata.BRASSFAHRT_II, BoatClassMasterdata.BRASSFAHRT_III,
60 60
BoatClassMasterdata.BRASSFAHRT_IV, BoatClassMasterdata.BRASSFAHRT_V,
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/polarmining/PolarBackendResultsPresenter.java
... ...
@@ -59,7 +59,6 @@ public class PolarBackendResultsPresenter extends AbstractSailingResultsPresente
59 59
public PolarBackendResultsPresenter(Component<?> parent, ComponentContext<?> context,
60 60
StringMessages stringMessages) {
61 61
super(parent, context, stringMessages);
62
-
63 62
polarChart = ChartFactory.createPolarChart();
64 63
polarChart.getYAxis().setMin(0);
65 64
polarChartWrapperPanel = new SimpleLayoutPanel() {
... ...
@@ -70,7 +69,6 @@ public class PolarBackendResultsPresenter extends AbstractSailingResultsPresente
70 69
}
71 70
};
72 71
polarChartWrapperPanel.add(polarChart);
73
-
74 72
speedChart = ChartFactory.createSpeedChart(stringMessages);
75 73
angleChart = ChartFactory.createAngleChart(stringMessages);
76 74
speedAndAngleChart = new DockLayoutPanel(Unit.PCT) {
... ...
@@ -84,16 +82,12 @@ public class PolarBackendResultsPresenter extends AbstractSailingResultsPresente
84 82
};
85 83
speedAndAngleChart.addNorth(speedChart, 50);
86 84
speedAndAngleChart.addSouth(angleChart, 50);
87
-
88 85
dockLayoutPanel = new DockLayoutPanel(Unit.PCT);
89 86
dockLayoutPanel.addWest(polarChartWrapperPanel, 40);
90 87
dockLayoutPanel.addEast(speedAndAngleChart, 60);
91
-
92 88
ChartToCsvExporter chartToCsvExporter = new ChartToCsvExporter(stringMessages.csvCopiedToClipboard());
93
-
94 89
Button exportStatisticsCurveToCsvButton = new Button(stringMessages.exportStatisticsCurveToCsv(),
95 90
new ClickHandler() {
96
-
97 91
@Override
98 92
public void onClick(ClickEvent event) {
99 93
chartToCsvExporter.exportChartAsCsvToClipboard(polarChart);
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/polarmining/PolarResultsPresenter.java
... ...
@@ -22,7 +22,6 @@ import org.moxieapps.gwt.highcharts.client.plotOptions.SeriesPlotOptions;
22 22
23 23
import com.google.gwt.core.client.Scheduler;
24 24
import com.google.gwt.dom.client.Style.Unit;
25
-import com.google.gwt.event.dom.client.ClickEvent;
26 25
import com.google.gwt.event.dom.client.ClickHandler;
27 26
import com.google.gwt.user.client.ui.Button;
28 27
import com.google.gwt.user.client.ui.DockLayoutPanel;
... ...
@@ -104,15 +103,16 @@ public class PolarResultsPresenter extends AbstractSailingResultsPresenter<Setti
104 103
dockLayoutPanel = new DockLayoutPanel(Unit.PCT);
105 104
dockLayoutPanel.addWest(polarChartWrapperPanel, 40);
106 105
dockLayoutPanel.addEast(histogramChartsWrapperPanel, 60);
107
- ChartToCsvExporter chartToCsvExporter = new ChartToCsvExporter(stringMessages.csvCopiedToClipboard());
108
- Button exportStatisticsCurveToCsvButton = new Button(stringMessages.exportStatisticsCurveToCsv(),
109
- new ClickHandler() {
110
- @Override
111
- public void onClick(ClickEvent event) {
112
- chartToCsvExporter.exportChartAsCsvToClipboard(polarChart);
113
- }
114
- });
106
+ final ChartToCsvExporter chartToCsvExporter = new ChartToCsvExporter(stringMessages.csvCopiedToClipboard());
107
+ final Button exportStatisticsCurveToCsvButton = new Button(stringMessages.exportStatisticsCurveToCsv(),
108
+ (ClickHandler) e->chartToCsvExporter.exportChartAsCsvToClipboard(polarChart));
115 109
addControl(exportStatisticsCurveToCsvButton);
110
+ final Button exportTWAHistogramToCsvButton = new Button(stringMessages.exportTWAHistogramToCsv(),
111
+ (ClickHandler) e->chartToCsvExporter.exportChartAsCsvToClipboard(dataCountHistogramChart));
112
+ addControl(exportTWAHistogramToCsvButton);
113
+ final Button exportWindSpeedHistogramToCsvButton = new Button(stringMessages.exportWindSpeedHistogramToCsv(),
114
+ (ClickHandler) e->chartToCsvExporter.exportChartAsCsvToClipboard(dataCountPerAngleHistogramChart));
115
+ addControl(exportWindSpeedHistogramToCsvButton);
116 116
setSeriesShowAndHideHandler();
117 117
}
118 118
... ...
@@ -230,6 +230,7 @@ public class PolarResultsPresenter extends AbstractSailingResultsPresenter<Setti
230 230
if (point != null) {
231 231
Map<Double, Integer> histogramDataForAngle = histogramData.get(index);
232 232
Series dataCountPerAngleSeries = dataCountPerAngleHistogramChart.createSeries();
233
+ dataCountPerAngleSeries.setName(key.asString() + " - " + convertedAngle + stringMessages.degreesShort());
233 234
// Iterating over the histogram data without sorting the x coordinates ascending leads
234 235
// to a massive occurrence of Highcharts error 15, freezing the complete UI
235 236
List<Double> sortedAngles = new ArrayList<>(histogramDataForAngle.keySet());
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
... ...
@@ -292,6 +292,9 @@ import com.sap.sailing.domain.regattalike.LeaderboardThatHasRegattaLike;
292 292
import com.sap.sailing.domain.regattalog.RegattaLogStore;
293 293
import com.sap.sailing.domain.resultimport.ResultUrlProvider;
294 294
import com.sap.sailing.domain.sharding.ShardingContext;
295
+import com.sap.sailing.domain.shared.tracking.LineDetails;
296
+import com.sap.sailing.domain.shared.tracking.Track;
297
+import com.sap.sailing.domain.shared.tracking.TrackingConnectorInfo;
295 298
import com.sap.sailing.domain.swisstimingadapter.SwissTimingAdapter;
296 299
import com.sap.sailing.domain.swisstimingadapter.SwissTimingAdapterFactory;
297 300
import com.sap.sailing.domain.swisstimingadapter.SwissTimingArchiveConfiguration;
... ...
@@ -307,14 +310,11 @@ import com.sap.sailing.domain.tracking.BravoFixTrack;
307 310
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
308 311
import com.sap.sailing.domain.tracking.DynamicTrackedRegatta;
309 312
import com.sap.sailing.domain.tracking.GPSFixTrack;
310
-import com.sap.sailing.domain.tracking.LineDetails;
311 313
import com.sap.sailing.domain.tracking.Maneuver;
312 314
import com.sap.sailing.domain.tracking.MarkPassing;
313
-import com.sap.sailing.domain.tracking.Track;
314 315
import com.sap.sailing.domain.tracking.TrackedLeg;
315 316
import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor;
316 317
import com.sap.sailing.domain.tracking.TrackedRace;
317
-import com.sap.sailing.domain.tracking.TrackingConnectorInfo;
318 318
import com.sap.sailing.domain.tracking.WindLegTypeAndLegBearingAndORCPerformanceCurveCache;
319 319
import com.sap.sailing.domain.tracking.WindTrack;
320 320
import com.sap.sailing.domain.tracking.WindWithConfidence;
... ...
@@ -6100,8 +6100,18 @@ public class SailingServiceImpl extends ResultCachingProxiedRemoteServiceServlet
6100 6100
return Activator.getInstance().getGoogleMapsLoaderAuthenticationParams();
6101 6101
}
6102 6102
6103
- protected IgtimiConnection createIgtimiConnection() {
6104
- return getIgtimiConnectionFactory().getOrCreateConnection(()->getSecurityService().getCurrentUser() != null ? getSecurityService().getAccessToken(getSecurityService().getCurrentUser().getName()) : null);
6103
+ /**
6104
+ * @param optionalBearerToken
6105
+ * if present, this bearer token is used to authenticate the Igtimi connection; otherwise, we try to use
6106
+ * Igtimi default credentials provided at startup through the {@code igtimi.bearer.token} system
6107
+ * property. Only if no such system property has been set, we look for a logged-in user in the
6108
+ * {@link #getSecurityService() security service} and use its access token
6109
+ */
6110
+ protected IgtimiConnection createIgtimiConnection(Optional<String> optionalBearerToken) {
6111
+ return optionalBearerToken.map(bearerToken->getIgtimiConnectionFactory().getOrCreateConnection(bearerToken))
6112
+ .orElse(getIgtimiConnectionFactory().getOrCreateConnection(()->getSecurityService().getCurrentUser() != null
6113
+ ? getSecurityService().getAccessToken(getSecurityService().getCurrentUser().getName())
6114
+ : null));
6105 6115
}
6106 6116
6107 6117
@Override
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.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/TrackingConnectorInfoDTO.java
... ...
@@ -2,7 +2,7 @@ package com.sap.sailing.gwt.ui.shared;
2 2
3 3
import com.google.gwt.core.shared.GwtIncompatible;
4 4
import com.google.gwt.user.client.rpc.IsSerializable;
5
-import com.sap.sailing.domain.tracking.TrackingConnectorInfo;
5
+import com.sap.sailing.domain.shared.tracking.TrackingConnectorInfo;
6 6
7 7
public class TrackingConnectorInfoDTO implements IsSerializable {
8 8
private String trackingConnectorName;
java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/BENETEAU_FIRST_36.png
... ...
Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/BENETEAU_FIRST_36.png differ
java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/ELAN_E4.png
... ...
Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/ELAN_E4.png differ
java/com.sap.sailing.ingestion/src/main/java/com/sap/sailing/ingestion/FixCombinationLambda.java
... ...
@@ -23,7 +23,7 @@ import com.amazonaws.services.lambda.runtime.Context;
23 23
import com.amazonaws.services.lambda.runtime.RequestStreamHandler;
24 24
import com.sap.sailing.domain.common.DeviceIdentifier;
25 25
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
26
-import com.sap.sailing.domain.tracking.impl.TimedComparator;
26
+import com.sap.sailing.domain.shared.tracking.impl.TimedComparator;
27 27
import com.sap.sailing.server.gateway.deserialization.impl.GPSFixMovingJsonDeserializer;
28 28
import com.sap.sailing.server.gateway.deserialization.impl.Helpers;
29 29
import com.sap.sailing.server.gateway.serialization.impl.GPSFixMovingJsonSerializer;
java/com.sap.sailing.nmeaconnector/META-INF/MANIFEST.MF
... ...
@@ -12,7 +12,8 @@ Require-Bundle: com.sap.sailing.domain,
12 12
com.sap.sse.common,
13 13
net.sf.marineapi,
14 14
com.sap.sailing.declination
15
-Import-Package: org.osgi.framework
15
+Import-Package: org.osgi.framework,
16
+ com.sap.sailing.domain.shared.tracking
16 17
Bundle-ActivationPolicy: lazy
17 18
Bundle-Activator: com.sap.sailing.nmeaconnector.impl.Activator
18 19
Automatic-Module-Name: com.sap.sailing.nmeaconnector
java/com.sap.sailing.nmeaconnector/src/com/sap/sailing/nmeaconnector/impl/KnotSpeedWithBearingAndTimepoint.java
... ...
@@ -1,13 +1,12 @@
1 1
package com.sap.sailing.nmeaconnector.impl;
2 2
3 3
import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
4
-import com.sap.sailing.domain.tracking.Track;
5 4
import com.sap.sailing.nmeaconnector.TimedSpeedWithBearing;
6 5
import com.sap.sse.common.Bearing;
7 6
import com.sap.sse.common.TimePoint;
8 7
9 8
/**
10
- * Can be used as a wind vector in a {@link Track} because it has a {@link TimePoint}.
9
+ * Can be used as a wind vector in a {@code Track} because it has a {@link TimePoint}.
11 10
*
12 11
* @author Axel Uhl (d043530)
13 12
*
java/com.sap.sailing.polars.datamining.shared/src/com/sap/sailing/polars/datamining/shared/PolarAggregation.java
... ...
@@ -15,6 +15,10 @@ public interface PolarAggregation extends Serializable {
15 15
16 16
PolarDataMiningSettings getSettings();
17 17
18
+ /**
19
+ * Keys are the angles in degrees (0...359). The {@link Double} key of the inner map is the histogram x-value (e.g.
20
+ * 5.0 for wind speed 4.5...5.5 with step 1.0).
21
+ */
18 22
Map<Integer, Map<Double, Integer>> getCountHistogramPerAngle();
19 23
20 24
}
java/com.sap.sailing.polars.datamining.shared/src/com/sap/sailing/polars/datamining/shared/PolarAggregationImpl.java
... ...
@@ -11,8 +11,12 @@ public class PolarAggregationImpl implements PolarAggregation {
11 11
private double[] sumSpeedsPerAngle = new double[360];
12 12
private int[] countPerAngle = new int[360];
13 13
/**
14
- * FIXME Right now the histogram data is only valid, if the results are grouped by windrange.
15
- * Otherwise the column-indices of different ranges are mixed in one histogram.
14
+ * FIXME Right now the histogram data is only valid, if the results are grouped by windrange. Otherwise the
15
+ * column-indices of different ranges are mixed in one histogram.
16
+ * <p>
17
+ *
18
+ * Keys are the angles in degrees (0...359). The {@link Double} key of the inner map is the histogram x-value (e.g.
19
+ * 5.0 for wind speed 4.5...5.5 with step 1.0).
16 20
*/
17 21
private Map<Integer, Map<Double, Integer>> histogramData;
18 22
private int count = 0;
... ...
@@ -80,6 +84,4 @@ public class PolarAggregationImpl implements PolarAggregation {
80 84
public Map<Integer, Map<Double, Integer>> getCountHistogramPerAngle() {
81 85
return histogramData;
82 86
}
83
-
84
-
85 87
}
java/com.sap.sailing.polars.test/META-INF/MANIFEST.MF
... ...
@@ -6,6 +6,8 @@ Bundle-Version: 1.0.0.qualifier
6 6
Bundle-Vendor: SAP
7 7
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
8 8
Fragment-Host: com.sap.sailing.polars
9
+Import-Package: com.sap.sailing.domain.shared.tracking,
10
+ com.sap.sailing.domain.shared.tracking.impl
9 11
Require-Bundle: org.hamcrest;bundle-version="2.2.0",
10 12
org.mockito.mockito-core;bundle-version="1.10.14",
11 13
junit-jupiter-api;bundle-version="5.11.3",
java/com.sap.sailing.polars/META-INF/MANIFEST.MF
... ...
@@ -39,6 +39,7 @@ Require-Bundle:
39 39
org.apache.httpcomponents.httpcore;bundle-version="4.4.9",
40 40
com.sap.sailing.shared.server
41 41
Import-Package:
42
+ com.sap.sailing.domain.shared.tracking,
42 43
com.sap.sailing.util,
43 44
javax.ws.rs;version="1.1.1",
44 45
javax.ws.rs.core;version="1.1.1",
java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/core/SeleniumTestInvocationProvider.java
... ...
@@ -60,4 +60,4 @@ public class SeleniumTestInvocationProvider implements TestTemplateInvocationCon
60 60
return Arrays.asList(new SeleniumTestEnvironmentInjector(driverDefinition));
61 61
}
62 62
}
63
-}
... ...
\ No newline at end of file
0
+}
java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/core/WindowManager.java
... ...
@@ -5,6 +5,7 @@ import java.util.Set;
5 5
import java.util.function.BiConsumer;
6 6
import java.util.function.Consumer;
7 7
import java.util.function.Supplier;
8
+import java.util.logging.Logger;
8 9
9 10
import org.openqa.selenium.Dimension;
10 11
import org.openqa.selenium.JavascriptExecutor;
... ...
@@ -21,6 +22,8 @@ import org.openqa.selenium.support.ui.WebDriverWait;
21 22
* Riccardo Nimser (D049941)
22 23
*/
23 24
public class WindowManager {
25
+ private static final Logger logger = Logger.getLogger(WindowManager.class.getName());
26
+
24 27
private WebDriverWindow defaultWindow;
25 28
private final Set<WebDriverWindow> allWindows = new HashSet<>();
26 29
private WebDriver driver;
... ...
@@ -108,12 +111,18 @@ public class WindowManager {
108 111
}
109 112
110 113
private boolean isDriverAlive(WebDriver driver) {
111
- try {
112
- driver.getWindowHandles();
113
- return true;
114
- } catch (NoSuchSessionException | SessionNotCreatedException e) {
115
- return false;
114
+ boolean result;
115
+ if (driver == null) {
116
+ result = false;
117
+ } else {
118
+ try {
119
+ driver.getWindowHandles();
120
+ result = true;
121
+ } catch (NoSuchSessionException | SessionNotCreatedException e) {
122
+ result = false;
123
+ }
116 124
}
125
+ return result;
117 126
}
118 127
119 128
private void setWindowMaximized(WebDriver driver) {
... ...
@@ -137,6 +146,16 @@ public class WindowManager {
137 146
138 147
public void closeAllWindows() {
139 148
forEachOpenedWindow(WebDriverWindow::close);
149
+ if (driver != null) {
150
+ try {
151
+ driver.close();
152
+ driver.quit();
153
+ } catch (org.openqa.selenium.NoSuchSessionException e) {
154
+ logger.warning("The Selenium driver seems to have already been closed");
155
+ // Already closed — ignore
156
+ }
157
+ driver = null;
158
+ }
140 159
}
141 160
142 161
private class ManagedWebDriverWindow extends WebDriverWindow {
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.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/deserialization/impl/EventBaseJsonDeserializer.java
... ...
@@ -16,7 +16,7 @@ import com.sap.sailing.domain.base.EventBase;
16 16
import com.sap.sailing.domain.base.LeaderboardGroupBase;
17 17
import com.sap.sailing.domain.base.Venue;
18 18
import com.sap.sailing.domain.base.impl.StrippedEventImpl;
19
-import com.sap.sailing.domain.tracking.TrackingConnectorInfo;
19
+import com.sap.sailing.domain.shared.tracking.TrackingConnectorInfo;
20 20
import com.sap.sailing.server.gateway.serialization.impl.EventBaseJsonSerializer;
21 21
import com.sap.sse.common.TimePoint;
22 22
import com.sap.sse.common.impl.MillisecondsTimePoint;
java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/deserialization/impl/TrackingConnectorInfoJsonDeserializer.java
... ...
@@ -2,8 +2,8 @@ package com.sap.sailing.server.gateway.deserialization.impl;
2 2
3 3
import org.json.simple.JSONObject;
4 4
5
-import com.sap.sailing.domain.tracking.TrackingConnectorInfo;
6
-import com.sap.sailing.domain.tracking.impl.TrackingConnectorInfoImpl;
5
+import com.sap.sailing.domain.shared.tracking.TrackingConnectorInfo;
6
+import com.sap.sailing.domain.shared.tracking.impl.TrackingConnectorInfoImpl;
7 7
import com.sap.sailing.server.gateway.serialization.impl.TrackingConnectorInfoJsonSerializer;
8 8
import com.sap.sse.shared.json.JsonDeserializationException;
9 9
import com.sap.sse.shared.json.JsonDeserializer;
java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/serialization/coursedata/impl/CourseBaseWithGeometryJsonSerializer.java
... ...
@@ -8,7 +8,7 @@ import com.sap.sailing.domain.base.CourseBase;
8 8
import com.sap.sailing.domain.base.Leg;
9 9
import com.sap.sailing.domain.base.Waypoint;
10 10
import com.sap.sailing.domain.common.NauticalSide;
11
-import com.sap.sailing.domain.tracking.LineDetails;
11
+import com.sap.sailing.domain.shared.tracking.LineDetails;
12 12
import com.sap.sailing.server.gateway.serialization.coursedata.impl.CourseBaseWithGeometryJsonSerializer.CourseGeometry;
13 13
import com.sap.sailing.server.gateway.serialization.impl.PositionJsonSerializer;
14 14
import com.sap.sse.common.Bearing;
java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/serialization/impl/EventBaseJsonSerializer.java
... ...
@@ -10,7 +10,7 @@ import org.json.simple.JSONObject;
10 10
import com.sap.sailing.domain.base.EventBase;
11 11
import com.sap.sailing.domain.base.LeaderboardGroupBase;
12 12
import com.sap.sailing.domain.base.Venue;
13
-import com.sap.sailing.domain.tracking.TrackingConnectorInfo;
13
+import com.sap.sailing.domain.shared.tracking.TrackingConnectorInfo;
14 14
import com.sap.sse.shared.json.JsonSerializer;
15 15
import com.sap.sse.shared.media.ImageDescriptor;
16 16
import com.sap.sse.shared.media.VideoDescriptor;
java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/serialization/impl/TrackingConnectorInfoJsonSerializer.java
... ...
@@ -2,7 +2,7 @@ package com.sap.sailing.server.gateway.serialization.impl;
2 2
3 3
import org.json.simple.JSONObject;
4 4
5
-import com.sap.sailing.domain.tracking.TrackingConnectorInfo;
5
+import com.sap.sailing.domain.shared.tracking.TrackingConnectorInfo;
6 6
import com.sap.sse.shared.json.JsonSerializer;
7 7
8 8
public class TrackingConnectorInfoJsonSerializer implements JsonSerializer<TrackingConnectorInfo> {
java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RegattasResource.java
... ...
@@ -115,9 +115,9 @@ import com.sap.sailing.domain.racelogtracking.DeviceMappingWithRegattaLogEvent;
115 115
import com.sap.sailing.domain.racelogtracking.impl.SmartphoneUUIDIdentifierImpl;
116 116
import com.sap.sailing.domain.ranking.RankingMetric.RankingInfo;
117 117
import com.sap.sailing.domain.sharding.ShardingContext;
118
+import com.sap.sailing.domain.shared.tracking.LineDetails;
118 119
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
119 120
import com.sap.sailing.domain.tracking.GPSFixTrack;
120
-import com.sap.sailing.domain.tracking.LineDetails;
121 121
import com.sap.sailing.domain.tracking.Maneuver;
122 122
import com.sap.sailing.domain.tracking.MarkPassing;
123 123
import com.sap.sailing.domain.tracking.RaceWindCalculator;
java/com.sap.sailing.server.interface/src/com/sap/sailing/server/interfaces/RacingEventService.java
... ...
@@ -94,6 +94,7 @@ import com.sap.sailing.domain.racelog.tracking.SensorFixStoreSupplier;
94 94
import com.sap.sailing.domain.ranking.RankingMetricConstructor;
95 95
import com.sap.sailing.domain.regattalike.LeaderboardThatHasRegattaLike;
96 96
import com.sap.sailing.domain.resultimport.ResultUrlProvider;
97
+import com.sap.sailing.domain.shared.tracking.TrackingConnectorInfo;
97 98
import com.sap.sailing.domain.statistics.Statistics;
98 99
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
99 100
import com.sap.sailing.domain.tracking.RaceHandle;
... ...
@@ -106,7 +107,6 @@ import com.sap.sailing.domain.tracking.TrackedRace;
106 107
import com.sap.sailing.domain.tracking.TrackedRegatta;
107 108
import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
108 109
import com.sap.sailing.domain.tracking.TrackerManager;
109
-import com.sap.sailing.domain.tracking.TrackingConnectorInfo;
110 110
import com.sap.sailing.domain.tracking.WindLegTypeAndLegBearingAndORCPerformanceCurveCache;
111 111
import com.sap.sailing.domain.tracking.WindStore;
112 112
import com.sap.sailing.domain.tracking.WindTracker;
java/com.sap.sailing.server.interface/src/com/sap/sailing/server/operationaltransformation/CreateTrackedRace.java
... ...
@@ -4,10 +4,10 @@ import com.sap.sailing.domain.base.RaceDefinition;
4 4
import com.sap.sailing.domain.base.Regatta;
5 5
import com.sap.sailing.domain.common.RaceIdentifier;
6 6
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
7
+import com.sap.sailing.domain.shared.tracking.TrackingConnectorInfo;
7 8
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
8 9
import com.sap.sailing.domain.tracking.TrackedRace;
9 10
import com.sap.sailing.domain.tracking.TrackedRegatta;
10
-import com.sap.sailing.domain.tracking.TrackingConnectorInfo;
11 11
import com.sap.sailing.domain.tracking.WindStore;
12 12
import com.sap.sailing.domain.tracking.impl.EmptyWindStore;
13 13
import com.sap.sailing.server.interfaces.RacingEventService;
java/com.sap.sailing.server.trackfiles.test/src/com/sap/sailing/server/trackfiles/test/DynamicTrackedRaceWithMarkPassingCalculator.java
... ...
@@ -0,0 +1,28 @@
1
+package com.sap.sailing.server.trackfiles.test;
2
+
3
+import com.sap.sailing.domain.base.RaceDefinition;
4
+import com.sap.sailing.domain.base.Sideline;
5
+import com.sap.sailing.domain.markpassingcalculation.MarkPassingCalculator;
6
+import com.sap.sailing.domain.markpassinghash.MarkPassingRaceFingerprintRegistry;
7
+import com.sap.sailing.domain.racelog.RaceLogAndTrackedRaceResolver;
8
+import com.sap.sailing.domain.ranking.RankingMetricConstructor;
9
+import com.sap.sailing.domain.shared.tracking.TrackingConnectorInfo;
10
+import com.sap.sailing.domain.tracking.TrackedRegatta;
11
+import com.sap.sailing.domain.tracking.WindStore;
12
+import com.sap.sailing.domain.tracking.impl.DynamicTrackedRaceImpl;
13
+
14
+public class DynamicTrackedRaceWithMarkPassingCalculator extends DynamicTrackedRaceImpl {
15
+ private static final long serialVersionUID = -8076705893930566222L;
16
+
17
+ public DynamicTrackedRaceWithMarkPassingCalculator(TrackedRegatta trackedRegatta, RaceDefinition race, Iterable<Sideline> sidelines,
18
+ WindStore windStore, long delayToLiveInMillis, long millisecondsOverWhichToAverageWind,
19
+ long millisecondsOverWhichToAverageSpeed, boolean useInternalMarkPassingAlgorithm,
20
+ RankingMetricConstructor rankingMetricConstructor, RaceLogAndTrackedRaceResolver raceLogResolver, TrackingConnectorInfo trackingConnectorInfo,
21
+ MarkPassingRaceFingerprintRegistry markPassingRaceFingerprintRegistry) {
22
+ super(trackedRegatta, race, sidelines, windStore, delayToLiveInMillis, millisecondsOverWhichToAverageWind, millisecondsOverWhichToAverageSpeed, useInternalMarkPassingAlgorithm, rankingMetricConstructor, raceLogResolver, trackingConnectorInfo, markPassingRaceFingerprintRegistry);
23
+ }
24
+
25
+ public MarkPassingCalculator getMarkPassingCalculator() {
26
+ return markPassingCalculator;
27
+ }
28
+}
java/com.sap.sailing.server.trackfiles.test/src/com/sap/sailing/server/trackfiles/test/JumpyTrackSmootheningTest.java
... ...
@@ -68,7 +68,6 @@ import com.sap.sailing.domain.tracking.MarkPassing;
68 68
import com.sap.sailing.domain.tracking.TrackedRegatta;
69 69
import com.sap.sailing.domain.tracking.WindTrack;
70 70
import com.sap.sailing.domain.tracking.impl.DynamicGPSFixMovingTrackImpl;
71
-import com.sap.sailing.domain.tracking.impl.DynamicTrackedRaceImpl;
72 71
import com.sap.sailing.domain.tracking.impl.DynamicTrackedRegattaImpl;
73 72
import com.sap.sailing.domain.tracking.impl.EmptyWindStore;
74 73
import com.sap.sailing.domain.tracking.impl.OutlierFilter;
... ...
@@ -197,10 +196,11 @@ public class JumpyTrackSmootheningTest {
197 196
durationForOriginalTrack = startedAt.until(doneAt);
198 197
logger.info("Duration for computing mark passings with original track: "+durationForOriginalTrack);
199 198
assertNotNull(markPassings);
200
- assertEquals(13, markPassings.size());
199
+ assertEquals(13, markPassings.size()); // TODO this is brittle... on some machines this results in only five mark passings
201 200
}
202
- assertTrue(durationForAdjustedTrack.times(8).compareTo(durationForOriginalTrack) < 0,
203
- "Expected duration for mark passing analysis on adjusted track to be at least eight times less than for original track");
201
+ assertTrue(durationForAdjustedTrack.times(2).compareTo(durationForOriginalTrack) < 0,
202
+ "Expected duration for mark passing analysis on adjusted track to be at least two times less than for original track: "+
203
+ durationForAdjustedTrack+" vs. "+durationForOriginalTrack);
204 204
}
205 205
206 206
private DynamicGPSFixTrack<Competitor, GPSFixMoving> readTrack(String filename) throws Exception {
... ...
@@ -227,7 +227,8 @@ public class JumpyTrackSmootheningTest {
227 227
}
228 228
229 229
/**
230
- * Simulates the "Oak cliff DH Distance Race" R1 with a single competitor, Gallagher / Zelenka, sail number "1" with
230
+ * Simulates the "Oak cliff DH Distance Race" R1 (see https://my.sapsailing.com/gwt/RaceBoard.html?regattaName=Oak+cliff+DH+Distance+Race&raceName=R1&leaderboardName=Oak+cliff+DH+Distance+Race&leaderboardGroupId=a3902560-6bfa-43be-85e1-2b82a4963416&eventId=bf48a59d-f2af-47b6-a2f7-a5b78b22b9f2)
231
+ * with a single competitor, Gallagher / Zelenka, sail number "1" with
231 232
* the marks pinged statically to establish the course. The track of Gallagher / Zelenka is provided as a track of
232 233
* their GPS positions. This could be the raw track, or it may be a filtered variant of the track with outliers
233 234
* removed or adjusted.<p>
... ...
@@ -236,9 +237,9 @@ public class JumpyTrackSmootheningTest {
236 237
* its calculation. As a result, a test may determine the impact filtering / adjusting the track may have on the
237 238
* mark passing analysis.
238 239
*/
239
- private DynamicTrackedRace createRace(DynamicGPSFixTrack<Competitor, GPSFixMoving> competitorTrack) throws PatchFailedException, ParseException {
240
+ private DynamicTrackedRace createRace(DynamicGPSFixTrack<Competitor, GPSFixMoving> competitorTrack) throws PatchFailedException, ParseException, InterruptedException {
240 241
final Competitor gallagherZelenka = competitorTrack.getTrackedItem();
241
- final DynamicTrackedRace trackedRace = createTrackedRace("Oak cliff DH Distance Race", "R1", BoatClassMasterdata.MELGES_24, gallagherZelenka);
242
+ final DynamicTrackedRaceWithMarkPassingCalculator trackedRace = createTrackedRace("Oak cliff DH Distance Race", "R1", BoatClassMasterdata.MELGES_24, gallagherZelenka);
242 243
final Series defaultSeries = trackedRace.getTrackedRegatta().getRegatta().getSeries().iterator().next();
243 244
final Fleet defaultFleet = defaultSeries.getFleets().iterator().next();
244 245
final RaceColumnInSeries r1RaceColumn = defaultSeries.getRaceColumns().iterator().next();
... ...
@@ -287,9 +288,8 @@ public class JumpyTrackSmootheningTest {
287 288
addFixedMarkPassingToRaceLog("2020-10-14T17:29:36Z", gallagherZelenka, 2, raceLog);
288 289
addFixedMarkPassingToRaceLog("2020-10-14T17:36:42Z", gallagherZelenka, 3, raceLog);
289 290
addFixedMarkPassingToRaceLog("2020-10-14T18:21:38Z", gallagherZelenka, 4, raceLog);
290
- trackedRace.setStatus(new TrackedRaceStatusImpl(TrackedRaceStatusEnum.LOADING, 0.0));
291
+ trackedRace.setStatus(new TrackedRaceStatusImpl(TrackedRaceStatusEnum.LOADING, 0.0)); // suspends mark passing calculator
291 292
final DynamicGPSFixTrack<Competitor, GPSFixMoving> competitorTrackInRace = trackedRace.getTrack(gallagherZelenka);
292
- // TODO switch race into suspended mode to avoid updates during mass fix insertion:
293 293
competitorTrack.lockForRead();
294 294
try {
295 295
for (final GPSFixMoving fix : competitorTrack.getRawFixes()) {
... ...
@@ -298,12 +298,12 @@ public class JumpyTrackSmootheningTest {
298 298
} finally {
299 299
competitorTrack.unlockAfterRead();
300 300
}
301
- trackedRace.setStatus(new TrackedRaceStatusImpl(TrackedRaceStatusEnum.TRACKING, 1.0));
302
- // TODO resume race
301
+ trackedRace.setStatus(new TrackedRaceStatusImpl(TrackedRaceStatusEnum.TRACKING, 1.0)); // resumes mark passing calculator
302
+ trackedRace.getMarkPassingCalculator().waitUntilStopped(/* timeout in millis */ Duration.ONE_MINUTE.times(15).asMillis());
303 303
return trackedRace;
304 304
}
305 305
306
- private DynamicTrackedRace createTrackedRace(String regattaName, String name, BoatClassMasterdata boatClassMasterData, Competitor gallagherZelenka) {
306
+ private DynamicTrackedRaceWithMarkPassingCalculator createTrackedRace(String regattaName, String name, BoatClassMasterdata boatClassMasterData, Competitor gallagherZelenka) {
307 307
final BoatClassImpl boatClass = new BoatClassImpl(boatClassMasterData);
308 308
final TrackedRegatta trackedRegatta = new DynamicTrackedRegattaImpl(new RegattaImpl(regattaName, boatClass,
309 309
/* canBoatsOfCompetitorsChangePerRace */ false, /* competitorRegistrationType */ CompetitorRegistrationType.CLOSED,
... ...
@@ -315,7 +315,7 @@ public class JumpyTrackSmootheningTest {
315 315
final Map<Competitor, Boat> competitorsAndTheirBoats = Util.<Competitor, Boat>mapBuilder().put(gallagherZelenka, boat).build();
316 316
final Course course = new CourseImpl("R1 Course", Collections.emptySet());
317 317
final RaceDefinition race = new RaceDefinitionImpl(name, course, boatClass, competitorsAndTheirBoats, UUID.randomUUID());
318
- return new DynamicTrackedRaceImpl(trackedRegatta, race, /* sidelines */ Collections.emptySet(), new EmptyWindStore(), /* delayToLiveInMillis */ 1000,
318
+ return new DynamicTrackedRaceWithMarkPassingCalculator(trackedRegatta, race, /* sidelines */ Collections.emptySet(), new EmptyWindStore(), /* delayToLiveInMillis */ 1000,
319 319
WindTrack.DEFAULT_MILLISECONDS_OVER_WHICH_TO_AVERAGE_WIND, /* time over which to average speed: */ boatClass.getApproximateManeuverDurationInMilliseconds(),
320 320
/* useInternalMarkPassingAlgorithm */ true, OneDesignRankingMetric::new, mock(RaceLogAndTrackedRaceResolver.class), /* trackingConnectorInfo */ null, /* markPassingRaceFingerprintRegistry */ null);
321 321
}
java/com.sap.sailing.server.trackfiles/META-INF/MANIFEST.MF
... ...
@@ -21,6 +21,7 @@ Import-Package: com.sap.sailing.domain.abstractlog,
21 21
com.sap.sailing.domain.abstractlog.regatta.events.impl,
22 22
com.sap.sailing.domain.common.sensordata,
23 23
com.sap.sailing.domain.racelogtracking,
24
+ com.sap.sailing.domain.shared.tracking,
24 25
com.sap.sse.security.shared,
25 26
javax.xml.bind,
26 27
javax.xml.bind.annotation,
java/com.sap.sailing.server.trackfiles/src/com/sap/sailing/server/trackfiles/impl/TrackReaderImpl.java
... ...
@@ -1,6 +1,6 @@
1 1
package com.sap.sailing.server.trackfiles.impl;
2 2
3
-import com.sap.sailing.domain.tracking.Track;
3
+import com.sap.sailing.domain.shared.tracking.Track;
4 4
import com.sap.sse.common.Timed;
5 5
6 6
class TrackReaderImpl<E, T extends Timed> implements TrackReader<E, T>, IterableLocker {
java/com.sap.sailing.server/SailingServer (No Proxy).launch
... ...
@@ -24,7 +24,7 @@
24 24
<stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
25 25
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-os ${target.os} -ws ${target.ws} -arch ${target.arch} -nl ${target.nl} -consoleLog -console 12001 -clean"/>
26 26
<stringAttribute key="org.eclipse.jdt.launching.SOURCE_PATH_PROVIDER" value="org.eclipse.pde.ui.workbenchClasspathProvider"/>
27
- <stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-ea -Declipse.ignoreApp=true -Dosgi.noShutdown=true -Dfile.encoding=cp1252 -Dexpedition.udp.port=5010 -Xmx6000m -XX:ThreadPriorityPolicy=2 -XX:+UseG1GC -Djetty.home=${project_loc:com.sap.sailing.server}/../target/configuration/jetty -Djava.util.logging.config.file=${project_loc:com.sap.sailing.server}/../target/configuration/logging_debug.properties -Dkiwo.results=${project_loc:com.sap.sailing.kiworesultimport.test}/resources -Dpersistentcompetitors.clear=false -Dpolardata.source.url=https://www.sapsailing.com -Dwindestimation.source.url=https://www.sapsailing.com -Drestore.tracked.races=true -Dorg.eclipse.jetty.server.Request.maxFormContentSize=50000000 -DAnniversaryRaceDeterminator.enabled=true -Djava.naming.factory.url.pkgs=org.eclipse.jetty.jndi -Djava.naming.factory.initial=org.eclipse.jetty.jndi.InitialContextFactory -Dorg.eclipse.jetty.annotations.maxWait=120 -Dchargebee.site=${CHARGEBEE_SITE} -Dchargebee.apikey=${CHARGEBEE_API_KEY} -Dmanage2sail.accesstoken=${MANAGE2SAIL_ACCESS_TOKEN} -Dsubscriptions.disableMailVerificationRequirement=true -Dgoogle.maps.authenticationparams=${GOOGLE_MAPS_AUTHENTICATION_PARAMS} -Dgwt.rpc.version=9 -Dwindestimation.source.bearertoken=${WIND_ESTIMATION_MODEL_BEARER_TOKEN} -Dpolardata.source.bearertoken=${POLAR_DATA_BEARER_TOKEN} -Dcom.sap.sse.debranding=false -Digtimi.riot.port=6000 -Digtimi.base.url=http://127.0.0.1:8888 -Dsap.aicore.credentials='${SAP_AICORE_CREDENTIALS}' -Dsap.sailing.aiagent.modelname=gpt-4o -Dgeonames.org.usernames=&quot;${GEONAMES_ORG_USERNAMES}&quot;"/>
27
+ <stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-ea -Declipse.ignoreApp=true -Dosgi.noShutdown=true -Dfile.encoding=cp1252 -Dexpedition.udp.port=5010 -Xmx6000m -XX:ThreadPriorityPolicy=2 -XX:+UseG1GC -Djetty.home=${project_loc:com.sap.sailing.server}/../target/configuration/jetty -Djava.util.logging.config.file=${project_loc:com.sap.sailing.server}/../target/configuration/logging_debug.properties -Dkiwo.results=${project_loc:com.sap.sailing.kiworesultimport.test}/resources -Dpersistentcompetitors.clear=false -Dpolardata.source.url=https://www.sapsailing.com -Dwindestimation.source.url=https://www.sapsailing.com -Drestore.tracked.races=true -Dorg.eclipse.jetty.server.Request.maxFormContentSize=50000000 -DAnniversaryRaceDeterminator.enabled=true -Djava.naming.factory.url.pkgs=org.eclipse.jetty.jndi -Djava.naming.factory.initial=org.eclipse.jetty.jndi.InitialContextFactory -Dorg.eclipse.jetty.annotations.maxWait=120 -Dchargebee.site=${CHARGEBEE_SITE} -Dchargebee.apikey=${CHARGEBEE_API_KEY} -Dmanage2sail.accesstoken=${MANAGE2SAIL_ACCESS_TOKEN} -Dsubscriptions.disableMailVerificationRequirement=true -Dgoogle.maps.authenticationparams=${GOOGLE_MAPS_AUTHENTICATION_PARAMS} -Dgwt.rpc.version=9 -Dwindestimation.source.bearertoken=${WIND_ESTIMATION_MODEL_BEARER_TOKEN} -Dpolardata.source.bearertoken=${POLAR_DATA_BEARER_TOKEN} -Dcom.sap.sse.debranding=false -Digtimi.riot.port=6000 -Digtimi.base.url=http://127.0.0.1:8888 -Dsap.aicore.credentials='${SAP_AICORE_CREDENTIALS}' -Dsap.sailing.aiagent.modelname=o4-mini -Dgeonames.org.usernames=&quot;${GEONAMES_ORG_USERNAMES}&quot;"/>
28 28
<stringAttribute key="org.eclipse.jdt.launching.WORKING_DIRECTORY" value="${workspace_loc}"/>
29 29
<stringAttribute key="pde.version" value="3.3"/>
30 30
<stringAttribute key="profilingTraceType-ALLOCATION_TRACE" value="KEY_APPLICATION_FILTER%CTX_KEY%*%CTX_ENTRY%INCREASE_COUNT%CTX_KEY%8192%CTX_ENTRY%KEY_MIN_SIZE%CTX_KEY%32%CTX_ENTRY%KEY_MAX_SIZE%CTX_KEY%65536%CTX_ENTRY%KEY_INC_LINE_NRS%CTX_KEY%true%CTX_ENTRY%KEY_SESSION_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_ENABLEMENT%CTX_KEY%false%CTX_ENTRY%CLASS_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_USER_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_REQUEST_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_TENANT_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_ADAPTIVE%CTX_KEY%false%CTX_ENTRY%"/>
... ...
@@ -204,7 +204,6 @@
204 204
<setEntry value="org.eclipse.osgi.services.source"/>
205 205
<setEntry value="org.eclipse.osgi.services@default:default"/>
206 206
<setEntry value="org.eclipse.osgi.source"/>
207
- <setEntry value="org.eclipse.osgi.util.source"/>
208 207
<setEntry value="org.eclipse.osgi.util@default:default"/>
209 208
<setEntry value="org.eclipse.osgi@-1:true"/>
210 209
<setEntry value="org.hyperic.sigar@default:default"/>
java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RacingEventServiceImpl.java
... ...
@@ -222,8 +222,9 @@ import com.sap.sailing.domain.regattalike.IsRegattaLike;
222 222
import com.sap.sailing.domain.regattalike.LeaderboardThatHasRegattaLike;
223 223
import com.sap.sailing.domain.regattalog.RegattaLogStore;
224 224
import com.sap.sailing.domain.resultimport.ResultUrlProvider;
225
+import com.sap.sailing.domain.shared.tracking.AddResult;
226
+import com.sap.sailing.domain.shared.tracking.TrackingConnectorInfo;
225 227
import com.sap.sailing.domain.statistics.Statistics;
226
-import com.sap.sailing.domain.tracking.AddResult;
227 228
import com.sap.sailing.domain.tracking.BravoFixTrack;
228 229
import com.sap.sailing.domain.tracking.DynamicRaceDefinitionSet;
229 230
import com.sap.sailing.domain.tracking.DynamicSensorFixTrack;
... ...
@@ -244,7 +245,6 @@ import com.sap.sailing.domain.tracking.TrackedRace;
244 245
import com.sap.sailing.domain.tracking.TrackedRaceStatus;
245 246
import com.sap.sailing.domain.tracking.TrackedRegatta;
246 247
import com.sap.sailing.domain.tracking.TrackedRegattaListener;
247
-import com.sap.sailing.domain.tracking.TrackingConnectorInfo;
248 248
import com.sap.sailing.domain.tracking.WindLegTypeAndLegBearingAndORCPerformanceCurveCache;
249 249
import com.sap.sailing.domain.tracking.WindPositionMode;
250 250
import com.sap.sailing.domain.tracking.WindStore;
java/com.sap.sailing.server/src/com/sap/sailing/server/security/PermissionAwareRaceTrackingHandler.java
... ...
@@ -27,13 +27,13 @@ import com.sap.sailing.domain.common.RegattaNameAndRaceName;
27 27
import com.sap.sailing.domain.common.security.SecuredDomainType;
28 28
import com.sap.sailing.domain.markpassinghash.MarkPassingRaceFingerprintRegistry;
29 29
import com.sap.sailing.domain.racelog.RaceLogAndTrackedRaceResolver;
30
+import com.sap.sailing.domain.shared.tracking.TrackingConnectorInfo;
30 31
import com.sap.sailing.domain.tracking.DynamicRaceDefinitionSet;
31 32
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
32 33
import com.sap.sailing.domain.tracking.RaceTrackingHandler;
33 34
import com.sap.sailing.domain.tracking.RaceTrackingHandler.DefaultRaceTrackingHandler;
34 35
import com.sap.sailing.domain.tracking.TrackedRace;
35 36
import com.sap.sailing.domain.tracking.TrackedRegatta;
36
-import com.sap.sailing.domain.tracking.TrackingConnectorInfo;
37 37
import com.sap.sailing.domain.tracking.WindStore;
38 38
import com.sap.sse.common.Color;
39 39
import com.sap.sse.common.Duration;
java/com.sap.sailing.server/src/com/sap/sailing/server/simulation/SimulationServiceImpl.java
... ...
@@ -40,7 +40,7 @@ import com.sap.sailing.domain.common.WindSource;
40 40
import com.sap.sailing.domain.common.tracking.GPSFix;
41 41
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
42 42
import com.sap.sailing.domain.polars.PolarDataService;
43
-import com.sap.sailing.domain.tracking.AddResult;
43
+import com.sap.sailing.domain.shared.tracking.AddResult;
44 44
import com.sap.sailing.domain.tracking.DynamicTrackedRegatta;
45 45
import com.sap.sailing.domain.tracking.MarkPassing;
46 46
import com.sap.sailing.domain.tracking.RaceListener;
java/com.sap.sailing.server/src/com/sap/sailing/server/statistics/TrackedRaceStatisticsCacheImpl.java
... ...
@@ -18,8 +18,8 @@ import com.sap.sailing.domain.common.Wind;
18 18
import com.sap.sailing.domain.common.WindSource;
19 19
import com.sap.sailing.domain.common.tracking.GPSFix;
20 20
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
21
+import com.sap.sailing.domain.shared.tracking.AddResult;
21 22
import com.sap.sailing.domain.tracking.AbstractTrackedRegattaAndRaceObserver;
22
-import com.sap.sailing.domain.tracking.AddResult;
23 23
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
24 24
import com.sap.sailing.domain.tracking.DynamicTrackedRegatta;
25 25
import com.sap.sailing.domain.tracking.TrackedRace;
java/com.sap.sailing.simulator.test/META-INF/MANIFEST.MF
... ...
@@ -3,6 +3,7 @@ Bundle-ManifestVersion: 2
3 3
Bundle-Name: Test
4 4
Bundle-SymbolicName: com.sap.sailing.simulator.test
5 5
Bundle-Version: 1.0.0.qualifier
6
+Import-Package: com.sap.sailing.domain.shared.tracking
6 7
Bundle-Vendor: SAP
7 8
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
8 9
Require-Bundle: com.sap.sailing.simulator;bundle-version="1.0.0",
java/com.sap.sailing.simulator/META-INF/MANIFEST.MF
... ...
@@ -14,7 +14,8 @@ Require-Bundle: com.sap.sailing.domain.common;bundle-version="1.0.0",
14 14
org.apache.httpcomponents.httpclient;bundle-version="4.5.5",
15 15
org.json.simple;bundle-version="1.1.0"
16 16
Bundle-ActivationPolicy: lazy
17
-Import-Package: org.osgi.framework;version="1.6.0"
17
+Import-Package: com.sap.sailing.domain.shared.tracking,
18
+ org.osgi.framework;version="1.6.0"
18 19
Bundle-ClassPath: .
19 20
Export-Package: com.sap.sailing.simulator,
20 21
com.sap.sailing.simulator.impl,
java/com.sap.sailing.www/release_notes_admin.html
... ...
@@ -23,6 +23,29 @@
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
+ <li>The source code of the Sailing Analytics including its companion apps (Race Manager App,
41
+ Buoy Pinger App, Sail Insight) has been published under the Apache 2.0 license on
42
+ GitHub. See <a href="https://github.com/SAP/sailing-analytics">https://github.com/SAP/sailing-analytics</a>.
43
+ Get engaged!</li>
44
+ </ul>
45
+ <h2 class="articleSubheadline">September 2025</h2>
46
+ <ul class="bulletList">
47
+ <li>Added boat classes "Elan E4" and "Beneteau First 36" to the list of boat classes.<p>
48
+ </ul>
26 49
<h2 class="articleSubheadline">August 2025</h2>
27 50
<ul class="bulletList">
28 51
<li>Pairings, e.g., in league set-ups, can not be copied across regattas/leaderboards.
java/com.sap.sailing.xrr.resultimport/META-INF/MANIFEST.MF
... ...
@@ -24,5 +24,6 @@ Require-Bundle: com.sap.sailing.domain,
24 24
com.sap.sse.common,
25 25
com.sun.xml.bind.jaxb-impl;bundle-version="2.3.0",
26 26
com.sap.sailing.domain.shared.android,
27
- com.sap.sse
27
+ com.sap.sse,
28
+ com.sap.sse.security.common
28 29
Bundle-Activator: com.sap.sailing.xrr.resultimport.impl.Activator
java/com.sap.sse.aicore/src/com/sap/sse/aicore/impl/ChatSessionImpl.java
... ...
@@ -39,7 +39,7 @@ public class ChatSessionImpl implements ChatSession {
39 39
private static final String USER_PROMPT = "user";
40 40
private static final String SYSTEM_PROPMPT = "system";
41 41
private static final String MESSAGES = "messages";
42
- private static final String API_VERSION = "2024-10-21";
42
+ private static final String API_VERSION = "2025-04-01-preview";
43 43
private static final String CHAT_PATH_TEMPLATE = "/v2/inference/deployments/%s/chat/completions?api-version="+API_VERSION;
44 44
45 45
private final List<String> systemPrompts;
java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/ChartToCsvExporter.java
... ...
@@ -1,7 +1,9 @@
1 1
package com.sap.sse.datamining.ui.client;
2 2
3
-import java.util.ArrayList;
4
-import java.util.List;
3
+import java.util.HashMap;
4
+import java.util.Map;
5
+import java.util.Map.Entry;
6
+import java.util.TreeMap;
5 7
6 8
import org.moxieapps.gwt.highcharts.client.Chart;
7 9
import org.moxieapps.gwt.highcharts.client.Point;
... ...
@@ -42,38 +44,33 @@ public class ChartToCsvExporter {
42 44
private static String createCsvExportContentForStatisticsCurve(Chart chartToExport) {
43 45
if (chartToExport != null && chartToExport.getSeries().length > 0) {
44 46
final StringBuilder csvStr = new StringBuilder("Series name");
45
- int minX = Integer.MAX_VALUE;
47
+ final Map<Number, Integer> columnIndexByXValue = new HashMap<>();
46 48
{
47
- final List<String> columnNames = new ArrayList<>();
49
+ final TreeMap<Number, String> orderedColumnNames = new TreeMap<>((a, b) -> Double.compare(a.doubleValue(), b.doubleValue()));
48 50
// collect column names across all series; some may not have points for all columns
49 51
for (final Series series : chartToExport.getSeries()) {
50
- for (final Point p : series.getPoints()) {
51
- if (p.getX().intValue() < minX) {
52
- minX = p.getX().intValue();
53
- }
54
- }
55 52
for (Point point : series.getPoints()) {
56
- final String pointName = point.getName();
57
- final String columnName = pointName != null && !pointName.isEmpty() ? pointName : point.getX().toString();
58
- while (columnNames.size() <= point.getX().intValue()-minX) {
59
- columnNames.add(columnNames.size(), null);
60
- }
61
- columnNames.set(point.getX().intValue()-minX, columnName);
53
+ final String columnName = getColumnName(point);
54
+ orderedColumnNames.put(point.getX(), columnName);
62 55
}
63 56
}
64
- for (final String columnName : columnNames) {
57
+ for (final String columnName : orderedColumnNames.values()) {
65 58
csvStr.append(';');
66 59
if (columnName != null && !columnName.isEmpty()) {
67 60
csvStr.append(columnName);
68 61
}
69 62
}
63
+ int columnIndex = 0;
64
+ for (final Entry<Number, String> e : orderedColumnNames.entrySet()) {
65
+ columnIndexByXValue.put(e.getKey(), columnIndex++);
66
+ }
70 67
}
71 68
csvStr.append("\r\n");
72 69
for (Series series : chartToExport.getSeries()) {
73 70
csvStr.append(series.getName());
74 71
int lastXIndex = -1;
75 72
for (Point point : series.getPoints()) {
76
- while (lastXIndex < point.getX().intValue()-minX) {
73
+ while (lastXIndex < columnIndexByXValue.get(point.getX())) {
77 74
csvStr.append(';');
78 75
lastXIndex++;
79 76
}
... ...
@@ -87,4 +84,10 @@ public class ChartToCsvExporter {
87 84
return "Statistics are empty";
88 85
}
89 86
87
+ private static String getColumnName(Point point) {
88
+ final String pointName = point.getName();
89
+ final String columnName = pointName != null && !pointName.isEmpty() ? pointName : point.getX().toString();
90
+ return columnName;
91
+ }
92
+
90 93
}
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.gwt/src/com/sap/sse/gwt/client/useragent/UserAgentCheckerImpl.java
... ...
@@ -9,15 +9,14 @@ public class UserAgentCheckerImpl implements UserAgentChecker {
9 9
/**
10 10
* Version numbers indicate minimum required browser (20 = at least this version)
11 11
*/
12
- @SuppressWarnings("serial")
13 12
private static final HashMap<AgentTypes, Integer> MINIMUM_SUPPORTED_AGENTS = new HashMap<AgentTypes, Integer>() {
13
+ private static final long serialVersionUID = -3972648919899828625L;
14 14
{
15 15
put(AgentTypes.MSIE, 9);
16 16
put(AgentTypes.SAFARI, 5);
17 17
put(AgentTypes.OPERA, 10);
18 18
put(AgentTypes.FIREFOX, 10);
19 19
put(AgentTypes.CHROME, 20);
20
-
21 20
}
22 21
};
23 22
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
}
java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/UserService.java
... ...
@@ -433,6 +433,8 @@ public class UserService implements TakedownNoticeService {
433 433
try {
434 434
if (stringValue != null) {
435 435
callback.accept(new MillisecondsTimePoint(Long.parseLong(stringValue)));
436
+ } else {
437
+ callback.accept(null);
436 438
}
437 439
} catch (Exception e) {
438 440
logger.warning("Error parsing localstore value '" + stringValue + "'");
java/com.tractrac.clientmodule/META-INF/MANIFEST.MF
... ...
@@ -2,7 +2,7 @@ Manifest-Version: 1.0
2 2
Bundle-ManifestVersion: 2
3 3
Bundle-Name: TracTrac Client module
4 4
Bundle-SymbolicName: com.tractrac.clientmodule
5
-Bundle-Version: 4.0.3
5
+Bundle-Version: 4.0.4
6 6
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
7 7
Bundle-ClassPath: .,
8 8
lib/TracAPI.jar
java/com.tractrac.clientmodule/README.txt
... ...
@@ -16,6 +16,18 @@ It contains also some files:
16 16
- Manifest.txt -> manifest used to create the test.jar file
17 17
18 18
********************************************
19
+ TracAPI 4.0.4
20
+********************************************
21
+This is the final version. It keeps the backward compatibility. This version has been compiled with
22
+Java 21 but the target compatibility continues being Java 8.
23
+
24
+ Release date: 01/10/2025
25
+
26
+ 1) Bugs
27
+
28
+ - The heartbeat monitoring the connection status can stop working (Reported by Jorge Piera, 01/10/2025)
29
+
30
+********************************************
19 31
TracAPI 4.0.3
20 32
********************************************
21 33
This is the final version. It keeps the backward compatibility. The new parameter files will be encoded in UTF-8,
java/com.tractrac.clientmodule/lib/TracAPI-src.jar
java/com.tractrac.clientmodule/lib/TracAPI.jar
java/com.tractrac.clientmodule/pom.xml
... ...
@@ -8,6 +8,6 @@
8 8
<version>1.0.0-SNAPSHOT</version>
9 9
</parent>
10 10
<artifactId>com.tractrac.clientmodule</artifactId>
11
- <version>4.0.3</version>
11
+ <version>4.0.4</version>
12 12
<packaging>eclipse-plugin</packaging>
13 13
</project>
java/com.tractrac.clientmodule/src/com/tractrac/subscription/app/tracapi/Main.java
... ...
@@ -62,8 +62,8 @@ public class Main {
62 62
race
63 63
);
64 64
raceSubscriber.subscribeConnectionStatus(listener);
65
- raceSubscriber.subscribePositionedItemPositions(listener);
66
- //raceSubscriber.subscribePositions(listener);
65
+ //raceSubscriber.subscribePositionedItemPositions(listener);
66
+ raceSubscriber.subscribePositions(listener);
67 67
//raceSubscriber.subscribePositionsSnapped(listener);
68 68
raceSubscriber.subscribeControlPassings(listener);
69 69
//raceSubscriber.subscribeCompetitorSensorData(listener);
wiki/howto/development/ci.md
... ...
@@ -1,8 +1,48 @@
1
-# Continuous Integration with Hudson/Jenkins
1
+# Continuous Integration with Hudson/Jenkins and Github Actions
2 2
3
-Our default Hudson runs on https://hudson.sapsailing.com. If you need access, please contact axel.uhl@sap.com or simon.marcel.pamies@sap.com. We have a build job running for the master branch which will automatically pick up any changes, run a build with tests and inform committers about flaws they introduced that broke the build.
3
+## Access
4 4
5
-It is good practice to set up a new Hudson job for major branches that require solid testing before being merged into the master branch. The entry page at http://hudson.sapsailing.com explains how to do this. It basically comes down to copying a template job and adjusting the branch name. As easy as that :-)
5
+Our default Hudson runs on https://hudson.sapsailing.com. If you need access, please contact axel.uhl@sap.com or petr.janacek@sap.com. Usernames typically are {firstname}{lastname}, all lowercase.
6
+
7
+Our use of a CI tool such as Hudson (could also be Jenkins, see [[below|#in-case-you-d-like-to-set-up-your-own-hudson-jenkins]]) is special in so far as the actual build is not run under its control. Instead, we use it only as an aggregator for build logs, test results and measurements collected during the builds. While historically we _did_ use Hudson to run our builds, also in conjunction with a fleet of AWS build slaves that allowed us to parallelize builds and configure hardware resources for it as needed, with the move to Github we also migrated our build to Github Actions, defining the [release](https://github.com/SAP/sailing-analytics/actions/workflows/release.yml) workflow which by and large takes over the heavy lifting of our build environment.
8
+
9
+It turns out, though, that Github Actions is not particularly good at managing build logs that exceed tens of thousands of lines (like ours), and it is not good at managing test results and measurements, or presenting and preserving Selenium-provided screenshots recorded from headless browsers used for UI testing. That's why we decided to keep a more "classic" CI around, but instead of using it for actually _running_ the builds, we would instead only forward the build outputs produced by Github Actions to the CI where it then gets evaluated, stored, evaluated statistically and presented in an easy-to-access way.
10
+
11
+The key to this type of integration is that at the end of the ``release`` workflow a Hudson job is triggered through an HTTP request made by the workflow. The scripts which are then used by the Hudson job to obtain the build results and if needed copy a Github release to https://releases.sapsailing.com can be found under [configuration/github-download-workflow-artifacts.sh](https://github.com/SAP/sailing-analytics/blob/main/configuration/github-download-workflow-artifacts.sh) and [configuration/github-copy-release-to-sapsailing-com.sh](https://github.com/SAP/sailing-analytics/blob/main/configuration/github-copy-release-to-sapsailing-com.sh), respectively.
12
+
13
+Access to the Github repository is granted to the Hudson jobs through a Github personal access token (PAT) to an account with read-only permissions to the repository.
14
+
15
+## Build Jobs
16
+
17
+There are a number of standard build jobs:
18
+* [SAPSailingAnalytics-master](https://hudson.sapsailing.com/job/SAPSailingAnalytics-master/): builds the ``main`` branch; if it shows "green" then a release and docker images will have been built
19
+* [SAPSailingAnalytics-master-fasttrack-no-tests](https://hudson.sapsailing.com/job/SAPSailingAnalytics-master-fasttrack-no-tests/): gets triggered by builds that were run with no tests being executed, e.g., by manually invoking the [Github Release Workflow](https://github.com/SAP/sailing-analytics/actions/workflows/release.yml) with the build parameter "skip all tests" set to ``true``
20
+* [SDBG](https://hudson.sapsailing.com/job/SDBG/) runs in the unlikely case that still some committer makes a change to the Eclipse GWT Super Dev Mode debugger; it would build a new release of the corresponding Plugin p2 repository and upload it to [https://p2.sapsailing.com/p2/sdbg](https://p2.sapsailing.com/p2/sdbg)
21
+* [translation](https://hudson.sapsailing.com/job/translation/) is triggered by the corresponding ``translation`` branch, and as such follows the general pattern of branch names equalling the build job names; however, at its end, if the build was successful, the ``translation`` branch will be merged into the ``main`` branch by the build job
22
+* ``bugXXXX`` jobs are the ones that correspond with branches in Git, just like a few for special branches such as ``management-console``; they are [[triggered by pushing to them in Git|wiki/howto/development/ci.md#github-webhook-on-push]]
23
+* [CopyTemplate](https://hudson.sapsailing.com/view/archived%20jobs%20/job/CopyTemplate/) is a disabled job that serves as the template used by the ``configuration/createdHudsonJobForBug.sh`` script, so don't delete this, and don't enable this!
24
+
25
+## How Jobs are Usually Triggered
26
+
27
+### Github Actions ``release`` Workflow
28
+
29
+The [release](https://github.com/SAP/sailing-analytics/actions/workflows/release.yml) workflow in Github is defined such that at its end it makes an HTTP request to https://hudson.sapsailing.com/job/${JOB}/build, passing a secret access token as defined in the Hudson build jobs (taken from the CopyTemplate job), which will trigger the build job that corresponds to the branch the ``release`` workflow just tried to build. No rule without exceptions: the ``main`` branch is mapped to ``SAPSailingAnalytics-master``, and if ``main`` was built without test execution, then ``SAPSailingAnalytics-master-fasttrack-no-tests``. Furthermore, branches named ``releases/...`` then the ``releases/`` is stripped from the branch name to obtain the name of the build job to trigger (see also [[Building with Release|wiki/info/landscape/development-environment.md#exceptionally-building-without-running-tests-more-fewer-cpus-and-with-release]]).
30
+
31
+### Manually Triggering a Job
32
+
33
+The ``release`` workflow can be dispatched manually. It has a few build parameters you can use to configure the build. If you skip tests but build a branch producing a release, that release will be named "untested-..." for clarity. You can also configure the number of CPUs within certain limits and steps. Should you have made changes to the [[target platform|wiki/info/general/workspace-bundles-projects-structure.md#target-platform]] on your branch that would require a different set of bundles in our base p2 repository under https://p2.sapsailing.com/p2/sailing, you can also explicitly ask for a build here which will first construct a temporary base p2 repository during the build and use that instead of the regular one.
34
+
35
+### Github Webhook on Push
36
+
37
+When commits are pushed to our Github repository, a [webhook](https://github.com/SAP/sailing-analytics/settings/hooks/480929064) is triggered which sends an HTTP request to [https://git.sapsailing.com/hooks/github.cgi](https://git.sapsailing.com/hooks/github.cgi). This, in turn, is backed by the script ``/var/www/cgi-bin/github.cgi`` which is installed there when the central reverse proxy is set up from [configuration/environments_scripts/central_reverse_proxy/files/var/www/cgi-bin/github.cgi](https://github.com/SAP/sailing-analytics/blob/main/configuration/environments_scripts/central_reverse_proxy/files/var/www/cgi-bin/github.cgi). Currently, that script's only task is to check whether the push originated from the translators making contributions to the ``translation`` branch and if so, push those changes also to the ``translation`` branch of our internal Git repository at ssh://trac@sapsailing.com/home/trac/git.
38
+
39
+## Disabling a Job
40
+
41
+When done with a branch for the time being or for good, you can disable the corresponding Hudson job that was created for it. The job's page has a corresponding "Disable job" button on it.
42
+
43
+Disabling (rather than deleting) jobs has the benefit of all data (logs, test runs, measurements) of those builds that are kept (usually the latest ten builds) will be preserved. This way it is usually safe to reference build and test results in your Bugzilla comments.
44
+
45
+Disabled jobs will not show directly on the landing page at [https://hudson.sapsailing.com](https://hudson.sapsailing.com) but instead can be found under the [archived jobs](https://hudson.sapsailing.com/view/archived%20jobs%20/) tab. This way, the landing page stays clean and tidy.
6 46
7 47
## Collecting measurements using Hudson/Jenkins
8 48
wiki/howto/eventmanagers/linking-race-videos.md
... ...
@@ -59,4 +59,4 @@ This articel provides a short guidance of how to synchronize races with their Yo
59 59
8. start again from step 3. of chaper 1
60 60
61 61
![Figure 4: Link all races covered by the video](https://s3-eu-west-1.amazonaws.com/media.sapsailing.com/wiki/how%20to/linking%20race%20videos/multi_sync.png)
62
-**Figure 4: Link all races covered by the video**
... ...
\ No newline at end of file
0
+**Figure 4: Link all races covered by the video**
wiki/howto/onboarding.md
... ...
@@ -1,17 +1,5 @@
1 1
# OnBoarding Information
2 2
3
-<!--
4
-This Markdown document is designed to work with Gollum not with GitHub. Internal links to chapters have the following syntax in for Gollum:
5
-
6
-Chapter Hierachy:
7
-# chapter
8
-## new subchapter
9
-### chapter to reference
10
-## another chapter
11
-
12
-[linkText](#chapter_new-subchapter_chapter-to-reference)
13
- -->
14
-
15 3
This document describes the onboarding process for a new team member (developer)
16 4
17 5
First of all, make sure you've looked at [http://www.amazon.de/Patterns-Elements-Reusable-Object-Oriented-Software/dp/0201633612](http://www.amazon.de/Patterns-Elements-Reusable-Object-Oriented-Software/dp/0201633612). That's a great book, and knowing at least some of it will help you a great deal finding your way around our solution.
... ...
@@ -21,29 +9,29 @@ First of all, make sure you've looked at [http://www.amazon.de/Patterns-Elements
21 9
### Accounts
22 10
23 11
1. Git Account
24
- 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. 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``.
25 12
26
- - For 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).
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
+
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).
27 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.
28 17
29 18
2. Bugzilla
30 19
31 20
- Create an account at https://bugzilla.sapsailing.com
32
- - Ask the Bugzilla administrator (axel.uhl@sap.com) to enable your account for editing bugs
33
- - Bugzilla url: [https://bugzilla.sapsailing.com](https://bugzilla.sapsailing.com)
21
+ - Ask a Bugzilla administrator (e.g., axel.uhl@sap.com) to enable your account for editing bugs
22
+ - Bugzilla URL: [https://bugzilla.sapsailing.com](https://bugzilla.sapsailing.com)
34 23
35 24
3. Wiki
36 25
37
-We have so far decided against migrating our existing Gollum-based Wiki to Github's Wiki pages. Therefore, for the time being, you can either push changes to the ``wiki/`` folder to the ``main`` branch of the repository from where they will make it to the Gollum-provided Wiki, or our can request a Gollum account to be created for you. This will also allow you to view non-public pages through [https://wiki.sapsailing.com](https://wiki.sapsailing.com).
38
-
39
-For a Gollum Wiki account send a request to Axel Uhl or Simon Marcel Pamies that includes the SHA1 hash of your desired password. Obtain such an SHA1 hash for your password here: [http://www.sha1-online.com/](http://www.sha1-online.com/).
26
+ - We have so far decided against migrating our existing Gollum-based Wiki to Github's Wiki pages. Therefore, for the time being, you can either push changes to the ``wiki/`` folder to the ``main`` branch of the repository from where they will make it to the Gollum-provided Wiki, or our can request a Gollum account to be created for you. This will also allow you to view non-public pages through [https://wiki.sapsailing.com](https://wiki.sapsailing.com).
27
+ - For a Gollum Wiki account send a request to Axel Uhl or Simon Marcel Pamies that includes the SHA1 hash of your desired password. Obtain such an SHA1 hash for your password here: [http://www.sha1-online.com/](http://www.sha1-online.com/).
28
+ - Once our Github repository is switched to a "public" repository, we will change our Gollum configuration such that at least read-only access is granted for all pages also for anonymous users.
40 29
41 30
4. Hudson
42 31
43 32
- Request a [Hudson](https://hudson.sapsailing.com) user by sending e-mail to Axel Uhl or Simon Marcel Pamies.
44 33
45 34
### Installations
46
-
47 35
1. Eclipse IDE for Eclipse Committers, version ["2025-03"](https://www.eclipse.org/downloads/packages/release/2025-03/r/eclipse-ide-eclipse-committers). If you are using a Mac and want to use SAPJVM, this has to be the 64 bit version. This is because SAPJVM is not available for Apple Silicon Macs, and Eclipse's OS architecture must match the JVM architecture.
48 36
2. JDK 1.8 (Java SE 8), ideal is the SAPJVM 1.8: Go to [https://tools.eu1.hana.ondemand.com/#cloud](https://tools.eu1.hana.ondemand.com/#cloud), scroll down to `SAP JVM` select your operating System, extract the downloaded .zip into desired location (e.g. Windows `C:\Program Files\Java`. If you want to make this your default JDK, set the `JAVA_HOME` variable to it. In any case, set the `JAVA8_HOME` variable to it which is required by a few build scripts where certain steps currently are not yet compatible with newer JDK releases, such as our Android build process, keeping us on Gradle 6.0.1 for the time being which isn't Java 17-compatible.
49 37
3. Git (e.g. Git for Windows v2.18), [http://git-scm.com](http://git-scm.com) / [https://git-for-windows.github.io](https://git-for-windows.github.io)still
... ...
@@ -54,13 +42,13 @@ For a Gollum Wiki account send a request to Axel Uhl or Simon Marcel Pamies that
54 42
A setup guide for windows can be found on this webpage: [https://maven.apache.org/guides/getting-started/windows-prerequisites.html](https://maven.apache.org/guides/getting-started/windows-prerequisites.html)
55 43
8. Forked GWT SDK 2.11.1 release candidate ([https://static.sapsailing.com/gwt-2.11.1.zip](https://static.sapsailing.com/gwt-2.11.1.zip)). The official releases can be found at [http://www.gwtproject.org/download.html](http://www.gwtproject.org/download.html)
56 44
but shouldn't be used unless we roll back the changes of branch ``bug5077`` or GWT has merged and released the [pull request 9779](https://github.com/gwtproject/gwt/pull/9779).
57
- Download the GWT DSK and extract it to a location of your preference (e.g. `C:\Program Files\gwt` on Windows or `/opt/gwt` on Linux or MacOS/X).
45
+ Download the GWT DSK and extract it to a location of your preference (e.g. `C:\Program Files\gwt` on Windows or `/opt` on Linux or MacOS/X).
58 46
You will see in section [Tuning the Eclipse Installation](#onboarding-information_sap-sailing-analytics-development-setup_tuning-the-eclipse-installation)
59 47
below how you announce this GWT SDK to your Eclipse installation.
60 48
9. Standalone Android SDK (see section "Additional steps required for Android projects"). OPTIONALLY: You may additionally install Android Studio ([https://developer.android.com/tools/studio/index.html](https://developer.android.com/tools/studio/index.html)) or IntelliJ IDEA ([https://www.jetbrains.com/idea/download/](https://www.jetbrains.com/idea/download/)).
61 49
Make sure that the environment variable `ANDROID_HOME` is set (e.g. Windows C:\Users\\**'user'**\AppData\Local\Android\Sdk )
62 50
10. Get the content of the git repository
63
- Clone the repository to your local file system from `ssh://[SAP-User]@git.wdf.sap.corp:29418/SAPSail/sapsailingcapture.git` or `ssh://trac@sapsailing.com/home/trac/git` User "trac" has all public ssh keys.
51
+ Clone the repository to your local file system from `git@github.com:SAP/sailing-analytics.git` or `ssh://trac@sapsailing.com/home/trac/git` User "trac" has all public ssh keys.
64 52
11. Install the eclipse plugins (see [Automatic Eclipse plugin installation](#onboarding-information_sap-sailing-analytics-development-setup_automatic-eclipse-plugin-installation))
65 53
12. Configure Eclipse (see [Tuning the Eclipse Installation](#onboarding-information_sap-sailing-analytics-development-setup_tuning-the-eclipse-installation))
66 54
13. Configure Maven to use the correct JRE by following the instructions in the paragraph [maven-setup](#onboarding-information_sap-sailing-analytics-development-setup_maven-setup)
... ...
@@ -71,14 +59,12 @@ For a Gollum Wiki account send a request to Axel Uhl or Simon Marcel Pamies that
71 59
72 60
### Further optional but recommended installations
73 61
74
-1. Cygwin, [http://www.cygwin.com/](http://www.cygwin.com/)
62
+1. For Windows users, [Cygwin](http://www.cygwin.com/) or a [Git Bash](https://git-scm.com/downloads) may be useful for being able to run any Bash scripts.
75 63
Please note that when using one of the newer versions of Cygwin, your Cygwin home folder setting might differ from your Windows home folder. This will likely lead to problems when issuing certain commands. For troubleshooting, take a look at the following thread: [https://stackoverflow.com/questions/1494658/how-can-i-change-my-cygwin-home-folder-after-installation](https://stackoverflow.com/questions/1494658/how-can-i-change-my-cygwin-home-folder-after-installation)
76 64
2. Eclipse Mylyn Bugzilla extension
77 65
3. kdiff3 (git tool)
78 66
4. Firebug (javascript & .css debugging, included in Firefox Developer Tools in newer versions of Firefox by default)
79 67
80
-
81
-
82 68
### Git repository configuration essentials
83 69
84 70
The project has some configuration of line endings for specific file types in ".gitattributes". To make this work as intended, you need to ensure that the git attribute "core.autocrlf" is set to "false". This can be done by navigating to your local repository in a Bash/Git Bash/Cygwin instance and executing the command `git config core.autocrlf false`.
... ...
@@ -89,10 +75,10 @@ Depending on the location of your local repository, it's filepaths might be too
89 75
90 76
### Maven Setup
91 77
92
-Copy the settings.xml (may be in $GIT_HOME/configuration/maven-settings.xml and $GIT_HOME/configuration/maven-settings.xml) **and** the toolchains.xml from the top-level git folder to your ~/.m2 directory. Adjust the proxy settings in settings.xml accordingly (suggested settings for corporate network inside). Set the paths inside of toolchains.xml to your JDKs depending on where you installed them (this is like setting the compiler for your IDE, but for Maven; This makes it possible to build with the same Maven configuration on every system). Make sure the mvn executable you installed above is in your path.
78
+Copy the settings.xml (may be in $GIT_HOME/configuration/maven-settings.xml and $GIT_HOME/configuration/maven-settings-proxy.xml) **and** the toolchains.xml from the top-level git folder to your ~/.m2 directory. Adjust the proxy settings in settings.xml accordingly (suggested settings for inside a corporate network requiring a HTTP proxy for access to external web). Set the paths inside of toolchains.xml to your JDKs depending on where you installed them (this is like setting the compiler for your IDE, but for Maven; This makes it possible to build with the same Maven configuration on every system). Make sure the mvn executable you installed above is in your path.
93 79
94 80
### Automatic Eclipse plugin installation
95
-The necessary Eclipse plugins can be automatically installed into a newly unzipped version of ["2023-09"](https://www.eclipse.org/downloads/packages/release/2023-09/r/eclipse-ide-eclipse-committers) by using the `./configuration/pluginsForEclipse2025-03.p2f` file, found in the git repository cloned in _step 11_. To install the plugins open Eclipse and install Software Items from File. (File ⇒ Import ⇒ Install ⇒ Install Software from File). The description file is located at `/configuration/pluginsForEclipse2025-03.p2f`.
81
+The necessary Eclipse plugins can be automatically installed into a newly unzipped version of ["2025-03"](https://www.eclipse.org/downloads/packages/release/2025-03/r/eclipse-ide-eclipse-committers) by using the `./configuration/pluginsForEclipse2025-03.p2f` file, found in the git repository cloned in _step 11_. To install the plugins open Eclipse and install Software Items from File. (File ⇒ Import ⇒ Install ⇒ Install Software from File). The description file is located at `/configuration/pluginsForEclipse2025-03.p2f`.
96 82
Make sure to select all Plugins (it might not be possible to select Lucene ignore that) and click next. In the pop-up dialog shown next, select the top radio button ("Update my installation to be compatible with the items being installed"). Skip the `Installation details`, accept the licence agreements and click finish. While Eclipse is installing the plugins a pop-up will appear in the background where you need to trust all plugins. Be aware that the installation may take several minutes depending on your Internet connection.
97 83
98 84
Be also aware that with this p2f-file it's not possible to update the plugins to newer versions.
... ...
@@ -136,68 +122,39 @@ Go to Window ⇒ Preferences and change the following two settings:
136 122
- For Eclipse-based debugging of GWT web applications with SDBG, make sure that Chrome is set as your default browser: "General ⇒ Web Browser". If missing, add a profile for Chrome and specify "%URL%" as the parameter.
137 123
- Consider installing [https://marketplace.eclipse.org/content/protocol-buffer-editor](https://marketplace.eclipse.org/content/protocol-buffer-editor) for a Protocol Buffers (protobuf) editor
138 124
139
-### Steps to build and run the Race Analysis Suite
125
+### Steps to build and run the Sailing Analytics
140 126
141
-1. Check out the 'master' branch from the git repository. The 'master' branch is the main development branch. Please check that you start your work on this branch.
127
+1. Check out the ``main`` branch from the git repository. The ``main`` branch is the main development branch. Please check that you start your work based on this branch.
142 128
2. Setup and configure Eclipse
143 129
- Import all Race Analysis projects from the `java/` subdirectory of the git main folder (make sure to import via the wizard [but without smart import] "Import ⇒ General ⇒ Projects from Folder or Archive" in Eclipse, and additionally make sure to scan for nested projects!)
144
- - Import all projects from the `mobile/` subdirectory of the git main folder; this in particular contains the race committee app projects
145 130
- In "Window ⇒ Preferences ⇒ Plug-in Development ⇒ Target Platform" set the Eclipse target platform to `Race Analysis Target` (located in com.sap.sailing.targetplatform/definitions//race-analysis-p2-remote.target)
146 131
- Wait until the target platform has been resolved completely
147 132
- Start a clean build (Project ⇒ Clean)
148
-
149
-3. On clear workspace additional steps should be performed once:
133
+3. To get a clean workspace, additional steps should be performed once:
150 134
1. Run "GWT Dashboards SDM" launch configuration. After successful start, launch configuration can be stopped.
151 135
2. Run "GWT Security SDM" launch configuration. After successful start, launch configuration can be stopped.
152 136
3. Run "GWT xdStorage Sample SDM" launch configuration. After successful start, launch configuration can be stopped.
153 137
4. Run the Race Analysis Suite
154
- 1. Start the MongoDB-Server
138
+ 1. Ensure your local MongoDB Server is running; depending on your platform, maybe you can start your MongoDB using ``sudo systemctl start mongod``, or you may have to do something like:
155 139
1. Create a folder for the mongoDB to store the data. For existing folders make sure they do not contain a `mongod.lock` file
156 140
2. Open a terminal and navigate to the location of the MongoDB installation `cd /somePathTo MongoDBInstallation/mongodb/bin`
157 141
3. Start the databse in the with the mongoDB Datafolder as db path:
158 142
`./mongod --dbpath /somePathTo/MongoDBDataDirectory`
159 143
2. Run "GWT Sailing SDM" in the debug dropdown
160
- 3. Start the appropriate Eclipse launch configuration (e.g. 'Sailing Server (no Proxy)') You´ll find this in the debug dropdown
144
+ 3. Start the appropriate Eclipse back-end launch configuration (in most cases 'Sailing Server (no Proxy)'). You´ll find this in the debug dropdown.
161 145
5. Import races within the Race Analysis Suite
162
- - Choose "GWT Sailing SDM" in the "Development Mode" Tab and open "...AdminConsole.html..." (It is normal that the first try fails. Reload the page after the first try)
146
+ - Choose "GWT Sailing SDM" in the "Development Mode" Tab and open "...AdminConsole.html...". This should open [http://127.0.0.1:8888/gwt/AdminConsole](http://127.0.0.1:8888/gwt/AdminConsole). (It is normal that the first try fails. Reload the page after the first try)
163 147
- Default Login: user "admin", password "admin"
164 148
- In the list on the left, click on "Connectors"
165 149
- For TracTrac Events: In the "TracTrac Connections" Form, fill in the JSON URL [http://germanmaster.traclive.dk/events/event_20120905_erEuropean/jsonservice.php](http://germanmaster.traclive.dk/events/event_20120905_erEuropean/jsonservice.php)(all other required information will be filled in automatically)
166 150
- Press "List Races"
167
-
168
-
169 151
6. Further useful launch configurations
170 152
- Use SAP JVM Profiler. If you used the script above and installed the SAPJVM instead of the jdk, you can now open the profiling perspective by clicking on Window ⇒ Perspective ⇒ Open Perspective ⇒ Profiling)
171 153
- Debugging gwt: For further instructions please see [here](./development/super-dev-mode)
172 154
173 155
If you want to use **breakpoints**, *avoid* clicking on the options in the Development Mode tab. Instead, within the _Debug Configurations_ menu, select the _Debug AdminConsole_ (found in the _Launch Browser_ subtab); change the browser search order, such that chrome is the leftmost; and then launch. This is necessary because SDBG is compatible with Chrome. Further, details of how GWT Super Dev Mode (SDM) works, can be found in the link above.
174 156
175
-
176
-### Build for deployment
177
-Open a shell (preferrably a git bash or a cygwin bash), cd to the git workspace's root folder and issue "./configuration/buildAndUpdateProduct.sh build". This should build the software and run all the tests. If you want to avoid the tests being executed, use the -t option. If you only want to build one GWT permutation (Chrome/English), use the -b option. When inside the SAP VPN, add the -p option for proxy use. Run the build script without arguments to get usage hints.
178
-
179
-
180
-
181
-## Further hints
182
-
183
-If you are working with a linux-system and you get the error message `error while loading shared libraries: libz.so.1: cannot open shared object file: No such file or directory` try to install lib32z1 and lib32stdc++6.
184
-
185
-### Steps to consider for using other modules
186
-
187
-1. For Eclipse Build
188
- - MANIFEST.MF , add module names unter dependencies
189
- - \*.gwt.xml , add `<inherits name="-modulename-.-gwt.xml file name-" />`
190
- - In DebugConfigurations => Classpaths, Add Sourcefolder where classes are you want to user from the module
191
-2. For Maven Build
192
- - pom.xml , Add Dependency to used module ie.
193
- `<dependency>`
194
- `<groupId>com.sap.sailing</groupId>`
195
- `<artifactId>com.sap.sailing.domain.common</artifactId>`
196
- `<version>1.0.0-SNAPSHOT</version>`
197
- `<classifier>sources</classifier>`
198
- `</dependency>`
199
-
200
-### Using Android Studio for Development
157
+### Using Android Studio for App Development (Only if You're Working on Mobile Apps)
201 158
202 159
The Android Apps can be built in Android Studio or gradle command line. Android Studio is built on top of IntelliJ IDEA, so it is possible to use IntelliJ IDEA as well.
203 160
... ...
@@ -227,6 +184,26 @@ The Android Apps can be built in Android Studio or gradle command line. Android
227 184
228 185
If git is not in the Path system environment variable, the gradle build will not work.
229 186
187
+## Further hints
188
+
189
+### Build for deployment
190
+Open a shell (preferrably a git bash or a cygwin bash), cd to the git workspace's root folder and issue "./configuration/buildAndUpdateProduct.sh build". This should build the software and run all the tests. If you want to avoid the tests being executed, use the -t option. If you only want to build one GWT permutation (Chrome/English), use the -b option. When inside the SAP VPN, add the -p option for proxy use. Run the build script without arguments to get usage hints.
191
+
192
+### Steps to consider for using other GWT modules
193
+
194
+1. For Eclipse Build
195
+ - MANIFEST.MF, add module names under dependencies
196
+ - \*.gwt.xml , add `<inherits name="-modulename-.-gwt.xml file name-" />`
197
+ - In DebugConfigurations => Classpaths, Add Sourcefolder where classes are you want to user from the module
198
+2. For Maven Build
199
+ - pom.xml , Add Dependency to used module ie.
200
+ `<dependency>`
201
+ `<groupId>com.sap.sailing</groupId>`
202
+ `<artifactId>com.sap.sailing.domain.common</artifactId>`
203
+ `<version>1.0.0-SNAPSHOT</version>`
204
+ `<classifier>sources</classifier>`
205
+ `</dependency>`
206
+
230 207
### Git usage troubleshooting
231 208
232 209
There are some inconsistencies regarding line endings (unix vs windows) in our git repository. There is a configuration named ".gitattributes" committed to the root folder of the repository that helps to prevent inconsistencies of line endings when committing files. Files that existed before are partially using unix (LF) or windows (CRLF) line endings. When committing files, git will ensure unix line endings for e.g. \*.java files. This can lead to big diffs that hurt when trying to merge/diff.
... ...
@@ -255,17 +232,23 @@ Irritating behavior can occur on ARM-based processors, such as on a MacBook. The
255 232
In such cases it might help to set AWT to headless mode (`-Djava.awt.headless=true`, see [stackoverflow](https://stackoverflow.com/questions/13796611/imageio-read-with-mac for more information)).
256 233
Another struggle can be to install the JVM Profiler Plug-in on ARM based Eclipse. It seems to block the necessary Software while the Automatic Eclipse plugin installation. One way is to use the Eclipse x64 Installer. It will take more time for transformation, but you will be able to use the Profiler.
257 234
258
-### GWT Browser Plugin
235
+### GWT Browser Plugin
236
+This applies only if you try to get old "GWT Dev Mode" to work, support for which has ended on very old Firefox version 24.
259 237
Install the GWT Browser Plugin for the GWT Development mode. As of 2016-08-31 Firefox is the only browser supporting the GWT plugin, you have to download Firefox version 24 for it to work. The Plugin can be found on this page: [https://code.google.com/archive/p/google-web-toolkit/downloads](https://code.google.com/archive/p/google-web-toolkit/downloads)
260 238
261 239
### Create Hudson Job
262 240
If you want a hudson job to run when you push your branch then you can run a script in `configuration` called . Run options for a branch titled `createHudsonJobForBug.sh`. For you bug branch titled `bug<bug number>`, create a build job, which will create a release, by running the script like so: `./createHudsonJobForBug.sh <bug number>`.
263
-If on Windows, you may need to disable any web shields in antivirus software, to allow `curl` to function. If on Mac, you may need to install gnu-sed.
241
+If on Windows, you may need to disable any web shields in antivirus software, to allow `curl` to function. If on Mac, you may need to install gnu-sed (``gsed``) via Homebrew.
264 242
265 243
### Issues when playing around with AWS
266 244
- The problem: **aws cli (used for aws ec2 describe-tags) hangs in eu-west-2** in all AZs on new instances I created, using a target group which permitted all outbound connections and inbound https, http and ssh connections. I tried permitting everything but that didn’t work. When I attached (at Axel’s suggestion) the Java Application with Reverse Proxy security group, it worked but — even if I duplicated this security group, and applied that copy instead — it still didn’t work.
267 245
Curl issue solution: it turns out that the network interface only permits certain outbound and inbounds from certain target groups.
268
-The path to the solution: On my instance in eu-west-2a, I ran aws --debug ec2 describe-tags (you may need to do aws configure first). This is much akin to verbose mode of other unix commands. I noticed it hang on a request to ec2.eu-west-2.amazonaws.com. If you do `dig -t any ec2.eu-west-2.amazonaws.com` you see 3 ip addresses, which — as you will see later — are IPs in each of the eu-west-2 availability zones. When I ran curl -v ec2.eu-west-2.amazonaws.com (the v flag is verbose), one of the IPs from dig was used (namely the one in eu-west-2a, where the instance resides) and it hangs. I then went to endpoints for the VPC and noticed a service for the service `com.amazonaws.eu-west-2.ec2`. It had the default security group, which turned out to only allow inbound rules from the default or Java Application with Reverse Proxy target group.
246
+The path to the solution: On my instance in eu-west-2a, I ran aws --debug ec2 describe-tags (you may need to do aws configure first). This is much akin to verbose mode of other unix commands. I noticed it hang on a request to ec2.eu-west-2.amazonaws.com. If you do `dig -t any ec2.eu-west-2.amazonaws.com` you see 3 ip addresses, which — as you will see later — are IPs in each of the eu-west-2 availability zones. When I ran curl -v ec2.eu-west-2.amazonaws.com (the v flag is verbose), one of the IPs from dig was used (namely the one in eu-west-2a, where the instance resides) and it hangs. I then went to endpoints for the VPC and noticed a service for the service `com.amazonaws.eu-west-2.ec2`. It had the default security group, which turned out to only allow inbound rules from the default or Java Application with Reverse Proxy target group.
269 247
- Problem: A load balancer's target group health checks fail. I was told the checks failed with 403 errors.
270 248
Solution: This was occurring because the website didn't have any content in the /var/www/html. Whilst a site was still served (namely the Apache test page) it does throw a 403 error. If you fill the directory with and index.html the test then passes and a 200 code is returned
271 249
- Problem: Target platform reload in Eclipse. Sometimes reloading via Window -> Plug-in Development -> Target Platform doesn't work, so open the target definition itself and try reloading there. Often a restart proves helpful after the reload. In addition, you can clean all projects and rebuild; then rebuild the individual projects that fail. Sometimes the errors are actually just warnings and you can try to run the GWT SDM (remember the other SDM's must be run if everything is brand new). Lastly, try clearing the plugins content found at `WORKSPACE_HOME/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins`.
250
+
251
+### Tips for Mac users
252
+Mac users can install SDKMAN! to manage and install different JDKs.
253
+
254
+
wiki/howto/tutorials/home.md
... ...
@@ -91,52 +91,142 @@
91 91
92 92
<h3>All Tutorials:</h3>
93 93
94
- <table>
95
- <tr>
96
- <td><a href="sailinganalytics/tracking-race-player.md">Tracking Race Player Overview</a></td>
97
- <td><a href="sailinganalytics/sign-up.md">Signing up for a user account</a></td>
98
- <td><a href="sailinganalytics/into-to-backend.md">Introduction to Analytics Backend</a></td>
99
- </tr>
100
- <tr>
101
- <td><a href="sailinganalytics/simple-event-creation.md">Create a simple event</a></td>
102
- <td><a href="sailinganalytics/video-tracking-management.md">Add &amp; align Youtube videos</a></td>
103
- <td><a href="sailinganalytics/create-regattas.md">Create complex regattas</a></td>
104
- </tr>
105
- <tr>
106
- <td><a href="sailinganalytics/competitors-data-management.md">Edit scores/results</a></td>
107
- <td><a href="sailinganalytics/importing-results.md">Importing results</a></td>
108
- <td><a href="sailinganalytics/adjust-mark-position.md">Adjust mark position</a></td>
109
- </tr>
110
- <tr>
111
- <td><a href="sailinganalytics/set-mark-position.md">Set mark position</a></td>
112
- <td><a href="sailinganalytics/configure-wind-settings.md">Configure wind settings</a></td>
113
- <td><a href="sailinganalytics/set-up-regattas.md">Set up multiple regattas</a></td>
114
- </tr>
115
- <tr>
116
- <td><a href="sailinganalytics/leaderboard-groups-explanation.md">Leaderboard groups explained</a></td>
117
- <td><a href="sailinganalytics/split-leaderboard.md">Split a leaderboard</a></td>
118
- <td><a href="sailinganalytics/course-areas.md">Working with course areas</a></td>
119
- </tr>
120
- <tr>
121
- <td><a href="sailinganalytics/sailing-race-manager.md">Work with Race Manager app</a></td>
122
- <td><a href="sailinganalytics/race-manager-connection.md">Connect Race Manager to event</a></td>
123
- <td><a href="sailinganalytics/edit-results.md">Editing results</a></td>
124
- </tr>
125
- <tr>
126
- <td><a href="sailinganalytics/set-matchrace-event.md">Set up matchrace event</a></td>
127
- <td><a href="sailinganalytics/security-system.md">Security system intro</a></td>
128
- <td><a href="sailinganalytics/default-creation-group.md">Default creation group</a></td>
129
- </tr>
130
- <tr>
131
- <td><a href="sailinganalytics/make-group-public.md">Make group public</a></td>
132
- <td><a href="sailinganalytics/publish-event.md">Publish an event</a></td>
133
- <td><a href="sailinganalytics/import-gpx-track.md">Import a GPX track</a></td>
134
- </tr>
135
- <tr>
136
- <td><a href="sailinganalytics/race-fixes.md">Fix device assignment</a></td>
137
- <td><a href="sailinganalytics/data-mining-tool.md">Data Mining Tool intro</a></td>
138
- <td></td>
139
- </tr>
94
+ <style>
95
+ table.guide-index { width: 100%; border-collapse: collapse; }
96
+ .guide-index col:first-child { width: 35%; }
97
+ .guide-index col:last-child { width: 65%; }
98
+ .guide-index td { padding: 8px 12px; vertical-align: top; }
99
+ .guide-index a { font-weight: 600; text-decoration: underline; }
100
+ /* optional zebra */
101
+ .guide-index tr:nth-child(odd) { background: #f7f7f7; }
102
+
103
+ /* optional: stack on small screens */
104
+ @media (max-width: 640px) {
105
+ .guide-index col { width: auto !important; }
106
+ .guide-index td { display: block; padding: 6px 12px; }
107
+ .guide-index td:first-child { padding-bottom: 0; }
108
+ }
109
+</style>
110
+
111
+<table class="guide-index">
112
+ <colgroup>
113
+ <col />
114
+ <col />
115
+ </colgroup>
116
+ <tbody>
117
+ <tr>
118
+ <td><a href="sailinganalytics/tracking-race-player.md">Tracking Race Player Overview</a></td>
119
+ <td>The video below gives an excellent overview of the main functionality of the SAP Race Player which is accessible on a per-race basis via the “Races/Tracking” tab of a regatta.</td>
120
+ </tr>
121
+ <tr>
122
+ <td><a href="sailinganalytics/sign-up.md">Signing up for a user account</a></td>
123
+ <td>This tutorial will show you how to sign up for a user account at the <strong>SAP Sailing Analytics</strong>.</td>
124
+ </tr>
125
+ <tr>
126
+ <td><a href="sailinganalytics/into-to-backend.md">Introduction to Analytics Backend</a></td>
127
+ <td>The guide <strong>General Introduction to SAP Sailing Analytics Backend</strong> will give you a look around the SAP Sailing Analytics <em>Admin Console</em>.</td>
128
+ </tr>
129
+ <tr>
130
+ <td><a href="sailinganalytics/simple-event-creation.md">Create a simple event</a></td>
131
+ <td>This tutorial shows you how to create an event with the SAP Analytics <em>Administration Console</em> on <a href="https://my.sapsailing.com/gwt/Home.html">my.sapsailing.com</a>.</td>
132
+ </tr>
133
+ <tr>
134
+ <td><a href="sailinganalytics/video-tracking-management.md">Add &amp; align Youtube videos</a></td>
135
+ <td>This tutorial shows how to add and align <em>Youtube videos</em> to the <strong>tracking timeline</strong>.</td>
136
+ </tr>
137
+ <tr>
138
+ <td><a href="sailinganalytics/create-regattas.md">Create complex regattas</a></td>
139
+ <td>This tutorial shows how to create <strong>more complex regattas</strong>.</td>
140
+ </tr>
141
+ <tr>
142
+ <td><a href="sailinganalytics/competitors-data-management.md">Edit scores/results</a></td>
143
+ <td>This tutorial shows how to <em>edit</em> scores/results in leaderboards and <em>change</em> competitor data in <strong>the SAP Sailing Analytics</strong>.</td>
144
+ </tr>
145
+ <tr>
146
+ <td><a href="sailinganalytics/importing-results.md">Importing results</a></td>
147
+ <td>The video explains how you can <em>import</em> results/scores from other regatta management systems for which <strong>the SAP Sailing Analytics</strong> have an integration in place, such as <strong>YachtScoring</strong> and <strong>Manage2Sail</strong>.</td>
148
+ </tr>
149
+ <tr>
150
+ <td><a href="sailinganalytics/adjust-mark-position.md">Adjust mark position</a></td>
151
+ <td>This episode shows you how to adjust <em>the position of a mark</em> with <strong>SAP Sailing Analytics</strong>.</td>
152
+ </tr>
153
+ <tr>
154
+ <td><a href="sailinganalytics/set-mark-position.md">Set mark position</a></td>
155
+ <td>This episode shows you how to set <em>the position of a mark</em> with <strong>SAP Sailing Analytics</strong> in case the tracker failed or you were not able to attach a <em>tracker</em> to the mark.</td>
156
+ </tr>
157
+ <tr>
158
+ <td><a href="sailinganalytics/configure-wind-settings.md">Configure wind settings</a></td>
159
+ <td>This tutorial shows how to configure <strong>wind settings.</strong>.</td>
160
+ </tr>
161
+ <tr>
162
+ <td><a href="sailinganalytics/set-up-regattas.md">Set up multiple regattas</a></td>
163
+ <td>This tutorial shows how to set up <em>multiple regattas</em> or <em>classes events</em> with the <strong>SAP Sailing Analytics</strong>.</td>
164
+ </tr>
165
+ <tr>
166
+ <td><a href="sailinganalytics/leaderboard-groups-explanation.md">Leaderboard groups explained</a></td>
167
+ <td>This tutorial explains how <em>Leaderboard Groups</em> work in <strong>the SAP Sailing Analytics</strong>.</td>
168
+ </tr>
169
+ <tr>
170
+ <td><a href="sailinganalytics/split-leaderboard.md">Split a leaderboard</a></td>
171
+ <td>This tutorial shows how to <em>split</em> a leaderboard into divisions with the <strong>SAP Sailing Analytics</strong>.</td>
172
+ </tr>
173
+ <tr>
174
+ <td><a href="sailinganalytics/course-areas.md">Working with course areas</a></td>
175
+ <td>This tutorial shows how to work with <em>course areas</em> in <strong>SAP Sailing Analytics</strong>.</td>
176
+ </tr>
177
+ <tr>
178
+ <td><a href="sailinganalytics/sailing-race-manager.md">Work with Race Manager app</a></td>
179
+ <td>This tutorial explains how to work with <strong>the SAP Sailing Race Manager Application</strong>.</td>
180
+ </tr>
181
+ <tr>
182
+ <td><a href="sailinganalytics/race-manager-connection.md">Connect Race Manager to event</a></td>
183
+ <td>This tutorial shows how to connect <strong>the Race Manager Application</strong> to an <em>event</em>.</td>
184
+ </tr>
185
+ <tr>
186
+ <td><a href="sailinganalytics/edit-results.md">Editing results</a></td>
187
+ <td>This tutorial explains how to <em>edit</em> results with <strong>the SAP Sailing Race Manager Application</strong>.</td>
188
+ </tr>
189
+ <tr>
190
+ <td><a href="sailinganalytics/set-matchrace-event.md">Set up matchrace event</a></td>
191
+ <td>This episode shows you how to set up a <em>matchrace event</em> with <strong>the SAP Sailing Analytics</strong>.</td>
192
+ </tr>
193
+ <tr>
194
+ <td><a href="sailinganalytics/security-system.md">Security system intro</a></td>
195
+ <td>This episode serves as an introduction to the <em>security system</em> of the <strong>SAP Sailing Analytics</strong>.</td>
196
+ </tr>
197
+ <tr>
198
+ <td><a href="sailinganalytics/default-creation-group.md">Default creation group</a></td>
199
+ <td>This episode shows how to set a default <em>creation group</em> in user details with <strong>the SAP Sailing Analytics</strong>.</td>
200
+ </tr>
201
+ <tr>
202
+ <td><a href="sailinganalytics/make-group-public.md">Make group public</a></td>
203
+ <td>This episode shows how to make everything your group owns <em>readable</em> for everyone in <strong>the SAP Sailing Analytics</strong>.</td>
204
+ </tr>
205
+ <tr>
206
+ <td><a href="sailinganalytics/publish-event.md">Publish an event</a></td>
207
+ <td>This episode shows how to <em>publish an event</em> through a dedicated user group in <strong>the SAP Sailing Analytics</strong>.</td>
208
+ </tr>
209
+ <tr>
210
+ <td><a href="sailinganalytics/import-gpx-track.md">Import a GPX track</a></td>
211
+ <td>This episode shows how to import a track from a <em>GPX file</em>.</td>
212
+ </tr>
213
+ <tr>
214
+ <td><a href="sailinganalytics/race-fixes.md">Fix device assignment</a></td>
215
+ <td>This episode shows how to <em>fix</em> races when a device is assigned to multiple competitors <strong>accidentally</strong>.</td>
216
+ </tr>
217
+ <tr>
218
+ <td><a href="sailinganalytics/data-mining-tool.md">Data Mining Tool intro</a></td>
219
+ <td>This tutorial gives an introduction to <strong>the SAP Sailing Analytics Data Mining Tool</strong>.</td>
220
+ </tr>
221
+ <tr>
222
+ <td><a href="sailinganalytics/copy-course-to-race.md">Copy course to race</a></td>
223
+ <td>This tutorial explains how to copy course to other race.</td>
224
+ </tr>
225
+ <tr>
226
+ <td><a href="sailinganalytics/dset-up-course.md">Course set up</a></td>
227
+ <td>This tutorial explains how to set up course in <strong>AdminConsole</strong>.</td>
228
+ </tr>
229
+ </tbody>
140 230
</table>
141 231
142 232
<hr>
wiki/howto/tutorials/sailinganalytics/adjust-mark-position.md
... ...
@@ -1,18 +1,5 @@
1 1
This episode shows you how to adjust *the postion of a mark* with **SAP Sailing Analytics** :
2 2
3
-<div style="text-align: center; line-height: 0;">
4
- <a href="https://vimeo.com/488483711" target="_blank">
5
- <img src="https://i.vimeocdn.com/video/1010892770-68eafb9d9777a29688f3fa3dd35f2c82574a8072d69f1ae144ef4a56d7835562-d?f=webp&region=us" alt="Adjust the position of a mark with the SAP Sailing Analytics" style="display: inline-block;">
6
- </a>
7
- <div style="line-height: normal; margin-top: -18em; margin-bottom: 16em">
8
- <a href="https://vimeo.com/488483711" target="_blank" style="
9
- display: inline-block;
10
- vertical-align: middle;
11
- background-color: #007BFF;
12
- color: white;
13
- padding: 10px 20px;
14
- border-radius: 4px;
15
- text-decoration: none;
16
- font-weight: bold;
17
- ">Watch the Video</a>
18
- </div>
... ...
\ No newline at end of file
0
+ <video controls="true" width="640" src="https://sapsailing-documentation.s3-eu-west-1.amazonaws.com/adminconsole/AdjustingMarkPositionByCreatingNewFixCut.mp4" type="video/mp4">
1
+ Your browser does not support the video tag.
2
+</video>
... ...
\ No newline at end of file
wiki/howto/tutorials/sailinganalytics/competitors-data-management.md
... ...
@@ -1,19 +1,5 @@
1 1
This tutorial shows how to *edit* scores/results in leaderboards and *change* competitor data in **the SAP Sailing Analytics** :
2 2
3
-<div style="text-align: center; line-height: 0;">
4
- <a href="https://vimeo.com/488484658" target="_blank">
5
- <img src="https://i.vimeocdn.com/video/1010898993-1939bb3284504da7d6ce0c7ca44c68bac843370b6b01723d78ee672bf2568701-d?f=webp&region=us" alt="How to edit scores/results in leaderboards and change competitor data in the SAP Sailing Analytics" style="display: inline-block;">
6
- </a>
7
- <div style="line-height: normal; margin-top: -18em; margin-bottom: 16em">
8
- <a href="https://vimeo.com/488484658" target="_blank" style="
9
- display: inline-block;
10
- vertical-align: middle;
11
- background-color: #007BFF;
12
- color: white;
13
- padding: 10px 20px;
14
- border-radius: 4px;
15
- text-decoration: none;
16
- font-weight: bold;
17
- ">Watch the Video</a>
18
- </div>
19
- <br><br><br>
... ...
\ No newline at end of file
0
+<video controls="true" width="640" src="https://sapsailing-documentation.s3-eu-west-1.amazonaws.com/adminconsole/editingcompetitors.mp4" type="video/mp4">
1
+  Your browser does not support the video tag.
2
+</video>
... ...
\ No newline at end of file
wiki/howto/tutorials/sailinganalytics/configure-wind-settings.md
... ...
@@ -1,19 +1,5 @@
1 1
This tutorial shows how to configure **wind settings** :
2 2
3
-<div style="text-align: center; line-height: 0;">
4
- <a href="https://vimeo.com/488860317" target="_blank">
5
- <img src="https://i.vimeocdn.com/video/1011532573-a72305c16fcb48b67ff30dd60ffb19612a7dc407c852424da37cd49cdc238351-d?f=webp&region=us" alt="Configuring wind settings" style="display: inline-block;">
6
- </a>
7
- <div style="line-height: normal; margin-top: -18em;">
8
- <a href="https://vimeo.com/488860317" target="_blank" style="
9
- display: inline-block;
10
- vertical-align: middle;
11
- background-color: #007BFF;
12
- color: white;
13
- padding: 10px 20px;
14
- border-radius: 4px;
15
- text-decoration: none;
16
- font-weight: bold;
17
- ">Watch the Video</a>
18
- </div>
19
-</div>
3
+<video controls="true" width="640" src="https://sapsailing-documentation.s3-eu-west-1.amazonaws.com/adminconsole/ConfiguringWindSettings.mp4" type="video/mp4">
4
+  Your browser does not support the video tag.
5
+</video>
wiki/howto/tutorials/sailinganalytics/copy-course-to-race.md
... ...
@@ -0,0 +1,5 @@
1
+This tutorial explains how to copy course to other race with **the SAP Sailing Race Manager Application** :
2
+
3
+ <video controls="true" width="640" src="https://sapsailing-documentation.s3-eu-west-1.amazonaws.com/adminconsole/CopyingCourseToOtherRaces.mp4" type="video/mp4">
4
+  Your browser does not support the video tag.
5
+</video>
... ...
\ No newline at end of file
wiki/howto/tutorials/sailinganalytics/course-areas.md
... ...
@@ -1,18 +1,5 @@
1 1
This tutorial shows how to work with *course areas* in **SAP Sailing Analytics** :
2 2
3
-<div style="text-align: center; line-height: 0;">
4
- <a href="https://vimeo.com/488486330" target="_blank">
5
- <img src="https://i.vimeocdn.com/video/1010908534-67799518b2812965dddb3595fdf0452eae2e61346ae69213748313856a6f3b96-d?f=webp&amp;region=us" alt="Working with course areas SAP Sailing Analytics" style="display: inline-block;">
6
- </a>
7
- <div style="line-height: normal; margin-top: -18em; margin-bottom: 16em">
8
- <a href="https://vimeo.com/488486330" target="_blank" style="
9
- display: inline-block;
10
- vertical-align: middle;
11
- background-color: #007BFF;
12
- color: white;
13
- padding: 10px 20px;
14
- border-radius: 4px;
15
- text-decoration: none;
16
- font-weight: bold;
17
- ">Watch the Video</a>
18
- </div>
... ...
\ No newline at end of file
0
+<video controls="true" width="640" src="https://sapsailing-documentation.s3-eu-west-1.amazonaws.com/adminconsole/WorkingWithCourseAreas_Part1.mp4" type="video/mp4">
1
+  Your browser does not support the video tag.
2
+</video>
... ...
\ No newline at end of file
wiki/howto/tutorials/sailinganalytics/create-regattas.md
... ...
@@ -1,18 +1,5 @@
1 1
This tutorial shows how to create **more complex regattas** :
2 2
3
-<div style="text-align: center; line-height: 0;">
4
- <a href="https://vimeo.com/634988326" target="_blank">
5
- <img src="https://i.vimeocdn.com/video/1277334038-e9c29f5bf85d54076b7aacb78786efdc39b079d048a97f71a?f=webp&region=us" alt="Creating more complex regattas (with multiple series)" style="display: inline-block;">
6
- </a>
7
- <div style="line-height: normal; margin-top: -18em; margin-bottom: 16em">
8
- <a href="https://vimeo.com/634988326" target="_blank" style="
9
- display: inline-block;
10
- vertical-align: middle;
11
- background-color: #007BFF;
12
- color: white;
13
- padding: 10px 20px;
14
- border-radius: 4px;
15
- text-decoration: none;
16
- font-weight: bold;
17
- ">Watch the Video</a>
18
- </div>
3
+<video controls="true" width="640" src="https://sapsailing-documentation.s3.eu-west-1.amazonaws.com/adminconsole/CreateMoreComplexRegattas.mp4" type="video/mp4">
4
+  Your browser does not support the video tag.
5
+</video>
... ...
\ No newline at end of file
wiki/howto/tutorials/sailinganalytics/data-mining-tool.md
... ...
@@ -1,18 +1,5 @@
1 1
This tutorial gives an introduction to **the SAP Sailing Analytics Data Mining Tool** :
2 2
3
-<div style="text-align: center; line-height: 0;">
4
- <a href="https://vimeo.com/517202211" target="_blank">
5
- <img src="https://i.vimeocdn.com/video/1070935518-8b0ba12a0b853e140c36fd9dd89b1fe4929bdab5d4e464017628f508588e650d-d?f=webp&region=us" alt="An Introduction to the SAP Sailing Analytics Data Mining Tool" style="display: inline-block;">
6
- </a>
7
- <div style="line-height: normal; margin-top: -18em; margin-bottom: 16em">
8
- <a href="https://vimeo.com/517202211" target="_blank" style="
9
- display: inline-block;
10
- vertical-align: middle;
11
- background-color: #007BFF;
12
- color: white;
13
- padding: 10px 20px;
14
- border-radius: 4px;
15
- text-decoration: none;
16
- font-weight: bold;
17
- ">Watch the Video</a>
18
- </div>
... ...
\ No newline at end of file
0
+ <video controls="true" width="640" src="https://sapsailing-documentation.s3.eu-west-1.amazonaws.com/adminconsole/DataMiningIntroduction.mp4" type="video/mp4">
1
+  Your browser does not support the video tag.
2
+</video>
... ...
\ No newline at end of file
wiki/howto/tutorials/sailinganalytics/default-creation-group.md
... ...
@@ -1,18 +1,5 @@
1 1
This episode shows how to set a default *creation group* in user details with **the SAP Sailing Analytics** :
2 2
3
-<div style="text-align: center; line-height: 0;">
4
- <a href="https://vimeo.com/488485747" target="_blank">
5
- <img src="https://i.vimeocdn.com/video/1010869111-2290caea7e6f6cac1a64b58af3423fc31700f24c2839a92c5e3dc7dd85c1e669-d?f=webp&region=us" alt="Setting a default creation group in user details with the SAP Sailing Analytics" style="display: inline-block;">
6
- </a>
7
- <div style="line-height: normal; margin-top: -18em; margin-bottom: 16em">
8
- <a href="https://vimeo.com/488485747" target="_blank" style="
9
- display: inline-block;
10
- vertical-align: middle;
11
- background-color: #007BFF;
12
- color: white;
13
- padding: 10px 20px;
14
- border-radius: 4px;
15
- text-decoration: none;
16
- font-weight: bold;
17
- ">Watch the Video</a>
18
- </div>
... ...
\ No newline at end of file
0
+ <video controls="true" width="640" src="https://sapsailing-documentation.s3-eu-west-1.amazonaws.com/adminconsole/SettingDefaultCreationGroupInUserDetails.mp4" type="video/mp4">
1
+  Your browser does not support the video tag.
2
+</video>
... ...
\ No newline at end of file
wiki/howto/tutorials/sailinganalytics/edit-results.md
... ...
@@ -1,18 +1,5 @@
1 1
This tutorial explains how to *edit* results with **the SAP Sailing Race Manager Application** :
2 2
3
-<div style="text-align: center; line-height: 0;">
4
- <a href="https://vimeo.com/488484797" target="_blank">
5
- <img src="https://i.vimeocdn.com/video/1010874720-12e724c4940d378318204f96d75247de239f0728a9bd5beb5415aad70df1d2ca-d?f=webp&region=us" alt="Editing results with the SAP Sailing Race Manager app" style="display: inline-block;">
6
- </a>
7
- <div style="line-height: normal; margin-top: -18em; margin-bottom: 16em">
8
- <a href="https://vimeo.com/488484797" target="_blank" style="
9
- display: inline-block;
10
- vertical-align: middle;
11
- background-color: #007BFF;
12
- color: white;
13
- padding: 10px 20px;
14
- border-radius: 4px;
15
- text-decoration: none;
16
- font-weight: bold;
17
- ">Watch the Video</a>
18
- </div>
... ...
\ No newline at end of file
0
+ <video controls="true" width="640" src="https://sapsailing-documentation.s3-eu-west-1.amazonaws.com/adminconsole/editingresultswiththeracemanagerapp.mp4" type="video/mp4">
1
+  Your browser does not support the video tag.
2
+</video>
... ...
\ No newline at end of file
wiki/howto/tutorials/sailinganalytics/import-gpx-track.md
... ...
@@ -1,18 +1,5 @@
1 1
This episode shows how to import a track from a *GPX file* :
2 2
3
-<div style="text-align: center; line-height: 0;">
4
- <a href="https://vimeo.com/517202462" target="_blank">
5
- <img src="https://i.vimeocdn.com/video/1070949814-e607721644fc5d9ed274d3fb23342634b75aefe69ef34b387095b86c8b4008ad-d?f=webp&region=us" alt="Import a track from a GPX file" style="display: inline-block;">
6
- </a>
7
- <div style="line-height: normal; margin-top: -18em; margin-bottom: 16em">
8
- <a href="https://vimeo.com/517202462" target="_blank" style="
9
- display: inline-block;
10
- vertical-align: middle;
11
- background-color: #007BFF;
12
- color: white;
13
- padding: 10px 20px;
14
- border-radius: 4px;
15
- text-decoration: none;
16
- font-weight: bold;
17
- ">Watch the Video</a>
18
- </div>
... ...
\ No newline at end of file
0
+ <video controls="true" width="640" src="https://sapsailing-documentation.s3.eu-west-1.amazonaws.com/adminconsole/ImportGPXTrack.mp4" type="video/mp4">
1
+  Your browser does not support the video tag.
2
+</video>
... ...
\ No newline at end of file
wiki/howto/tutorials/sailinganalytics/importing-results.md
... ...
@@ -1,18 +1,5 @@
1 1
The video explains how you can *import* results/scores from other regatta management systems for which **the SAP Sailing Analytics** have an integration in place, such as **YachtScoring** and **Manage2Sail**:
2 2
3
-<div style="text-align: center; line-height: 0;">
4
- <a href="https://vimeo.com/539278602" target="_blank">
5
- <img src="https://i.vimeocdn.com/video/1116473176-be00159b5f0aae985461bb734efb6adc52f27e5536d0b8f8960dde6192b6b66d-d?f=webp&amp;region=us" alt="Importing Results" style="display: inline-block;">
6
- </a>
7
- <div style="line-height: normal; margin-top: -18em; margin-bottom: 16em">
8
- <a href="https://vimeo.com/539278602" target="_blank" style="
9
- display: inline-block;
10
- vertical-align: middle;
11
- background-color: #007BFF;
12
- color: white;
13
- padding: 10px 20px;
14
- border-radius: 4px;
15
- text-decoration: none;
16
- font-weight: bold;
17
- ">Watch the Video</a>
18
- </div>
... ...
\ No newline at end of file
0
+ <video controls="true" width="640" src="https://sapsailing-documentation.s3.eu-west-1.amazonaws.com/adminconsole/VideoEditLeaderBoard.mp4" type="video/mp4">
1
+  Your browser does not support the video tag.
2
+</video>
... ...
\ No newline at end of file
wiki/howto/tutorials/sailinganalytics/into-to-backend.md
... ...
@@ -1,18 +1,5 @@
1 1
The guide **General Introduction to SAP Sailing Analytics Backend** will give you a look around the SAP Sailing Analytics *Admin Console* :
2 2
3
-<div style="text-align: center; line-height: 0;margin-bottom: 14em;">
4
- <a href="https://vimeo.com/488483856" target="_blank">
5
- <img src="https://i.vimeocdn.com/video/1010905634-646b489c3740b5d1c6d1e83fe88fbe50cd43be60a2073d209520983a4c4ce9c2-d?f=webp&region=us" alt="General Introduction to SAP Sailing Analytics Backend" style="display: inline-block;">
6
- </a>
7
- <div style="line-height: normal; margin-top: -18em; margin-bottom: 16em">
8
- <a href="https://vimeo.com/488483856" target="_blank" style="
9
- display: inline-block;
10
- vertical-align: middle;
11
- background-color: #007BFF;
12
- color: white;
13
- padding: 10px 20px;
14
- border-radius: 4px;
15
- text-decoration: none;
16
- font-weight: bold;
17
- ">Watch the Video</a>
18
- </div>
... ...
\ No newline at end of file
0
+<video controls="true" width="640" src="https://sapsailing-documentation.s3-eu-west-1.amazonaws.com/adminconsole/ALookAroundAdminConsole.mp4" type="video/mp4">
1
+ Your browser does not support the video tag.
2
+</video>
... ...
\ No newline at end of file
wiki/howto/tutorials/sailinganalytics/leaderboard-groups-explanation.md
... ...
@@ -1,18 +1,5 @@
1 1
This tutorial explains how *Leaderboard Groups* work in **the SAP Sailing Analytics** :
2 2
3
-<div style="text-align: center; line-height: 0;">
4
- <a href="https://vimeo.com/489321478" target="_blank">
5
- <img src="https://i.vimeocdn.com/video/1015856109-bb64efa3a2a4f304576789690646aaad0a7646c37c85c013ab43e016899e1aa1-d?f=webp&region=us" alt="Leaderboard groups explained in the SAP Sailing Analytics" style="display: inline-block;">
6
- </a>
7
- <div style="line-height: normal; margin-top: -18em; margin-bottom: 16em">
8
- <a href="https://vimeo.com/489321478" target="_blank" style="
9
- display: inline-block;
10
- vertical-align: middle;
11
- background-color: #007BFF;
12
- color: white;
13
- padding: 10px 20px;
14
- border-radius: 4px;
15
- text-decoration: none;
16
- font-weight: bold;
17
- ">Watch the Video</a>
18
- </div>
... ...
\ No newline at end of file
0
+<video controls="true" width="640" src="https://sapsailing-documentation.s3-eu-west-1.amazonaws.com/adminconsole/Advanced+Topics/Leaderboard+Group+explained.mp4" type="video/mp4">
1
+ Your browser does not support the video tag.
2
+</video>
... ...
\ No newline at end of file
wiki/howto/tutorials/sailinganalytics/make-group-public.md
... ...
@@ -1,18 +1,5 @@
1 1
This episode shows how to make everything your group owns *readable* for everyone in **the SAP Sailing Analytics** :
2 2
3
-<div style="text-align: center; line-height: 0;">
4
- <a href="https://vimeo.com/488485239" target="_blank">
5
- <img src="https://i.vimeocdn.com/video/1010886232-bf31fae06774e2abed01713779a86d50704dbf34ce2341c9bfe3751468c64d20-d?f=webp&amp;region=us" alt="Make everything your group owns readable for everyone in the SAP Sailing Analytics" style="display: inline-block;">
6
- </a>
7
- <div style="line-height: normal; margin-top: -18em; margin-bottom: 16em">
8
- <a href="https://vimeo.com/488485239" target="_blank" style="
9
- display: inline-block;
10
- vertical-align: middle;
11
- background-color: #007BFF;
12
- color: white;
13
- padding: 10px 20px;
14
- border-radius: 4px;
15
- text-decoration: none;
16
- font-weight: bold;
17
- ">Watch the Video</a>
18
- </div>
... ...
\ No newline at end of file
0
+ <video controls="true" width="640" src="https://sapsailing-documentation.s3.eu-west-1.amazonaws.com/adminconsole/MakingEverythingYourGroupOwnsReadableForEveryone.mp4" type="video/mp4">
1
+  Your browser does not support the video tag.
2
+</video>
... ...
\ No newline at end of file
wiki/howto/tutorials/sailinganalytics/publish-event.md
... ...
@@ -1,18 +1,5 @@
1 1
This episode shows how to *publish an event* through a dedicated user group in **the SAP Sailing Analytics** :
2 2
3
-<div style="text-align: center; line-height: 0;">
4
- <a href="https://vimeo.com/488485433" target="_blank">
5
- <img src="https://i.vimeocdn.com/video/1010883117-c1919ac1563d2d40fc5a6ff49f4ff66918ef1de005d0820a928d5478f0b98a64-d?f=webp&region=us" alt="Publish an event through a dedicated user group in the SAP Sailing Analytics" style="display: inline-block;">
6
- </a>
7
- <div style="line-height: normal; margin-top: -18em; margin-bottom: 16em">
8
- <a href="https://vimeo.com/488485433" target="_blank" style="
9
- display: inline-block;
10
- vertical-align: middle;
11
- background-color: #007BFF;
12
- color: white;
13
- padding: 10px 20px;
14
- border-radius: 4px;
15
- text-decoration: none;
16
- font-weight: bold;
17
- ">Watch the Video</a>
18
- </div>
... ...
\ No newline at end of file
0
+ <video controls="true" width="640" src="https://sapsailing-documentation.s3-eu-west-1.amazonaws.com/adminconsole/PublishingEventThroughDedicatedUserGroup.mp4" type="video/mp4">
1
+  Your browser does not support the video tag.
2
+</video>
... ...
\ No newline at end of file
wiki/howto/tutorials/sailinganalytics/race-fixes.md
... ...
@@ -1,18 +1,5 @@
1 1
This episode shows how to *fix* races when a device is assigned to multiple competitors **accidentally** :
2 2
3
-<div style="text-align: center; line-height: 0;">
4
- <a href="https://vimeo.com/560018855" target="_blank">
5
- <img src="https://i.vimeocdn.com/video/1157667852-8e3dae0f1479eee9c82e71f93a5b0a2b09b6065a406b11027d3fbff828fd03f5-d?f=webp&region=us" alt="Fixing races when a device is assigned to multiple competitors accidentally" style="display: inline-block;">
6
- </a>
7
- <div style="line-height: normal; margin-top: -18em; margin-bottom: 16em">
8
- <a href="https://vimeo.com/560018855" target="_blank" style="
9
- display: inline-block;
10
- vertical-align: middle;
11
- background-color: #007BFF;
12
- color: white;
13
- padding: 10px 20px;
14
- border-radius: 4px;
15
- text-decoration: none;
16
- font-weight: bold;
17
- ">Watch the Video</a>
18
- </div>
... ...
\ No newline at end of file
0
+ <video controls="true" width="640" src="https://sapsailing-documentation.s3.eu-west-1.amazonaws.com/adminconsole/FixRaces.mp4" type="video/mp4">
1
+  Your browser does not support the video tag.
2
+</video>
... ...
\ No newline at end of file
wiki/howto/tutorials/sailinganalytics/race-manager-connection.md
... ...
@@ -1,18 +1,5 @@
1 1
This tutorial shows how to connect **the Race Manager Application** to an *event* :
2 2
3
-<div style="text-align: center; line-height: 0;">
4
- <a href="https://vimeo.com/496362004" target="_blank">
5
- <img src="https://i.vimeocdn.com/video/1027112686-11119a6afa84a2196b43d0952cd504e43ab27ed67b256f138dac4481d9568cbf-d?f=webp&region=us" alt="How to connect the Race Manager App to an event" style="display: inline-block;">
6
- </a>
7
- <div style="line-height: normal; margin-top: -18em; margin-bottom: 16em">
8
- <a href="https://vimeo.com/496362004" target="_blank" style="
9
- display: inline-block;
10
- vertical-align: middle;
11
- background-color: #007BFF;
12
- color: white;
13
- padding: 10px 20px;
14
- border-radius: 4px;
15
- text-decoration: none;
16
- font-weight: bold;
17
- ">Watch the Video</a>
18
- </div>
... ...
\ No newline at end of file
0
+ <video controls="true" width="640" src="https://sapsailing-documentation.s3.eu-west-1.amazonaws.com/adminconsole/ConnectToEvent.mp4">
1
+  Your browser does not support the video tag.
2
+</video>
... ...
\ No newline at end of file
wiki/howto/tutorials/sailinganalytics/sailing-race-manager.md
... ...
@@ -1,18 +1,5 @@
1 1
This tutorial explains how to work with **the SAP Sailing Race Manager Application** :
2 2
3
-<div style="text-align: center; line-height: 0;">
4
- <a href="https://vimeo.com/488484868" target="_blank">
5
- <img src="https://i.vimeocdn.com/video/1010878230-c2c61789493de4080b910dbb08521af8ff8f7036aa66c9b1eabe51690a68e5a0-d?f=webp&region=us" alt="How to work with the SAP Sailing Race Manager app" style="display: inline-block;">
6
- </a>
7
- <div style="line-height: normal; margin-top: -18em; margin-bottom: 16em">
8
- <a href="https://vimeo.com/488484868" target="_blank" style="
9
- display: inline-block;
10
- vertical-align: middle;
11
- background-color: #007BFF;
12
- color: white;
13
- padding: 10px 20px;
14
- border-radius: 4px;
15
- text-decoration: none;
16
- font-weight: bold;
17
- ">Watch the Video</a>
18
- </div>
... ...
\ No newline at end of file
0
+ <video controls="true" width="640" src="https://sapsailing-documentation.s3-eu-west-1.amazonaws.com/adminconsole/howtogetgoingwiththeracemanagerapp.mp4" type="video/mp4">
1
+  Your browser does not support the video tag.
2
+</video>
... ...
\ No newline at end of file
wiki/howto/tutorials/sailinganalytics/security-system.md
... ...
@@ -1,18 +1,5 @@
1 1
This episode serves as an introduction to the *security system* of the **SAP Sailing Analytics** :
2 2
3
-<div style="text-align: center; line-height: 0;">
4
- <a href="https://vimeo.com/488484953" target="_blank">
5
- <img src="https://i.vimeocdn.com/video/1010855456-9066a0a6e5a3c8d8fb4a5804e04ece6688246ac7d34932ca3e68d4e51a70fffd-d?f=webp&region=us" alt="Introduction to the security system of the SAP Sailing Analytics" style="display: inline-block;">
6
- </a>
7
- <div style="line-height: normal; margin-top: -18em; margin-bottom: 16em">
8
- <a href="https://vimeo.com/488484953" target="_blank" style="
9
- display: inline-block;
10
- vertical-align: middle;
11
- background-color: #007BFF;
12
- color: white;
13
- padding: 10px 20px;
14
- border-radius: 4px;
15
- text-decoration: none;
16
- font-weight: bold;
17
- ">Watch the Video</a>
18
- </div>
... ...
\ No newline at end of file
0
+<video controls="true" width="640" src="https://sapsailing-documentation.s3-eu-west-1.amazonaws.com/adminconsole/IntroductionSecuritySystem.mp4" type="video/mp4">
1
+  Your browser does not support the video tag.
2
+</video>
... ...
\ No newline at end of file
wiki/howto/tutorials/sailinganalytics/set-mark-position.md
... ...
@@ -1,18 +1,5 @@
1 1
This episode shows you how to set *the postion of a mark* with **SAP Sailing Analytics** in case the tracker failed or you were not able to attach a *tracker* to the mark :
2 2
3
-<div style="text-align: center; line-height: 0;">
4
- <a href="https://vimeo.com/488486072" target="_blank">
5
- <img src="https://i.vimeocdn.com/video/1010865518-60ba1a02a2fadb08c1f15bc00e8ad6fc5549c45abc521ca268ba291157e95480-d?f=webp&region=us" alt="Set the position of a mark with the SAP Sailing Analytics" style="display: inline-block;">
6
- </a>
7
- <div style="line-height: normal; margin-top: -18em; margin-bottom: 16em">
8
- <a href="https://vimeo.com/488486072" target="_blank" style="
9
- display: inline-block;
10
- vertical-align: middle;
11
- background-color: #007BFF;
12
- color: white;
13
- padding: 10px 20px;
14
- border-radius: 4px;
15
- text-decoration: none;
16
- font-weight: bold;
17
- ">Watch the Video</a>
18
- </div>
... ...
\ No newline at end of file
0
+<video controls="true" width="640" src="https://sapsailing-documentation.s3-eu-west-1.amazonaws.com/adminconsole/SettingFirstPositionForAMarkOnTheMapCut.mp4" type="video/mp4">
1
+  Your browser does not support the video tag.
2
+</video>
... ...
\ No newline at end of file
wiki/howto/tutorials/sailinganalytics/set-matchrace-event.md
... ...
@@ -1,18 +1,5 @@
1 1
This episode shows you how to set up a *matchrace event* with **the SAP Sailing Analytics** :
2 2
3
-<div style="text-align: center; line-height: 0;">
4
- <a href="https://vimeo.com/489321685" target="_blank">
5
- <img src="https://i.vimeocdn.com/video/1015853549-810174185dc750b52c9b061ff92f1579424fa5f89eeab0d12ef53ac6b4bc2cc5-d?f=webp&region=us" alt="Setting up a matchrace event with the SAP Sailing Analytics" style="display: inline-block;">
6
- </a>
7
- <div style="line-height: normal; margin-top: -18em; margin-bottom: 16em">
8
- <a href="https://vimeo.com/489321685" target="_blank" style="
9
- display: inline-block;
10
- vertical-align: middle;
11
- background-color: #007BFF;
12
- color: white;
13
- padding: 10px 20px;
14
- border-radius: 4px;
15
- text-decoration: none;
16
- font-weight: bold;
17
- ">Watch the Video</a>
18
- </div>
... ...
\ No newline at end of file
0
+ <video controls="true" width="640" src="https://sapsailing-documentation.s3-eu-west-1.amazonaws.com/adminconsole/Advanced+Topics/MatchraceSetup.mp4" type="video/mp4">
1
+  Your browser does not support the video tag.
2
+</video>
... ...
\ No newline at end of file
wiki/howto/tutorials/sailinganalytics/set-up-course.md
... ...
@@ -0,0 +1,5 @@
1
+This tutorial explains how to set up course in *AdminConsole*:
2
+
3
+ <video controls="true" width="640" src="https://sapsailing-documentation.s3-eu-west-1.amazonaws.com/adminconsole/SettingUpCourseInAdminConsole.mp4" type="video/mp4">
4
+  Your browser does not support the video tag.
5
+</video>
... ...
\ No newline at end of file
wiki/howto/tutorials/sailinganalytics/set-up-regattas.md
... ...
@@ -1,18 +1,5 @@
1 1
This tutorial shows how to set up *multiple regattas* or *classes events* with the **SAP Sailing Analytics** :
2 2
3
-<div style="text-align: center; line-height: 0;">
4
- <a href="https://vimeo.com/489321331" target="_blank">
5
- <img src="https://i.vimeocdn.com/video/1015852481-18078496a5c92d5a7c894e3fa44678686e719e304baa3b0af31c976d1419e6d2-d?f=webp&amp;region=us" alt="Setting up multiple regattas or classes events with the SAP Sailing Analytics" style="display: inline-block;">
6
- </a>
7
- <div style="line-height: normal; margin-top: -18em; margin-bottom: 16em">
8
- <a href="https://vimeo.com/489321331" target="_blank" style="
9
- display: inline-block;
10
- vertical-align: middle;
11
- background-color: #007BFF;
12
- color: white;
13
- padding: 10px 20px;
14
- border-radius: 4px;
15
- text-decoration: none;
16
- font-weight: bold;
17
- ">Watch the Video</a>
18
- </div>
... ...
\ No newline at end of file
0
+ <video controls="true" width="640" src="https://sapsailing-documentation.s3-eu-west-1.amazonaws.com/adminconsole/Advanced+Topics/Setting+up+Events+with+multiple+Regattas+or+Classes.mp4" type="video/mp4">
1
+  Your browser does not support the video tag.
2
+</video>
... ...
\ No newline at end of file
wiki/howto/tutorials/sailinganalytics/sign-up.md
... ...
@@ -1,20 +1,6 @@
1 1
This tutorial will show you how to sign up for a user account at the **SAP Sailing Analytics** :
2 2
3
-
4
-<div style="text-align: center; line-height: 0;margin-bottom: 14em;">
5
- <a href="https://vimeo.com/488486248" target="_blank">
6
- <img src="https://i.vimeocdn.com/video/1010859699-4efcb1ff2d25befb03951c2ed835ca7e6dc07906902a6b80d832d1e8dcc6b74b-d?f=webp&region=us" alt="Signing up for a user account at the SAP Sailing Analytics" style="display: inline-block;">
7
- </a>
8
- <div style="line-height: normal; margin-top: -18em;margin-bottom: 16em ">
9
- <a href="https://vimeo.com/488486248" target="_blank" style="
10
- display: inline-block;
11
- vertical-align: middle;
12
- background-color: #007BFF;
13
- color: white;
14
- padding: 10px 20px;
15
- border-radius: 4px;
16
- text-decoration: none;
17
- font-weight: bold;
18
- ">Watch the Video</a>
19
- </div>
3
+<video controls="true" width="640" src="https://sapsailing-documentation.s3-eu-west-1.amazonaws.com/adminconsole/SigningUpForAUserAccount.mp4" type="video/mp4">
4
+  Your browser does not support the video tag.
5
+</video>
20 6
wiki/howto/tutorials/sailinganalytics/simple-event-creation.md
... ...
@@ -1,18 +1,5 @@
1 1
This tutorial shows you how to create an event with the SAP Analytics *Administration Console* on [my.sapsailing.com](https://my.sapsailing.com/gwt/Home.html) :
2 2
3
-<div style="text-align: center; line-height: 0;margin-bottom: 14em;">
4
- <a href="https://vimeo.com/488484401" target="_blank">
5
- <img src="https://i.vimeocdn.com/video/1010901946-6ef9df5238d5f517e69b301fc9eddb2069c59f1a41218f17539bfc5372fa685b-d?f=webp&region=us" alt="create an event with the SAP Analytics Administration Console" style="display: inline-block;">
6
- </a>
7
- <div style="line-height: normal; margin-top: -18em; margin-bottom: 16em">
8
- <a href="https://vimeo.com/488484401" target="_blank" style="
9
- display: inline-block;
10
- vertical-align: middle;
11
- background-color: #007BFF;
12
- color: white;
13
- padding: 10px 20px;
14
- border-radius: 4px;
15
- text-decoration: none;
16
- font-weight: bold;
17
- ">Watch the Video</a>
18
- </div>
... ...
\ No newline at end of file
0
+<video controls="true" width="640" src="https://sapsailing-documentation.s3-eu-west-1.amazonaws.com/adminconsole/CreatingYourFirstEvent.mp4" type="video/mp4">
1
+  Your browser does not support the video tag.
2
+</video>
... ...
\ No newline at end of file
wiki/howto/tutorials/sailinganalytics/split-leaderboard.md
... ...
@@ -1,18 +1,5 @@
1 1
This tutorial shows how to *split* a Leaderboard into divisions with the **SAP Sailing Analytics** :
2 2
3
-<div style="text-align: center; line-height: 0;">
4
- <a href="https://vimeo.com/555773483" target="_blank">
5
- <img src="https://i.vimeocdn.com/video/1148490178-0abd3500b3e9f273c259bf4e690dd3ff4fd59d24fc615c5f03db1659ee232a6a-d?f=webp&region=us" alt="Splitting a Leaderboard into divisions" style="display: inline-block;">
6
- </a>
7
- <div style="line-height: normal; margin-top: -18em; margin-bottom: 16em">
8
- <a href="https://vimeo.com/555773483" target="_blank" style="
9
- display: inline-block;
10
- vertical-align: middle;
11
- background-color: #007BFF;
12
- color: white;
13
- padding: 10px 20px;
14
- border-radius: 4px;
15
- text-decoration: none;
16
- font-weight: bold;
17
- ">Watch the Video</a>
18
- </div>
... ...
\ No newline at end of file
0
+ <video controls="true" width="640" src="https://sapsailing-documentation.s3.eu-west-1.amazonaws.com/adminconsole/SplitLeaderBoard.mp4" type="video/mp4">
1
+  Your browser does not support the video tag.
2
+</video>
... ...
\ No newline at end of file
wiki/howto/tutorials/sailinganalytics/tracking-race-player.md
... ...
@@ -1,19 +1,5 @@
1 1
The video below gives an excellent overview of the main functionality of the SAP Race Player which is accessible on a per-race basis via the "Races/Tracking" Tab of a regatta.
2 2
3
-<div style="text-align: center; line-height: 0;margin-bottom: 18em;">
4
- <a href="https://player.vimeo.com/video/786236240?h=5837f0d8b3" target="_blank">
5
- <img src="https://i.vimeocdn.com/video/1580518450-d061bcba753035fe130227e4b1c3ac735440041fabb61c3a14e289b9cd102dd5-d?mw=640&q=85" alt="tracking race player" style="display: inline-block;">
6
- </a>
7
- <div style="line-height: normal; margin-top: -12em; margin-bottom: 16em">
8
- <a href="https://player.vimeo.com/video/786236240?h=5837f0d8b3" target="_blank" style="
9
- display: inline-block;
10
- vertical-align: middle;
11
- background-color: #007BFF;
12
- color: white;
13
- padding: 10px 20px;
14
- border-radius: 4px;
15
- text-decoration: none;
16
- font-weight: bold;
17
- ">Watch the Video</a>
18
- </div>
19
-</div>
3
+<video controls="true" width="640" src="https://sapsailing-documentation.s3.eu-west-1.amazonaws.com/adminconsole/RaceBoardDemo.mp4" type="video/mp4">
4
+  Your browser does not support the video tag.
5
+</video>
... ...
\ No newline at end of file
wiki/howto/tutorials/sailinganalytics/video-tracking-management.md
... ...
@@ -1,18 +1,5 @@
1 1
This tutorial shows how to add and align *Youtube videos* to the **tracking timeline** :
2 2
3
-<div style="text-align: center; line-height: 0;">
4
- <a href="https://vimeo.com/634989080" target="_blank">
5
- <img src="https://i.vimeocdn.com/video/1277334324-ee19c413fefcee3578a6b2e395e45cd678e31de2d3299670b?f=webp&region=us" alt="Adding and aligning Youtube videos to the tracking timeline" style="display: inline-block;">
6
- </a>
7
- <div style="line-height: normal; margin-top: -18em; margin-bottom: 16em">
8
- <a href="https://vimeo.com/634989080" target="_blank" style="
9
- display: inline-block;
10
- vertical-align: middle;
11
- background-color: #007BFF;
12
- color: white;
13
- padding: 10px 20px;
14
- border-radius: 4px;
15
- text-decoration: none;
16
- font-weight: bold;
17
- ">Watch the Video</a>
18
- </div>
... ...
\ No newline at end of file
0
+ <video controls="true" width="640" src="https://sapsailing-documentation.s3.eu-west-1.amazonaws.com/adminconsole/AddingYouTubeVideos.mp4" type="video/mp4">
1
+  Your browser does not support the video tag.
2
+</video>
... ...
\ No newline at end of file
wiki/howto/tutorials/sailingracemanager/configure-wind-settings.md
... ...
@@ -1,19 +1,5 @@
1
-This episode shows you how to configure *wind settings* with **the SAP Sailing Race Manager App** :
1
+This tutorial shows how to configure **wind settings** :
2 2
3
-<div style="text-align: center; line-height: 0;margin-bottom: 14em;">
4
- <a href="thttps://vimeo.com/488860317" target="_blank">
5
- <img src="https://i.vimeocdn.com/video/1011532573-a72305c16fcb48b67ff30dd60ffb19612a7dc407c852424da37cd49cdc238351-d?f=webp&region=us" alt="How to configure wind settings" style="display: inline-block;">
6
- </a>
7
- <div style="line-height: normal; margin-top: -18em; margin-bottom: 16em;">
8
- <a href="hhttps://vimeo.com/488860317" target="_blank" style="
9
- display: inline-block;
10
- vertical-align: middle;
11
- background-color: #007BFF;
12
- color: white;
13
- padding: 10px 20px;
14
- border-radius: 4px;
15
- text-decoration: none;
16
- font-weight: bold;
17
- ">Watch the Video</a>
18
- </div>
19
-</div>
... ...
\ No newline at end of file
0
+<video controls="true" width="640" src="https://sapsailing-documentation.s3-eu-west-1.amazonaws.com/adminconsole/ConfiguringWindSettings.mp4" type="video/mp4">
1
+  Your browser does not support the video tag.
2
+</video>
... ...
\ No newline at end of file
wiki/howto/tutorials/sailingracemanager/connect-to-event.md
... ...
@@ -1,19 +1,5 @@
1 1
This episode shows you how to connect **the Race Manager App** to an *Event* :
2 2
3
-<div style="text-align: center; line-height: 0;margin-bottom: 14em;">
4
- <a href="https://vimeo.com/496362004" target="_blank">
5
- <img src="https://i.vimeocdn.com/video/1027112686-11119a6afa84a2196b43d0952cd504e43ab27ed67b256f138dac4481d9568cbf-d?f=webp&region=us" style="display: inline-block;">
6
- </a>
7
- <div style="line-height: normal; margin-top: -18em;margin-bottom: 16em;">
8
- <a href="https://vimeo.com/496362004" target="_blank" style="
9
- display: inline-block;
10
- vertical-align: middle;
11
- background-color: #007BFF;
12
- color: white;
13
- padding: 10px 20px;
14
- border-radius: 4px;
15
- text-decoration: none;
16
- font-weight: bold;
17
- ">Watch the Video</a>
18
- </div>
19
-</div>
... ...
\ No newline at end of file
0
+<video controls="true" width="640" src="https://sapsailing-documentation.s3-eu-west-1.amazonaws.com/adminconsole/Advanced+Topics/Leaderboard+Group+explained.mp4" type="video/mp4">
1
+  Your browser does not support the video tag.
2
+</video>
... ...
\ No newline at end of file
wiki/howto/tutorials/sailingracemanager/edit-results.md
... ...
@@ -1,19 +1,5 @@
1 1
This episode shows you how to edit results with **the SAP Sailing Race Manager App** :
2 2
3
-<div style="text-align: center; line-height: 0;margin-bottom: 14em;">
4
- <a href="https://vimeo.com/488484797" target="_blank">
5
- <img src="https://i.vimeocdn.com/video/1010874720-12e724c4940d378318204f96d75247de239f0728a9bd5beb5415aad70df1d2ca-d?f=webp&region=us" alt="Editing results with the SAP Sailing Race Manager App" style="display: inline-block;">
6
- </a>
7
- <div style="line-height: normal; margin-top: -18em;margin-bottom: 16em;">
8
- <a href="https://vimeo.com/488484797" target="_blank" style="
9
- display: inline-block;
10
- vertical-align: middle;
11
- background-color: #007BFF;
12
- color: white;
13
- padding: 10px 20px;
14
- border-radius: 4px;
15
- text-decoration: none;
16
- font-weight: bold;
17
- ">Watch the Video</a>
18
- </div>
19
-</div>
... ...
\ No newline at end of file
0
+ <video controls="true" width="640" src="https://sapsailing-documentation.s3-eu-west-1.amazonaws.com/adminconsole/editingresultswiththeracemanagerapp.mp4" type="video/mp4">
1
+  Your browser does not support the video tag.
2
+</video>
... ...
\ No newline at end of file
wiki/howto/tutorials/sailingracemanager/sailing-race-guide.md
... ...
@@ -1,19 +1,5 @@
1 1
This episode shows you how to work with **the SAP Sailing Race Manager App** :
2 2
3
-<div style="text-align: center; line-height: 0;margin-bottom: 14em;">
4
- <a href="https://vimeo.com/488484868" target="_blank">
5
- <img src="https://i.vimeocdn.com/video/1010878230-c2c61789493de4080b910dbb08521af8ff8f7036aa66c9b1eabe51690a68e5a0-d?f=webp&region=us" alt="How to work with the SAP Sailing Race Manager App" style="display: inline-block;">
6
- </a>
7
- <div style="line-height: normal; margin-top: -18em;margin-bottom: 16em;">
8
- <a href="https://vimeo.com/488484868" target="_blank" style="
9
- display: inline-block;
10
- vertical-align: middle;
11
- background-color: #007BFF;
12
- color: white;
13
- padding: 10px 20px;
14
- border-radius: 4px;
15
- text-decoration: none;
16
- font-weight: bold;
17
- ">Watch the Video</a>
18
- </div>
19
-</div>
... ...
\ No newline at end of file
0
+<video controls="true" width="640" src="https://sapsailing-documentation.s3.eu-west-1.amazonaws.com/adminconsole/WorkWithSailingManagerApp.mp4" type="video/mp4">
1
+  Your browser does not support the video tag.
2
+</video>
... ...
\ No newline at end of file
wiki/howto/tutorials/sailinsight/boat-team-creation.md
... ...
@@ -2,23 +2,13 @@ This guide will show you how to create your *first boat* or manage multiple boat
2 2
3 3
**Sail Insight** allows you to sail with multiple boats or teams. When you first set up **Sail Insight**, we ask you to set up your first boat, but you can edit it or create more at any time.
4 4
5
-<div style="text-align: center; line-height: 0;margin-bottom: 13em;">
6
- <a href="https://www.youtube.com/watch?v=rbdmoTf7w64$t=1s" target="_blank">
7
- <img src="https://img.youtube.com/vi/rbdmoTf7w64/0.jpg" alt="Boat and team creation in Sail Insight" style="display: inline-block;">
8
- </a>
9
- <div style="line-height: normal; margin-top: -14em;">
10
- <a href="https://www.youtube.com/watch?v=rbdmoTf7w64$t=1s" target="_blank" style="
11
- display: inline-block;
12
- vertical-align: middle;
13
- background-color: #007BFF;
14
- color: white;
15
- padding: 10px 20px;
16
- border-radius: 4px;
17
- text-decoration: none;
18
- font-weight: bold;
19
- ">Watch the Video</a>
20
- </div>
21
-</div>
5
+<video
6
+ controls="true"
7
+ src="https://sapsailing-documentation.s3.eu-west-1.amazonaws.com/SailInsight/Boats+and+Team+Creation+%5BrbdmoTf7w64%5D.webm"
8
+ type="video/webm"
9
+ style="display:block;margin:1rem auto;max-width:100%;width:340px;height:auto;">
10
+ Your browser does not support the video tag.
11
+</video>
22 12
23 13
## Edit a boat
24 14
To edit a previsouly created boat, click the *"Account"* tab near the bottom right of the screen. Then click the *"My Boats"* menu item. Pick the boat that you previously created and edit the necesarry fields. Scroll down to press *"Save"*.
wiki/howto/tutorials/sailinsight/create-events.md
... ...
@@ -1,22 +1,12 @@
1 1
This guide will show you how to create an event with **Sail Insight**.
2 2
3
-<div style="text-align: center; line-height: 0;margin-bottom: 14em;">
4
- <a href="https://www.youtube.com/watch?v=f_sguquMRsQ" target="_blank">
5
- <img src="https://img.youtube.com/vi/f_sguquMRsQ/0.jpg" alt="How-to: Create an event with Sail Insight" style="display: inline-block;">
6
- </a>
7
- <div style="line-height: normal; margin-top: -14em;">
8
- <a href="https://www.youtube.com/watch?v=f_sguquMRsQ" target="_blank" style="
9
- display: inline-block;
10
- vertical-align: middle;
11
- background-color: #007BFF;
12
- color: white;
13
- padding: 10px 20px;
14
- border-radius: 4px;
15
- text-decoration: none;
16
- font-weight: bold;
17
- ">Watch the Video</a>
18
- </div>
19
-</div>
3
+<video
4
+ controls="true"
5
+ src="https://sapsailing-documentation.s3.eu-west-1.amazonaws.com/SailInsight/Create+an+Event+%5Bf_sguquMRsQ%5D.mp4"
6
+ type="video/mp4"
7
+ style="display:block;margin:1rem auto;max-width:100%;width:340px;height:auto;">
8
+ Your browser does not support the video tag.
9
+</video>
20 10
21 11
There are two ways of *placing marks*:
22 12
- Pair a mark with a real-world tracking device (another mobile phone)
wiki/howto/tutorials/sailinsight/getting-started.md
... ...
@@ -5,23 +5,13 @@ You can use **Sail Insight** without an account, if you have been invited to par
5 5
6 6
If you want to track your own trips or set up an event to sail with or against other people you will need to sign up for an account.
7 7
8
-<div style="text-align: center; line-height: 0;margin-bottom: 13em;">
9
- <a href="https://www.youtube.com/watch?v=ZnRooWkRvas" target="_blank">
10
- <img src="https://img.youtube.com/vi/ZnRooWkRvas/0.jpg" alt="Getting started with Sail Insight" style="display: inline-block;">
11
- </a>
12
- <div style="line-height: normal; margin-top: -14em;">
13
- <a href="https://www.youtube.com/watch?v=ZnRooWkRvas" target="_blank" style="
14
- display: inline-block;
15
- vertical-align: middle;
16
- background-color: #007BFF;
17
- color: white;
18
- padding: 10px 20px;
19
- border-radius: 4px;
20
- text-decoration: none;
21
- font-weight: bold;
22
- ">Watch the Video</a>
23
- </div>
24
-</div>
8
+<video
9
+ controls="true"
10
+ src="https://sapsailing-documentation.s3.eu-west-1.amazonaws.com/SailInsight/Getting+started+with+Sail+Insight.+%5BZnRooWkRvas%5D.webm"
11
+ type="video/webm"
12
+ style="display:block;margin:1rem auto;max-width:100%;width:340px;height:auto;">
13
+ Your browser does not support the video tag.
14
+</video>
25 15
26 16
## How-to create an account
27 17
After opening **Sail Insight** create a new account by clicking on *"Register"*.
wiki/howto/tutorials/sailinsight/manage-marks.md
... ...
@@ -1,22 +1,13 @@
1 1
This episode shows you how to manage *marks* or pair them in **Sail Insight** :
2 2
3
-<div style="text-align: center; line-height: 0;margin-bottom: 14em;">
4
- <a href="https://www.youtube.com/watch?v=f3fjGuP-SbU" target="_blank">
5
- <img src="https://img.youtube.com/vi/f3fjGuP-SbU/0.jpg" alt="manage marks or pair them" style="display: inline-block;">
6
- </a>
7
- <div style="line-height: normal; margin-top: -14em;">
8
- <a href="https://www.youtube.com/watch?v=f3fjGuP-SbU" target="_blank" style="
9
- display: inline-block;
10
- vertical-align: middle;
11
- background-color: #007BFF;
12
- color: white;
13
- padding: 10px 20px;
14
- border-radius: 4px;
15
- text-decoration: none;
16
- font-weight: bold;
17
- ">Watch the Video</a>
18
- </div>
19
-</div>
3
+<video
4
+ controls="true"
5
+ src="https://sapsailing-documentation.s3.eu-west-1.amazonaws.com/SailInsight/Managing+Marks+%5Bf3fjGuP-SbU%5D.webm"
6
+ type="video/webm"
7
+ style="display:block;margin:1rem auto;max-width:100%;width:340px;height:auto;">
8
+ Your browser does not support the video tag.
9
+</video>
10
+
20 11
There are **two ways** of placing marks:
21 12
- Pair a mark with a real-world tracking device (another mobile phone)
22 13
- Give it a fixed location by providing latitude and longitude.
wiki/info/landscape/amazon-ec2.md
... ...
@@ -153,7 +153,7 @@ Any geo-blocking Web ACL that shall automatically be associated with ALBs that a
153 153
```
154 154
Note that in order to run this command you have to have valid credentials for the AWS region you're targeting with the request. Also consider using the ``--region`` argument if you're trying to tag a Web ACL in a region other than your AWS CLI's default region. Check your ``~/.aws/config`` file. Also see ``configuration/environments_scripts/repo/usr/local/bin/awsmfalogon.sh`` for logging on to the AWS CLI.
155 155
156
-### MongoDB Replica Setsn
156
+### MongoDB Replica Sets
157 157
158 158
There are currently three MongoDB replica sets:
159 159
wiki/info/landscape/aws-automation.md
... ...
@@ -328,7 +328,7 @@ The user can assign values to that variable that are then used as default propos
328 328
7. Configure target group health check
329 329
8. Register instance within target group
330 330
9. Create new rule within https listener that points to the correct target group
331
-10. Append „Use Event-SSL [domain] [eventId] 127.0.0.1 8888“ or „Use Home-SSL [domain] 127.0.0.1 8888“ to etc/httpd/conf.d/001-events.conf
331
+10. Append „Use Event-SSL \[domain\] \[eventId\] 127.0.0.1 8888“ or „Use Home-SSL \[domain\] 127.0.0.1 8888“ to etc/httpd/conf.d/001-events.conf
332 332
333 333
#### SAP instance on a shared EC2 instance
334 334
... ...
@@ -349,7 +349,7 @@ The user can assign values to that variable that are then used as default propos
349 349
15. Create target group with name „S-hared-instanceshortname“
350 350
16. Configuration of the target group health check with the server port of the sap instance
351 351
17. Create new rule within https listener that points to the correct target group
352
-18. Append „Use Event-SSL [domain] [eventId] 127.0.0.1 8888“ or „Use Home-SSL [domain] 127.0.0.1 8888“ to etc/httpd/conf.d/001-events.conf
352
+18. Append „Use Event-SSL \[domain\] \[eventId\] 127.0.0.1 8888“ or „Use Home-SSL \[domain\] 127.0.0.1 8888“ to etc/httpd/conf.d/001-events.conf
353 353
19. Check apache configuration with "apachectl configtest" and reload with "sudo service httpd reload“
354 354
355 355
#### SAP instance on a dedicated EC2 instance as a master
wiki/info/landscape/development-environment.md
... ...
@@ -2,33 +2,41 @@
2 2
3 3
[[_TOC_]]
4 4
5
-## Git and Our Branches
6
-Our main Git repository lives at ssh://<user>@sapsailing.com/home/trac/git. For those working in the SAP corporate network and therefore unable to access the external sapsailing.com server using SSH, the repository contents are replicated on an hourly basis into ssh://dxxxxxx@git.wdf.sap.corp:29418/SAPSail/sapsailingcapture.git where dxxxxxx obviously is to be replaced by your D- or I- or C-user. You need to have an account at https://git.wdf.sap.corp:8080/ to be able to access this Git repository.
5
+Here, we describe the process for doing simple standard development for the Sailing Analytics project, with a focus on how we handle Git w.r.t. branches, Bugzilla, Hudson-CI, and Github Actions. Other development scenarios you'll find described in more depth [[here|wiki/info/landscape/typical-development-scenarios]].
7 6
8
-Small, obvious and non-disruptive developments are usually carried out immediately on our master branch. This branch is configured such that Maven can be used to run the tests inside the SAP corporate network. The master branch is never deployed onto the sapsailing.com server and hence has no corresponding /home/trac/servers/ subdirectory.
7
+## Git, Bugzilla, and Our Branches
8
+Our main Git repository lives at github.com/SAP/sailing-analytics. Its ``main`` branch is mirrored to ssh://trac@sapsailing.com/home/trac/git periodically.
9 9
10
-If a change looks reasonably good on the master branch and related JUnit tests or manual UI tests have succeeded locally, it is permissible to merge the master branch into the dev branch and run a central build on sapsailing.com. The dev branch is configured to run the Maven tests with direct Internet access. It can therefore also be used to run the tests locally if connected to the public Internet.
10
+Small, minor, obvious and non-disruptive developments are usually carried out immediately on our ``main`` branch.
11 11
12
-Ideally, the build should be run including the test cases. If the tests succeed , the branch can be installed and the corresponding server instance can be restarted. The branch can then also be promoted to the next level (dev-->test-->prod1/prod2). Note, that currently re-starting a server instance may require re-loading the races that were previously loaded, particularly for the prod1 and prod2 instances, because several externally-announced URLs point to them.
12
+Everything else should follow the pattern
13
+- create Bugzilla issue (e.g., issue #12345)
14
+- create branch for Bugzilla issue ``bug12345``, typically branching from latest ``main`` tip
15
+- create Hudson job for branch using ``configuration/createHudsonJobForBug.sh 12345``
16
+- make your changes on your branch and commit and push regularly
17
+- pushing triggers the [release workflow](https://github.com/SAP/sailing-analytics/actions/workflows/release.yml) which runs a build with tests
18
+- when the workflow has finished, it triggers your Hudson job which collects the [build and test results](https://hudson.sapsailing.com/job/bug12345)
19
+- be verbose and document your changes, progress, hold-ups and problems on your Bugzilla issue
20
+- when build is "green," suggest your branch for review; so far we do this informally by assigning the Bugzilla issue to the reviewer and in a comment asking for review; in the future, we may want to use Github Pull Requests for this
21
+- after your branch has been merged into ``main``, disable your Hudson build job for your branch
22
+- the ``main`` branch will then build a new release that you can roll out into the production landscape
23
+- in case of changes to i18n-related message properties files, merge ``main`` into ``translation`` which triggers the translation process; the completed translations will arrive as pushes to the ``translations`` branch, triggering another ``release`` workflow, and---if successful---an automated merge into ``main`` with the corresponding build/release process happens, based on the [translation Hudson job](https://hudson.sapsailing.com/job/translation/configure)'s special logic
24
+- a successful ``main`` build (still on Java 8) will lead to an automatic merge into one or more branches for newer Java releases (such as ``docker-24``) with the corresponding build/release process
13 25
14
-We typically promote the changes to the next branch by also merging the current master branch into the next-"higher" branch. This should lead to equivalent results compared to merging the "previous" branch (e.g., dev) into the "next" branch (e.g., test). The branches differ largely in the configurations used for the servers, particularly the port assignments for the Jetty web server, the UDP ports used for listening for Expedition wind messages, and the queue names used for the replication based on RabbitMQ (see also Scale-Out through Replication).
26
+Be eager to equip your features and functions with tests. There should be enough examples to learn from. For UI testing, use Selenium (see the ``java/com.sap.sailing.selenium.test`` project).
15 27
16
-## Eclipse Setup, Required Plug-Ins
17
-We use Eclipse as our development environment. Using a different IDE would make it hard to re-use the project configurations (.project and .classpath files which are specific to Eclipse) as well as the GWT plug-in which assists in locally compiling and refactoring the GWT code, in particular the RPC code.
18
-
19
-The recommended and tested Eclipse version is currently Indigo (3.7). Colleagues have reported that they succeeded with a Juno (3.8/4.x) set-up as well.
20
-
21
-To get started, install Eclipse with at least PDE, Git, GWT (use http://dl.google.com/eclipse/plugin/3.7 as update site) and JSP editing support enabled. Eclipse Maven support is not recommended as in many cases it has caused trouble with the local Eclipse build.
22
-
23
-## Target Platform
24
-After the Eclipse installation and importing all projects under java/ from Git, it is required to set the target platform in Eclipse (Window --> Preferences --> Plugin Development --> Target Platform). The project com.sap.sailing.targetplatform contains "Race Analysis Target (IDE)" as target platform definition. It uses a number of p2 update sites and defines the OSGi bundles that constitute the target platform for the application. If this is not set in Eclipse, the local build environment assumes that the developer wants to implement Eclipse plug-ins and offers the entire set of Eclipse plug-ins and only those as the target platform which doesn't make any sense for our application.
28
+### Exceptionally Building Without Running Tests, More/Fewer CPUs, and With Release
29
+Ideally, the build should be run including the test cases. However, for exceptional cases you can trigger a build using the ``release`` workflow in Github Actions manually and can choose to ignore tests, change the number of CPUs to use for the build, and run the build with an OSGi target platform built according to the specifications of the branch you're building from.
25 30
26
-Major parts of our target platform are hosted on sapsailing.com as a p2 repository which makes it possible to have only one central target platform configuration used by everyone. The target platform can be re-built, e.g., after adding another bundle to it, using the script in com.sap.sailing.targetplatform/scripts.
31
+Furthermore, if you push your branch, say ``bug12345`` to ``releases/bug12345`` then the Github Actions build triggered by the push will also build and publish a release (currently published on [https://releases.sapsailing.com](https://releases.sapsailing.com)) named after your branch. You can use such as release, e.g., to deploy it to a staging server such as [https://dev.sapsailing.com](https://dev.sapsailing.com).
27 32
28
-It then needs to be installed again (by using a tool like scp for instance) to the /home/trac/p2-repositories directory from where it is exposed as http://p2.sapsailing.com/p2/sailing/ by the Apache web server. After such a change, all developers need to reload the target platform into their Eclipse environment.
33
+## Eclipse Setup, Required Plug-Ins
34
+The Eclipse setup is explained in our [[Onboarding|wiki/howto/onboarding]] description.
29 35
30 36
## Maven Build and Tests
31
-We use Maven to build our software and run our JUnit tests. The global setting.xml file to install in everyone's ~/.m2 directory is checked into the top-level Git folder. The checked-in copy assumes the developer is using Maven inside the SAP corporate network. If not, uncomment the <proxy> tag in the settings.xml file. See also section Git and Our Branches for details on which branch is configured to work in which network setup.
37
+We recommend using a local Maven-based build only if you try to understand or reproduce issues with the Github Actions build. In most other cases you should be fine using Eclipse with its local build/run/debug cycle.
38
+
39
+If you still feel you want to run a local Maven build, make sure again (see also [[Onboarding|wiki/howto/onboarding]]) to get your ``maven-settings.xml`` and ``toolchains.xml`` files in ``~/.m2`` in good shape first.
32 40
33 41
We have a top-level Maven pom.xml configuration file in the root folder of our Git workspace. It delegates to the pom.xml file in the java/ folder where all the bundle projects are defined. We rely on the Tycho Maven plug-in to build our OSGi bundles, also known as the "manifest-first approach." The key idea is to mainly develop using Eclipse means, including its OSGi manifest editing capabilities, and keep the Maven infrastructure as simple as possible, deriving component dependencies from the OSGi manifests. See the various pom.xml files in the projects to see the project-specific settings. By and large, a pom.xml file for a bundle needs to have the bundle name and version defined (we currently have most bundles at version 1.0.0.qualifier in the manifest or 1.0.0.SNAPSHOT in Maven), and whether the bundle is a test or non-test bundle, expressed as the packaging type which here can be one of eclipse-plugin or ecplise-test-plugin.
34 42
... ...
@@ -52,18 +60,9 @@ When building on sapsailing.com you should stick with the buildAndUpdateProduct.
52 60
53 61
All these build lines also creates a log file with all error messages, just in case the screen buffer is not sufficient to hold all scrolling error messages.
54 62
55
-## Automated Builds using Hudson
56
-
57
-The project uses a Hudson build server installation that can be reached at [hudson.sapsailing.com](http://hudson.sapsailing.com). Please ask a project administrator for an account. This Hudson server builds all new commits pushed to the master branch, performs the JUnit tests and publishes the JUnit test results. New jobs for other branches can easily be created by copying from the SAPSailingAnalytics-master job and updating the git branch to be checked out for build. This way, you can create your own job for your own branch. Don't forget to set yourself as the e-mail recipient for failing builds.
58
-
59
-As a special feature, release builds can automatically be performed and published to [releases.sapsailing.com](http:///releases.sapsailing.com) by pushing the tag named "release" to the version that you want to release. This can be done using the following series of git commands:
63
+When you're done with your local Maven build, finally run "mvn clean" to clean up the artifacts produced by the Maven build. Without this, remnants and outputs from the Maven build may collide with the local Eclipse build, such as JAR files that ended up in projects' ``bin/`` folders.
60 64
61
- git tag -f release
62
- git push origin release:release
63
-
64
-You can follow the build triggered by this [here](http://hudson.sapsailing.com/job/SAPSailingAnalytics-release/).
65
-
66
-### Plotting test results with the Measurement Plugin
65
+## Plotting test results with the Measurement Plugin
67 66
68 67
By default the duration of each test is published and can be viewed in comparison with older builds. It is possible to publish other values using the Measurement Plugin, which reads them out of a `MeasurementXMLFile`.
69 68
... ...
@@ -72,19 +71,3 @@ MeasurementXMLFile performanceReport = new MeasurementXMLFile("TEST-" + getClass
72 71
MeasurementCase performanceReportCase = performanceReport.addCase(getClass().getSimpleName());
73 72
performanceReportCase.addMeasurement(new Measurement("MeasurementName", measurementValue));
74 73
```
75
-
76
-## Product, Features and Target Platform
77
-The result of the build process is a p2 repository with a product consisting of a number of features. The product configuration is provided by the file raceanalysis.product in the com.sap.sailing.feature.p2build project. In its dependencies it defines the features of which it is built, which currently are com.sap.sailing.feature and com.sap.sailing.feature.runtime, each described in an equal-named bundle. The feature specified by com.sap.sailing.feature lists the bundles we develop ourselves as part of the project. The com.sap.sailing.feature.runtime feature lists those 3rd-party bundles from the target platform which are required by the product.
78
-
79
-The [target platform](#development-environment_target-platform) is defined in the various flavors for local and central environments in com.sap.sailing.targetplatform/definitions/*.target. It mainly uses Eclipse p2 repositories and our own p2 repository at http://p2.sapsailing.com/p2/sailing/ where we store those bundles required by our runtime which cannot be found as OSGi bundles in any other public p2 repository of which we are aware.
80
-
81
-This p2 repository at sapsailing.com can be re-built and correspondingly extended by the process explained [here](wiki/typical-development-scenarios#Adding-a-Bundle-to-the-Target-Platform).
82
-
83
-## External Libraries
84
-
85
-### Highcharts and jQuery
86
-We use the Highcharts library to present graphs to the user. These graphs are used on the RaceBoardPanel and (at the time of writing still under development) the PolarSheetsPanel. In the past, there were difficulties concerning the versions of the three interacting libraries:
87
-
88
-* The GWT Highcharts Wrapper – The source code can be found in our project and it’s slightly modified to match our scenario
89
-* The actual Highcharts Library
90
-* The jQuery Library
... ...
\ No newline at end of file
wiki/info/landscape/operating-system-upgrade.md
... ...
@@ -0,0 +1,131 @@
1
+# Operating System Upgrade Across Landscape
2
+
3
+[[_TOC_]]
4
+
5
+Mainly for security reasons we strive to keep the operating systems on which our EC2 instances are running up to date. This includes running the latest Linux kernels and having all packages updated to their latest versions as per the Amazon Linux or other Linux versions used. While doing so, we aim to keep service interruptions to a minimum and in particular keep services available at least in read-only mode also during upgrades.
6
+
7
+We distinguish between in-place upgrades without the need to re-boot, in-place upgrade requiring a reboot (e.g., due to Linux kernel updates), and upgrades that replace EC2 instances by new EC2 instances. The latter case can be sub-divided into cases where an incremental image upgrade can be used to produce a new version of an Amazon Machine Image (AMI) used for that instance type, and cases where a new from-scratch AMI set-up will be required. Also, the procedures to use depend on the type of service run on the instance that requires an upgrade.
8
+
9
+## Approaches for Operating System Updates
10
+
11
+### Using AdminConsole Landscape Management Panel
12
+
13
+The AdminConsole offers the Landscape Management panel (see, e.g., [https://security-service.sapsailing.com/gwt/AdminConsole.html#LandscapeManagementPlace:](https://security-service.sapsailing.com/gwt/AdminConsole.html#LandscapeManagementPlace:)) with a table entitled "Amazon Machine Images (AMIs)." It shows the different AMIs in use, among them the ``sailing-analytics-server``, the ``mongodb-server`` and the ``disposable-reverse-proxy`` images. Each of them have an "Upgrade" action icon in the "Actions" column that can be used to launch an instance off the image and then apply the steps necessary to upgrade the image to the latest version of kernel, all packages, and Java VM (if installed), then creates a new version of the AMI.
14
+
15
+See below for how to proceed with the upgraded images for the different image types.
16
+
17
+### Log On with SSH and Use Package Manager for Live Upgrade
18
+
19
+Instead of or in addition to upgrading the AMIs to new package and kernel versions, you can also log in to a running instance using SSH, and as root (using, e.g., ``sudo``) upgrade packages and kernel in place. Should a reboot be required, however, it depends on the particular instance you have been applying this to. Some instances should not simply be rebooted as this may unnecessarily reduce availability of some services and may not always lead to a clean recovery of all services after the reboot.
20
+
21
+For example, when rebooting an instance that runs one or more primary application processes for which replica processes run on other instances, inconsistencies between primary and replicas may result by a brute-force restart of the primary in some cases. See below for cleaner ways to do this.
22
+
23
+#### Amazon Linux
24
+
25
+We use Amazon Linux as the default for most instance types and hence most AMIs, particularly those for running the Sailing Analytics application, the MongoDB instances, the reverse proxy instances, and MariaDB for our Bugzilla service.
26
+
27
+Amazon Linux 2023 uses ``dnf`` as its package manager. An operating system upgrade is performed by running, as ``root`` (e.g., by logging in as ``ec2-user`` and then using ``sudo``):
28
+```
29
+ dnf --releasever=latest upgrade
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 -r`` command, delivering an exit status of ``1`` if a reboot is required.
32
+
33
+#### Debian
34
+
35
+Our use of Debian is currently restricted to running RabbitMQ which is a lot harder to install and configure on Amazon Linux.
36
+
37
+Debian uses ``apt`` as its package manager. Its default login user differs from Amazon Linux, where it is ``ec2-user`` and is called ``admin`` instead. Like the ``ec2-user`` on Amazon Linux, ``admin`` is eligible to use ``sudo`` to run commands with root privileges.
38
+
39
+Executing an update with apt works like this:
40
+```
41
+ apt-get update
42
+ apt-get upgrade
43
+```
44
+If this creates a file ``/var/run/reboot-required`` then the instance must be rebooted for all changes to take effect.
45
+
46
+## Upgrading the Different Instance Types
47
+
48
+### ``security-service.sapsailing.com`` Primary
49
+
50
+The corresponding ``security_service`` replica set usually has a single instance running only the primary application service. It offers a few ``Replicable``s that all other replica sets (except ``DEV``) replicate, such as the ``SecurityService`` and ``SharedSailingData`` services. It acts as a hub in particular for user, group, role and permission management. Other instances have their replicated versions of the service and can make decisions locally, sign in/up and authenticate users and manage their sessions locally. Replication through the ``security_service`` replica set serves the purpose of letting users roam about the landscape. Temporary outages of the ``security_service`` replica set will delay replication of these aspects across the landscape. However, transactions will not be lost but will be queued and applied when the service becomes available again.
51
+
52
+With this in mind, a restart of either the Java VM (in order to upgrade the application to a new version) or even a reboot of the EC2 instance, both typically done in less than 60s, will rarely cause effects noticeable to users. Therefore, we typically afford to upgrade the instance running the single primary process for the ``security_service`` replica set "in place:"
53
+
54
+- log on with ssh as ``ec2-user``
55
+- run ``dnf --releasever=latest upgrade``
56
+- if a reboot is required, reboot the instance
57
+
58
+It is useful to wait with the reboot until at least no known Sailing Analytics process start-up is happening which is in the middle of obtaining an initial load from the ``security_service`` replica set because this would be aborted and hence fail upon the reboot. Other than that, existing replicas will synchronize with the rebooted instance and the freshly started service once available again.
59
+
60
+Should you find good reasons against an in-place upgrade, make sure you have an upgraded ``sailing-analytics-server`` AMI, remove the running instance from the ``S-security-service`` and ``S-security-service-m`` target groups, launch a new instance off the upgraded AMI with the user data copied from the running instance, with only the ``INSTALL_FROM_RELEASE`` parameter upgrade to the latest release:
61
+```
62
+INSTALL_FROM_RELEASE=main-202502181141
63
+SERVER_NAME=security_service
64
+USE_ENVIRONMENT=security-service-master
65
+```
66
+
67
+Then add the new instance to the ``S-security-service`` and ``S-security-service-m`` target groups and terminate the old instance.
68
+
69
+### ``DEV``
70
+
71
+The ``DEV`` replica set is for testing only. Other than that, the instance runs our Hudson CI environment. Both are not expected to be highly available. Therefore, the same in-place update as for the ``security_service`` replica set is possible. For a clean Hudson shut-down, consider using [this link](https://hudson.sapsailing.com/quietDown).
72
+
73
+### ``ARCHIVE``
74
+
75
+Make sure you have an up-to-date ``sailing-analytics-server`` AMI. Then, see [[Upgrading ARCHIVE server|wiki/info/landscape/archive-server-upgrade]] for how to launch a new ARCHIVE candidate with that new AMI and how to switch to it once the loading of all races has finished successfully.
76
+
77
+### ``my``
78
+
79
+You can try an [in-place upgrade](#log-on-with-ssh-and-use-package-manager-for-live-upgrade) for these. Should this, however, require a reboot, you should then apply the following procedure:
80
+
81
+To start with, make sure you have an up-to-date ``sailing-analytics-server`` AMI (see above). Also make sure the auto-scaling group for the ``my`` replica set is set to use this latest AMI for any replicas launched by the auto-scaling group.
82
+
83
+The ``my`` replica set is special in comparison to most other replica sets. It runs its primary process on a dedicated instance and requires an instance type with at least 500GB of swap space. A good default is an ``i3.2xlarge`` instance type. The application settings, as of this writing, require 350GB of heap size, indicated by ``MEMORY="350000m"`` in the user data section for the instance.
84
+
85
+In order to move the ``my`` primary process to a new instance with a new operating system, use the AdminConsole's Landscape Management panel, and there the "Move master process to another instance" action. Make sure to select an appropricate ``i3....`` instance type with sufficient swap space, *not* the default ``C5_2_XLARGE`` suggestion. Explicitly enter the amount of memory you'd like to assign to the process, such as "350000" into the "Memory (MB)" field of the pop-up dialog, then confirm using the "OK" button.
86
+
87
+This will detach all running replicas (usually exactly one) from the primary process, remove the primary process from the ``S-my`` and ``S-my-m`` target groups, then stop and remove the primary process, which will also lead to the instance being terminated as this was the last (only) application process running on it. Then, a new instance off the latest AMI will be launched, deploying and starting a new primary process for the ``my`` replica set. Once this has loaded all contents from the DB and reports a healthy status, an explicit "Upgrade Replica" is launched which uses the explicit primary instance's IP address instead of the DNS host name to obtain an initial load. This works around the fact that so far the new primary hasn't been added to any target groups yet and hence isn't reachable under the ``my.sapsailing.com `` domain name.
88
+
89
+When the upgrade replica has reported a healthy status, the primary is added to the ``S-my`` and ``S-my-m`` target groups, and the upgrade replica is added to the ``S-my`` target group. Then, the old auto-replica which is expected to have been launched using the auto-scaling group will be terminated, causing the launching of a new instance to which a ``my`` replica is deployed and started. Once the auto-replica is healthy, the upgrade replica will be terminated which removes it from the ``S-my`` target group.
90
+
91
+### Sailing Analytics Multi-Servers
92
+
93
+You can try an [in-place upgrade](#log-on-with-ssh-and-use-package-manager-for-live-upgrade) for these. Should this, however, require a reboot, you should then apply the following procedure:
94
+
95
+To start with, make sure you have an up-to-date ``sailing-analytics-server`` AMI (see above). Also make sure that all auto-scaling groups for the application replica sets are set to use this latest AMI for any replicas launched by the auto-scaling group. (This can be achieved, e.g., using the AdminConsole's Landscape Management panel, and there the "Update machine image for auto-scaling replicas" button above the replica sets table.)
96
+
97
+Then, sort the replica sets table by the "Master Instance ID" column and identify the instances configured as "Multi-Server." Should this not be obvious, compare with the instances in the AWS EC2 console instance list named "SL Multi-Server". Click the "Move all application processes away from this replica set's master host to a new host" button. This will launch a new instance and move all application processes away from the old host, one by one. All replication aspects are handled automatically. The duration of migration varies depending on the content volumes hosted by the respective replica set. An empty replica sets migrates in a few minutes. Large replica sets may take an hour or more to migrate. The AdminConsole process may run into a timeout, but don't worry, the migration continues all the way to the end regardess of the web UI timeout.
98
+
99
+Still, should something take suspiciously long, maybe check the server logs of the server you used the Landscape Management panel on (usually ``security-service.sapsailing.com``). The ``logs/sailing.log.0`` file may show details of what went wrong or is taking long. Sometimes it may be the loading of one or more races that fails and doesn't let the instance report a healthy status. In such cases, a manual restart of that process may help, cd'ing into its folder and running ``./stop; ./start`` explicitly.
100
+
101
+### MariaDB
102
+
103
+This is a clear candidate for an in-place upgrade. Should a reboot be required, just reboot. It only takes about 10s, and for Bugzilla as system used mostly internally we can afford a 10s unavailability period.
104
+
105
+### RabbitMQ
106
+
107
+This is Debian-based. Try to go for an in-place upgrade. Should a reboot be required, ideally choose a time outside of major events and ongoing instance upgrades as those will require the RabbitMQ service to succeed.
108
+
109
+### MongoDB Replica Sets
110
+
111
+We currently have three MongoDB instances running in our EC2 landscape: ``[dbserver|mongo0|mongo1].internal.sapsailing.com``. The first hosts three ``mongod`` processes, for three separate replica sets: the single primary of the ``archive`` and the ``slow`` replica sets, and a hidden replica of the ``live`` replica set. The two other instance have primary/secondary ``mongod`` processes for the ``live`` replica set. Try an in-place upgrade first. If that doesn't require a reboot, you're done.
112
+
113
+If a reboot is required after an in-place upgrade, be gentle in how you carry out those reboots. The ``dbserver`` instance can be rebooted as long as no ARCHIVE server start-up is currently going on. During an ARCHIVE server start-up, failure to reach the database may lead to an incomplete state in the new ARCHIVE candidate which may require you to start over with the anyhow very time-consuming ARCHIVE start-up. The ``slow`` replica set and the hidden ``live`` replica, however, pose no obstacles regarding a reboot.
114
+
115
+For ``mongo0`` and ``mongo``, log on as ``ec2-user`` using SSH and use ``mongosh`` to see whether that instance is currently PRIMARY or SECONDARY. Reboot the SECONDARY first. When that has completed, SSH into the PRIMARY and in ``mongosh`` issue the command ``rs.stepDown()`` so that the PRIMARY becomes a SECONDARY, and the other instance that previously was a SECONDARY takes over as the new PRIMARY. With this courtesy, ongoing writing transactions will not even have to go through a re-try as you reboot the now SECONDAY instance.
116
+
117
+Should you choose to work with an upgraded ``mongodb-server`` AMI and the AdminConsole's Landscape Management panel, use the "Scale in/out" action on the respective MongoDB replica set to add new instances launched off the new AMI, then, once healthy, scale in to delete the old instances. This way you can handle the ``mongo0`` and ``mongo1`` instances. You should, however, have to adjust the DNS records in Route53 for ``mongo[01].internal.sapsailing.com`` to reflect the new instances because despite all tags-based resource discovery there are still some older configuration and environments files around that bootstrap new application instances by explicitly referring to ``mongo0.internal.sapsailing.com`` and ``mongo1.internal.sapsailing.com`` as their MongoDB instances to use.
118
+
119
+Should you need to upgrade the central ``dbserver.internal.sapsailing.com`` instance without an in-place upgrade, use the ``configuration/environments_scripts/central_mongo_setup/setup-central-mongo-instance.sh`` script to produce a new instance. When it has launched the new instance, it prints detailed instructions to its standard output for how to unmount the data volumes from the old and mount them to the new instance, as well as which DNS actions to take in Route53 and how to name and tag the new instance.
120
+
121
+### Central Reverse Proxy
122
+
123
+The central reverse proxy, currently running as ``sapsailing.com``, can typically be upgraded in-place. Should a re-boot be required, launch a disposable reverse proxy in the same availability zone as the central reverse proxy first, using the AdminConsole's Landscape Management panel with its "Reverse proxies" table and the corresponding "Add" button. Once that new disposable reverse proxy is shown as healthy in the corresponding ``CentralWebServerHTTP-Dyn`` target group you can reboot the central reverse proxy. It will lead to less than 30s of downtime regarding [https://bugzilla.sapsailing.com](https://bugzilla.sapsailing.com) and [https://wiki.sapsailing.com](https://wiki.sapsailing.com), as well as our self-hosted Git repository which is still used for back-up and cross-synchronization with the checked-out workspace for our Wiki.
124
+
125
+Should an in-place upgrade not be what you want, look into the ``configuration/environments_scripts/central_reverse_proxy`` folder with its setup scripts. They automate most parts of providing a new central reverse proxy instance that has been set up with the latest Amazon Linux from scratch. You will have to carry out a few steps manually, e.g., in the AWS Console, and the scripts will tell you in their standard output what these are.
126
+
127
+When done, terminate the additional disposable reverse proxy that you launched in the central reverse proxy's availability zone.
128
+
129
+### Disposable Reverse Proxy
130
+
131
+Make sure you have an upgraded ``disposable-reverse-proxy`` AMI, then use the AdminConsole Landscape Management panel and its "Reverse proxies" section to launch one or more disposable reverse proxies with the "Add" button. The default instance type suggested is usually a good fit. Make sure to launch one per availability zone in which you'd like to replace an old reverse proxy. When your new reverse proxy is healthy (this includes an error code 503 which only indicates that the reverse proxy is not in the same availability zone as the currently active production ARCHIVE server), terminate the corresponding old reverse proxy.
... ...
\ No newline at end of file
wiki/info/landscape/typical-development-scenarios.md
... ...
@@ -2,8 +2,37 @@
2 2
3 3
[[_TOC_]]
4 4
5
-## Adding a Bundle
6
-We distinguish two cases: adding a 3rd-party bundle to the target platform and adding a new development bundle as a Java project.
5
+## Adding a New Boat Class
6
+
7
+Support for different boat classes comes with the following aspects:
8
+* Name and possible alias names for the boat class
9
+* hull dimensions
10
+* separate downwind sail configuration for display
11
+* boat class symbol / icon
12
+* map visualization
13
+
14
+If you'd like to add a boat class that the system does not yet support with its own boat class symbol or even a separate, new boat visualization on the map, the necessary extensions currently need to me implemented as code.
15
+
16
+To start with, add an enumeration literal for the new class in [BoatClassMasterdata](https://github.com/SAP/sailing-analytics/blob/main/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/BoatClassMasterdata.java). The constructor needs to tell the default boat class name, whether races in this class typically have an upwind start (in which case we'll be more confident in guessing wind direction for races in this class based on the first leg's bearing), the hull length and beam in meters, the hull type, whether or not boats of that class fly a separate downwind sail, and optionally a list of alternative boat class names that can help users find the class, or to reflect name changes over time (Laser becoming ILCA, for example).
17
+
18
+To understand the next steps, you may also search your workspace for usages of other ``BoatClassMasterdata`` enumeration literals. You will typically find at least two more: one in [BoatClassImageResolver](https://github.com/SAP/sailing-analytics/blob/main/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/BoatClassImageResolver.java) which helps define the boat class symbol / icon to be displayed in the UI and made available through the REST API, e.g., for the companion apps; and another in [BoatClassVectorGraphicsResolver](https://github.com/SAP/sailing-analytics/blob/main/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/BoatClassVectorGraphicsResolver.java) which defines how boats of this class are to be visualized on the map.
19
+
20
+For the boat class symbol you have to create a transparent 140x140 pixel PNG file and place it in the ``java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass`` folder. The file's base name must match the ``BoatClassMasterdata`` enumeration literal you created _exactly_. The file extension shall be ``.png``. Then, define a GWT image resource in [BoatClassImageResolver](https://github.com/SAP/sailing-analytics/blob/main/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/BoatClassImageResolver.java), using one of the other resource definitions in that class as an example:
21
+```
22
+ @Source("com/sap/sailing/gwt/ui/client/images/boatclass/FINN.png")
23
+ @ImageOptions(preventInlining = true)
24
+ ImageResource FinnIcon();
25
+```
26
+In ``BoatClassImageResolver`` then use this image resource, e.g., as follows:
27
+```
28
+ boatClassIconsMap.put(BoatClassMasterdata.FINN.getDisplayName(), imageResources.FinnIcon());
29
+```
30
+If any of the already existing boat class map visualizations work for your new class, simply add your new enumeration literal to the ``BoatClassVectorGraphics`` constructor in ``BoatClassVectorGraphicsResolver``'s static initializer that best matches your boat class. If you feel you need a better, more specific boat class visualization on the map, you'll have to [[implement your own|wiki/howto/development/boatgraphicssvg.md]] ``BoatClassVectorGraphics`` class and use that in the static initializer of ``BoatClassVectorGraphicsResolver``.
31
+
32
+Mention the new boat class in the release notes, and don't forget to add all new files to your Git index before committing ;-).
33
+
34
+## Adding an OSGi Bundle
35
+Make sure you have read [[Workspace, Bundles, Projects|wiki/info/general/workspace-bundles-projects-structure]] before continuing. We distinguish two cases: adding a 3rd-party bundle to the target platform and adding a new development bundle as a Java project.
7 36
8 37
## Adding a Bundle to the Target Platform
9 38
* Add a New Library which can not be found in any SAP Repository
... ...
@@ -26,7 +55,8 @@ Bundle-Version: 3.6.4
26 55
* Currently this scripts work for the cygwin shell and the git bash, if the path to your files in the target definition is incorrect, this may be the reason.
27 56
* Test the new overall target platform
28 57
* by setting the race-analysis-p2-local.target as target platform in the IDE
29
- * by running the local maven build via ''buildAndUpdateProduct.sh -v build'' (the ''-v'' switch builds and uses the local p2 repository)
58
+ * by running the ``release`` workflow on Github using the "Build with a local target platform (set to "true" to use local target platform)" switch, or
59
+ * run the local maven build via ''buildAndUpdateProduct.sh -v build'' (the ''-v'' switch builds and uses the local p2 repository)
30 60
* The admin of the central p2 repository (currently at sapsailing.com) must now replace the content of the central server /home/trac/p2-repositories/sailing with the content of the new local base p2 repository (com.sap.sailing.targetplatform.base/target/repository), using the 'uploadRepositoryToServer.sh' script
31 61
* Reload the target platform in the IDE
32 62
... ...
@@ -339,4 +369,4 @@ When TracTrac publishes a TracAPI upgrade, they usually notify us by e-mail and
339 369
340 370
After downloading, extract the ``lib/TracAPI.jar`` and ``src/TracAPI-src.jar`` from the ``TracAPI-x.y.z.tar.gz`` to ``java/com.tractrac.clientmodule/lib``. Unpack the ``TracAPI-x.y.z-javadoc.tar.gz`` into ``java/com.tractrac.clientmodule/javadoc`` and adjust the versions in ``META-INF/MANIFEST.MF`` and ``pom.xml`` accordingly. Unpack the ``Readme.txt`` file from ``TracAPI-x.y.z.tar.gz`` to ``java/com.tractrac.clientmodule``. Optionally, unpack the sample sources from the ``src/com`` folder contained in ``TracAPI-x.y.z.tar.gz`` into ``java/com.tractrac.clientmodule/src``.
341 371
342
-For non-trivial upgrades use of a git branch and dedicated build job is recommended. Otherwise, committing and pushing to master with the ``SAPSailingAnalytics-master`` build job picking up and testing the changes should suffice.
... ...
\ No newline at end of file
0
+For non-trivial upgrades use of a git branch and dedicated build job is recommended. Otherwise, committing and pushing to the ``main`` branch with the ``SAPSailingAnalytics-master`` build job picking up and testing the changes should suffice.
... ...
\ No newline at end of file
wiki/info/landscape/usermanagement.md
... ...
@@ -2,7 +2,7 @@
2 2
3 3
This document describes the basics of how the Shiro framework has been selected and is being used to implement role-based access control. See [Permission Concept](/wiki/info/security/permission-concept) to understand how, based on Shiro, a security architecture with user groups, users, qualified roles, ownerships and access control lists works.
4 4
5
-As a feature of the Sports Sponsorships Engine (SSE) which underlies the SAP Sailing Analytics, our Tennis engagements, parts of the Equestrian contributions and in the future perhaps more, we are about to introduce user management to the platform. Based on [Benjamin Ebling's Bachelor thesis](/doc/theses/20140915_Ebling_Authentication_and_Authorization_for_SAP_Sailing_Analytics.pdf) we are introducing [Apache Shiro](http://shiro.apache.org) to the platform. Our Bugzilla has a separate [component for User and Account Management](http://bugzilla.sapsailing.com/bugzilla/buglist.cgi?query_format=advanced&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&component=User%20and%20Account%20Management&product=Sailing%20Race%20Analytics) that documents the open issues.
5
+As a feature of the Sports Sponsorships Engine (SSE) which underlies the Sailing Analytics, our Tennis engagements, parts of the Equestrian contributions and in the future perhaps more, we are about to introduce user management to the platform. Based on [Benjamin Ebling's Bachelor thesis](/doc/theses/20140915_Ebling_Authentication_and_Authorization_for_SAP_Sailing_Analytics.pdf) we are introducing [Apache Shiro](http://shiro.apache.org) to the platform. Our Bugzilla has a separate [component for User and Account Management](http://bugzilla.sapsailing.com/bugzilla/buglist.cgi?query_format=advanced&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&component=User%20and%20Account%20Management&product=Sailing%20Race%20Analytics) that documents the open issues.
6 6
7 7
[[_TOC_]]
8 8
... ...
@@ -133,6 +133,12 @@ The `LoginPanel` component may be used by applications to display sign-up/sign-i
133 133
134 134
We plan to turn the `UserManagementPanel` which is the widget behind the `UserManagementEntryPoint` into a drop-in component for a generalized administration console concept. So, when the AdminConsole becomes an SSE concept then the user management tab can be made available to all applications using the AdminConsole concept. See also bugs [2424](http://bugzilla.sapsailing.com/bugzilla/show_bug.cgi?id=2424) and [2425](http://bugzilla.sapsailing.com/bugzilla/show_bug.cgi?id=2425).
135 135
136
+## Authorization Checks
137
+
138
+We generally check authorizations in our GWT RPC service implementations, and in our REST API service implementations. As a common pattern, both these types of service implementations will start with performing the necessary authorization check, and only then obtain an underlying OSGi service, such as ``RacingEventService`` for sailing-related things, or ``SecurityService`` for security, user, role and permission management, and invoke the actual "business logic." This keeps our business logic mostly free of authorization checks.
139
+
140
+This also means that filtering responses based on the permissions happens not in the business logic but at the "API level," so in the GWT RPC service method implementation or the REST API servlet method implementation.
141
+
136 142
## Sample Session
137 143
138 144
When you try to reach a protected resource without having the necessary permissions, Shiro will redirect you to the sign-in page.
wiki/info/mobile/sailinsight-privacy-policy.md
... ...
@@ -0,0 +1,77 @@
1
+# Sail Insight Privacy Policy
2
+
3
+Sail Insight (or the “App”) is a mobile application that allows its users to manage, participate, and watch regattas. The App is licensed for publication by SAP S.E. to d-labs (datenschutz@d-labs.com, Jörn Hartwig, D‑LABS GmbH, Marlene-Dietrich-Allee 15, D-14482 Potsdam).
4
+
5
+This policy aims to help you better understand the App privacy processes and your rights in connection with your personal data. To sign up and use the App, you have to carefully read this policy and provide your consent for us to use your personal data in line with this policy.
6
+
7
+The term “personal data” or “personal information” used in this policy means any information relating to any natural person, who can be identified, directly or indirectly, in particular by reference to such information.
8
+
9
+## WHAT INFORMATION DOES THE APP COLLECT?
10
+The App collects and stores different types of personal data:
11
+
12
+- Data that is provided directly by you
13
+- Data produced during your usage of the App
14
+- Data received from the partners of the App and the third-parties
15
+
16
+User profile: The App collects data when you create or update your account. This may include name, email, and password.
17
+
18
+Data about boats: You may provide the App with the data about your boat (type, registration number, boat rating, scoring, scoring group) or upload it from an external website.
19
+
20
+Location data: Once you grant the App access to your geolocation (and only in-between the timepoints when the user selects “start tracking” and “stop tracking), it may collect location data from your mobile device. The location data is collected when the app is in the foreground and when it’s running in the background. If you don’t grant the App access to the geolocation data, this may affect some functionality of the App.
21
+
22
+Usage data: The App collects data about how you and other users interact with the application. This includes data such as regattas that you participated in, classes, marks, courses, races you’ve created. It may also include specific technical data such as your access dates and times, app features, or pages viewed, app crashes.
23
+
24
+Device data: the App may collect data about the devices used to access the application, operating systems and versions, software, preferred languages, and other system settings of your device.
25
+
26
+## HOW DO WE USE THE INFORMATION?
27
+Initiate, participate, and watch regattas. The App collects and uses personal data to allow you to use the functionality of the application, personalize, maintain, and improve it.
28
+
29
+Marketing. The App may use your personal data for marketing purposes, which may include communication with the user about newsletters, upcoming events, new functionality, promotions.
30
+
31
+Other communications. The App may inform you about changes in the policies, EULA services, or send additional information about the application that is not intended for marketing purposes.
32
+
33
+Research and development. We may use the data we collect for development, testing, research, to improve the user experience.
34
+
35
+## GROUNDS FOR PROCESSING
36
+The App collects and uses personal data based on lawful grounds, that generally include processing personal data to provide you and other users with services and features or/and upon your direct consent.
37
+
38
+## HOW LONG DO WE KEEP YOUR DATA?
39
+We may store and use anonymized data for our Research and Development activities and public regatta statistics. Request for deletion of any personal data shall be sent to support@sapsailing.com.
40
+
41
+## THIRD PARTIES THAT HAVE ACCESS TO YOUR PERSONAL DATA
42
+The App may share your personal information with its partners and other third parties.
43
+
44
+The App has specific agreements with third parties who have access to your personal data. Such agreements include sufficient measures (1) to safeguard your personal data, your rights, freedoms, legitimate interest with regard to such information, (2) to prevent abuse or unlawful access or transfer of your personal data.
45
+
46
+## WHO MAY HAVE ACCESS TO YOUR DATA?
47
+Additionally, we may disclose specific types of personal data to third parties in the following cases:
48
+
49
+- To the purchaser or seller of our business;
50
+- To the extent that we are required to do so by law;
51
+- In the enforcement and/or court procedure and/or when it is required to disclose to a governmental agency in response to a valid court order or subpoena or similar requirement of the court or other regulatory bodies of other countries;
52
+- To establish, exercise, or defend the legal rights of our business, employees, and authorized legal representatives.
53
+
54
+## WHERE DO WE STORE YOUR PERSONAL INFORMATION?
55
+Your personal data is being stored on AWS servers. You can read more about AWS GDPR policies here https://aws.amazon.com/compliance/gdpr-center/.
56
+
57
+The App is exercising and implementing all the sufficient and suitable technical and organizational safeguards and measures needed to provide secure processing and storage of your data in accordance with GDPR and trying to ensure that the third parties that receive your personal data also adhere to the GDPR.
58
+
59
+## YOUR RIGHTS
60
+Given that this version of the App is a beta release that includes separate specific functionality for limited distribution among selected users functionality that allows exercising the rights of the data subjects established by GDPR is not realised in full. The subsequent versions of the application as soon as they are available will include the full functionality that will ensure GDPR compliance of the application as listed below.
61
+
62
+- To request to amend/renew the inaccurate and/or incomplete personal data concerning you;
63
+- To delete your personal data, partially or fully
64
+- To restrict the processing of your personal data;
65
+- To object to the processing of your personal data for profiling to the extent that it is related to direct marketing;
66
+- To request to familiarize and access the personal data that was collected.
67
+- You may submit to us a request to exercise any of the abovementioned rights. We will review your request within 30 calendar days and notify you of the results of its consideration.
68
+- For any requests connected with the rights listed above please contact the person that invited you to test the application.
69
+
70
+## LINKS TO THE SITES OF THE THIRD PARTIES
71
+The App may, from time to time, contain links to websites of third parties. Please note that we are not responsible for the content, terms of use and/or service as well as for the privacy policies of these websites.
72
+
73
+## MODIFICATIONS AND AMENDMENTS
74
+We may amend this privacy policy at any time by posting a new version and sending you a separate notification via email. It will become effective at the time it will be published. If you continue to use the App, it will be regarded as an acceptance of the amended policy.
75
+
76
+## CONTACT
77
+If you have any complaints about the policy and your personal data, please contact us at support@sapsailing.com