configuration/syncEC2ElbLogs
... ...
@@ -0,0 +1,2 @@
1
+#!/bin/sh
2
+/usr/bin/s3cmd -c /root/.s3cfg sync s3://sapsailing-access-logs/elb-access-logs/ /var/log/old/elb-access-logs/ >/var/log/last-elb-access-logs-sync.out 2>/var/log/last-elb-access-logs-sync.err
configuration/unique_ips_per_referrer
... ...
@@ -16,6 +16,8 @@ mkdir -p "$STATS"
16 16
# A sample line:
17 17
# 505Worlds2012.sapsailing.com 66.249.73.53 - - [12/Jan/2014:03:33:11 +0000] "GET /robots.txt HTTP/1.1" 404 238 "-" "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"
18 18
19
+echo Starting "$0" at `date` on file set "$*"
20
+
19 21
for i in $*; do
20 22
grep -q "^$i\$" $VISITED_FILES
21 23
if [ "$?" = "0" ]; then
... ...
@@ -50,4 +52,4 @@ for month in `cat $MONTHS`; do
50 52
done | tee $STATS/unique-ips-days-useragents-per-event-$month
51 53
echo "Wrote per-month results to $STATS/unique-ips-days-useragents-per-event-$month"
52 54
done
53
-echo "Done."
55
+echo "Done at `date`."
configuration/unique_ips_per_referrer_generate_month_results_only
... ...
@@ -0,0 +1,16 @@
1
+#!/bin/bash
2
+
3
+CACHE=/var/log/old/cache/unique-ips-per-referrer
4
+VISITED_FILES=$CACHE/visited
5
+STATS=$CACHE/stats
6
+MONTHS=$STATS/months
7
+
8
+for month in `cat $MONTHS`; do
9
+ for i in ${STATS}/*.ips; do
10
+ echo -n "`basename $i .ips` "
11
+ cat $i | grep "^[^ ]* [0-9]*/$month .*" | sort -u | wc | awk '{ print $1; }'
12
+ # TODO consider producing an AWStats configuration file for each virtual host name and splitting all new logs into virtual host name-specific log files
13
+ done | tee $STATS/unique-ips-days-useragents-per-event-`echo $month | tr / -`
14
+ echo "Wrote per-month results to $STATS/unique-ips-days-useragents-per-event-$month"
15
+done
16
+echo "Done."
configuration/unique_ips_per_referrer_generate_results_only
... ...
@@ -0,0 +1,26 @@
1
+#!/bin/bash
2
+
3
+CACHE=/var/log/old/cache/unique-ips-per-referrer
4
+VISITED_FILES=$CACHE/visited
5
+STATS=$CACHE/stats
6
+MONTHS=$STATS/months
7
+
8
+for i in ${STATS}/*.ips; do
9
+ echo -n "`basename $i .ips` "
10
+ cat $i | sort -u | wc | awk '{ print $1; }'
11
+ # TODO consider producing an AWStats configuration file for each virtual host name and splitting all new logs into virtual host name-specific log files
12
+done | tee $STATS/unique-ips-days-useragents-per-event
13
+echo "Wrote total results to $STATS/unique-ips-days-useragents-per-event"
14
+
15
+
16
+# Now filter and group by month
17
+cat ${STATS}/*.ips | awk '{ print $2; }' | sed -e 's/^[0-9]*\///' | sort -u >$MONTHS
18
+for month in `cat $MONTHS`; do
19
+ for i in ${STATS}/*.ips; do
20
+ echo -n "`basename $i .ips` "
21
+ cat $i | grep "^[^ ]* [0-9]*/$month .*" | sort -u | wc | awk '{ print $1; }'
22
+ # TODO consider producing an AWStats configuration file for each virtual host name and splitting all new logs into virtual host name-specific log files
23
+ done | tee $STATS/unique-ips-days-useragents-per-event-`echo $month | tr / -`
24
+ echo "Wrote per-month results to $STATS/unique-ips-days-useragents-per-event-$month"
25
+done
26
+echo "Done."
java/com.sap.sailing.autoload/src/com/sap/sailing/autoload/TracTrac.java
... ...
@@ -94,8 +94,8 @@ public class TracTrac {
94 94
new MillisecondsTimePoint(record.getTrackingStartTime().asMillis()),
95 95
new MillisecondsTimePoint(record.getTrackingEndTime().asMillis()),
96 96
raceLogStore, regattaLogStore,
97
- RaceTracker.TIMEOUT_FOR_RECEIVING_RACE_DEFINITION_IN_MILLISECONDS, /* simulateWithStartTimeNow */
98
- false, /*useTracTracMarkPassings*/ false,
97
+ RaceTracker.TIMEOUT_FOR_RECEIVING_RACE_DEFINITION_IN_MILLISECONDS, /* offsetToStartTimeOfSimulatedRace */
98
+ null, /*useTracTracMarkPassings*/ false,
99 99
/* tracTracUsername */"", /* tracTracPassword */"", record.getRaceStatus(),
100 100
record.getRaceVisibility());
101 101
} else {
java/com.sap.sailing.dashboards.gwt/pom.xml
... ...
@@ -11,19 +11,6 @@
11 11
<artifactId>com.sap.sailing.dashboards.gwt</artifactId>
12 12
<packaging>eclipse-plugin</packaging>
13 13
14
- <repositories>
15
- <repository>
16
- <id>sonatype</id>
17
- <url>http://oss.sonatype.org/content/repositories/snapshots</url>
18
- <snapshots>
19
- <enabled>true</enabled>
20
- </snapshots>
21
- <releases>
22
- <enabled>false</enabled>
23
- </releases>
24
- </repository>
25
- </repositories>
26
-
27 14
<dependencies>
28 15
<dependency>
29 16
<groupId>javax.validation</groupId>
java/com.sap.sailing.domain.common/pom.xml
... ...
@@ -1,40 +1,40 @@
1
-<?xml version="1.0" encoding="UTF-8"?>
2
-<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
3
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
4
- <modelVersion>4.0.0</modelVersion>
5
- <parent>
6
- <artifactId>root</artifactId>
7
- <groupId>com.sap.sailing</groupId>
8
- <version>1.0.0-SNAPSHOT</version>
9
- </parent>
10
- <artifactId>com.sap.sailing.domain.common</artifactId>
11
- <packaging>eclipse-plugin</packaging>
12
-
13
- <build>
14
- <plugins>
15
- <plugin>
16
- <groupId>org.eclipse.tycho</groupId>
17
- <artifactId>tycho-compiler-plugin</artifactId>
18
- <version>${tycho-version}</version>
19
- <configuration>
20
- <source>1.7</source>
21
- <target>1.7</target>
22
- </configuration>
23
- </plugin>
24
- <plugin>
25
- <groupId>org.eclipse.tycho</groupId>
26
- <artifactId>tycho-source-plugin</artifactId>
27
- <version>${tycho-version}</version>
28
- <executions>
29
- <execution>
30
- <id>plugin-source</id>
31
- <phase>generate-sources</phase>
32
- <goals>
33
- <goal>plugin-source</goal>
34
- </goals>
35
- </execution>
36
- </executions>
37
- </plugin>
38
- </plugins>
39
- </build>
40
-</project>
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
3
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
4
+ <modelVersion>4.0.0</modelVersion>
5
+ <parent>
6
+ <artifactId>root</artifactId>
7
+ <groupId>com.sap.sailing</groupId>
8
+ <version>1.0.0-SNAPSHOT</version>
9
+ </parent>
10
+ <artifactId>com.sap.sailing.domain.common</artifactId>
11
+ <packaging>eclipse-plugin</packaging>
12
+
13
+ <build>
14
+ <plugins>
15
+ <plugin>
16
+ <groupId>org.eclipse.tycho</groupId>
17
+ <artifactId>tycho-compiler-plugin</artifactId>
18
+ <version>${tycho-version}</version>
19
+ <configuration>
20
+ <source>1.7</source>
21
+ <target>1.7</target>
22
+ </configuration>
23
+ </plugin>
24
+ <plugin>
25
+ <groupId>org.eclipse.tycho</groupId>
26
+ <artifactId>tycho-source-plugin</artifactId>
27
+ <version>${tycho-version}</version>
28
+ <executions>
29
+ <execution>
30
+ <id>plugin-source</id>
31
+ <phase>generate-sources</phase>
32
+ <goals>
33
+ <goal>plugin-source</goal>
34
+ </goals>
35
+ </execution>
36
+ </executions>
37
+ </plugin>
38
+ </plugins>
39
+ </build>
40
+</project>
java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/AbstractSpeedImpl.java
... ...
@@ -21,7 +21,7 @@ public abstract class AbstractSpeedImpl implements Speed {
21 21
22 22
@Override
23 23
public Duration getDuration(Distance distance) {
24
- return new MillisecondsDurationImpl((long) (1000 * distance.getMeters() / getMetersPerSecond()));
24
+ return distance == null ? null : new MillisecondsDurationImpl((long) (1000 * distance.getMeters() / getMetersPerSecond()));
25 25
}
26 26
27 27
@Override
java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/AbstractLeaderboardDTO.java
... ...
@@ -1,430 +1,433 @@
1
-package com.sap.sailing.domain.common.dto;
2
-
3
-import java.io.Serializable;
4
-import java.util.ArrayList;
5
-import java.util.Arrays;
6
-import java.util.Date;
7
-import java.util.HashSet;
8
-import java.util.List;
9
-import java.util.Map;
10
-import java.util.Set;
11
-import java.util.UUID;
12
-
13
-import com.sap.sailing.domain.common.LeaderboardType;
14
-import com.sap.sailing.domain.common.RaceIdentifier;
15
-import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
16
-import com.sap.sailing.domain.common.ScoringSchemeType;
17
-import com.sap.sse.common.Util;
18
-
19
-public abstract class AbstractLeaderboardDTO implements Serializable {
20
- private static final long serialVersionUID = -205106531931903527L;
21
-
22
- public String name;
23
-
24
- private List<RaceColumnDTO> races;
25
- public Map<CompetitorDTO, String> competitorDisplayNames;
26
- public Map<CompetitorDTO, LeaderboardRowDTO> rows;
27
- public boolean hasCarriedPoints;
28
- public int[] discardThresholds;
29
-
30
- /**
31
- * Set to the non-<code>null</code> regatta name if this DTO represents a <code>RegattaLeaderboard</code>.
32
- */
33
- public String regattaName;
34
- public String displayName;
35
- public UUID defaultCourseAreaId;
36
- public String defaultCourseAreaName;
37
- public ScoringSchemeType scoringScheme;
38
- public LeaderboardType type;
39
-
40
- private Long delayToLiveInMillisForLatestRace;
41
-
42
- public AbstractLeaderboardDTO() {
43
- races = new ArrayList<RaceColumnDTO>();
44
- }
45
-
46
- public Set<BoatClassDTO> getBoatClasses() {
47
- Set<BoatClassDTO> result = new HashSet<BoatClassDTO>();
48
- if(rows != null) {
49
- for (CompetitorDTO competitor : rows.keySet()) {
50
- result.add(competitor.getBoatClass());
51
- }
52
- }
53
- return result;
54
- }
55
-
56
- public String getDisplayName() {
57
- return displayName;
58
- }
59
-
60
- public String getDisplayName(CompetitorDTO competitor) {
61
- if (competitorDisplayNames == null || competitorDisplayNames.get(competitor) == null) {
62
- return competitor.getName();
63
- } else {
64
- return competitorDisplayNames.get(competitor);
65
- }
66
- }
67
-
68
- /**
69
- * If the race column whose name is specified in <code>raceColumnName</code> has at least one competitor who has valid
70
- * {@link LeaderboardEntryDTO#legDetails} for that race column, the maximum number of entries of all such competitors in
71
- * the leg details is returned, telling the number of legs that the race column shall display. Otherwise, -1 is returned.
72
- * If you specify an non null <code>preselectedRace</code> the leg count is calculated only for that race for the column
73
- * holding that race. All other columns will be unaffected by <code>preselectedRace</code> and will return their regular
74
- * leg count. Note that this may lead to a situation where some competitors may have sailed more legs than others because
75
- * their fleet association may have varied across races.<p>
76
- *
77
- * See also bug 2604 and bug 2035.
78
- */
79
- public int getLegCount(String raceColumnName, RaceIdentifier preselectedRace) {
80
- int result = -1;
81
- final boolean preselectedRaceIsInRaceColumn = isRaceInColumn(preselectedRace, raceColumnName);
82
- for (LeaderboardRowDTO row : rows.values()) {
83
- LeaderboardEntryDTO leaderboardEntryDTO = row.fieldsByRaceColumnName.get(raceColumnName);
84
- if (leaderboardEntryDTO != null && leaderboardEntryDTO.legDetails != null
85
- // when no race is pre-selected, always use the actual leg count;
86
- // otherwise, if the pre-selected race is in the column identified by
87
- // raceColumnName, use the entry only if it's in the pre-selected race;
88
- // otherwise (a race is pre-selected but a different column's leg count
89
- // is requested), use the regular leg count as is.
90
- && (preselectedRace == null || !preselectedRaceIsInRaceColumn || preselectedRace.equals(leaderboardEntryDTO.race))) {
91
- result = Math.max(result, leaderboardEntryDTO.legDetails.size());
92
- }
93
- }
94
- return result;
95
- }
96
-
97
- private boolean isRaceInColumn(RaceIdentifier preselectedRace, String raceColumnName) {
98
- RaceColumnDTO raceColumn = getRaceColumnByName(raceColumnName);
99
- return raceColumn.containsRace(preselectedRace);
100
- }
101
-
102
- /**
103
- * Tells if the <code>competitor</code> scored (and therefore presumably participated) in a medal race represented
104
- * in this leaderboard.
105
- */
106
- public boolean scoredInMedalRace(CompetitorDTO competitor) {
107
- LeaderboardRowDTO row = rows.get(competitor);
108
- for (RaceColumnDTO race : races) {
109
- if (race.isMedalRace() && row.fieldsByRaceColumnName.get(race.getRaceColumnName()).totalPoints > 0) {
110
- return true;
111
- }
112
- }
113
- return false;
114
- }
115
-
116
- public Double getNetPoints(CompetitorDTO competitor, String nameOfLastRaceSoFar) {
117
- Double netPoints = null;
118
- LeaderboardRowDTO row = rows.get(competitor);
119
- if (row != null) {
120
- LeaderboardEntryDTO field = row.fieldsByRaceColumnName.get(nameOfLastRaceSoFar);
121
- if (field != null && field.netPoints != null) {
122
- netPoints = field.netPoints;
123
- }
124
- }
125
- return netPoints;
126
- }
127
-
128
- public boolean raceIsTracked(String raceColumnName) {
129
- for (RaceColumnDTO race : races) {
130
- if (race.getRaceColumnName().equals(raceColumnName)) {
131
- for (FleetDTO fleet : race.getFleets()) {
132
- if (race.isTrackedRace(fleet)) {
133
- return true;
134
- }
135
- }
136
- }
137
- }
138
- return false;
139
-
140
- }
141
-
142
- public boolean raceIsMedalRace(String raceColumnName) {
143
- return getRaceColumnByName(raceColumnName).isMedalRace();
144
- }
145
-
146
- /**
147
- * If the {@link RaceColumnDTO} by the name <code>raceColumnName</code> doesn't exist yet within this leaderboard
148
- * DTO, it is created, setting is {@link RaceColumnDTO#isValidInTotalScore()} to <code>true</code>. This method
149
- * ensures that a fleet named <code>fleetName</code> is present. If it's not present yet, it's added to the race
150
- * column's fleet name list. The <code>trackedRaceIdentifier</code> and <code>race</code> are associated with the
151
- * column for the fleet identified by <code>fleetName</code>.
152
- *
153
- * @param explicitFactor
154
- * factor by which to multiply the race column's points for the overall score; if <code>null</code>, the
155
- * default will be determined by whether or not the column is marked as medal race
156
- * @param effectiveFactor
157
- * is what you get when you call {@link RaceColumn#getFactor()} on the race column that the resulting
158
- * {@link RaceColumnDTO} represents
159
- * @param regattaName
160
- * must not be <code>null</code> if <code>seriesName</code> is not <code>null</code>; specified
161
- * separately from <code>trackedRaceIdentifier</code> because a column may belong to regatta/series
162
- * despite not having a tracked race associated
163
- * @param seriesName
164
- * if <code>null</code>, this method will produce a {@link RaceColumnDTO}, otherwise a
165
- * {@link RaceColumnInSeriesDTO}
166
- * @param fleetDTO
167
- * must not be null
168
- */
169
- public RaceColumnDTO addRace(String raceColumnName, Double explicitFactor, double effectiveFactor,
170
- String regattaName, String seriesName, FleetDTO fleetDTO, boolean medalRace, RegattaAndRaceIdentifier trackedRaceIdentifier, RaceDTO race) {
171
- assert fleetDTO != null;
172
- RaceColumnDTO raceColumnDTO = getRaceColumnByName(raceColumnName);
173
- if (raceColumnDTO == null) {
174
- raceColumnDTO = RaceColumnDTOFactory.INSTANCE.createRaceColumnDTO(raceColumnName, medalRace,
175
- explicitFactor, regattaName, seriesName);
176
- races.add(raceColumnDTO);
177
- }
178
- raceColumnDTO.setEffectiveFactor(effectiveFactor);
179
- boolean contains = false;
180
- for (FleetDTO fleet : raceColumnDTO.getFleets()) {
181
- if (fleet.getName().equals(fleetDTO.getName())) {
182
- contains = true;
183
- break;
184
- }
185
- }
186
- if (!contains) {
187
- raceColumnDTO.addFleet(fleetDTO);
188
- }
189
- raceColumnDTO.setRaceIdentifier(fleetDTO, trackedRaceIdentifier);
190
- raceColumnDTO.setRace(fleetDTO, race);
191
- return raceColumnDTO;
192
- }
193
-
194
- public RaceColumnDTO createEmptyRaceColumn(String raceColumnName, boolean medalRace, String regattaName, String seriesName) {
195
- final RaceColumnDTO raceColumn = RaceColumnDTOFactory.INSTANCE.createRaceColumnDTO(raceColumnName,
196
- medalRace, /* explicit factor */ null, regattaName, seriesName);
197
- races.add(raceColumn);
198
- return raceColumn;
199
- }
200
-
201
- public RaceColumnDTO getRaceColumnByName(String raceColumnName) {
202
- for (RaceColumnDTO race : races) {
203
- if (race.getRaceColumnName().equals(raceColumnName)) {
204
- return race;
205
- }
206
- }
207
- return null;
208
- }
209
-
210
- public int getRaceColumnsCount() {
211
- return races.size();
212
- }
213
-
214
- public int getRacesCount() {
215
- int result = 0;
216
- for (RaceColumnDTO race : getRaceList()) {
217
- result += race.getFleets().size();
218
- }
219
- return result;
220
- }
221
-
222
- public int getTrackedRacesCount() {
223
- int result = 0;
224
- for (RaceColumnDTO raceColumn : getRaceList()) {
225
- for (FleetDTO fleet : raceColumn.getFleets()) {
226
- RaceDTO race = raceColumn.getRace(fleet);
227
- if(race != null && race.trackedRace != null && race.trackedRace.hasGPSData && race.trackedRace.hasWindData) {
228
- result++;
229
- }
230
- }
231
- }
232
- return result;
233
- }
234
-
235
- public List<RaceColumnDTO> getRaceList() {
236
- return races;
237
- }
238
-
239
- public void setRaceList(List<RaceColumnDTO> raceList) {
240
- this.races = raceList;
241
- }
242
-
243
- public boolean raceListContains(String raceColumnName) {
244
- return getRaceColumnByName(raceColumnName) != null;
245
- }
246
-
247
- public void moveRaceUp(String raceColumnName) {
248
- RaceColumnDTO race = getRaceColumnByName(raceColumnName);
249
- int index = races.indexOf(race);
250
- index--;
251
- if (index >= 0) {
252
- races.remove(index + 1);
253
- races.add(index, race);
254
- }
255
- }
256
-
257
- public void moveRaceDown(String raceColumnName) {
258
- RaceColumnDTO race = getRaceColumnByName(raceColumnName);
259
- int index = races.indexOf(race);
260
- if (index != -1) {
261
- index++;
262
- if (index < races.size()) {
263
- races.remove(index - 1);
264
- races.add(index, race);
265
- }
266
- }
267
- }
268
-
269
- public void setIsMedalRace(String raceColumnName, boolean medalRace) {
270
- getRaceColumnByName(raceColumnName).setMedalRace(medalRace);
271
- }
272
-
273
- /**
274
- * @return The earliest start date of the races, or <code>null</code> if no start dates of the races are available.
275
- */
276
- public Date getStartDate() {
277
- Date leaderboardStart = null;
278
- for (RaceColumnDTO race : getRaceList()) {
279
- for (FleetDTO fleet: race.getFleets()) {
280
- Date raceStart = race.getStartDate(fleet);
281
- if (raceStart != null) {
282
- if (leaderboardStart == null) {
283
- leaderboardStart = new Date();
284
- } else {
285
- leaderboardStart = leaderboardStart.before(raceStart) ? leaderboardStart : raceStart;
286
- }
287
- }
288
- }
289
- }
290
- return leaderboardStart;
291
- }
292
-
293
- /**
294
- * Takes the {@link PlacemarkOrderDTO} of all races in this leaderboard, if the PlacemarkOrderDTO for the race is
295
- * available, and fills all {@link PlacemarkDTO} in a new PlacemarkOrderDTO.<br />
296
- * The order of the races in this leaderboard determine the order of the PlacemarkDTOs in the PlacemarkOrderDTO.
297
- *
298
- * @return The places of this leaderboard in form of a {@link PlacemarkOrderDTO}, or <code>null</code> if the
299
- * {@link PlacemarkOrderDTO places} of no race are available
300
- */
301
- public PlacemarkOrderDTO getPlaces() {
302
- PlacemarkOrderDTO leaderboardPlaces = null;
303
- for (RaceColumnDTO race : getRaceList()) {
304
- PlacemarkOrderDTO racePlaces = race.getPlaces();
305
- if (racePlaces != null) {
306
- if (leaderboardPlaces == null) {
307
- leaderboardPlaces = new PlacemarkOrderDTO();
308
- }
309
- leaderboardPlaces.getPlacemarks().addAll(racePlaces.getPlacemarks());
310
- }
311
- }
312
- return leaderboardPlaces;
313
- }
314
-
315
- /**
316
- * @return <code>true</code> if the leaderboard contains a race which is live
317
- */
318
- public boolean hasLiveRace(long serverTimePointAsMillis) {
319
- for (RaceColumnDTO race : getRaceList()) {
320
- for (FleetDTO fleet : race.getFleets()) {
321
- if (race.isLive(fleet, serverTimePointAsMillis)) {
322
- return true;
323
- }
324
- }
325
- }
326
- return false;
327
- }
328
-
329
- public List<Util.Pair<RaceColumnDTO, FleetDTO>> getLiveRaces(long serverTimePointAsMillis) {
330
- List<Util.Pair<RaceColumnDTO, FleetDTO>> result = new ArrayList<Util.Pair<RaceColumnDTO, FleetDTO>>();
331
- for (RaceColumnDTO race : getRaceList()) {
332
- for (FleetDTO fleet : race.getFleets()) {
333
- if (race.isLive(fleet, serverTimePointAsMillis)) {
334
- result.add(new Util.Pair<RaceColumnDTO, FleetDTO>(race, fleet));
335
- }
336
- }
337
- }
338
- return result;
339
- }
340
-
341
- @Override
342
- public int hashCode() {
343
- final int prime = 31;
344
- int result = 1;
345
- result = prime * result + ((competitorDisplayNames == null) ? 0 : competitorDisplayNames.hashCode());
346
- result = prime * result + Arrays.hashCode(discardThresholds);
347
- result = prime * result + (hasCarriedPoints ? 1231 : 1237);
348
- result = prime * result + ((name == null) ? 0 : name.hashCode());
349
- result = prime * result + ((scoringScheme == null) ? 0 : scoringScheme.hashCode());
350
- if (races == null) {
351
- result = prime * result;
352
- } else {
353
- List<String> raceNames = new ArrayList<String>();
354
- for (RaceColumnDTO race : races) {
355
- raceNames.add(race.getName());
356
- }
357
- result = prime * result + raceNames.hashCode();
358
- }
359
- result = prime * result + ((rows == null) ? 0 : rows.hashCode());
360
- return result;
361
- }
362
-
363
- @Override
364
- public boolean equals(Object obj) {
365
- if (this == obj)
366
- return true;
367
- if (obj == null)
368
- return false;
369
- if (getClass() != obj.getClass())
370
- return false;
371
- AbstractLeaderboardDTO other = (AbstractLeaderboardDTO) obj;
372
- if (competitorDisplayNames == null) {
373
- if (other.competitorDisplayNames != null)
374
- return false;
375
- } else if (!competitorDisplayNames.equals(other.competitorDisplayNames))
376
- return false;
377
- if (!Arrays.equals(discardThresholds, other.discardThresholds))
378
- return false;
379
- if (hasCarriedPoints != other.hasCarriedPoints)
380
- return false;
381
- if (type != other.type)
382
- return false;
383
- if (scoringScheme != other.scoringScheme)
384
- return false;
385
- if (name == null) {
386
- if (other.name != null)
387
- return false;
388
- } else if (!name.equals(other.name))
389
- return false;
390
- if (races == null) {
391
- if (other.races != null)
392
- return false;
393
- } else {
394
- // compare race column names only, not the contents
395
- if (races.size() != (other.races==null?0:other.races.size())) {
396
- return false;
397
- }
398
- List<String> raceColumnNames = new ArrayList<String>(races.size());
399
- List<String> otherRaceColumnNames = new ArrayList<String>(races.size());
400
- for (RaceColumnDTO race : races) {
401
- raceColumnNames.add(race.getName());
402
- }
403
- if (other.races != null) {
404
- for (RaceColumnDTO otherRace : other.races) {
405
- otherRaceColumnNames.add(otherRace.getName());
406
- }
407
- }
408
- if (!raceColumnNames.equals(otherRaceColumnNames))
409
- return false;
410
- if (rows == null) {
411
- if (other.rows != null)
412
- return false;
413
- } else if (!rows.equals(other.rows))
414
- return false;
415
- }
416
- return true;
417
- }
418
-
419
- public boolean isDisplayNameSet(CompetitorDTO competitor) {
420
- return competitorDisplayNames.get(competitor) != null;
421
- }
422
-
423
- public Long getDelayToLiveInMillisForLatestRace() {
424
- return delayToLiveInMillisForLatestRace;
425
- }
426
-
427
- public void setDelayToLiveInMillisForLatestRace(Long delayToLiveInMillisForLatestRace) {
428
- this.delayToLiveInMillisForLatestRace = delayToLiveInMillisForLatestRace;
429
- }
430
-}
1
+package com.sap.sailing.domain.common.dto;
2
+
3
+import java.io.Serializable;
4
+import java.util.ArrayList;
5
+import java.util.Arrays;
6
+import java.util.Date;
7
+import java.util.HashSet;
8
+import java.util.List;
9
+import java.util.Map;
10
+import java.util.Set;
11
+import java.util.UUID;
12
+
13
+import com.sap.sailing.domain.common.LeaderboardType;
14
+import com.sap.sailing.domain.common.RaceIdentifier;
15
+import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
16
+import com.sap.sailing.domain.common.ScoringSchemeType;
17
+import com.sap.sse.common.Util;
18
+
19
+public abstract class AbstractLeaderboardDTO implements Serializable {
20
+ private static final long serialVersionUID = -205106531931903527L;
21
+
22
+ public String name;
23
+
24
+ private List<RaceColumnDTO> races;
25
+ public Map<CompetitorDTO, String> competitorDisplayNames;
26
+ public Map<CompetitorDTO, LeaderboardRowDTO> rows;
27
+ public boolean hasCarriedPoints;
28
+ public int[] discardThresholds;
29
+
30
+ /**
31
+ * Set to the non-<code>null</code> regatta name if this DTO represents a <code>RegattaLeaderboard</code>.
32
+ */
33
+ public String regattaName;
34
+ public String displayName;
35
+ public UUID defaultCourseAreaId;
36
+ public String defaultCourseAreaName;
37
+ public ScoringSchemeType scoringScheme;
38
+ public LeaderboardType type;
39
+
40
+ private Long delayToLiveInMillisForLatestRace;
41
+
42
+ public AbstractLeaderboardDTO() {
43
+ races = new ArrayList<RaceColumnDTO>();
44
+ }
45
+
46
+ public Set<BoatClassDTO> getBoatClasses() {
47
+ Set<BoatClassDTO> result = new HashSet<BoatClassDTO>();
48
+ if(rows != null) {
49
+ for (CompetitorDTO competitor : rows.keySet()) {
50
+ result.add(competitor.getBoatClass());
51
+ }
52
+ }
53
+ return result;
54
+ }
55
+
56
+ public String getDisplayName() {
57
+ return displayName;
58
+ }
59
+
60
+ public String getDisplayName(CompetitorDTO competitor) {
61
+ if (competitorDisplayNames == null || competitorDisplayNames.get(competitor) == null) {
62
+ return competitor.getName();
63
+ } else {
64
+ return competitorDisplayNames.get(competitor);
65
+ }
66
+ }
67
+
68
+ /**
69
+ * If the race column whose name is specified in <code>raceColumnName</code> has at least one competitor who has valid
70
+ * {@link LeaderboardEntryDTO#legDetails} for that race column, the maximum number of entries of all such competitors in
71
+ * the leg details is returned, telling the number of legs that the race column shall display. Otherwise, -1 is returned.
72
+ * If you specify an non null <code>preselectedRace</code> the leg count is calculated only for that race for the column
73
+ * holding that race. All other columns will be unaffected by <code>preselectedRace</code> and will return their regular
74
+ * leg count. Note that this may lead to a situation where some competitors may have sailed more legs than others because
75
+ * their fleet association may have varied across races.<p>
76
+ *
77
+ * See also bug 2604 and bug 2035.
78
+ */
79
+ public int getLegCount(String raceColumnName, RaceIdentifier preselectedRace) {
80
+ int result = -1;
81
+ final boolean preselectedRaceIsInRaceColumn = isRaceInColumn(preselectedRace, raceColumnName);
82
+ for (LeaderboardRowDTO row : rows.values()) {
83
+ LeaderboardEntryDTO leaderboardEntryDTO = row.fieldsByRaceColumnName.get(raceColumnName);
84
+ if (leaderboardEntryDTO != null && leaderboardEntryDTO.legDetails != null
85
+ // when no race is pre-selected, always use the actual leg count;
86
+ // otherwise, if the pre-selected race is in the column identified by
87
+ // raceColumnName, use the entry only if it's in the pre-selected race;
88
+ // otherwise (a race is pre-selected but a different column's leg count
89
+ // is requested), use the regular leg count as is.
90
+ && (preselectedRace == null || !preselectedRaceIsInRaceColumn || preselectedRace.equals(leaderboardEntryDTO.race))) {
91
+ result = Math.max(result, leaderboardEntryDTO.legDetails.size());
92
+ }
93
+ }
94
+ return result;
95
+ }
96
+
97
+ private boolean isRaceInColumn(RaceIdentifier preselectedRace, String raceColumnName) {
98
+ RaceColumnDTO raceColumn = getRaceColumnByName(raceColumnName);
99
+ return raceColumn.containsRace(preselectedRace);
100
+ }
101
+
102
+ /**
103
+ * Tells if the <code>competitor</code> scored (and therefore presumably participated) in a medal race represented
104
+ * in this leaderboard.
105
+ */
106
+ public boolean scoredInMedalRace(CompetitorDTO competitor) {
107
+ LeaderboardRowDTO row = rows.get(competitor);
108
+ for (RaceColumnDTO race : races) {
109
+ if (race.isMedalRace() && row.fieldsByRaceColumnName.get(race.getRaceColumnName()).totalPoints > 0) {
110
+ return true;
111
+ }
112
+ }
113
+ return false;
114
+ }
115
+
116
+ public Double getNetPoints(CompetitorDTO competitor, String nameOfLastRaceSoFar) {
117
+ Double netPoints = null;
118
+ LeaderboardRowDTO row = rows.get(competitor);
119
+ if (row != null) {
120
+ LeaderboardEntryDTO field = row.fieldsByRaceColumnName.get(nameOfLastRaceSoFar);
121
+ if (field != null && field.netPoints != null) {
122
+ netPoints = field.netPoints;
123
+ }
124
+ }
125
+ return netPoints;
126
+ }
127
+
128
+ public boolean raceIsTracked(String raceColumnName) {
129
+ for (RaceColumnDTO race : races) {
130
+ if (race.getRaceColumnName().equals(raceColumnName)) {
131
+ for (FleetDTO fleet : race.getFleets()) {
132
+ if (race.isTrackedRace(fleet)) {
133
+ return true;
134
+ }
135
+ }
136
+ }
137
+ }
138
+ return false;
139
+
140
+ }
141
+
142
+ public boolean raceIsMedalRace(String raceColumnName) {
143
+ return getRaceColumnByName(raceColumnName).isMedalRace();
144
+ }
145
+
146
+ /**
147
+ * If the {@link RaceColumnDTO} by the name <code>raceColumnName</code> doesn't exist yet within this leaderboard
148
+ * DTO, it is created, setting is {@link RaceColumnDTO#isValidInTotalScore()} to <code>true</code>. This method
149
+ * ensures that a fleet named <code>fleetName</code> is present. If it's not present yet, it's added to the race
150
+ * column's fleet name list. The <code>trackedRaceIdentifier</code> and <code>race</code> are associated with the
151
+ * column for the fleet identified by <code>fleetName</code>.
152
+ *
153
+ * @param explicitFactor
154
+ * factor by which to multiply the race column's points for the overall score; if <code>null</code>, the
155
+ * default will be determined by whether or not the column is marked as medal race
156
+ * @param effectiveFactor
157
+ * is what you get when you call {@link RaceColumn#getFactor()} on the race column that the resulting
158
+ * {@link RaceColumnDTO} represents
159
+ * @param regattaName
160
+ * must not be <code>null</code> if <code>seriesName</code> is not <code>null</code>; specified
161
+ * separately from <code>trackedRaceIdentifier</code> because a column may belong to regatta/series
162
+ * despite not having a tracked race associated
163
+ * @param seriesName
164
+ * if <code>null</code>, this method will produce a {@link RaceColumnDTO}, otherwise a
165
+ * {@link RaceColumnInSeriesDTO}
166
+ * @param fleetDTO
167
+ * must not be null
168
+ */
169
+ public RaceColumnDTO addRace(String raceColumnName, Double explicitFactor, double effectiveFactor,
170
+ String regattaName, String seriesName, FleetDTO fleetDTO, boolean medalRace,
171
+ RegattaAndRaceIdentifier trackedRaceIdentifier, RaceDTO race, boolean isMetaLeaderboardColumn) {
172
+ assert fleetDTO != null;
173
+ RaceColumnDTO raceColumnDTO = getRaceColumnByName(raceColumnName);
174
+ if (raceColumnDTO == null) {
175
+ raceColumnDTO = RaceColumnDTOFactory.INSTANCE.createRaceColumnDTO(raceColumnName, medalRace,
176
+ explicitFactor, regattaName, seriesName, isMetaLeaderboardColumn);
177
+ races.add(raceColumnDTO);
178
+ }
179
+ raceColumnDTO.setEffectiveFactor(effectiveFactor);
180
+ boolean contains = false;
181
+ for (FleetDTO fleet : raceColumnDTO.getFleets()) {
182
+ if (fleet.getName().equals(fleetDTO.getName())) {
183
+ contains = true;
184
+ break;
185
+ }
186
+ }
187
+ if (!contains) {
188
+ raceColumnDTO.addFleet(fleetDTO);
189
+ }
190
+ raceColumnDTO.setRaceIdentifier(fleetDTO, trackedRaceIdentifier);
191
+ raceColumnDTO.setRace(fleetDTO, race);
192
+ return raceColumnDTO;
193
+ }
194
+
195
+ public RaceColumnDTO createEmptyRaceColumn(String raceColumnName, boolean medalRace, String regattaName,
196
+ String seriesName, boolean isMetaLeaderboardColumn) {
197
+ final RaceColumnDTO raceColumn = RaceColumnDTOFactory.INSTANCE.createRaceColumnDTO(raceColumnName,
198
+ medalRace, /* explicit factor */ null, regattaName, seriesName, isMetaLeaderboardColumn);
199
+ races.add(raceColumn);
200
+ return raceColumn;
201
+ }
202
+
203
+ public RaceColumnDTO getRaceColumnByName(String raceColumnName) {
204
+ for (RaceColumnDTO race : races) {
205
+ if (race.getRaceColumnName().equals(raceColumnName)) {
206
+ return race;
207
+ }
208
+ }
209
+ return null;
210
+ }
211
+
212
+ public int getRaceColumnsCount() {
213
+ return races.size();
214
+ }
215
+
216
+ public int getRacesCount() {
217
+ int result = 0;
218
+ for (RaceColumnDTO race : getRaceList()) {
219
+ result += race.getFleets().size();
220
+ }
221
+ return result;
222
+ }
223
+
224
+ public int getTrackedRacesCount() {
225
+ int result = 0;
226
+ for (RaceColumnDTO raceColumn : getRaceList()) {
227
+ for (FleetDTO fleet : raceColumn.getFleets()) {
228
+ RaceDTO race = raceColumn.getRace(fleet);
229
+ if(race != null && race.trackedRace != null && race.trackedRace.hasGPSData && race.trackedRace.hasWindData) {
230
+ result++;
231
+ }
232
+ }
233
+ }
234
+ return result;
235
+ }
236
+
237
+ public List<RaceColumnDTO> getRaceList() {
238
+ return races;
239
+ }
240
+
241
+ public void setRaceList(List<RaceColumnDTO> raceList) {
242
+ this.races = raceList;
243
+ }
244
+
245
+ public boolean raceListContains(String raceColumnName) {
246
+ return getRaceColumnByName(raceColumnName) != null;
247
+ }
248
+
249
+ public void moveRaceUp(String raceColumnName) {
250
+ RaceColumnDTO race = getRaceColumnByName(raceColumnName);
251
+ int index = races.indexOf(race);
252
+ index--;
253
+ if (index >= 0) {
254
+ races.remove(index + 1);
255
+ races.add(index, race);
256
+ }
257
+ }
258
+
259
+ public void moveRaceDown(String raceColumnName) {
260
+ RaceColumnDTO race = getRaceColumnByName(raceColumnName);
261
+ int index = races.indexOf(race);
262
+ if (index != -1) {
263
+ index++;
264
+ if (index < races.size()) {
265
+ races.remove(index - 1);
266
+ races.add(index, race);
267
+ }
268
+ }
269
+ }
270
+
271
+ public void setIsMedalRace(String raceColumnName, boolean medalRace) {
272
+ getRaceColumnByName(raceColumnName).setMedalRace(medalRace);
273
+ }
274
+
275
+ /**
276
+ * @return The earliest start date of the races, or <code>null</code> if no start dates of the races are available.
277
+ */
278
+ public Date getStartDate() {
279
+ Date leaderboardStart = null;
280
+ for (RaceColumnDTO race : getRaceList()) {
281
+ for (FleetDTO fleet: race.getFleets()) {
282
+ Date raceStart = race.getStartDate(fleet);
283
+ if (raceStart != null) {
284
+ if (leaderboardStart == null) {
285
+ leaderboardStart = new Date();
286
+ } else {
287
+ leaderboardStart = leaderboardStart.before(raceStart) ? leaderboardStart : raceStart;
288
+ }
289
+ }
290
+ }
291
+ }
292
+ return leaderboardStart;
293
+ }
294
+
295
+ /**
296
+ * Takes the {@link PlacemarkOrderDTO} of all races in this leaderboard, if the PlacemarkOrderDTO for the race is
297
+ * available, and fills all {@link PlacemarkDTO} in a new PlacemarkOrderDTO.<br />
298
+ * The order of the races in this leaderboard determine the order of the PlacemarkDTOs in the PlacemarkOrderDTO.
299
+ *
300
+ * @return The places of this leaderboard in form of a {@link PlacemarkOrderDTO}, or <code>null</code> if the
301
+ * {@link PlacemarkOrderDTO places} of no race are available
302
+ */
303
+ public PlacemarkOrderDTO getPlaces() {
304
+ PlacemarkOrderDTO leaderboardPlaces = null;
305
+ for (RaceColumnDTO race : getRaceList()) {
306
+ PlacemarkOrderDTO racePlaces = race.getPlaces();
307
+ if (racePlaces != null) {
308
+ if (leaderboardPlaces == null) {
309
+ leaderboardPlaces = new PlacemarkOrderDTO();
310
+ }
311
+ leaderboardPlaces.getPlacemarks().addAll(racePlaces.getPlacemarks());
312
+ }
313
+ }
314
+ return leaderboardPlaces;
315
+ }
316
+
317
+ /**
318
+ * @return <code>true</code> if the leaderboard contains a race which is live
319
+ */
320
+ public boolean hasLiveRace(long serverTimePointAsMillis) {
321
+ for (RaceColumnDTO race : getRaceList()) {
322
+ for (FleetDTO fleet : race.getFleets()) {
323
+ if (race.isLive(fleet, serverTimePointAsMillis)) {
324
+ return true;
325
+ }
326
+ }
327
+ }
328
+ return false;
329
+ }
330
+
331
+ public List<Util.Pair<RaceColumnDTO, FleetDTO>> getLiveRaces(long serverTimePointAsMillis) {
332
+ List<Util.Pair<RaceColumnDTO, FleetDTO>> result = new ArrayList<Util.Pair<RaceColumnDTO, FleetDTO>>();
333
+ for (RaceColumnDTO race : getRaceList()) {
334
+ for (FleetDTO fleet : race.getFleets()) {
335
+ if (race.isLive(fleet, serverTimePointAsMillis)) {
336
+ result.add(new Util.Pair<RaceColumnDTO, FleetDTO>(race, fleet));
337
+ }
338
+ }
339
+ }
340
+ return result;
341
+ }
342
+
343
+ @Override
344
+ public int hashCode() {
345
+ final int prime = 31;
346
+ int result = 1;
347
+ result = prime * result + ((competitorDisplayNames == null) ? 0 : competitorDisplayNames.hashCode());
348
+ result = prime * result + Arrays.hashCode(discardThresholds);
349
+ result = prime * result + (hasCarriedPoints ? 1231 : 1237);
350
+ result = prime * result + ((type == null) ? 0 : type.hashCode());
351
+ result = prime * result + ((name == null) ? 0 : name.hashCode());
352
+ result = prime * result + ((scoringScheme == null) ? 0 : scoringScheme.hashCode());
353
+ if (races == null) {
354
+ result = prime * result;
355
+ } else {
356
+ List<String> raceNames = new ArrayList<String>();
357
+ for (RaceColumnDTO race : races) {
358
+ raceNames.add(race.getName());
359
+ }
360
+ result = prime * result + raceNames.hashCode();
361
+ }
362
+ result = prime * result + ((rows == null) ? 0 : rows.hashCode());
363
+ return result;
364
+ }
365
+
366
+ @Override
367
+ public boolean equals(Object obj) {
368
+ if (this == obj)
369
+ return true;
370
+ if (obj == null)
371
+ return false;
372
+ if (getClass() != obj.getClass())
373
+ return false;
374
+ AbstractLeaderboardDTO other = (AbstractLeaderboardDTO) obj;
375
+ if (competitorDisplayNames == null) {
376
+ if (other.competitorDisplayNames != null)
377
+ return false;
378
+ } else if (!competitorDisplayNames.equals(other.competitorDisplayNames))
379
+ return false;
380
+ if (!Arrays.equals(discardThresholds, other.discardThresholds))
381
+ return false;
382
+ if (hasCarriedPoints != other.hasCarriedPoints)
383
+ return false;
384
+ if (type != other.type)
385
+ return false;
386
+ if (scoringScheme != other.scoringScheme)
387
+ return false;
388
+ if (name == null) {
389
+ if (other.name != null)
390
+ return false;
391
+ } else if (!name.equals(other.name))
392
+ return false;
393
+ if (races == null) {
394
+ if (other.races != null)
395
+ return false;
396
+ } else {
397
+ // compare race column names only, not the contents
398
+ if (races.size() != (other.races==null?0:other.races.size())) {
399
+ return false;
400
+ }
401
+ List<String> raceColumnNames = new ArrayList<String>(races.size());
402
+ List<String> otherRaceColumnNames = new ArrayList<String>(races.size());
403
+ for (RaceColumnDTO race : races) {
404
+ raceColumnNames.add(race.getName());
405
+ }
406
+ if (other.races != null) {
407
+ for (RaceColumnDTO otherRace : other.races) {
408
+ otherRaceColumnNames.add(otherRace.getName());
409
+ }
410
+ }
411
+ if (!raceColumnNames.equals(otherRaceColumnNames))
412
+ return false;
413
+ if (rows == null) {
414
+ if (other.rows != null)
415
+ return false;
416
+ } else if (!rows.equals(other.rows))
417
+ return false;
418
+ }
419
+ return true;
420
+ }
421
+
422
+ public boolean isDisplayNameSet(CompetitorDTO competitor) {
423
+ return competitorDisplayNames.get(competitor) != null;
424
+ }
425
+
426
+ public Long getDelayToLiveInMillisForLatestRace() {
427
+ return delayToLiveInMillisForLatestRace;
428
+ }
429
+
430
+ public void setDelayToLiveInMillisForLatestRace(Long delayToLiveInMillisForLatestRace) {
431
+ this.delayToLiveInMillisForLatestRace = delayToLiveInMillisForLatestRace;
432
+ }
433
+}
java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/BasicRaceDTO.java
... ...
@@ -0,0 +1,112 @@
1
+package com.sap.sailing.domain.common.dto;
2
+
3
+import java.util.Date;
4
+
5
+import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
6
+import com.sap.sailing.domain.common.TimingConstants;
7
+
8
+/**
9
+ * Master data about a single race that is to be transferred to the client. Holds only timing and a bit
10
+ * of information about the tracked race, if any. See also {@link RaceDTO} for a more comprehensive set
11
+ * of data about a race to be serialized to a client.<p>
12
+ *
13
+ * @author Axel Uhl (d043530)
14
+ *
15
+ */
16
+public class BasicRaceDTO extends NamedDTO {
17
+ private static final long serialVersionUID = -7884808503795229609L;
18
+
19
+ public Date startOfRace;
20
+ public Date endOfRace;
21
+ public TrackedRaceDTO trackedRace;
22
+
23
+ public BasicRaceDTO() {} // for GWT serialization only
24
+
25
+ public BasicRaceDTO(RegattaAndRaceIdentifier raceIdentifier, TrackedRaceDTO trackedRace) {
26
+ super(raceIdentifier.getRaceName());
27
+ this.trackedRace = trackedRace;
28
+ }
29
+
30
+ /**
31
+ * @see {@link TrackedRace#isLive} for further explanation.
32
+ * @param serverTimePointAsMillis
33
+ * the time point (in server clock time) at which to determine whether the race is/was live
34
+ * @return <code>true</code> if <code>serverTimePointAsMillis</code> is between (inclusively) the start and end time
35
+ * point of the "live" interval as defined above.
36
+ */
37
+ public boolean isLive(long serverTimePointAsMillis) {
38
+ final Date startOfLivePeriod;
39
+ final Date endOfLivePeriod;
40
+ if (trackedRace == null || !trackedRace.hasGPSData || !trackedRace.hasWindData) {
41
+ startOfLivePeriod = null;
42
+ endOfLivePeriod = null;
43
+ } else {
44
+ if (startOfRace == null) {
45
+ startOfLivePeriod = trackedRace.startOfTracking;
46
+ } else {
47
+ startOfLivePeriod = new Date(startOfRace.getTime() - TimingConstants.PRE_START_PHASE_DURATION_IN_MILLIS);
48
+ }
49
+ if (endOfRace == null) {
50
+ if (trackedRace.timePointOfNewestEvent != null) {
51
+ endOfLivePeriod = new Date(trackedRace.timePointOfNewestEvent.getTime()
52
+ + TimingConstants.IS_LIVE_GRACE_PERIOD_IN_MILLIS);
53
+ } else {
54
+ endOfLivePeriod = null;
55
+ }
56
+ } else {
57
+ endOfLivePeriod = new Date(endOfRace.getTime() + TimingConstants.IS_LIVE_GRACE_PERIOD_IN_MILLIS);
58
+ }
59
+ }
60
+
61
+ // if an empty timepoint is given then take the start of the race
62
+ if (serverTimePointAsMillis == 0) {
63
+ serverTimePointAsMillis = startOfLivePeriod.getTime()+1;
64
+ }
65
+
66
+ // whenLastTrackedRaceWasLive is null if there is no tracked race for fleet, or the tracked race hasn't started yet at the server time
67
+ // when this DTO was assembled, or there were no GPS or wind data
68
+ final boolean result =
69
+ startOfLivePeriod != null &&
70
+ endOfLivePeriod != null &&
71
+ startOfLivePeriod.getTime() <= serverTimePointAsMillis &&
72
+ serverTimePointAsMillis <= endOfLivePeriod.getTime();
73
+ return result;
74
+ }
75
+
76
+ @Override
77
+ public int hashCode() {
78
+ final int prime = 31;
79
+ int result = super.hashCode();
80
+ result = prime * result + ((endOfRace == null) ? 0 : endOfRace.hashCode());
81
+ result = prime * result + ((startOfRace == null) ? 0 : startOfRace.hashCode());
82
+ result = prime * result + ((trackedRace == null) ? 0 : trackedRace.hashCode());
83
+ return result;
84
+ }
85
+
86
+ @Override
87
+ public boolean equals(Object obj) {
88
+ if (this == obj)
89
+ return true;
90
+ if (!super.equals(obj))
91
+ return false;
92
+ if (getClass() != obj.getClass())
93
+ return false;
94
+ BasicRaceDTO other = (BasicRaceDTO) obj;
95
+ if (endOfRace == null) {
96
+ if (other.endOfRace != null)
97
+ return false;
98
+ } else if (!endOfRace.equals(other.endOfRace))
99
+ return false;
100
+ if (startOfRace == null) {
101
+ if (other.startOfRace != null)
102
+ return false;
103
+ } else if (!startOfRace.equals(other.startOfRace))
104
+ return false;
105
+ if (trackedRace == null) {
106
+ if (other.trackedRace != null)
107
+ return false;
108
+ } else if (!trackedRace.equals(other.trackedRace))
109
+ return false;
110
+ return true;
111
+ }
112
+}
java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/LeaderboardDTO.java
... ...
@@ -62,7 +62,6 @@ public class LeaderboardDTO extends AbstractLeaderboardDTO implements Serializab
62 62
/**
63 63
* @param uuidGenerator used to provide the {@link #id ID} for this object (see also {@link #getId()}) and for any clones produced
64 64
* from it by the {@link #clone()} operation.
65
- * @param hasOverallDetails TODO
66 65
*/
67 66
public LeaderboardDTO(Date timePointOfLastCorrectionsValidity, String comment, ScoringSchemeType scoringScheme, boolean higherScoreIsBetter, UUIDGenerator uuidGenerator, boolean hasOverallDetails) {
68 67
initCollections();
... ...
@@ -79,7 +78,7 @@ public class LeaderboardDTO extends AbstractLeaderboardDTO implements Serializab
79 78
this.suppressedCompetitors = new ArrayList<CompetitorDTO>();
80 79
}
81 80
82
- public LeaderboardDTO(String id) {
81
+ protected LeaderboardDTO(String id) {
83 82
initCollections();
84 83
this.id = id;
85 84
}
java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/MetaLeaderboardRaceColumnDTO.java
... ...
@@ -0,0 +1,49 @@
1
+package com.sap.sailing.domain.common.dto;
2
+
3
+import java.util.ArrayList;
4
+
5
+public class MetaLeaderboardRaceColumnDTO extends RaceColumnDTO {
6
+
7
+ private static final long serialVersionUID = 6152752963316150432L;
8
+
9
+ private ArrayList<BasicRaceDTO> raceList = new ArrayList<BasicRaceDTO>();
10
+
11
+ public boolean isLive(FleetDTO fleet, long serverTimePointAsMillis) {
12
+ for (BasicRaceDTO race : raceList) {
13
+ if (race.isLive(serverTimePointAsMillis)) {
14
+ return true;
15
+ }
16
+ }
17
+ return false;
18
+ }
19
+
20
+ public void addRace(BasicRaceDTO race) {
21
+ raceList.add(race);
22
+ }
23
+
24
+ @Override
25
+ public int hashCode() {
26
+ final int prime = 31;
27
+ int result = super.hashCode();
28
+ result = prime * result + ((raceList == null) ? 0 : raceList.hashCode());
29
+ return result;
30
+ }
31
+
32
+ @Override
33
+ public boolean equals(Object obj) {
34
+ if (this == obj)
35
+ return true;
36
+ if (!super.equals(obj))
37
+ return false;
38
+ if (getClass() != obj.getClass())
39
+ return false;
40
+ MetaLeaderboardRaceColumnDTO other = (MetaLeaderboardRaceColumnDTO) obj;
41
+ if (raceList == null) {
42
+ if (other.raceList != null)
43
+ return false;
44
+ } else if (!raceList.equals(other.raceList))
45
+ return false;
46
+ return true;
47
+ }
48
+
49
+}
java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/RaceColumnDTOFactory.java
... ...
@@ -7,15 +7,17 @@ public class RaceColumnDTOFactory {
7 7
* @param regattaName
8 8
* must not be <code>null</code> if <code>seriesName</code> is not <code>null</code>
9 9
* @param seriesName
10
- * when <code>null</code>, a regular {@link RaceColumnDTO} will be produced and initialized from the
11
- * properties passed; otherwise, a {@link RaceColumnInSeriesDTO} will be created.
10
+ * when <code>null</code>, a regular {@link RaceColumnDTO} or {@link MetaLeaderboardRaceColumnDTO} will
11
+ * be produced (depending on <code>isMetaLeaderboardColumn</code>) and initialized from the properties
12
+ * passed; otherwise, a {@link RaceColumnInSeriesDTO} will be created.
12 13
*/
13
- public RaceColumnDTO createRaceColumnDTO(String columnName, boolean isMedal, Double explicitFactor, String regattaName, String seriesName) {
14
+ public RaceColumnDTO createRaceColumnDTO(String columnName, boolean isMedal, Double explicitFactor,
15
+ String regattaName, String seriesName, boolean isMetaLeaderboardColumn) {
14 16
final RaceColumnDTO raceColumnDTO;
15 17
if (seriesName != null) {
16 18
raceColumnDTO = new RaceColumnInSeriesDTO(seriesName, regattaName);
17 19
} else {
18
- raceColumnDTO = new RaceColumnDTO();
20
+ raceColumnDTO = isMetaLeaderboardColumn ? new MetaLeaderboardRaceColumnDTO() : new RaceColumnDTO();
19 21
}
20 22
fillRaceColumnDTO(raceColumnDTO, columnName, isMedal, explicitFactor);
21 23
return raceColumnDTO;
java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/RaceDTO.java
... ...
@@ -1,10 +1,7 @@
1 1
package com.sap.sailing.domain.common.dto;
2 2
3
-import java.util.Date;
4
-
5 3
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
6 4
import com.sap.sailing.domain.common.RegattaNameAndRaceName;
7
-import com.sap.sailing.domain.common.TimingConstants;
8 5
9 6
/**
10 7
* Master data about a single race that is to be transferred to the client.<p>
... ...
@@ -12,7 +9,7 @@ import com.sap.sailing.domain.common.TimingConstants;
12 9
* @author Axel Uhl (d043530)
13 10
*
14 11
*/
15
-public class RaceDTO extends NamedDTO {
12
+public class RaceDTO extends BasicRaceDTO {
16 13
private static final long serialVersionUID = 2613189982608149975L;
17 14
18 15
public enum RaceLiveState { NOT_TRACKED, TRACKED, TRACKED_AND_LIVE, TRACKED_BUT_NOT_SCHEDULED };
... ...
@@ -24,13 +21,10 @@ public class RaceDTO extends NamedDTO {
24 21
*/
25 22
public boolean isTracked;
26 23
27
- public Date startOfRace;
28
- public Date endOfRace;
29 24
public RaceStatusDTO status;
30 25
31 26
public PlacemarkOrderDTO places;
32 27
33
- public TrackedRaceDTO trackedRace;
34 28
public TrackedRaceStatisticsDTO trackedRaceStatistics;
35 29
36 30
private String regattaName;
... ...
@@ -43,21 +37,20 @@ public class RaceDTO extends NamedDTO {
43 37
}
44 38
45 39
public RaceDTO(RegattaAndRaceIdentifier raceIdentifier, TrackedRaceDTO trackedRace, boolean isCurrentlyTracked) {
46
- super(raceIdentifier.getRaceName());
40
+ super(raceIdentifier, trackedRace);
47 41
this.regattaName = raceIdentifier.getRegattaName();
48
- this.trackedRace = trackedRace;
49 42
this.isTracked = isCurrentlyTracked;
50 43
}
51 44
52 45
public RaceLiveState getLiveState(long serverTimePointAsMillis) {
53
- RaceLiveState result = RaceLiveState.NOT_TRACKED;
54
- if(trackedRace != null && trackedRace.hasGPSData && trackedRace.hasWindData) {
46
+ RaceLiveState result = RaceLiveState.NOT_TRACKED;
47
+ if (trackedRace != null && trackedRace.hasGPSData && trackedRace.hasWindData) {
55 48
result = RaceLiveState.TRACKED;
56
- if(isLive(serverTimePointAsMillis)) {
49
+ if (isLive(serverTimePointAsMillis)) {
57 50
result = RaceLiveState.TRACKED_AND_LIVE;
58
- if(startOfRace == null) {
51
+ if (startOfRace == null) {
59 52
result = RaceLiveState.TRACKED_BUT_NOT_SCHEDULED;
60
- }
53
+ }
61 54
}
62 55
}
63 56
return result;
... ...
@@ -71,64 +64,15 @@ public class RaceDTO extends NamedDTO {
71 64
return regattaName;
72 65
}
73 66
74
- /**
75
- * @see {@link TrackedRace#isLive} for further explanation.
76
- * @param serverTimePointAsMillis
77
- * the time point (in server clock time) at which to determine whether the race is/was live
78
- * @return <code>true</code> if <code>serverTimePointAsMillis</code> is between (inclusively) the start and end time
79
- * point of the "live" interval as defined above.
80
- */
81
- public boolean isLive(long serverTimePointAsMillis) {
82
- final Date startOfLivePeriod;
83
- final Date endOfLivePeriod;
84
- if (trackedRace == null || !trackedRace.hasGPSData || !trackedRace.hasWindData) {
85
- startOfLivePeriod = null;
86
- endOfLivePeriod = null;
87
- } else {
88
- if (startOfRace == null) {
89
- startOfLivePeriod = trackedRace.startOfTracking;
90
- } else {
91
- startOfLivePeriod = new Date(startOfRace.getTime() - TimingConstants.PRE_START_PHASE_DURATION_IN_MILLIS);
92
- }
93
- if (endOfRace == null) {
94
- if (trackedRace.timePointOfNewestEvent != null) {
95
- endOfLivePeriod = new Date(trackedRace.timePointOfNewestEvent.getTime()
96
- + TimingConstants.IS_LIVE_GRACE_PERIOD_IN_MILLIS);
97
- } else {
98
- endOfLivePeriod = null;
99
- }
100
- } else {
101
- endOfLivePeriod = new Date(endOfRace.getTime() + TimingConstants.IS_LIVE_GRACE_PERIOD_IN_MILLIS);
102
- }
103
- }
104
-
105
- // if an empty timepoint is given then take the start of the race
106
- if (serverTimePointAsMillis == 0) {
107
- serverTimePointAsMillis = startOfLivePeriod.getTime()+1;
108
- }
109
-
110
- // whenLastTrackedRaceWasLive is null if there is no tracked race for fleet, or the tracked race hasn't started yet at the server time
111
- // when this DTO was assembled, or there were no GPS or wind data
112
- final boolean result =
113
- startOfLivePeriod != null &&
114
- endOfLivePeriod != null &&
115
- startOfLivePeriod.getTime() <= serverTimePointAsMillis &&
116
- serverTimePointAsMillis <= endOfLivePeriod.getTime();
117
- return result;
118
- }
119
-
120 67
@Override
121 68
public int hashCode() {
122 69
final int prime = 31;
123 70
int result = super.hashCode();
124 71
result = prime * result + ((boatClass == null) ? 0 : boatClass.hashCode());
125
- result = prime * result + ((endOfRace == null) ? 0 : endOfRace.hashCode());
126 72
result = prime * result + (isTracked ? 1231 : 1237);
127 73
result = prime * result + ((places == null) ? 0 : places.hashCode());
128 74
result = prime * result + ((regattaName == null) ? 0 : regattaName.hashCode());
129
- result = prime * result + ((startOfRace == null) ? 0 : startOfRace.hashCode());
130 75
result = prime * result + ((status == null) ? 0 : status.hashCode());
131
- result = prime * result + ((trackedRace == null) ? 0 : trackedRace.hashCode());
132 76
return result;
133 77
}
134 78
... ...
@@ -146,11 +90,6 @@ public class RaceDTO extends NamedDTO {
146 90
return false;
147 91
} else if (!boatClass.equals(other.boatClass))
148 92
return false;
149
- if (endOfRace == null) {
150
- if (other.endOfRace != null)
151
- return false;
152
- } else if (!endOfRace.equals(other.endOfRace))
153
- return false;
154 93
if (isTracked != other.isTracked)
155 94
return false;
156 95
if (places == null) {
... ...
@@ -163,21 +102,11 @@ public class RaceDTO extends NamedDTO {
163 102
return false;
164 103
} else if (!regattaName.equals(other.regattaName))
165 104
return false;
166
- if (startOfRace == null) {
167
- if (other.startOfRace != null)
168
- return false;
169
- } else if (!startOfRace.equals(other.startOfRace))
170
- return false;
171 105
if (status == null) {
172 106
if (other.status != null)
173 107
return false;
174 108
} else if (!status.equals(other.status))
175 109
return false;
176
- if (trackedRace == null) {
177
- if (other.trackedRace != null)
178
- return false;
179
- } else if (!trackedRace.equals(other.trackedRace))
180
- return false;
181 110
return true;
182 111
}
183 112
}
java/com.sap.sailing.domain.persistence/META-INF/MANIFEST.MF
... ...
@@ -14,7 +14,8 @@ Require-Bundle: com.sap.sse.mongodb,
14 14
com.sap.sailing.server.gateway.serialization,
15 15
com.sap.sse.common,
16 16
com.sap.sse,
17
- com.sap.sailing.server.gateway.serialization.shared.android
17
+ com.sap.sailing.server.gateway.serialization.shared.android,
18
+ com.sap.sse.shared.android
18 19
Export-Package: com.sap.sailing.domain.persistence,
19 20
com.sap.sailing.domain.persistence.impl;
20 21
x-friends:="com.sap.sailing.domain.persistence.test,
java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/DomainObjectFactoryImpl.java
... ...
@@ -224,11 +224,11 @@ import com.sap.sse.common.impl.MillisecondsDurationImpl;
224 224
import com.sap.sse.common.impl.MillisecondsTimePoint;
225 225
import com.sap.sse.common.impl.RGBColor;
226 226
import com.sap.sse.common.impl.TimeRangeImpl;
227
-import com.sap.sse.common.media.ImageDescriptor;
228
-import com.sap.sse.common.media.ImageDescriptorImpl;
229 227
import com.sap.sse.common.media.MimeType;
230
-import com.sap.sse.common.media.VideoDescriptor;
231
-import com.sap.sse.common.media.VideoDescriptorImpl;
228
+import com.sap.sse.shared.media.ImageDescriptor;
229
+import com.sap.sse.shared.media.VideoDescriptor;
230
+import com.sap.sse.shared.media.impl.ImageDescriptorImpl;
231
+import com.sap.sse.shared.media.impl.VideoDescriptorImpl;
232 232
233 233
public class DomainObjectFactoryImpl implements DomainObjectFactory {
234 234
private static final Logger logger = Logger.getLogger(DomainObjectFactoryImpl.class.getName());
java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoObjectFactoryImpl.java
... ...
@@ -1,1533 +1,1533 @@
1
-package com.sap.sailing.domain.persistence.impl;
2
-
3
-import java.io.Serializable;
4
-import java.net.URL;
5
-import java.util.List;
6
-import java.util.Map.Entry;
7
-import java.util.logging.Level;
8
-import java.util.logging.Logger;
9
-
10
-import org.bson.types.ObjectId;
11
-import org.json.simple.JSONObject;
12
-
13
-import com.mongodb.BasicDBList;
14
-import com.mongodb.BasicDBObject;
15
-import com.mongodb.BasicDBObjectBuilder;
16
-import com.mongodb.DB;
17
-import com.mongodb.DBCollection;
18
-import com.mongodb.DBObject;
19
-import com.mongodb.WriteConcern;
20
-import com.mongodb.util.JSON;
21
-import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor;
22
-import com.sap.sailing.domain.abstractlog.race.RaceLogCourseAreaChangedEvent;
23
-import com.sap.sailing.domain.abstractlog.race.RaceLogCourseDesignChangedEvent;
24
-import com.sap.sailing.domain.abstractlog.race.RaceLogDependentStartTimeEvent;
25
-import com.sap.sailing.domain.abstractlog.race.RaceLogEndOfTrackingEvent;
26
-import com.sap.sailing.domain.abstractlog.race.RaceLogEvent;
27
-import com.sap.sailing.domain.abstractlog.race.RaceLogFinishPositioningConfirmedEvent;
28
-import com.sap.sailing.domain.abstractlog.race.RaceLogFinishPositioningListChangedEvent;
29
-import com.sap.sailing.domain.abstractlog.race.RaceLogFixedMarkPassingEvent;
30
-import com.sap.sailing.domain.abstractlog.race.RaceLogFlagEvent;
31
-import com.sap.sailing.domain.abstractlog.race.RaceLogGateLineOpeningTimeEvent;
32
-import com.sap.sailing.domain.abstractlog.race.RaceLogPassChangeEvent;
33
-import com.sap.sailing.domain.abstractlog.race.RaceLogPathfinderEvent;
34
-import com.sap.sailing.domain.abstractlog.race.RaceLogProtestStartTimeEvent;
35
-import com.sap.sailing.domain.abstractlog.race.RaceLogRaceStatusEvent;
36
-import com.sap.sailing.domain.abstractlog.race.RaceLogRevokeEvent;
37
-import com.sap.sailing.domain.abstractlog.race.RaceLogStartOfTrackingEvent;
38
-import com.sap.sailing.domain.abstractlog.race.RaceLogStartProcedureChangedEvent;
39
-import com.sap.sailing.domain.abstractlog.race.RaceLogStartTimeEvent;
40
-import com.sap.sailing.domain.abstractlog.race.RaceLogSuppressedMarkPassingsEvent;
41
-import com.sap.sailing.domain.abstractlog.race.RaceLogWindFixEvent;
42
-import com.sap.sailing.domain.abstractlog.race.scoring.RaceLogAdditionalScoringInformationEvent;
43
-import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogCloseOpenEndedDeviceMappingEvent;
44
-import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogDefineMarkEvent;
45
-import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogDenoteForTrackingEvent;
46
-import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogDeviceCompetitorMappingEvent;
47
-import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogDeviceMarkMappingEvent;
48
-import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogRegisterCompetitorEvent;
49
-import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogStartTrackingEvent;
50
-import com.sap.sailing.domain.abstractlog.regatta.RegattaLogEvent;
51
-import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogCloseOpenEndedDeviceMappingEvent;
52
-import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceCompetitorMappingEvent;
53
-import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceMarkMappingEvent;
54
-import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogRegisterCompetitorEvent;
55
-import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogRevokeEvent;
56
-import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogSetCompetitorTimeOnDistanceAllowancePerNauticalMileEvent;
57
-import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogSetCompetitorTimeOnTimeFactorEvent;
58
-import com.sap.sailing.domain.abstractlog.shared.events.DeviceMappingEvent;
59
-import com.sap.sailing.domain.base.Competitor;
60
-import com.sap.sailing.domain.base.ControlPoint;
61
-import com.sap.sailing.domain.base.ControlPointWithTwoMarks;
62
-import com.sap.sailing.domain.base.CourseArea;
63
-import com.sap.sailing.domain.base.CourseBase;
64
-import com.sap.sailing.domain.base.Event;
65
-import com.sap.sailing.domain.base.Fleet;
66
-import com.sap.sailing.domain.base.Mark;
67
-import com.sap.sailing.domain.base.RaceColumn;
68
-import com.sap.sailing.domain.base.RaceDefinition;
69
-import com.sap.sailing.domain.base.Regatta;
70
-import com.sap.sailing.domain.base.RemoteSailingServerReference;
71
-import com.sap.sailing.domain.base.SailingServerConfiguration;
72
-import com.sap.sailing.domain.base.Series;
73
-import com.sap.sailing.domain.base.Venue;
74
-import com.sap.sailing.domain.base.Waypoint;
75
-import com.sap.sailing.domain.base.configuration.DeviceConfiguration;
76
-import com.sap.sailing.domain.base.configuration.DeviceConfigurationMatcher;
77
-import com.sap.sailing.domain.base.configuration.RegattaConfiguration;
78
-import com.sap.sailing.domain.base.configuration.impl.DeviceConfigurationMatcherMulti;
79
-import com.sap.sailing.domain.base.configuration.impl.DeviceConfigurationMatcherSingle;
80
-import com.sap.sailing.domain.base.impl.FleetImpl;
81
-import com.sap.sailing.domain.common.Bearing;
82
-import com.sap.sailing.domain.common.MaxPointsReason;
83
-import com.sap.sailing.domain.common.PassingInstruction;
84
-import com.sap.sailing.domain.common.Positioned;
85
-import com.sap.sailing.domain.common.RaceIdentifier;
86
-import com.sap.sailing.domain.common.Speed;
87
-import com.sap.sailing.domain.common.SpeedWithBearing;
88
-import com.sap.sailing.domain.common.Wind;
89
-import com.sap.sailing.domain.common.WindSource;
90
-import com.sap.sailing.domain.common.racelog.tracking.TransformationException;
91
-import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard;
92
-import com.sap.sailing.domain.leaderboard.Leaderboard;
93
-import com.sap.sailing.domain.leaderboard.LeaderboardGroup;
94
-import com.sap.sailing.domain.leaderboard.RegattaLeaderboard;
95
-import com.sap.sailing.domain.leaderboard.ResultDiscardingRule;
96
-import com.sap.sailing.domain.leaderboard.SettableScoreCorrection;
97
-import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule;
98
-import com.sap.sailing.domain.persistence.MongoObjectFactory;
99
-import com.sap.sailing.domain.persistence.racelog.tracking.DeviceIdentifierMongoHandler;
100
-import com.sap.sailing.domain.persistence.racelog.tracking.impl.PlaceHolderDeviceIdentifierMongoHandler;
101
-import com.sap.sailing.domain.racelog.RaceLogIdentifier;
102
-import com.sap.sailing.domain.racelogtracking.DeviceIdentifier;
103
-import com.sap.sailing.domain.regattalike.RegattaLikeIdentifier;
104
-import com.sap.sailing.domain.tracking.TrackedRace;
105
-import com.sap.sailing.domain.tracking.TrackedRegatta;
106
-import com.sap.sailing.domain.tracking.WindTrack;
107
-import com.sap.sailing.server.gateway.serialization.JsonSerializer;
108
-import com.sap.sailing.server.gateway.serialization.impl.CompetitorJsonSerializer;
109
-import com.sap.sailing.server.gateway.serialization.impl.DeviceConfigurationJsonSerializer;
110
-import com.sap.sailing.server.gateway.serialization.impl.RegattaConfigurationJsonSerializer;
111
-import com.sap.sse.common.Duration;
112
-import com.sap.sse.common.NoCorrespondingServiceRegisteredException;
113
-import com.sap.sse.common.TimePoint;
114
-import com.sap.sse.common.TimeRange;
115
-import com.sap.sse.common.Timed;
116
-import com.sap.sse.common.TypeBasedServiceFinder;
117
-import com.sap.sse.common.TypeBasedServiceFinderFactory;
118
-import com.sap.sse.common.Util;
119
-import com.sap.sse.common.impl.MillisecondsTimePoint;
120
-import com.sap.sse.common.media.ImageDescriptor;
121
-import com.sap.sse.common.media.VideoDescriptor;
122
-
123
-public class MongoObjectFactoryImpl implements MongoObjectFactory {
124
- private static Logger logger = Logger.getLogger(MongoObjectFactoryImpl.class.getName());
125
- private final DB database;
126
- private final CompetitorJsonSerializer competitorSerializer = CompetitorJsonSerializer.create();
127
- private final TypeBasedServiceFinder<DeviceIdentifierMongoHandler> deviceIdentifierServiceFinder;
128
-
129
- /**
130
- * Uses <code>null</code> for the device type service finder and hence will be unable to store device identifiers.
131
- * Use this constructor only for testing purposes or in cases where there will happen absolutely no access to
132
- * {@link DeviceIdentifier} objects.
133
- */
134
- public MongoObjectFactoryImpl(DB database) {
135
- this(database, /* deviceTypeServiceFinder */ null);
136
- }
137
-
138
- public MongoObjectFactoryImpl(DB database, TypeBasedServiceFinderFactory serviceFinderFactory) {
139
- this.database = database;
140
- if (serviceFinderFactory != null) {
141
- this.deviceIdentifierServiceFinder = serviceFinderFactory.createServiceFinder(DeviceIdentifierMongoHandler.class);
142
- this.deviceIdentifierServiceFinder.setFallbackService(new PlaceHolderDeviceIdentifierMongoHandler());
143
- } else {
144
- this.deviceIdentifierServiceFinder = null;
145
- }
146
- }
147
-
148
- @Override
149
- public DB getDatabase() {
150
- return database;
151
- }
152
-
153
- public DBObject storeWind(Wind wind) {
154
- DBObject result = new BasicDBObject();
155
- storePositioned(wind, result);
156
- storeTimed(wind, result);
157
- storeSpeedWithBearing(wind, result);
158
- return result;
159
- }
160
-
161
- public static void storeTimePoint(TimePoint timePoint, DBObject result, String fieldName) {
162
- if (timePoint != null) {
163
- result.put(fieldName, timePoint.asMillis());
164
- }
165
- }
166
-
167
- public static void storeTimePoint(TimePoint timePoint, DBObject result, FieldNames field) {
168
- storeTimePoint(timePoint, result, field.name());
169
- }
170
-
171
- public static void storeTimeRange(TimeRange timeRange, DBObject result, FieldNames field) {
172
- if (timeRange != null) {
173
- DBObject timeRangeObj = new BasicDBObject();
174
- storeTimePoint(timeRange.from(), timeRangeObj, FieldNames.FROM_MILLIS);
175
- storeTimePoint(timeRange.to(), timeRangeObj, FieldNames.TO_MILLIS);
176
- result.put(field.name(), timeRangeObj);
177
- }
178
- }
179
-
180
- public void storeTimed(Timed timed, DBObject result) {
181
- if (timed.getTimePoint() != null) {
182
- storeTimePoint(timed.getTimePoint(), result, FieldNames.TIME_AS_MILLIS);
183
- }
184
- }
185
-
186
- public void storeSpeedWithBearing(SpeedWithBearing speedWithBearing, DBObject result) {
187
- storeSpeed(speedWithBearing, result);
188
- storeBearing(speedWithBearing.getBearing(), result);
189
-
190
- }
191
-
192
- public void storeBearing(Bearing bearing, DBObject result) {
193
- result.put(FieldNames.DEGREE_BEARING.name(), bearing.getDegrees());
194
- }
195
-
196
- public void storeSpeed(Speed speed, DBObject result) {
197
- result.put(FieldNames.KNOT_SPEED.name(), speed.getKnots());
198
- }
199
-
200
- public void storePositioned(Positioned positioned, DBObject result) {
201
- if (positioned.getPosition() != null) {
202
- result.put(FieldNames.LAT_DEG.name(), positioned.getPosition().getLatDeg());
203
- result.put(FieldNames.LNG_DEG.name(), positioned.getPosition().getLngDeg());
204
- }
205
- }
206
-
207
- @Override
208
- public void addWindTrackDumper(TrackedRegatta trackedRegatta, TrackedRace trackedRace, WindSource windSource) {
209
- WindTrack windTrack = trackedRace.getOrCreateWindTrack(windSource);
210
- windTrack.addListener(new MongoWindListener(trackedRace, trackedRegatta.getRegatta().getName(), windSource, this, database));
211
- }
212
-
213
- public DBCollection getWindTrackCollection() {
214
- DBCollection result = database.getCollection(CollectionNames.WIND_TRACKS.name());
215
- result.createIndex(new BasicDBObject(FieldNames.REGATTA_NAME.name(), null));
216
- return result;
217
- }
218
-
219
- public DBCollection getGPSFixCollection() {
220
- DBCollection gpsFixCollection = database.getCollection(CollectionNames.GPS_FIXES.name());
221
- DBObject index = new BasicDBObject();
222
- index.put(FieldNames.DEVICE_ID.name(), null);
223
- index.put(FieldNames.TIME_AS_MILLIS.name(), null);
224
- gpsFixCollection.createIndex(index);
225
- return gpsFixCollection;
226
- }
227
-
228
- public DBCollection getGPSFixMetadataCollection() {
229
- DBCollection collection = database.getCollection(CollectionNames.GPS_FIXES_METADATA.name());
230
- DBObject index = new BasicDBObject();
231
- index.put(FieldNames.DEVICE_ID.name(), null);
232
- collection.createIndex(index);
233
- return collection;
234
- }
235
-
236
- /**
237
- * @param regattaName
238
- * the regatta name is stored only for human readability purposes because a time stamp may be a bit unhandy for
239
- * identifying where the wind fix was collected
240
- */
241
- public DBObject storeWindTrackEntry(RaceDefinition race, String regattaName, WindSource windSource, Wind wind) {
242
- BasicDBObject result = new BasicDBObject();
243
- result.put(FieldNames.RACE_ID.name(), race.getId());
244
- result.put(FieldNames.REGATTA_NAME.name(), regattaName);
245
- result.put(FieldNames.WIND_SOURCE_NAME.name(), windSource.name());
246
- if (windSource.getId() != null) {
247
- result.put(FieldNames.WIND_SOURCE_ID.name(), windSource.getId());
248
- }
249
- result.put(FieldNames.WIND.name(), storeWind(wind));
250
- return result;
251
- }
252
-
253
- private void storeRaceIdentifiers(RaceColumn raceColumn, DBObject dbObject) {
254
- BasicDBObject raceIdentifiersPerFleet = new BasicDBObject();
255
- for (Fleet fleet : raceColumn.getFleets()) {
256
- RaceIdentifier raceIdentifier = raceColumn.getRaceIdentifier(fleet);
257
- if (raceIdentifier != null) {
258
- DBObject raceIdentifierForFleet = new BasicDBObject();
259
- storeRaceIdentifier(raceIdentifierForFleet, raceIdentifier);
260
- raceIdentifiersPerFleet.put(MongoUtils.escapeDollarAndDot(fleet.getName()), raceIdentifierForFleet);
261
- }
262
- }
263
- dbObject.put(FieldNames.RACE_IDENTIFIERS.name(), raceIdentifiersPerFleet);
264
- }
265
-
266
- private void storeRaceIdentifier(DBObject dbObject, RaceIdentifier raceIdentifier) {
267
- if (raceIdentifier != null) {
268
- dbObject.put(FieldNames.EVENT_NAME.name(), raceIdentifier.getRegattaName());
269
- dbObject.put(FieldNames.RACE_NAME.name(), raceIdentifier.getRaceName());
270
- }
271
- }
272
-
273
- @Override
274
- public void storeLeaderboard(Leaderboard leaderboard) {
275
- DBCollection leaderboardCollection = database.getCollection(CollectionNames.LEADERBOARDS.name());
276
- try {
277
- leaderboardCollection.createIndex(new BasicDBObject(FieldNames.LEADERBOARD_NAME.name(), 1));
278
- } catch (NullPointerException npe) {
279
- // sometimes, for reasons yet to be clarified, ensuring an index on the name field causes an NPE
280
- logger.log(Level.SEVERE, "storeLeaderboard", npe);
281
- }
282
- BasicDBObject query = new BasicDBObject(FieldNames.LEADERBOARD_NAME.name(), leaderboard.getName());
283
- BasicDBObject dbLeaderboard = new BasicDBObject();
284
- dbLeaderboard.put(FieldNames.LEADERBOARD_NAME.name(), leaderboard.getName());
285
- if (leaderboard.getDisplayName() != null) {
286
- dbLeaderboard.put(FieldNames.LEADERBOARD_DISPLAY_NAME.name(), leaderboard.getDisplayName());
287
- }
288
- BasicDBList dbSuppressedCompetitorIds = new BasicDBList();
289
- for (Competitor suppressedCompetitor : leaderboard.getSuppressedCompetitors()) {
290
- dbSuppressedCompetitorIds.add(suppressedCompetitor.getId());
291
- }
292
- dbLeaderboard.put(FieldNames.LEADERBOARD_SUPPRESSED_COMPETITOR_IDS.name(), dbSuppressedCompetitorIds);
293
- if (leaderboard instanceof FlexibleLeaderboard) {
294
- storeFlexibleLeaderboard((FlexibleLeaderboard) leaderboard, dbLeaderboard);
295
- } else if (leaderboard instanceof RegattaLeaderboard) {
296
- storeRegattaLeaderboard((RegattaLeaderboard) leaderboard, dbLeaderboard);
297
- } else {
298
- // at least store the scoring scheme
299
- dbLeaderboard.put(FieldNames.SCORING_SCHEME_TYPE.name(), leaderboard.getScoringScheme().getType().name());
300
- }
301
- if (leaderboard.getDefaultCourseArea() != null) {
302
- dbLeaderboard.put(FieldNames.COURSE_AREA_ID.name(), leaderboard.getDefaultCourseArea().getId().toString());
303
- } else {
304
- dbLeaderboard.put(FieldNames.COURSE_AREA_ID.name(), null);
305
- }
306
- storeColumnFactors(leaderboard, dbLeaderboard);
307
- storeLeaderboardCorrectionsAndDiscards(leaderboard, dbLeaderboard);
308
- leaderboardCollection.update(query, dbLeaderboard, /* upsrt */ true, /* multi */ false, WriteConcern.SAFE);
309
- }
310
-
311
- private void storeColumnFactors(Leaderboard leaderboard, BasicDBObject dbLeaderboard) {
312
- DBObject raceColumnFactors = new BasicDBObject();
313
- for (RaceColumn raceColumn : leaderboard.getRaceColumns()) {
314
- Double explicitFactor = raceColumn.getExplicitFactor();
315
- if (explicitFactor != null) {
316
- raceColumnFactors.put(MongoUtils.escapeDollarAndDot(raceColumn.getName()), explicitFactor);
317
- }
318
- }
319
- dbLeaderboard.put(FieldNames.LEADERBOARD_COLUMN_FACTORS.name(), raceColumnFactors);
320
- }
321
-
322
- private void storeRegattaLeaderboard(RegattaLeaderboard leaderboard, DBObject dbLeaderboard) {
323
- dbLeaderboard.put(FieldNames.REGATTA_NAME.name(), leaderboard.getRegatta().getName());
324
- }
325
-
326
- private void storeFlexibleLeaderboard(FlexibleLeaderboard leaderboard, BasicDBObject dbLeaderboard) {
327
- BasicDBList dbRaceColumns = new BasicDBList();
328
- dbLeaderboard.put(FieldNames.SCORING_SCHEME_TYPE.name(), leaderboard.getScoringScheme().getType().name());
329
- dbLeaderboard.put(FieldNames.LEADERBOARD_COLUMNS.name(), dbRaceColumns);
330
- for (RaceColumn raceColumn : leaderboard.getRaceColumns()) {
331
- BasicDBObject dbRaceColumn = storeRaceColumn(raceColumn);
332
- dbRaceColumns.add(dbRaceColumn);
333
- }
334
- }
335
-
336
- private void storeLeaderboardCorrectionsAndDiscards(Leaderboard leaderboard, BasicDBObject dbLeaderboard) {
337
- if (leaderboard.hasCarriedPoints()) {
338
- BasicDBList dbCarriedPoints = new BasicDBList();
339
- dbLeaderboard.put(FieldNames.LEADERBOARD_CARRIED_POINTS_BY_ID.name(), dbCarriedPoints);
340
- for (Entry<Competitor, Double> competitorWithCarriedPoints : leaderboard
341
- .getCompetitorsForWhichThereAreCarriedPoints().entrySet()) {
342
- double carriedPoints = competitorWithCarriedPoints.getValue();
343
- Competitor competitor = competitorWithCarriedPoints.getKey();
344
- DBObject dbCarriedPointsForCompetitor = new BasicDBObject();
345
- dbCarriedPointsForCompetitor.put(FieldNames.COMPETITOR_ID.name(), competitor.getId());
346
- dbCarriedPointsForCompetitor.put(FieldNames.LEADERBOARD_CARRIED_POINTS.name(), carriedPoints);
347
- dbCarriedPoints.add(dbCarriedPointsForCompetitor);
348
- }
349
- }
350
- BasicDBObject dbScoreCorrections = new BasicDBObject();
351
- storeScoreCorrections(leaderboard, dbScoreCorrections);
352
- dbLeaderboard.put(FieldNames.LEADERBOARD_SCORE_CORRECTIONS.name(), dbScoreCorrections);
353
- final ResultDiscardingRule resultDiscardingRule = leaderboard.getResultDiscardingRule();
354
- storeResultDiscardingRule(dbLeaderboard, resultDiscardingRule, FieldNames.LEADERBOARD_DISCARDING_THRESHOLDS);
355
- BasicDBList competitorDisplayNames = new BasicDBList();
356
- for (Competitor competitor : leaderboard.getCompetitors()) {
357
- String displayNameForCompetitor = leaderboard.getDisplayName(competitor);
358
- if (displayNameForCompetitor != null) {
359
- DBObject dbDisplayName = new BasicDBObject();
360
- dbDisplayName.put(FieldNames.COMPETITOR_ID.name(), competitor.getId());
361
- dbDisplayName.put(FieldNames.COMPETITOR_DISPLAY_NAME.name(), displayNameForCompetitor);
362
- competitorDisplayNames.add(dbDisplayName);
363
- }
364
- }
365
- dbLeaderboard.put(FieldNames.LEADERBOARD_COMPETITOR_DISPLAY_NAMES.name(), competitorDisplayNames);
366
- }
367
-
368
- /**
369
- * Stores the result discarding rule to <code>dbObject</code>'s field identified by <code>field</code> if the result discarding
370
- * rule is not <code>null</code> and is of type {@link ThresholdBasedResultDiscardingRule}. Otherwise, it is assumed that the
371
- * result discarding rule is otherwise implicitly obtained, e.g., from a definition of a regatta with its series, stored elsewhere.
372
- */
373
- private void storeResultDiscardingRule(DBObject dbObject,
374
- final ResultDiscardingRule resultDiscardingRule, FieldNames field) {
375
- if (resultDiscardingRule != null && resultDiscardingRule instanceof ThresholdBasedResultDiscardingRule) {
376
- BasicDBList dbResultDiscardingThresholds = new BasicDBList();
377
- for (int threshold : ((ThresholdBasedResultDiscardingRule) resultDiscardingRule).getDiscardIndexResultsStartingWithHowManyRaces()) {
378
- dbResultDiscardingThresholds.add(threshold);
379
- }
380
- dbObject.put(field.name(), dbResultDiscardingThresholds);
381
- }
382
- }
383
-
384
- private BasicDBObject storeRaceColumn(RaceColumn raceColumn) {
385
- BasicDBObject dbRaceColumn = new BasicDBObject();
386
- dbRaceColumn.put(FieldNames.LEADERBOARD_COLUMN_NAME.name(), raceColumn.getName());
387
- dbRaceColumn.put(FieldNames.LEADERBOARD_IS_MEDAL_RACE_COLUMN.name(), raceColumn.isMedalRace());
388
- storeRaceIdentifiers(raceColumn, dbRaceColumn);
389
- return dbRaceColumn;
390
- }
391
-
392
- private void storeScoreCorrections(Leaderboard leaderboard, BasicDBObject dbScoreCorrections) {
393
- TimePoint now = MillisecondsTimePoint.now();
394
- SettableScoreCorrection scoreCorrection = leaderboard.getScoreCorrection();
395
- for (RaceColumn raceColumn : scoreCorrection.getRaceColumnsThatHaveCorrections()) {
396
- BasicDBList dbCorrectionForRace = new BasicDBList();
397
- for (Competitor competitor : scoreCorrection.getCompetitorsThatHaveCorrectionsIn(raceColumn)) {
398
- // TODO bug 655: make score corrections time dependent
399
- if (scoreCorrection.isScoreCorrected(competitor, raceColumn, now)) {
400
- BasicDBObject dbCorrectionForCompetitor = new BasicDBObject();
401
- dbCorrectionForCompetitor.put(FieldNames.COMPETITOR_ID.name(), competitor.getId());
402
- MaxPointsReason maxPointsReason = scoreCorrection.getMaxPointsReason(competitor, raceColumn, now);
403
- if (maxPointsReason != MaxPointsReason.NONE) {
404
- dbCorrectionForCompetitor.put(FieldNames.LEADERBOARD_SCORE_CORRECTION_MAX_POINTS_REASON.name(),
405
- maxPointsReason.name());
406
- }
407
- Double explicitScoreCorrection = scoreCorrection
408
- .getExplicitScoreCorrection(competitor, raceColumn);
409
- if (explicitScoreCorrection != null) {
410
- dbCorrectionForCompetitor.put(FieldNames.LEADERBOARD_CORRECTED_SCORE.name(),
411
- explicitScoreCorrection);
412
- }
413
- dbCorrectionForRace.add(dbCorrectionForCompetitor);
414
- }
415
- }
416
- if (!dbCorrectionForRace.isEmpty()) {
417
- // using the column name as the key for the score corrections requires re-writing the score corrections
418
- // of a meta-leaderboard if the name of one of its leaderboards changes
419
- dbScoreCorrections.put(MongoUtils.escapeDollarAndDot(raceColumn.getName()), dbCorrectionForRace);
420
- }
421
- }
422
- final TimePoint timePointOfLastCorrectionsValidity = scoreCorrection.getTimePointOfLastCorrectionsValidity();
423
- if (timePointOfLastCorrectionsValidity != null) {
424
- dbScoreCorrections.put(FieldNames.LEADERBOARD_SCORE_CORRECTION_TIMESTAMP.name(), timePointOfLastCorrectionsValidity.asMillis());
425
- }
426
- if (scoreCorrection.getComment() != null) {
427
- dbScoreCorrections.put(FieldNames.LEADERBOARD_SCORE_CORRECTION_COMMENT.name(), scoreCorrection.getComment());
428
- }
429
- }
430
-
431
- @Override
432
- public void removeLeaderboard(String leaderboardName) {
433
- DBCollection leaderboardCollection = database.getCollection(CollectionNames.LEADERBOARDS.name());
434
- BasicDBObject query = new BasicDBObject(FieldNames.LEADERBOARD_NAME.name(), leaderboardName);
435
- leaderboardCollection.remove(query);
436
- }
437
-
438
- @Override
439
- public void renameLeaderboard(String oldName, String newName) {
440
- DBCollection leaderboardCollection = database.getCollection(CollectionNames.LEADERBOARDS.name());
441
- BasicDBObject query = new BasicDBObject(FieldNames.LEADERBOARD_NAME.name(), oldName);
442
- BasicDBObject renameUpdate = new BasicDBObject("$set", new BasicDBObject(FieldNames.LEADERBOARD_NAME.name(), newName));
443
- leaderboardCollection.update(query, renameUpdate, /* upsert */ true, /* multi */ false, WriteConcern.SAFE);
444
- }
445
-
446
- @Override
447
- public void storeLeaderboardGroup(LeaderboardGroup leaderboardGroup) {
448
- DBCollection leaderboardGroupCollection = database.getCollection(CollectionNames.LEADERBOARD_GROUPS.name());
449
- DBCollection leaderboardCollection = database.getCollection(CollectionNames.LEADERBOARDS.name());
450
-
451
- try {
452
- leaderboardGroupCollection.createIndex(new BasicDBObject(FieldNames.LEADERBOARD_GROUP_NAME.name(), 1));
453
- } catch (NullPointerException npe) {
454
- // sometimes, for reasons yet to be clarified, ensuring an index on the name field causes an NPE
455
- logger.log(Level.SEVERE, "storeLeaderboardGroup", npe);
456
- }
457
- BasicDBObject query = new BasicDBObject(FieldNames.LEADERBOARD_GROUP_NAME.name(), leaderboardGroup.getName());
458
- BasicDBObject dbLeaderboardGroup = new BasicDBObject();
459
- dbLeaderboardGroup.put(FieldNames.LEADERBOARD_GROUP_UUID.name(), leaderboardGroup.getId());
460
- dbLeaderboardGroup.put(FieldNames.LEADERBOARD_GROUP_NAME.name(), leaderboardGroup.getName());
461
- dbLeaderboardGroup.put(FieldNames.LEADERBOARD_GROUP_DESCRIPTION.name(), leaderboardGroup.getDescription());
462
- dbLeaderboardGroup.put(FieldNames.LEADERBOARD_GROUP_DISPLAY_NAME.name(), leaderboardGroup.getDisplayName());
463
- dbLeaderboardGroup.put(FieldNames.LEADERBOARD_GROUP_DISPLAY_IN_REVERSE_ORDER.name(), leaderboardGroup.isDisplayGroupsInReverseOrder());
464
- final Leaderboard overallLeaderboard = leaderboardGroup.getOverallLeaderboard();
465
- if (overallLeaderboard != null) {
466
- BasicDBObject overallLeaderboardQuery = new BasicDBObject(FieldNames.LEADERBOARD_NAME.name(), overallLeaderboard.getName());
467
- DBObject dbOverallLeaderboard = leaderboardCollection.findOne(overallLeaderboardQuery);
468
- if (dbOverallLeaderboard == null) {
469
- storeLeaderboard(overallLeaderboard);
470
- dbOverallLeaderboard = leaderboardCollection.findOne(overallLeaderboardQuery);
471
- }
472
- ObjectId dbOverallLeaderboardId = (ObjectId) dbOverallLeaderboard.get("_id");
473
- dbLeaderboardGroup.put(FieldNames.LEADERBOARD_GROUP_OVERALL_LEADERBOARD.name(), dbOverallLeaderboardId);
474
- }
475
- BasicDBList dbLeaderboardIds = new BasicDBList();
476
- for (Leaderboard leaderboard : leaderboardGroup.getLeaderboards()) {
477
- BasicDBObject leaderboardQuery = new BasicDBObject(FieldNames.LEADERBOARD_NAME.name(), leaderboard.getName());
478
- DBObject dbLeaderboard = leaderboardCollection.findOne(leaderboardQuery);
479
- if (dbLeaderboard == null) {
480
- storeLeaderboard(leaderboard);
481
- dbLeaderboard = leaderboardCollection.findOne(leaderboardQuery);
482
- }
483
- ObjectId dbLeaderboardId = (ObjectId) dbLeaderboard.get("_id");
484
- dbLeaderboardIds.add(dbLeaderboardId);
485
- }
486
- dbLeaderboardGroup.put(FieldNames.LEADERBOARD_GROUP_LEADERBOARDS.name(), dbLeaderboardIds);
487
- leaderboardGroupCollection.update(query, dbLeaderboardGroup, true, false, WriteConcern.SAFE);
488
- }
489
-
490
- @Override
491
- public void removeLeaderboardGroup(String groupName) {
492
- DBCollection leaderboardGroupCollection = database.getCollection(CollectionNames.LEADERBOARD_GROUPS.name());
493
- BasicDBObject query = new BasicDBObject(FieldNames.LEADERBOARD_GROUP_NAME.name(), groupName);
494
- leaderboardGroupCollection.remove(query);
495
- }
496
-
497
- @Override
498
- public void renameLeaderboardGroup(String oldName, String newName) {
499
- DBCollection leaderboardGroupCollection = database.getCollection(CollectionNames.LEADERBOARD_GROUPS.name());
500
- BasicDBObject query = new BasicDBObject(FieldNames.LEADERBOARD_GROUP_NAME.name(), oldName);
501
- BasicDBObject update = new BasicDBObject("$set", new BasicDBObject(FieldNames.LEADERBOARD_GROUP_NAME.name(), newName));
502
- leaderboardGroupCollection.update(query, update, /* upsert */ true, /* multi */ false, WriteConcern.SAFE);
503
- }
504
-
505
- @Override
506
- public void storeServerConfiguration(SailingServerConfiguration serverConfiguration) {
507
- DBCollection serverCollection = database.getCollection(CollectionNames.SERVER_CONFIGURATION.name());
508
- DBObject newServerConfig = new BasicDBObject();
509
- newServerConfig.put(FieldNames.SERVER_IS_STANDALONE.name(), serverConfiguration.isStandaloneServer());
510
- DBObject currentServerConfig = serverCollection.findOne();
511
- if(currentServerConfig != null) {
512
- serverCollection.update(currentServerConfig, newServerConfig, /* upsrt */ true, /* multi */ false, WriteConcern.SAFE);
513
- } else {
514
- serverCollection.save(newServerConfig);
515
- }
516
- }
517
-
518
- @Override
519
- public void storeSailingServer(RemoteSailingServerReference server) {
520
- DBCollection serverCollection = database.getCollection(CollectionNames.SAILING_SERVERS.name());
521
- serverCollection.createIndex(new BasicDBObject(FieldNames.SERVER_NAME.name(), 1));
522
- DBObject query = new BasicDBObject();
523
- query.put(FieldNames.SERVER_NAME.name(), server.getName());
524
- DBObject serverDBObject = new BasicDBObject();
525
- serverDBObject.put(FieldNames.SERVER_NAME.name(), server.getName());
526
- serverDBObject.put(FieldNames.SERVER_URL.name(), server.getURL().toExternalForm());
527
- serverCollection.update(query, serverDBObject, /* upsrt */ true, /* multi */ false, WriteConcern.SAFE);
528
- }
529
-
530
- @Override
531
- public void removeSailingServer(String name) {
532
- DBCollection serverCollection = database.getCollection(CollectionNames.SAILING_SERVERS.name());
533
- BasicDBObject query = new BasicDBObject(FieldNames.SERVER_NAME.name(), name);
534
- serverCollection.remove(query);
535
- }
536
-
537
- /**
538
- * StoreEvent() uses some deprecated methods of event to keep backward compatibility.
539
- */
540
- @SuppressWarnings("deprecation")
541
- @Override
542
- public void storeEvent(Event event) {
543
- DBCollection eventCollection = database.getCollection(CollectionNames.EVENTS.name());
544
- eventCollection.createIndex(new BasicDBObject(FieldNames.EVENT_ID.name(), 1));
545
- DBObject query = new BasicDBObject();
546
- query.put(FieldNames.EVENT_ID.name(), event.getId());
547
- DBObject eventDBObject = new BasicDBObject();
548
- eventDBObject.put(FieldNames.EVENT_NAME.name(), event.getName());
549
- eventDBObject.put(FieldNames.EVENT_DESCRIPTION.name(), event.getDescription());
550
- eventDBObject.put(FieldNames.EVENT_ID.name(), event.getId());
551
- eventDBObject.put(FieldNames.EVENT_LOGO_IMAGE_URL.name(), event.getLogoImageURL() != null ? event.getLogoImageURL().toString() : null);
552
- eventDBObject.put(FieldNames.EVENT_OFFICIAL_WEBSITE_URL.name(), event.getOfficialWebsiteURL() != null ? event.getOfficialWebsiteURL().toString() : null);
553
- eventDBObject.put(FieldNames.EVENT_SAILORS_INFO_WEBSITE_URL.name(), event.getSailorsInfoWebsiteURL() != null ? event.getSailorsInfoWebsiteURL().toString() : null);
554
- storeTimePoint(event.getStartDate(), eventDBObject, FieldNames.EVENT_START_DATE);
555
- storeTimePoint(event.getEndDate(), eventDBObject, FieldNames.EVENT_END_DATE);
556
- eventDBObject.put(FieldNames.EVENT_IS_PUBLIC.name(), event.isPublic());
557
- DBObject venueDBObject = getVenueAsDBObject(event.getVenue());
558
- eventDBObject.put(FieldNames.VENUE.name(), venueDBObject);
559
- BasicDBList imageURLs = new BasicDBList();
560
- for (URL imageURL : event.getImageURLs()) {
561
- imageURLs.add(imageURL.toString());
562
- }
563
- eventDBObject.put(FieldNames.EVENT_IMAGE_URLS.name(), imageURLs);
564
- BasicDBList videoURLs = new BasicDBList();
565
- for (URL videoURL : event.getVideoURLs()) {
566
- videoURLs.add(videoURL.toString());
567
- }
568
- eventDBObject.put(FieldNames.EVENT_VIDEO_URLS.name(), videoURLs);
569
- BasicDBList sponsorImageURLs = new BasicDBList();
570
- for (URL sponsorImageURL : event.getSponsorImageURLs()) {
571
- sponsorImageURLs.add(sponsorImageURL.toString());
572
- }
573
- eventDBObject.put(FieldNames.EVENT_SPONSOR_IMAGE_URLS.name(), sponsorImageURLs);
574
- BasicDBList images = new BasicDBList();
575
- for (ImageDescriptor image : event.getImages()) {
576
- DBObject imageObject = createImageObject(image);
577
- images.add(imageObject);
578
- }
579
- eventDBObject.put(FieldNames.EVENT_IMAGES.name(), images);
580
- BasicDBList videos = new BasicDBList();
581
- for (VideoDescriptor video: event.getVideos()) {
582
- DBObject videoObject = createVideoObject(video);
583
- videos.add(videoObject);
584
- }
585
- eventDBObject.put(FieldNames.EVENT_VIDEOS.name(), videos);
586
- eventCollection.update(query, eventDBObject, /* upsrt */ true, /* multi */ false, WriteConcern.SAFE);
587
- // now store the links to the leaderboard groups
588
- DBCollection linksCollection = database.getCollection(CollectionNames.LEADERBOARD_GROUP_LINKS_FOR_EVENTS.name());
589
- linksCollection.createIndex(new BasicDBObject(FieldNames.EVENT_ID.name(), 1));
590
- BasicDBList lgUUIDs = new BasicDBList();
591
- for (LeaderboardGroup lg : event.getLeaderboardGroups()) {
592
- lgUUIDs.add(lg.getId());
593
- }
594
- DBObject dbLinks = new BasicDBObject();
595
- dbLinks.put(FieldNames.EVENT_ID.name(), event.getId());
596
- dbLinks.put(FieldNames.LEADERBOARD_GROUP_UUID.name(), lgUUIDs);
597
- linksCollection.update(query, dbLinks, /* upsrt */ true, /* multi */ false, WriteConcern.SAFE);
598
- }
599
-
600
- @Override
601
- public void renameEvent(Serializable id, String newName) {
602
- DBCollection eventCollection = database.getCollection(CollectionNames.EVENTS.name());
603
- BasicDBObject query = new BasicDBObject(FieldNames.EVENT_ID.name(), id);
604
- BasicDBObject renameUpdate = new BasicDBObject("$set", new BasicDBObject(FieldNames.EVENT_NAME.name(), newName));
605
- eventCollection.update(query, renameUpdate, /* upsert */ true, /* multi */ false, WriteConcern.SAFE);
606
- }
607
-
608
- @Override
609
- public void removeEvent(Serializable id) {
610
- DBCollection eventsCollection = database.getCollection(CollectionNames.EVENTS.name());
611
- BasicDBObject query = new BasicDBObject(FieldNames.EVENT_ID.name(), id);
612
- eventsCollection.remove(query);
613
- }
614
-
615
- private DBObject getVenueAsDBObject(Venue venue) {
616
- DBObject result = new BasicDBObject();
617
- result.put(FieldNames.VENUE_NAME.name(), venue.getName());
618
- BasicDBList courseAreaList = new BasicDBList();
619
- result.put(FieldNames.COURSE_AREAS.name(), courseAreaList);
620
- for (CourseArea courseArea : venue.getCourseAreas()) {
621
- DBObject dbCourseArea = new BasicDBObject();
622
- courseAreaList.add(dbCourseArea);
623
- dbCourseArea.put(FieldNames.COURSE_AREA_NAME.name(), courseArea.getName());
624
- dbCourseArea.put(FieldNames.COURSE_AREA_ID.name(), courseArea.getId());
625
- }
626
- return result;
627
- }
628
-
629
- @Override
630
- public void storeRegatta(Regatta regatta) {
631
- DBCollection regattasCollection = database.getCollection(CollectionNames.REGATTAS.name());
632
- regattasCollection.createIndex(new BasicDBObject(FieldNames.REGATTA_NAME.name(), 1));
633
- regattasCollection.createIndex(new BasicDBObject(FieldNames.REGATTA_ID.name(), 1));
634
- DBObject dbRegatta = new BasicDBObject();
635
- DBObject query = new BasicDBObject(FieldNames.REGATTA_NAME.name(), regatta.getName());
636
- dbRegatta.put(FieldNames.REGATTA_NAME.name(), regatta.getName());
637
- dbRegatta.put(FieldNames.REGATTA_ID.name(), regatta.getId());
638
- storeTimePoint(regatta.getStartDate(), dbRegatta, FieldNames.REGATTA_START_DATE);
639
- storeTimePoint(regatta.getEndDate(), dbRegatta, FieldNames.REGATTA_END_DATE);
640
- dbRegatta.put(FieldNames.SCORING_SCHEME_TYPE.name(), regatta.getScoringScheme().getType().name());
641
- if (regatta.getBoatClass() != null) {
642
- dbRegatta.put(FieldNames.BOAT_CLASS_NAME.name(), regatta.getBoatClass().getName());
643
- dbRegatta.put(FieldNames.BOAT_CLASS_TYPICALLY_STARTS_UPWIND.name(), regatta.getBoatClass().typicallyStartsUpwind());
644
- }
645
- dbRegatta.put(FieldNames.REGATTA_SERIES.name(), storeSeries(regatta.getSeries()));
646
-
647
- if (regatta.getDefaultCourseArea() != null) {
648
- dbRegatta.put(FieldNames.COURSE_AREA_ID.name(), regatta.getDefaultCourseArea().getId().toString());
649
- } else {
650
- dbRegatta.put(FieldNames.COURSE_AREA_ID.name(), null);
651
- }
652
- if (regatta.getRegattaConfiguration() != null) {
653
- JsonSerializer<RegattaConfiguration> serializer = RegattaConfigurationJsonSerializer.create();
654
- JSONObject json = serializer.serialize(regatta.getRegattaConfiguration());
655
- DBObject configurationObject = (DBObject) JSON.parse(json.toString());
656
- dbRegatta.put(FieldNames.REGATTA_REGATTA_CONFIGURATION.name(), configurationObject);
657
- }
658
- dbRegatta.put(FieldNames.REGATTA_USE_START_TIME_INFERENCE.name(), regatta.useStartTimeInference());
659
- dbRegatta.put(FieldNames.REGATTA_RANKING_METRIC.name(), storeRankingMetric(regatta));
660
- regattasCollection.update(query, dbRegatta, /* upsrt */ true, /* multi */ false, WriteConcern.SAFE);
661
- }
662
-
663
- private DBObject storeRankingMetric(Regatta regatta) {
664
- DBObject rankingMetricJson = new BasicDBObject();
665
- final String rankingMetricTypeName = regatta.getRankingMetricType().name();
666
- rankingMetricJson.put(FieldNames.REGATTA_RANKING_METRIC_TYPE.name(), rankingMetricTypeName);
667
- return rankingMetricJson;
668
- }
669
-
670
- @Override
671
- public void removeRegatta(Regatta regatta) {
672
- DBCollection regattasCollection = database.getCollection(CollectionNames.REGATTAS.name());
673
- DBObject query = new BasicDBObject(FieldNames.REGATTA_NAME.name(), regatta.getName());
674
- regattasCollection.remove(query);
675
- }
676
-
677
- private BasicDBList storeSeries(Iterable<? extends Series> series) {
678
- BasicDBList dbSeries = new BasicDBList();
679
- for (Series s : series) {
680
- dbSeries.add(storeSeries(s));
681
- }
682
- return dbSeries;
683
- }
684
-
685
- private DBObject storeSeries(Series s) {
686
- DBObject dbSeries = new BasicDBObject();
687
- dbSeries.put(FieldNames.SERIES_NAME.name(), s.getName());
688
- dbSeries.put(FieldNames.SERIES_IS_MEDAL.name(), s.isMedal());
689
- dbSeries.put(FieldNames.SERIES_HAS_SPLIT_FLEET_CONTIGUOUS_SCORING.name(), s.hasSplitFleetContiguousScoring());
690
- dbSeries.put(FieldNames.SERIES_STARTS_WITH_ZERO_SCORE.name(), s.isStartsWithZeroScore());
691
- dbSeries.put(FieldNames.SERIES_STARTS_WITH_NON_DISCARDABLE_CARRY_FORWARD.name(), s.isFirstColumnIsNonDiscardableCarryForward());
692
- BasicDBList dbFleets = new BasicDBList();
693
- for (Fleet fleet : s.getFleets()) {
694
- dbFleets.add(storeFleet(fleet));
695
- }
696
- dbSeries.put(FieldNames.SERIES_FLEETS.name(), dbFleets);
697
- BasicDBList dbRaceColumns = new BasicDBList();
698
- for (RaceColumn raceColumn : s.getRaceColumns()) {
699
- dbRaceColumns.add(storeRaceColumn(raceColumn));
700
- }
701
- dbSeries.put(FieldNames.SERIES_RACE_COLUMNS.name(), dbRaceColumns);
702
- if (s.getResultDiscardingRule() != null) {
703
- storeResultDiscardingRule(dbSeries, s.getResultDiscardingRule(), FieldNames.SERIES_DISCARDING_THRESHOLDS);
704
- }
705
- return dbSeries;
706
- }
707
-
708
- private DBObject storeFleet(Fleet fleet) {
709
- DBObject dbFleet = new BasicDBObject(FieldNames.FLEET_NAME.name(), fleet.getName());
710
- if (fleet instanceof FleetImpl) {
711
- dbFleet.put(FieldNames.FLEET_ORDERING.name(), ((FleetImpl) fleet).getOrdering());
712
- if(fleet.getColor() != null) {
713
- com.sap.sse.common.Util.Triple<Integer, Integer, Integer> colorAsRGB = fleet.getColor().getAsRGB();
714
- // we save the color as a integer value representing the RGB values
715
- int colorAsInt = (256 * 256 * colorAsRGB.getC()) + colorAsRGB.getB() * 256 + colorAsRGB.getA();
716
- dbFleet.put(FieldNames.FLEET_COLOR.name(), colorAsInt);
717
- } else {
718
- dbFleet.put(FieldNames.FLEET_COLOR.name(), null);
719
- }
720
- }
721
- return dbFleet;
722
- }
723
-
724
- @Override
725
- public void storeRegattaForRaceID(String raceIDAsString, Regatta regatta) {
726
- DBCollection regattaForRaceIDCollection = database.getCollection(CollectionNames.REGATTA_FOR_RACE_ID.name());
727
- DBObject query = new BasicDBObject(FieldNames.RACE_ID_AS_STRING.name(), raceIDAsString);
728
- DBObject entry = new BasicDBObject(FieldNames.RACE_ID_AS_STRING.name(), raceIDAsString);
729
- entry.put(FieldNames.REGATTA_NAME.name(), regatta.getName());
730
- regattaForRaceIDCollection.update(query, entry, /* upsrt */ true, /* multi */ false, WriteConcern.SAFE);
731
- }
732
-
733
- @Override
734
- public void removeRegattaForRaceID(String raceIDAsString, Regatta regatta) {
735
- DBCollection regattaForRaceIDCollection = database.getCollection(CollectionNames.REGATTA_FOR_RACE_ID.name());
736
- DBObject query = new BasicDBObject(FieldNames.RACE_ID_AS_STRING.name(), raceIDAsString);
737
- regattaForRaceIDCollection.remove(query);
738
- }
739
-
740
- public DBCollection getRaceLogCollection() {
741
- DBCollection result = database.getCollection(CollectionNames.RACE_LOGS.name());
742
- result.createIndex(new BasicDBObject(FieldNames.RACE_LOG_IDENTIFIER.name(), null));
743
- return result;
744
- }
745
-
746
- private void storeRaceLogEventAuthor(DBObject dbObject, AbstractLogEventAuthor author) {
747
- if (author != null) {
748
- dbObject.put(FieldNames.RACE_LOG_EVENT_AUTHOR_NAME.name(), author.getName());
749
- dbObject.put(FieldNames.RACE_LOG_EVENT_AUTHOR_PRIORITY.name(), author.getPriority());
750
- }
751
- }
752
-
753
- public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogFlagEvent flagEvent) {
754
- BasicDBObject result = new BasicDBObject();
755
- storeRaceLogIdentifier(raceLogIdentifier, result);
756
- result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogFlagEvent(flagEvent));
757
- return result;
758
- }
759
-
760
- private void storeRaceLogIdentifier(RaceLogIdentifier raceLogIdentifier, DBObject result) {
761
- result.put(FieldNames.RACE_LOG_IDENTIFIER.name(), TripleSerializer.serialize(raceLogIdentifier.getIdentifier()));
762
- }
763
-
764
- public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogStartTimeEvent startTimeEvent) {
765
- BasicDBObject result = new BasicDBObject();
766
- storeRaceLogIdentifier(raceLogIdentifier, result);
767
- result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogStartTimeEvent(startTimeEvent));
768
- return result;
769
- }
770
-
771
- public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogPassChangeEvent passChangeEvent) {
772
- BasicDBObject result = new BasicDBObject();
773
- storeRaceLogIdentifier(raceLogIdentifier, result);
774
- result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogPassChangeEvent(passChangeEvent));
775
- return result;
776
- }
777
-
778
- public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogRaceStatusEvent raceStatusEvent) {
779
- BasicDBObject result = new BasicDBObject();
780
- storeRaceLogIdentifier(raceLogIdentifier, result);
781
- result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogRaceStatusEvent(raceStatusEvent));
782
- return result;
783
- }
784
-
785
- public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogCourseAreaChangedEvent courseAreaChangedEvent) {
786
- BasicDBObject result = new BasicDBObject();
787
- storeRaceLogIdentifier(raceLogIdentifier, result);
788
- result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogCourseAreaChangedEvent(courseAreaChangedEvent));
789
- return result;
790
- }
791
-
792
- public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogCourseDesignChangedEvent courseDesignChangedEvent) {
793
- BasicDBObject result = new BasicDBObject();
794
- storeRaceLogIdentifier(raceLogIdentifier, result);
795
- result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogCourseDesignChangedEvent(courseDesignChangedEvent));
796
- return result;
797
- }
798
-
799
- public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogFinishPositioningListChangedEvent finishPositioningListChangedEvent) {
800
- BasicDBObject result = new BasicDBObject();
801
- storeRaceLogIdentifier(raceLogIdentifier, result);
802
- result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogFinishPositioningListChangedEvent(finishPositioningListChangedEvent));
803
- return result;
804
- }
805
-
806
- public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogFinishPositioningConfirmedEvent finishPositioningConfirmedEvent) {
807
- BasicDBObject result = new BasicDBObject();
808
- storeRaceLogIdentifier(raceLogIdentifier, result);
809
- result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogFinishPositioningConfirmedEvent(finishPositioningConfirmedEvent));
810
- return result;
811
- }
812
-
813
- public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogPathfinderEvent pathfinderEvent) {
814
- BasicDBObject result = new BasicDBObject();
815
- storeRaceLogIdentifier(raceLogIdentifier, result);
816
- result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogPathfinderEvent(pathfinderEvent));
817
- return result;
818
- }
819
-
820
- public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogGateLineOpeningTimeEvent gateLineOpeningTimeEvent) {
821
- BasicDBObject result = new BasicDBObject();
822
- storeRaceLogIdentifier(raceLogIdentifier, result);
823
- result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogGateLineOpeningTimeEvent(gateLineOpeningTimeEvent));
824
- return result;
825
- }
826
-
827
- public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogStartProcedureChangedEvent event) {
828
- DBObject result = new BasicDBObject();
829
- storeRaceLogIdentifier(raceLogIdentifier, result);
830
- result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogStartProcedureChangedEvent(event));
831
- return result;
832
- }
833
-
834
- public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogProtestStartTimeEvent event) {
835
- DBObject result = new BasicDBObject();
836
- storeRaceLogIdentifier(raceLogIdentifier, result);
837
- result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogProtestStartTimeEvent(event));
838
- return result;
839
- }
840
-
841
- public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogWindFixEvent event) {
842
- DBObject result = new BasicDBObject();
843
- storeRaceLogIdentifier(raceLogIdentifier, result);
844
- result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogWindFix(event));
845
- return result;
846
- }
847
-
848
- public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogDeviceCompetitorMappingEvent event) {
849
- BasicDBObject result = new BasicDBObject();
850
- storeRaceLogIdentifier(raceLogIdentifier, result);
851
- result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogDeviceCompetitorMappingEvent(event));
852
- return result;
853
- }
854
-
855
- public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogDeviceMarkMappingEvent event) {
856
- BasicDBObject result = new BasicDBObject();
857
- storeRaceLogIdentifier(raceLogIdentifier, result);
858
- result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogDeviceMarkMappingEvent(event));
859
- return result;
860
- }
861
-
862
- public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogDenoteForTrackingEvent event) {
863
- BasicDBObject result = new BasicDBObject();
864
- storeRaceLogIdentifier(raceLogIdentifier, result);
865
- result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogDenoteForTrackingEvent(event));
866
- return result;
867
- }
868
-
869
- public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogStartTrackingEvent event) {
870
- BasicDBObject result = new BasicDBObject();
871
- storeRaceLogIdentifier(raceLogIdentifier, result);
872
- result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogStartTrackingEvent(event));
873
- return result;
874
- }
875
-
876
- public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogRevokeEvent event) {
877
- BasicDBObject result = new BasicDBObject();
878
- storeRaceLogIdentifier(raceLogIdentifier, result);
879
- result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogRevokeEvent(event));
880
- return result;
881
- }
882
-
883
- public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogRegisterCompetitorEvent event) {
884
- BasicDBObject result = new BasicDBObject();
885
- storeRaceLogIdentifier(raceLogIdentifier, result);
886
- result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogRegisterCompetitorEvent(event));
887
- return result;
888
- }
889
-
890
- public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogDefineMarkEvent event) {
891
- BasicDBObject result = new BasicDBObject();
892
- storeRaceLogIdentifier(raceLogIdentifier, result);
893
- result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogDefineMarkEvent(event));
894
- return result;
895
- }
896
-
897
- public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogCloseOpenEndedDeviceMappingEvent event) {
898
- BasicDBObject result = new BasicDBObject();
899
- storeRaceLogIdentifier(raceLogIdentifier, result);
900
- result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogCloseOpenEndedDeviceMappingEvent(event));
901
- return result;
902
- }
903
-
904
- public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogEndOfTrackingEvent event) {
905
- BasicDBObject result = new BasicDBObject();
906
- storeRaceLogIdentifier(raceLogIdentifier, result);
907
- result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogEndOfTrackingEvent(event));
908
- return result;
909
- }
910
-
911
- public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogStartOfTrackingEvent event) {
912
- BasicDBObject result = new BasicDBObject();
913
- storeRaceLogIdentifier(raceLogIdentifier, result);
914
- result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogStartOfTrackingEvent(event));
915
- return result;
916
- }
917
-
918
- public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogAdditionalScoringInformationEvent event) {
919
- BasicDBObject result = new BasicDBObject();
920
- storeRaceLogIdentifier(raceLogIdentifier, result);
921
- result.put(FieldNames.RACE_LOG_EVENT.name(), storeAdditionalScoringInformation(event));
922
- return result;
923
- }
924
-
925
- private Object storeAdditionalScoringInformation(RaceLogAdditionalScoringInformationEvent event) {
926
- DBObject result = new BasicDBObject();
927
- storeRaceLogEventProperties(event, result);
928
- result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogAdditionalScoringInformationEvent.class.getSimpleName());
929
- result.put(FieldNames.RACE_LOG_ADDITIONAL_SCORING_INFORMATION_TYPE.name(), event.getType().name());
930
- return result;
931
- }
932
-
933
- public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogFixedMarkPassingEvent event) {
934
- BasicDBObject result = new BasicDBObject();
935
- storeRaceLogIdentifier(raceLogIdentifier, result);
936
- result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogFixedMarkPassingEvent(event));
937
- return result;
938
- }
939
-
940
- public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogSuppressedMarkPassingsEvent event) {
941
- BasicDBObject result = new BasicDBObject();
942
- storeRaceLogIdentifier(raceLogIdentifier, result);
943
- result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogSuppressedMarkPassingsEvent(event));
944
- return result;
945
- }
946
-
947
- private Object storeRaceLogWindFix(RaceLogWindFixEvent event) {
948
- DBObject result = new BasicDBObject();
949
- storeRaceLogEventProperties(event, result);
950
- result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogWindFixEvent.class.getSimpleName());
951
- result.put(FieldNames.WIND.name(), storeWind(event.getWindFix()));
952
- result.put(FieldNames.IS_MAGNETIC.name(), event.isMagnetic());
953
- return result;
954
- }
955
-
956
- private Object storeRaceLogProtestStartTimeEvent(RaceLogProtestStartTimeEvent event) {
957
- DBObject result = new BasicDBObject();
958
- storeRaceLogEventProperties(event, result);
959
- result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogProtestStartTimeEvent.class.getSimpleName());
960
- storeTimePoint(event.getProtestStartTime(), result, FieldNames.RACE_LOG_PROTEST_START_TIME);
961
- return result;
962
- }
963
-
964
- private Object storeRaceLogEndOfTrackingEvent(RaceLogEndOfTrackingEvent event) {
965
- DBObject result = new BasicDBObject();
966
- storeRaceLogEventProperties(event, result);
967
- result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogEndOfTrackingEvent.class.getSimpleName());
968
- return result;
969
- }
970
-
971
- private Object storeRaceLogStartOfTrackingEvent(RaceLogStartOfTrackingEvent event) {
972
- DBObject result = new BasicDBObject();
973
- storeRaceLogEventProperties(event, result);
974
- result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogStartOfTrackingEvent.class.getSimpleName());
975
- return result;
976
- }
977
-
978
- private Object storeRaceLogStartProcedureChangedEvent(RaceLogStartProcedureChangedEvent event) {
979
- DBObject result = new BasicDBObject();
980
- storeRaceLogEventProperties(event, result);
981
- result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogStartProcedureChangedEvent.class.getSimpleName());
982
- result.put(FieldNames.RACE_LOG_START_PROCEDURE_TYPE.name(), event.getStartProcedureType().name());
983
- return result;
984
- }
985
-
986
- private Object storeRaceLogPathfinderEvent(RaceLogPathfinderEvent pathfinderEvent) {
987
- DBObject result = new BasicDBObject();
988
- storeRaceLogEventProperties(pathfinderEvent, result);
989
- result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogPathfinderEvent.class.getSimpleName());
990
- result.put(FieldNames.RACE_LOG_PATHFINDER_ID.name(), pathfinderEvent.getPathfinderId());
991
- return result;
992
- }
993
-
994
- private void storeDeviceMappingEvent(DeviceMappingEvent<?, ?> event, DBObject result, FieldNames fromField, FieldNames toField) {
995
- try {
996
- result.put(FieldNames.DEVICE_ID.name(), storeDeviceId(deviceIdentifierServiceFinder, event.getDevice()));
997
- } catch (TransformationException | NoCorrespondingServiceRegisteredException e) {
998
- logger.log(Level.WARNING, "Could not store device identifier for mappng event", e);
999
- }
1000
- if (event.getFrom() != null) {
1001
- storeTimePoint(event.getFrom(), result, fromField);
1002
- }
1003
- if (event.getTo() != null) {
1004
- storeTimePoint(event.getTo(), result, toField);
1005
- }
1006
- }
1007
-
1008
- private Object storeRaceLogDeviceCompetitorMappingEvent(RaceLogDeviceCompetitorMappingEvent event) {
1009
- DBObject result = new BasicDBObject();
1010
- storeRaceLogEventProperties(event, result);
1011
- result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogDeviceCompetitorMappingEvent.class.getSimpleName());
1012
- storeDeviceMappingEvent(event, result, FieldNames.RACE_LOG_FROM, FieldNames.RACE_LOG_TO);
1013
- result.put(FieldNames.COMPETITOR_ID.name(), event.getMappedTo().getId());
1014
- return result;
1015
- }
1016
-
1017
- private Object storeRaceLogDeviceMarkMappingEvent(RaceLogDeviceMarkMappingEvent event) {
1018
- DBObject result = new BasicDBObject();
1019
- storeRaceLogEventProperties(event, result);
1020
- result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogDeviceMarkMappingEvent.class.getSimpleName());
1021
- storeDeviceMappingEvent(event, result, FieldNames.RACE_LOG_FROM, FieldNames.RACE_LOG_TO);
1022
- result.put(FieldNames.MARK.name(), storeMark(event.getMappedTo()));
1023
- return result;
1024
- }
1025
-
1026
- private Object storeRaceLogDenoteForTrackingEvent(RaceLogDenoteForTrackingEvent event) {
1027
- DBObject result = new BasicDBObject();
1028
- storeRaceLogEventProperties(event, result);
1029
- result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogDenoteForTrackingEvent.class.getSimpleName());
1030
- result.put(FieldNames.RACE_NAME.name(), event.getRaceName());
1031
- result.put(FieldNames.BOAT_CLASS_NAME.name(), event.getBoatClass().getName());
1032
- result.put(FieldNames.RACE_ID.name(), event.getRaceId());
1033
- return result;
1034
- }
1035
-
1036
- private Object storeRaceLogStartTrackingEvent(RaceLogStartTrackingEvent event) {
1037
- DBObject result = new BasicDBObject();
1038
- storeRaceLogEventProperties(event, result);
1039
- result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogStartTrackingEvent.class.getSimpleName());
1040
- return result;
1041
- }
1042
-
1043
- private Object storeRaceLogRevokeEvent(RaceLogRevokeEvent event) {
1044
- DBObject result = new BasicDBObject();
1045
- storeRaceLogEventProperties(event, result);
1046
- result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogRevokeEvent.class.getSimpleName());
1047
- result.put(FieldNames.RACE_LOG_REVOKED_EVENT_ID.name(), event.getRevokedEventId());
1048
- result.put(FieldNames.RACE_LOG_REVOKED_EVENT_TYPE.name(), event.getRevokedEventType());
1049
- result.put(FieldNames.RACE_LOG_REVOKED_EVENT_SHORT_INFO.name(), event.getRevokedEventShortInfo());
1050
- result.put(FieldNames.RACE_LOG_REVOKED_REASON.name(), event.getReason());
1051
- return result;
1052
- }
1053
-
1054
- private Object storeRaceLogRegisterCompetitorEvent(RaceLogRegisterCompetitorEvent event) {
1055
- DBObject result = new BasicDBObject();
1056
- storeRaceLogEventProperties(event, result);
1057
- result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogRegisterCompetitorEvent.class.getSimpleName());
1058
- result.put(FieldNames.RACE_LOG_COMPETITOR_ID.name(), event.getCompetitor().getId());
1059
- return result;
1060
- }
1061
-
1062
- private Object storeRaceLogDefineMarkEvent(RaceLogDefineMarkEvent event) {
1063
- DBObject result = new BasicDBObject();
1064
- storeRaceLogEventProperties(event, result);
1065
- result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogDefineMarkEvent.class.getSimpleName());
1066
- result.put(FieldNames.RACE_LOG_MARK.name(), storeMark(event.getMark()));
1067
- return result;
1068
- }
1069
-
1070
- private Object storeRaceLogCloseOpenEndedDeviceMappingEvent(RaceLogCloseOpenEndedDeviceMappingEvent event) {
1071
- DBObject result = new BasicDBObject();
1072
- storeRaceLogEventProperties(event, result);
1073
- result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogCloseOpenEndedDeviceMappingEvent.class.getSimpleName());
1074
- result.put(FieldNames.RACE_LOG_DEVICE_MAPPING_EVENT_ID.name(), event.getDeviceMappingEventId());
1075
- storeTimePoint(event.getClosingTimePoint(), result, FieldNames.RACE_LOG_CLOSING_TIMEPOINT);
1076
- return result;
1077
- }
1078
-
1079
- private Object storeRaceLogFixedMarkPassingEvent(RaceLogFixedMarkPassingEvent event) {
1080
- DBObject result = new BasicDBObject();
1081
- storeRaceLogEventProperties(event, result);
1082
- result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogFixedMarkPassingEvent.class.getSimpleName());
1083
- result.put(FieldNames.INDEX_OF_PASSED_WAYPOINT.name(), event.getZeroBasedIndexOfPassedWaypoint());
1084
- result.put(FieldNames.TIMEPOINT_OF_FIXED_MARKPASSING.name(), event.getTimePointOfFixedPassing().asMillis());
1085
- return result;
1086
- }
1087
-
1088
- private Object storeRaceLogSuppressedMarkPassingsEvent(RaceLogSuppressedMarkPassingsEvent event) {
1089
- DBObject result = new BasicDBObject();
1090
- storeRaceLogEventProperties(event, result);
1091
- result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogSuppressedMarkPassingsEvent.class.getSimpleName());
1092
- result.put(FieldNames.INDEX_OF_FIRST_SUPPRESSED_WAYPOINT.name(), event.getZeroBasedIndexOfFirstSuppressedWaypoint());
1093
- return result;
1094
- }
1095
-
1096
- public DBObject storeRaceLogFlagEvent(RaceLogFlagEvent flagEvent) {
1097
- DBObject result = new BasicDBObject();
1098
- storeRaceLogEventProperties(flagEvent, result);
1099
- result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogFlagEvent.class.getSimpleName());
1100
- result.put(FieldNames.RACE_LOG_EVENT_FLAG_UPPER.name(), flagEvent.getUpperFlag().name());
1101
- result.put(FieldNames.RACE_LOG_EVENT_FLAG_LOWER.name(), flagEvent.getLowerFlag().name());
1102
- result.put(FieldNames.RACE_LOG_EVENT_FLAG_DISPLAYED.name(), String.valueOf(flagEvent.isDisplayed()));
1103
- return result;
1104
- }
1105
-
1106
- private DBObject storeRaceLogStartTimeEvent(RaceLogStartTimeEvent startTimeEvent) {
1107
- DBObject result = new BasicDBObject();
1108
- storeRaceLogEventProperties(startTimeEvent, result);
1109
- result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogStartTimeEvent.class.getSimpleName());
1110
- storeTimePoint(startTimeEvent.getStartTime(), result, FieldNames.RACE_LOG_EVENT_START_TIME);
1111
- result.put(FieldNames.RACE_LOG_EVENT_NEXT_STATUS.name(), startTimeEvent.getNextStatus().name());
1112
- return result;
1113
- }
1114
-
1115
- private void storeRaceLogEventProperties(RaceLogEvent event, DBObject result) {
1116
- // for compatibility reasons we reuse the field name of Timed
1117
- storeTimePoint(event.getLogicalTimePoint(), result, FieldNames.TIME_AS_MILLIS);
1118
- storeTimePoint(event.getCreatedAt(), result, FieldNames.RACE_LOG_EVENT_CREATED_AT);
1119
- result.put(FieldNames.RACE_LOG_EVENT_ID.name(), event.getId());
1120
- result.put(FieldNames.RACE_LOG_EVENT_PASS_ID.name(), event.getPassId());
1121
- result.put(FieldNames.RACE_LOG_EVENT_INVOLVED_BOATS.name(), storeInvolvedBoatsForRaceLogEvent(event.getInvolvedBoats()));
1122
- storeRaceLogEventAuthor(result, event.getAuthor());
1123
- }
1124
-
1125
-
1126
- private BasicDBList storeInvolvedBoatsForRaceLogEvent(List<Competitor> competitors) {
1127
- BasicDBList dbInvolvedCompetitorIds = new BasicDBList();
1128
- for (Competitor competitor : competitors) {
1129
- dbInvolvedCompetitorIds.add(competitor.getId());
1130
- }
1131
- return dbInvolvedCompetitorIds;
1132
- }
1133
-
1134
- private DBObject storeRaceLogPassChangeEvent(RaceLogPassChangeEvent passChangeEvent) {
1135
- DBObject result = new BasicDBObject();
1136
- storeRaceLogEventProperties(passChangeEvent, result);
1137
- result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogPassChangeEvent.class.getSimpleName());
1138
- return result;
1139
- }
1140
-
1141
- private DBObject storeRaceLogDependentStartTimeEvent(RaceLogDependentStartTimeEvent dependentStartTimeEvent) {
1142
- DBObject result = new BasicDBObject();
1143
- storeRaceLogEventProperties(dependentStartTimeEvent, result);
1144
- result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogDependentStartTimeEvent.class.getSimpleName());
1145
- result.put(FieldNames.RACE_LOG_DEPDENDENT_ON_REGATTALIKE.name(), dependentStartTimeEvent.getDependentOnRaceIdentifier().getRegattaLikeParentName());
1146
- result.put(FieldNames.RACE_LOG_DEPDENDENT_ON_RACECOLUMN.name(), dependentStartTimeEvent.getDependentOnRaceIdentifier().getRaceColumnName());
1147
- result.put(FieldNames.RACE_LOG_DEPDENDENT_ON_FLEET.name(), dependentStartTimeEvent.getDependentOnRaceIdentifier().getFleetName());
1148
- storeDuration(dependentStartTimeEvent.getStartTimeDifference(), result, FieldNames.RACE_LOG_START_TIME_DIFFERENCE_IN_MS);
1149
- result.put(FieldNames.RACE_LOG_EVENT_NEXT_STATUS.name(), dependentStartTimeEvent.getNextStatus().name());
1150
- return result;
1151
- }
1152
-
1153
- private void storeDuration(Duration duration, DBObject result, FieldNames fieldName) {
1154
- if (duration != null) {
1155
- result.put(fieldName.name(), duration.asMillis());
1156
- }
1157
- }
1158
-
1159
- private DBObject storeRaceLogRaceStatusEvent(RaceLogRaceStatusEvent raceStatusEvent) {
1160
- DBObject result = new BasicDBObject();
1161
- storeRaceLogEventProperties(raceStatusEvent, result);
1162
- result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogRaceStatusEvent.class.getSimpleName());
1163
- result.put(FieldNames.RACE_LOG_EVENT_NEXT_STATUS.name(), raceStatusEvent.getNextStatus().name());
1164
- return result;
1165
- }
1166
-
1167
- private DBObject storeRaceLogCourseAreaChangedEvent(RaceLogCourseAreaChangedEvent courseAreaChangedEvent) {
1168
- DBObject result = new BasicDBObject();
1169
- storeRaceLogEventProperties(courseAreaChangedEvent, result);
1170
- result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogCourseAreaChangedEvent.class.getSimpleName());
1171
- result.put(FieldNames.COURSE_AREA_ID.name(), courseAreaChangedEvent.getCourseAreaId());
1172
- return result;
1173
- }
1174
-
1175
- private DBObject storeRaceLogCourseDesignChangedEvent(RaceLogCourseDesignChangedEvent courseDesignChangedEvent) {
1176
- DBObject result = new BasicDBObject();
1177
- storeRaceLogEventProperties(courseDesignChangedEvent, result);
1178
- result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogCourseDesignChangedEvent.class.getSimpleName());
1179
- result.put(FieldNames.RACE_LOG_COURSE_DESIGN_NAME.name(), courseDesignChangedEvent.getCourseDesign().getName());
1180
- result.put(FieldNames.RACE_LOG_COURSE_DESIGN.name(), storeCourseBase(courseDesignChangedEvent.getCourseDesign()));
1181
- return result;
1182
- }
1183
-
1184
- private Object storeRaceLogFinishPositioningListChangedEvent(RaceLogFinishPositioningListChangedEvent finishPositioningListChangedEvent) {
1185
- DBObject result = new BasicDBObject();
1186
- storeRaceLogEventProperties(finishPositioningListChangedEvent, result);
1187
- result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogFinishPositioningListChangedEvent.class.getSimpleName());
1188
- result.put(FieldNames.RACE_LOG_POSITIONED_COMPETITORS.name(), storePositionedCompetitors(finishPositioningListChangedEvent.getPositionedCompetitorsIDsNamesMaxPointsReasons()));
1189
-
1190
- return result;
1191
- }
1192
-
1193
- private Object storeRaceLogFinishPositioningConfirmedEvent(RaceLogFinishPositioningConfirmedEvent finishPositioningConfirmedEvent) {
1194
- DBObject result = new BasicDBObject();
1195
- storeRaceLogEventProperties(finishPositioningConfirmedEvent, result);
1196
- result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogFinishPositioningConfirmedEvent.class.getSimpleName());
1197
- result.put(FieldNames.RACE_LOG_POSITIONED_COMPETITORS.name(), storePositionedCompetitors(finishPositioningConfirmedEvent.getPositionedCompetitorsIDsNamesMaxPointsReasons()));
1198
-
1199
- return result;
1200
- }
1201
-
1202
- private Object storeRaceLogGateLineOpeningTimeEvent(RaceLogGateLineOpeningTimeEvent gateLineOpeningTimeEvent){
1203
- DBObject result = new BasicDBObject();
1204
- storeRaceLogEventProperties(gateLineOpeningTimeEvent, result);
1205
- result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogGateLineOpeningTimeEvent.class.getSimpleName());
1206
- result.put(FieldNames.RACE_LOG_GATE_LINE_OPENING_TIME.name(), gateLineOpeningTimeEvent.getGateLineOpeningTimes().getGateLaunchStopTime());
1207
- result.put(FieldNames.RACE_LOG_GOLF_DOWN_TIME.name(), gateLineOpeningTimeEvent.getGateLineOpeningTimes().getGolfDownTime());
1208
- return result;
1209
- }
1210
-
1211
- private BasicDBList storePositionedCompetitors(List<com.sap.sse.common.Util.Triple<Serializable, String, MaxPointsReason>> positionedCompetitors) {
1212
- BasicDBList dbList = new BasicDBList();
1213
- if (positionedCompetitors != null) {
1214
- for (com.sap.sse.common.Util.Triple<Serializable, String, MaxPointsReason> competitorPair : positionedCompetitors) {
1215
- dbList.add(storePositionedCompetitor(competitorPair));
1216
- }
1217
- }
1218
- return dbList;
1219
- }
1220
-
1221
- private DBObject storePositionedCompetitor(com.sap.sse.common.Util.Triple<Serializable, String, MaxPointsReason> competitorTriple) {
1222
- DBObject result = new BasicDBObject();
1223
- result.put(FieldNames.COMPETITOR_ID.name(), competitorTriple.getA());
1224
- result.put(FieldNames.COMPETITOR_DISPLAY_NAME.name(), competitorTriple.getB());
1225
- result.put(FieldNames.LEADERBOARD_SCORE_CORRECTION_MAX_POINTS_REASON.name(), competitorTriple.getC().name());
1226
-
1227
- return result;
1228
- }
1229
-
1230
- private BasicDBList storeCourseBase(CourseBase courseData) {
1231
- BasicDBList dbList = new BasicDBList();
1232
-
1233
- for (Waypoint waypoint : courseData.getWaypoints()) {
1234
- dbList.add(storeWaypoint(waypoint));
1235
- }
1236
- return dbList;
1237
- }
1238
-
1239
- private DBObject storeWaypoint(Waypoint waypoint) {
1240
- DBObject result = new BasicDBObject();
1241
- result.put(FieldNames.WAYPOINT_PASSINGINSTRUCTIONS.name(), getPassingInstructions(waypoint.getPassingInstructions()));
1242
- result.put(FieldNames.CONTROLPOINT.name(), storeControlPoint(waypoint.getControlPoint()));
1243
- return result;
1244
- }
1245
-
1246
- private DBObject storeControlPoint(ControlPoint controlPoint) {
1247
- DBObject result = new BasicDBObject();
1248
- if (controlPoint instanceof Mark) {
1249
- result.put(FieldNames.CONTROLPOINT_CLASS.name(), Mark.class.getSimpleName());
1250
- result.put(FieldNames.CONTROLPOINT_VALUE.name(), storeMark((Mark) controlPoint));
1251
- } else if (controlPoint instanceof ControlPointWithTwoMarks) {
1252
- result.put(FieldNames.CONTROLPOINT_CLASS.name(), ControlPointWithTwoMarks.class.getSimpleName());
1253
- result.put(FieldNames.CONTROLPOINT_VALUE.name(), storeControlPointWithTwoMarks((ControlPointWithTwoMarks) controlPoint));
1254
- }
1255
- return result;
1256
- }
1257
-
1258
- private DBObject storeControlPointWithTwoMarks(ControlPointWithTwoMarks cpwtm) {
1259
- DBObject result = new BasicDBObject();
1260
- result.put(FieldNames.CONTROLPOINTWITHTWOMARKS_ID.name(), cpwtm.getId());
1261
- result.put(FieldNames.CONTROLPOINTWITHTWOMARKS_NAME.name(), cpwtm.getName());
1262
- result.put(FieldNames.CONTROLPOINTWITHTWOMARKS_LEFT.name(), storeMark(cpwtm.getLeft()));
1263
- result.put(FieldNames.CONTROLPOINTWITHTWOMARKS_RIGHT.name(), storeMark(cpwtm.getRight()));
1264
- return result;
1265
- }
1266
-
1267
- private DBObject storeMark(Mark mark) {
1268
- DBObject result = new BasicDBObject();
1269
- result.put(FieldNames.MARK_ID.name(), mark.getId());
1270
- result.put(FieldNames.MARK_COLOR.name(), mark.getColor());
1271
- result.put(FieldNames.MARK_NAME.name(), mark.getName());
1272
- result.put(FieldNames.MARK_PATTERN.name(), mark.getPattern());
1273
- result.put(FieldNames.MARK_SHAPE.name(), mark.getShape());
1274
- result.put(FieldNames.MARK_TYPE.name(), mark.getType() == null ? null : mark.getType().name());
1275
- return result;
1276
- }
1277
-
1278
- private String getPassingInstructions(PassingInstruction passingInstructions) {
1279
- final String passing;
1280
- if (passingInstructions != null) {
1281
- passing = passingInstructions.name();
1282
- } else {
1283
- passing = null;
1284
- }
1285
- return passing;
1286
- }
1287
- @Override
1288
- public void storeCompetitor(Competitor competitor) {
1289
- DBCollection collection = database.getCollection(CollectionNames.COMPETITORS.name());
1290
- JSONObject json = competitorSerializer.serialize(competitor);
1291
- DBObject query = (DBObject) JSON.parse(CompetitorJsonSerializer.getCompetitorIdQuery(competitor).toString());
1292
- DBObject entry = (DBObject) JSON.parse(json.toString());
1293
- collection.update(query, entry, /* upsrt */true, /* multi */false, WriteConcern.SAFE);
1294
- }
1295
-
1296
- @Override
1297
- public void removeAllCompetitors() {
1298
- logger.info("Removing all persistent competitor info");
1299
- DBCollection collection = database.getCollection(CollectionNames.COMPETITORS.name());
1300
- collection.drop();
1301
- }
1302
-
1303
- @Override
1304
- public void removeCompetitor(Competitor competitor) {
1305
- logger.info("Removing persistent competitor info for competitor "+competitor.getName()+" with ID "+competitor.getId());
1306
- DBCollection collection = database.getCollection(CollectionNames.COMPETITORS.name());
1307
- DBObject query = (DBObject) JSON.parse(CompetitorJsonSerializer.getCompetitorIdQuery(competitor).toString());
1308
- collection.remove(query, WriteConcern.SAFE);
1309
- }
1310
-
1311
- @Override
1312
- public void storeDeviceConfiguration(DeviceConfigurationMatcher matcher, DeviceConfiguration configuration) {
1313
- DBCollection configurationsCollections = database.getCollection(CollectionNames.CONFIGURATIONS.name());
1314
-
1315
- DBObject query = new BasicDBObject();
1316
- query.put(FieldNames.CONFIGURATION_MATCHER_ID.name(), matcher.getMatcherIdentifier());
1317
-
1318
- DBObject entryObject = new BasicDBObject();
1319
- entryObject.put(FieldNames.CONFIGURATION_MATCHER_ID.name(), matcher.getMatcherIdentifier());
1320
- entryObject.put(FieldNames.CONFIGURATION_MATCHER.name(), createDeviceConfigurationMatcherObject(matcher));
1321
- entryObject.put(FieldNames.CONFIGURATION_CONFIG.name(), createDeviceConfigurationObject(configuration));
1322
-
1323
- configurationsCollections.update(query, entryObject, /* upsrt */ true, /* multi */ false, WriteConcern.SAFE);
1324
- }
1325
-
1326
- private DBObject createDeviceConfigurationMatcherObject(DeviceConfigurationMatcher matcher) {
1327
- DBObject matcherObject = new BasicDBObject();
1328
- matcherObject.put(FieldNames.CONFIGURATION_MATCHER_TYPE.name(), matcher.getMatcherType().name());
1329
- if (matcher instanceof DeviceConfigurationMatcherSingle) {
1330
- BasicDBList client = new BasicDBList();
1331
- client.add(((DeviceConfigurationMatcherSingle)matcher).getClientIdentifier());
1332
- matcherObject.put(FieldNames.CONFIGURATION_MATCHER_CLIENTS.name(), client);
1333
- } else if (matcher instanceof DeviceConfigurationMatcherMulti) {
1334
- BasicDBList clients = new BasicDBList();
1335
- Util.addAll(((DeviceConfigurationMatcherMulti)matcher).getClientIdentifiers(), clients);
1336
- matcherObject.put(FieldNames.CONFIGURATION_MATCHER_CLIENTS.name(), clients);
1337
- }
1338
- return matcherObject;
1339
- }
1340
-
1341
- private DBObject createDeviceConfigurationObject(DeviceConfiguration configuration) {
1342
- JsonSerializer<DeviceConfiguration> serializer = DeviceConfigurationJsonSerializer.create();
1343
- JSONObject json = serializer.serialize(configuration);
1344
- DBObject entry = (DBObject) JSON.parse(json.toString());
1345
- return entry;
1346
- }
1347
-
1348
- @Override
1349
- public void removeDeviceConfiguration(DeviceConfigurationMatcher matcher) {
1350
- DBCollection configurationsCollections = database.getCollection(CollectionNames.CONFIGURATIONS.name());
1351
- DBObject query = new BasicDBObject();
1352
- query.put(FieldNames.CONFIGURATION_MATCHER_ID.name(), matcher.getMatcherIdentifier());
1353
- configurationsCollections.remove(query);
1354
- }
1355
-
1356
- public static DBObject storeDeviceId(
1357
- TypeBasedServiceFinder<DeviceIdentifierMongoHandler> deviceIdentifierServiceFinder, DeviceIdentifier device)
1358
- throws TransformationException, NoCorrespondingServiceRegisteredException {
1359
- String type = device.getIdentifierType();
1360
- DeviceIdentifierMongoHandler handler = deviceIdentifierServiceFinder.findService(type);
1361
- com.sap.sse.common.Util.Pair<String, ? extends Object> pair = handler.serialize(device);
1362
- type = pair.getA();
1363
- Object deviceTypeSpecificId = pair.getB();
1364
- return new BasicDBObjectBuilder()
1365
- .add(FieldNames.DEVICE_TYPE.name(), type)
1366
- .add(FieldNames.DEVICE_TYPE_SPECIFIC_ID.name(), deviceTypeSpecificId)
1367
- .add(FieldNames.DEVICE_STRING_REPRESENTATION.name(), device.getStringRepresentation()).get();
1368
- }
1369
-
1370
- void storeRaceLogEventEvent(DBObject eventEntry) {
1371
- getRaceLogCollection().insert(eventEntry);
1372
- }
1373
-
1374
- @Override
1375
- public void removeRaceLog(RaceLogIdentifier identifier) {
1376
- DBObject query = new BasicDBObject();
1377
- storeRaceLogIdentifier(identifier, query);
1378
- getRaceLogCollection().remove(query);
1379
- }
1380
-
1381
- @Override
1382
- public void removeRegattaLog(RegattaLikeIdentifier identifier) {
1383
- DBObject query = new BasicDBObject();
1384
- addRegattaLikeIdentifier(identifier, query);
1385
- getRegattaLogCollection().remove(query);
1386
- }
1387
-
1388
- @Override
1389
- public void storeResultUrl(String resultProviderName, URL url) {
1390
- DBCollection resultUrlsCollection = database.getCollection(CollectionNames.RESULT_URLS.name());
1391
- DBObject query = new BasicDBObject(FieldNames.RESULT_PROVIDERNAME.name(), resultProviderName);
1392
- DBObject entry = new BasicDBObject(FieldNames.RESULT_PROVIDERNAME.name(), resultProviderName);
1393
- entry.put(FieldNames.RESULT_URL.name(), url.toString());
1394
- resultUrlsCollection.update(query, entry, /* upsrt */true, /* multi */false, WriteConcern.SAFE);
1395
- }
1396
-
1397
- @Override
1398
- public void removeResultUrl(String resultProviderName, URL url) {
1399
- DBCollection resultUrlsCollection = database.getCollection(CollectionNames.RESULT_URLS.name());
1400
- DBObject query = new BasicDBObjectBuilder().add(FieldNames.RESULT_PROVIDERNAME.name(), resultProviderName)
1401
- .add(FieldNames.RESULT_URL.name(), url.toString()).get();
1402
- resultUrlsCollection.remove(query);
1403
- }
1404
-
1405
- public DBCollection getRegattaLogCollection() {
1406
- DBCollection result = database.getCollection(CollectionNames.REGATTA_LOGS.name());
1407
- DBObject index = new BasicDBObject(FieldNames.REGATTA_LOG_IDENTIFIER_TYPE.name(), null);
1408
- index.put(FieldNames.REGATTA_LOG_IDENTIFIER_NAME.name(), null);
1409
- result.createIndex(index);
1410
- return result;
1411
- }
1412
-
1413
- private DBObject createBasicRegattaLogEventDBObject(RegattaLogEvent event) {
1414
- DBObject result = new BasicDBObject();
1415
- storeTimed(event, result);
1416
- storeTimePoint(event.getCreatedAt(), result, FieldNames.REGATTA_LOG_EVENT_CREATED_AT);
1417
- result.put(FieldNames.REGATTA_LOG_EVENT_ID.name(), event.getId());
1418
- result.put(FieldNames.REGATTA_LOG_EVENT_AUTHOR_NAME.name(), event.getAuthor().getName());
1419
- result.put(FieldNames.REGATTA_LOG_EVENT_AUTHOR_PRIORITY.name(), event.getAuthor().getPriority());
1420
- return result;
1421
- }
1422
-
1423
- private void addRegattaLikeIdentifier(RegattaLikeIdentifier regattaLikeId, DBObject toObject) {
1424
- toObject.put(FieldNames.REGATTA_LOG_IDENTIFIER_TYPE.name(), regattaLikeId.getIdentifierType());
1425
- toObject.put(FieldNames.REGATTA_LOG_IDENTIFIER_NAME.name(), regattaLikeId.getName());
1426
- }
1427
-
1428
- private void storeRegattaLogEvent(RegattaLikeIdentifier regattaLikeId, DBObject innerObject) {
1429
- DBObject result = new BasicDBObject(FieldNames.REGATTA_LOG_EVENT.name(), innerObject);
1430
- addRegattaLikeIdentifier(regattaLikeId, result);
1431
- getRegattaLogCollection().insert(result);
1432
- }
1433
-
1434
- public void storeRegattaLogEvent(RegattaLikeIdentifier regattaLikeId, RegattaLogDeviceCompetitorMappingEvent event) {
1435
- DBObject result = createBasicRegattaLogEventDBObject(event);
1436
- result.put(FieldNames.REGATTA_LOG_EVENT_CLASS.name(), RegattaLogDeviceCompetitorMappingEvent.class.getSimpleName());
1437
- storeDeviceMappingEvent(event, result, FieldNames.REGATTA_LOG_FROM, FieldNames.REGATTA_LOG_TO);
1438
- result.put(FieldNames.COMPETITOR_ID.name(), event.getMappedTo().getId());
1439
- storeRegattaLogEvent(regattaLikeId, result);
1440
- }
1441
-
1442
- public void storeRegattaLogEvent(RegattaLikeIdentifier regattaLikeId, RegattaLogDeviceMarkMappingEvent event) {
1443
- DBObject result = createBasicRegattaLogEventDBObject(event);
1444
- result.put(FieldNames.REGATTA_LOG_EVENT_CLASS.name(), RegattaLogDeviceMarkMappingEvent.class.getSimpleName());
1445
- storeDeviceMappingEvent(event, result, FieldNames.REGATTA_LOG_FROM, FieldNames.REGATTA_LOG_TO);
1446
- result.put(FieldNames.MARK.name(), storeMark(event.getMappedTo()));
1447
- storeRegattaLogEvent(regattaLikeId, result);
1448
- }
1449
-
1450
- public void storeRegattaLogEvent(RegattaLikeIdentifier regattaLikeId, RegattaLogRevokeEvent event) {
1451
- DBObject result = createBasicRegattaLogEventDBObject(event);
1452
- result.put(FieldNames.REGATTA_LOG_EVENT_CLASS.name(), RegattaLogRevokeEvent.class.getSimpleName());
1453
- result.put(FieldNames.REGATTA_LOG_REVOKED_EVENT_ID.name(), event.getRevokedEventId());
1454
- result.put(FieldNames.REGATTA_LOG_REVOKED_EVENT_TYPE.name(), event.getRevokedEventType());
1455
- result.put(FieldNames.REGATTA_LOG_REVOKED_EVENT_SHORT_INFO.name(), event.getRevokedEventShortInfo());
1456
- result.put(FieldNames.REGATTA_LOG_REVOKED_REASON.name(), event.getReason());
1457
- storeRegattaLogEvent(regattaLikeId, result);
1458
- }
1459
-
1460
- public void storeRegattaLogEvent(RegattaLikeIdentifier regattaLikeId, RegattaLogRegisterCompetitorEvent event) {
1461
- DBObject result = createBasicRegattaLogEventDBObject(event);
1462
- result.put(FieldNames.REGATTA_LOG_EVENT_CLASS.name(), RegattaLogRegisterCompetitorEvent.class.getSimpleName());
1463
- result.put(FieldNames.REGATTA_LOG_COMPETITOR_ID.name(), event.getCompetitor().getId());
1464
- storeRegattaLogEvent(regattaLikeId, result);
1465
- }
1466
-
1467
- public void storeRegattaLogEvent(RegattaLikeIdentifier regattaLikeId, RegattaLogCloseOpenEndedDeviceMappingEvent event) {
1468
- DBObject result = createBasicRegattaLogEventDBObject(event);
1469
- result.put(FieldNames.REGATTA_LOG_EVENT_CLASS.name(), RegattaLogCloseOpenEndedDeviceMappingEvent.class.getSimpleName());
1470
- result.put(FieldNames.REGATTA_LOG_DEVICE_MAPPING_EVENT_ID.name(), event.getDeviceMappingEventId());
1471
- storeTimePoint(event.getClosingTimePoint(), result, FieldNames.REGATTA_LOG_CLOSING_TIMEPOINT);
1472
- storeRegattaLogEvent(regattaLikeId, result);
1473
- }
1474
-
1475
- public void storeRegattaLogEvent(RegattaLikeIdentifier regattaLikeId, RegattaLogSetCompetitorTimeOnTimeFactorEvent event) {
1476
- DBObject result = createBasicRegattaLogEventDBObject(event);
1477
- result.put(FieldNames.REGATTA_LOG_EVENT_CLASS.name(), RegattaLogCloseOpenEndedDeviceMappingEvent.class.getSimpleName());
1478
- result.put(FieldNames.REGATTA_LOG_COMPETITOR_ID.name(), event.getCompetitor().getId());
1479
- result.put(FieldNames.REGATTA_LOG_TIME_ON_TIME_FACTOR.name(), event.getTimeOnTimeFactor());
1480
- }
1481
-
1482
- public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogDependentStartTimeEvent event) {
1483
- BasicDBObject result = new BasicDBObject();
1484
- storeRaceLogIdentifier(raceLogIdentifier, result);
1485
- result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogDependentStartTimeEvent(event));
1486
- return result;
1487
- }
1488
-
1489
- public void storeRegattaLogEvent(RegattaLikeIdentifier regattaLikeId, RegattaLogSetCompetitorTimeOnDistanceAllowancePerNauticalMileEvent event) {
1490
- DBObject result = createBasicRegattaLogEventDBObject(event);
1491
- result.put(FieldNames.REGATTA_LOG_EVENT_CLASS.name(), RegattaLogCloseOpenEndedDeviceMappingEvent.class.getSimpleName());
1492
- result.put(FieldNames.REGATTA_LOG_COMPETITOR_ID.name(), event.getCompetitor().getId());
1493
- result.put(FieldNames.REGATTA_LOG_TIME_ON_DISTANCE_SECONDS_ALLOWANCE_PER_NAUTICAL_MILE.name(), event.getTimeOnDistanceAllowancePerNauticalMile().asSeconds());
1494
- storeRegattaLogEvent(regattaLikeId, result);
1495
- }
1496
-
1497
- private DBObject createImageObject(ImageDescriptor image) {
1498
- DBObject result = new BasicDBObject();
1499
- result.put(FieldNames.IMAGE_URL.name(), image.getURL().toString());
1500
- result.put(FieldNames.IMAGE_LOCALE.name(), image.getLocale() != null ? image.getLocale().toLanguageTag() : null);
1501
- result.put(FieldNames.IMAGE_TITLE.name(), image.getTitle());
1502
- result.put(FieldNames.IMAGE_SUBTITLE.name(), image.getSubtitle());
1503
- result.put(FieldNames.IMAGE_COPYRIGHT.name(), image.getCopyright());
1504
- result.put(FieldNames.IMAGE_WIDTH_IN_PX.name(), image.getWidthInPx());
1505
- result.put(FieldNames.IMAGE_HEIGHT_IN_PX.name(), image.getHeightInPx());
1506
- storeTimePoint(image.getCreatedAtDate(), result, FieldNames.IMAGE_CREATEDATDATE);
1507
- BasicDBList tags = new BasicDBList();
1508
- for (String tag : image.getTags()) {
1509
- tags.add(tag);
1510
- }
1511
- result.put(FieldNames.IMAGE_TAGS.name(), tags);
1512
- return result;
1513
- }
1514
-
1515
- private DBObject createVideoObject(VideoDescriptor video) {
1516
- DBObject result = new BasicDBObject();
1517
- result.put(FieldNames.VIDEO_URL.name(), video.getURL().toString());
1518
- result.put(FieldNames.VIDEO_LOCALE.name(), video.getLocale() != null ? video.getLocale().toLanguageTag() : null);
1519
- result.put(FieldNames.VIDEO_THUMBNAIL_URL.name(), video.getThumbnailURL() != null ? video.getThumbnailURL().toString() : null);
1520
- result.put(FieldNames.VIDEO_TITLE.name(), video.getTitle());
1521
- result.put(FieldNames.VIDEO_SUBTITLE.name(), video.getSubtitle());
1522
- result.put(FieldNames.VIDEO_MIMETYPE.name(), video.getMimeType() != null ? video.getMimeType().name() : null);
1523
- result.put(FieldNames.VIDEO_COPYRIGHT.name(), video.getCopyright());
1524
- result.put(FieldNames.VIDEO_LENGTH_IN_SECONDS.name(), video.getLengthInSeconds());
1525
- storeTimePoint(video.getCreatedAtDate(), result, FieldNames.VIDEO_CREATEDATDATE);
1526
- BasicDBList tags = new BasicDBList();
1527
- for (String tag : video.getTags()) {
1528
- tags.add(tag);
1529
- }
1530
- result.put(FieldNames.VIDEO_TAGS.name(), tags);
1531
- return result;
1532
- }
1533
-}
1
+package com.sap.sailing.domain.persistence.impl;
2
+
3
+import java.io.Serializable;
4
+import java.net.URL;
5
+import java.util.List;
6
+import java.util.Map.Entry;
7
+import java.util.logging.Level;
8
+import java.util.logging.Logger;
9
+
10
+import org.bson.types.ObjectId;
11
+import org.json.simple.JSONObject;
12
+
13
+import com.mongodb.BasicDBList;
14
+import com.mongodb.BasicDBObject;
15
+import com.mongodb.BasicDBObjectBuilder;
16
+import com.mongodb.DB;
17
+import com.mongodb.DBCollection;
18
+import com.mongodb.DBObject;
19
+import com.mongodb.WriteConcern;
20
+import com.mongodb.util.JSON;
21
+import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor;
22
+import com.sap.sailing.domain.abstractlog.race.RaceLogCourseAreaChangedEvent;
23
+import com.sap.sailing.domain.abstractlog.race.RaceLogCourseDesignChangedEvent;
24
+import com.sap.sailing.domain.abstractlog.race.RaceLogDependentStartTimeEvent;
25
+import com.sap.sailing.domain.abstractlog.race.RaceLogEndOfTrackingEvent;
26
+import com.sap.sailing.domain.abstractlog.race.RaceLogEvent;
27
+import com.sap.sailing.domain.abstractlog.race.RaceLogFinishPositioningConfirmedEvent;
28
+import com.sap.sailing.domain.abstractlog.race.RaceLogFinishPositioningListChangedEvent;
29
+import com.sap.sailing.domain.abstractlog.race.RaceLogFixedMarkPassingEvent;
30
+import com.sap.sailing.domain.abstractlog.race.RaceLogFlagEvent;
31
+import com.sap.sailing.domain.abstractlog.race.RaceLogGateLineOpeningTimeEvent;
32
+import com.sap.sailing.domain.abstractlog.race.RaceLogPassChangeEvent;
33
+import com.sap.sailing.domain.abstractlog.race.RaceLogPathfinderEvent;
34
+import com.sap.sailing.domain.abstractlog.race.RaceLogProtestStartTimeEvent;
35
+import com.sap.sailing.domain.abstractlog.race.RaceLogRaceStatusEvent;
36
+import com.sap.sailing.domain.abstractlog.race.RaceLogRevokeEvent;
37
+import com.sap.sailing.domain.abstractlog.race.RaceLogStartOfTrackingEvent;
38
+import com.sap.sailing.domain.abstractlog.race.RaceLogStartProcedureChangedEvent;
39
+import com.sap.sailing.domain.abstractlog.race.RaceLogStartTimeEvent;
40
+import com.sap.sailing.domain.abstractlog.race.RaceLogSuppressedMarkPassingsEvent;
41
+import com.sap.sailing.domain.abstractlog.race.RaceLogWindFixEvent;
42
+import com.sap.sailing.domain.abstractlog.race.scoring.RaceLogAdditionalScoringInformationEvent;
43
+import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogCloseOpenEndedDeviceMappingEvent;
44
+import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogDefineMarkEvent;
45
+import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogDenoteForTrackingEvent;
46
+import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogDeviceCompetitorMappingEvent;
47
+import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogDeviceMarkMappingEvent;
48
+import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogRegisterCompetitorEvent;
49
+import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogStartTrackingEvent;
50
+import com.sap.sailing.domain.abstractlog.regatta.RegattaLogEvent;
51
+import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogCloseOpenEndedDeviceMappingEvent;
52
+import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceCompetitorMappingEvent;
53
+import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceMarkMappingEvent;
54
+import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogRegisterCompetitorEvent;
55
+import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogRevokeEvent;
56
+import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogSetCompetitorTimeOnDistanceAllowancePerNauticalMileEvent;
57
+import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogSetCompetitorTimeOnTimeFactorEvent;
58
+import com.sap.sailing.domain.abstractlog.shared.events.DeviceMappingEvent;
59
+import com.sap.sailing.domain.base.Competitor;
60
+import com.sap.sailing.domain.base.ControlPoint;
61
+import com.sap.sailing.domain.base.ControlPointWithTwoMarks;
62
+import com.sap.sailing.domain.base.CourseArea;
63
+import com.sap.sailing.domain.base.CourseBase;
64
+import com.sap.sailing.domain.base.Event;
65
+import com.sap.sailing.domain.base.Fleet;
66
+import com.sap.sailing.domain.base.Mark;
67
+import com.sap.sailing.domain.base.RaceColumn;
68
+import com.sap.sailing.domain.base.RaceDefinition;
69
+import com.sap.sailing.domain.base.Regatta;
70
+import com.sap.sailing.domain.base.RemoteSailingServerReference;
71
+import com.sap.sailing.domain.base.SailingServerConfiguration;
72
+import com.sap.sailing.domain.base.Series;
73
+import com.sap.sailing.domain.base.Venue;
74
+import com.sap.sailing.domain.base.Waypoint;
75
+import com.sap.sailing.domain.base.configuration.DeviceConfiguration;
76
+import com.sap.sailing.domain.base.configuration.DeviceConfigurationMatcher;
77
+import com.sap.sailing.domain.base.configuration.RegattaConfiguration;
78
+import com.sap.sailing.domain.base.configuration.impl.DeviceConfigurationMatcherMulti;
79
+import com.sap.sailing.domain.base.configuration.impl.DeviceConfigurationMatcherSingle;
80
+import com.sap.sailing.domain.base.impl.FleetImpl;
81
+import com.sap.sailing.domain.common.Bearing;
82
+import com.sap.sailing.domain.common.MaxPointsReason;
83
+import com.sap.sailing.domain.common.PassingInstruction;
84
+import com.sap.sailing.domain.common.Positioned;
85
+import com.sap.sailing.domain.common.RaceIdentifier;
86
+import com.sap.sailing.domain.common.Speed;
87
+import com.sap.sailing.domain.common.SpeedWithBearing;
88
+import com.sap.sailing.domain.common.Wind;
89
+import com.sap.sailing.domain.common.WindSource;
90
+import com.sap.sailing.domain.common.racelog.tracking.TransformationException;
91
+import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard;
92
+import com.sap.sailing.domain.leaderboard.Leaderboard;
93
+import com.sap.sailing.domain.leaderboard.LeaderboardGroup;
94
+import com.sap.sailing.domain.leaderboard.RegattaLeaderboard;
95
+import com.sap.sailing.domain.leaderboard.ResultDiscardingRule;
96
+import com.sap.sailing.domain.leaderboard.SettableScoreCorrection;
97
+import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule;
98
+import com.sap.sailing.domain.persistence.MongoObjectFactory;
99
+import com.sap.sailing.domain.persistence.racelog.tracking.DeviceIdentifierMongoHandler;
100
+import com.sap.sailing.domain.persistence.racelog.tracking.impl.PlaceHolderDeviceIdentifierMongoHandler;
101
+import com.sap.sailing.domain.racelog.RaceLogIdentifier;
102
+import com.sap.sailing.domain.racelogtracking.DeviceIdentifier;
103
+import com.sap.sailing.domain.regattalike.RegattaLikeIdentifier;
104
+import com.sap.sailing.domain.tracking.TrackedRace;
105
+import com.sap.sailing.domain.tracking.TrackedRegatta;
106
+import com.sap.sailing.domain.tracking.WindTrack;
107
+import com.sap.sailing.server.gateway.serialization.JsonSerializer;
108
+import com.sap.sailing.server.gateway.serialization.impl.CompetitorJsonSerializer;
109
+import com.sap.sailing.server.gateway.serialization.impl.DeviceConfigurationJsonSerializer;
110
+import com.sap.sailing.server.gateway.serialization.impl.RegattaConfigurationJsonSerializer;
111
+import com.sap.sse.common.Duration;
112
+import com.sap.sse.common.NoCorrespondingServiceRegisteredException;
113
+import com.sap.sse.common.TimePoint;
114
+import com.sap.sse.common.TimeRange;
115
+import com.sap.sse.common.Timed;
116
+import com.sap.sse.common.TypeBasedServiceFinder;
117
+import com.sap.sse.common.TypeBasedServiceFinderFactory;
118
+import com.sap.sse.common.Util;
119
+import com.sap.sse.common.impl.MillisecondsTimePoint;
120
+import com.sap.sse.shared.media.ImageDescriptor;
121
+import com.sap.sse.shared.media.VideoDescriptor;
122
+
123
+public class MongoObjectFactoryImpl implements MongoObjectFactory {
124
+ private static Logger logger = Logger.getLogger(MongoObjectFactoryImpl.class.getName());
125
+ private final DB database;
126
+ private final CompetitorJsonSerializer competitorSerializer = CompetitorJsonSerializer.create();
127
+ private final TypeBasedServiceFinder<DeviceIdentifierMongoHandler> deviceIdentifierServiceFinder;
128
+
129
+ /**
130
+ * Uses <code>null</code> for the device type service finder and hence will be unable to store device identifiers.
131
+ * Use this constructor only for testing purposes or in cases where there will happen absolutely no access to
132
+ * {@link DeviceIdentifier} objects.
133
+ */
134
+ public MongoObjectFactoryImpl(DB database) {
135
+ this(database, /* deviceTypeServiceFinder */ null);
136
+ }
137
+
138
+ public MongoObjectFactoryImpl(DB database, TypeBasedServiceFinderFactory serviceFinderFactory) {
139
+ this.database = database;
140
+ if (serviceFinderFactory != null) {
141
+ this.deviceIdentifierServiceFinder = serviceFinderFactory.createServiceFinder(DeviceIdentifierMongoHandler.class);
142
+ this.deviceIdentifierServiceFinder.setFallbackService(new PlaceHolderDeviceIdentifierMongoHandler());
143
+ } else {
144
+ this.deviceIdentifierServiceFinder = null;
145
+ }
146
+ }
147
+
148
+ @Override
149
+ public DB getDatabase() {
150
+ return database;
151
+ }
152
+
153
+ public DBObject storeWind(Wind wind) {
154
+ DBObject result = new BasicDBObject();
155
+ storePositioned(wind, result);
156
+ storeTimed(wind, result);
157
+ storeSpeedWithBearing(wind, result);
158
+ return result;
159
+ }
160
+
161
+ public static void storeTimePoint(TimePoint timePoint, DBObject result, String fieldName) {
162
+ if (timePoint != null) {
163
+ result.put(fieldName, timePoint.asMillis());
164
+ }
165
+ }
166
+
167
+ public static void storeTimePoint(TimePoint timePoint, DBObject result, FieldNames field) {
168
+ storeTimePoint(timePoint, result, field.name());
169
+ }
170
+
171
+ public static void storeTimeRange(TimeRange timeRange, DBObject result, FieldNames field) {
172
+ if (timeRange != null) {
173
+ DBObject timeRangeObj = new BasicDBObject();
174
+ storeTimePoint(timeRange.from(), timeRangeObj, FieldNames.FROM_MILLIS);
175
+ storeTimePoint(timeRange.to(), timeRangeObj, FieldNames.TO_MILLIS);
176
+ result.put(field.name(), timeRangeObj);
177
+ }
178
+ }
179
+
180
+ public void storeTimed(Timed timed, DBObject result) {
181
+ if (timed.getTimePoint() != null) {
182
+ storeTimePoint(timed.getTimePoint(), result, FieldNames.TIME_AS_MILLIS);
183
+ }
184
+ }
185
+
186
+ public void storeSpeedWithBearing(SpeedWithBearing speedWithBearing, DBObject result) {
187
+ storeSpeed(speedWithBearing, result);
188
+ storeBearing(speedWithBearing.getBearing(), result);
189
+
190
+ }
191
+
192
+ public void storeBearing(Bearing bearing, DBObject result) {
193
+ result.put(FieldNames.DEGREE_BEARING.name(), bearing.getDegrees());
194
+ }
195
+
196
+ public void storeSpeed(Speed speed, DBObject result) {
197
+ result.put(FieldNames.KNOT_SPEED.name(), speed.getKnots());
198
+ }
199
+
200
+ public void storePositioned(Positioned positioned, DBObject result) {
201
+ if (positioned.getPosition() != null) {
202
+ result.put(FieldNames.LAT_DEG.name(), positioned.getPosition().getLatDeg());
203
+ result.put(FieldNames.LNG_DEG.name(), positioned.getPosition().getLngDeg());
204
+ }
205
+ }
206
+
207
+ @Override
208
+ public void addWindTrackDumper(TrackedRegatta trackedRegatta, TrackedRace trackedRace, WindSource windSource) {
209
+ WindTrack windTrack = trackedRace.getOrCreateWindTrack(windSource);
210
+ windTrack.addListener(new MongoWindListener(trackedRace, trackedRegatta.getRegatta().getName(), windSource, this, database));
211
+ }
212
+
213
+ public DBCollection getWindTrackCollection() {
214
+ DBCollection result = database.getCollection(CollectionNames.WIND_TRACKS.name());
215
+ result.createIndex(new BasicDBObject(FieldNames.REGATTA_NAME.name(), null));
216
+ return result;
217
+ }
218
+
219
+ public DBCollection getGPSFixCollection() {
220
+ DBCollection gpsFixCollection = database.getCollection(CollectionNames.GPS_FIXES.name());
221
+ DBObject index = new BasicDBObject();
222
+ index.put(FieldNames.DEVICE_ID.name(), null);
223
+ index.put(FieldNames.TIME_AS_MILLIS.name(), null);
224
+ gpsFixCollection.createIndex(index);
225
+ return gpsFixCollection;
226
+ }
227
+
228
+ public DBCollection getGPSFixMetadataCollection() {
229
+ DBCollection collection = database.getCollection(CollectionNames.GPS_FIXES_METADATA.name());
230
+ DBObject index = new BasicDBObject();
231
+ index.put(FieldNames.DEVICE_ID.name(), null);
232
+ collection.createIndex(index);
233
+ return collection;
234
+ }
235
+
236
+ /**
237
+ * @param regattaName
238
+ * the regatta name is stored only for human readability purposes because a time stamp may be a bit unhandy for
239
+ * identifying where the wind fix was collected
240
+ */
241
+ public DBObject storeWindTrackEntry(RaceDefinition race, String regattaName, WindSource windSource, Wind wind) {
242
+ BasicDBObject result = new BasicDBObject();
243
+ result.put(FieldNames.RACE_ID.name(), race.getId());
244
+ result.put(FieldNames.REGATTA_NAME.name(), regattaName);
245
+ result.put(FieldNames.WIND_SOURCE_NAME.name(), windSource.name());
246
+ if (windSource.getId() != null) {
247
+ result.put(FieldNames.WIND_SOURCE_ID.name(), windSource.getId());
248
+ }
249
+ result.put(FieldNames.WIND.name(), storeWind(wind));
250
+ return result;
251
+ }
252
+
253
+ private void storeRaceIdentifiers(RaceColumn raceColumn, DBObject dbObject) {
254
+ BasicDBObject raceIdentifiersPerFleet = new BasicDBObject();
255
+ for (Fleet fleet : raceColumn.getFleets()) {
256
+ RaceIdentifier raceIdentifier = raceColumn.getRaceIdentifier(fleet);
257
+ if (raceIdentifier != null) {
258
+ DBObject raceIdentifierForFleet = new BasicDBObject();
259
+ storeRaceIdentifier(raceIdentifierForFleet, raceIdentifier);
260
+ raceIdentifiersPerFleet.put(MongoUtils.escapeDollarAndDot(fleet.getName()), raceIdentifierForFleet);
261
+ }
262
+ }
263
+ dbObject.put(FieldNames.RACE_IDENTIFIERS.name(), raceIdentifiersPerFleet);
264
+ }
265
+
266
+ private void storeRaceIdentifier(DBObject dbObject, RaceIdentifier raceIdentifier) {
267
+ if (raceIdentifier != null) {
268
+ dbObject.put(FieldNames.EVENT_NAME.name(), raceIdentifier.getRegattaName());
269
+ dbObject.put(FieldNames.RACE_NAME.name(), raceIdentifier.getRaceName());
270
+ }
271
+ }
272
+
273
+ @Override
274
+ public void storeLeaderboard(Leaderboard leaderboard) {
275
+ DBCollection leaderboardCollection = database.getCollection(CollectionNames.LEADERBOARDS.name());
276
+ try {
277
+ leaderboardCollection.createIndex(new BasicDBObject(FieldNames.LEADERBOARD_NAME.name(), 1));
278
+ } catch (NullPointerException npe) {
279
+ // sometimes, for reasons yet to be clarified, ensuring an index on the name field causes an NPE
280
+ logger.log(Level.SEVERE, "storeLeaderboard", npe);
281
+ }
282
+ BasicDBObject query = new BasicDBObject(FieldNames.LEADERBOARD_NAME.name(), leaderboard.getName());
283
+ BasicDBObject dbLeaderboard = new BasicDBObject();
284
+ dbLeaderboard.put(FieldNames.LEADERBOARD_NAME.name(), leaderboard.getName());
285
+ if (leaderboard.getDisplayName() != null) {
286
+ dbLeaderboard.put(FieldNames.LEADERBOARD_DISPLAY_NAME.name(), leaderboard.getDisplayName());
287
+ }
288
+ BasicDBList dbSuppressedCompetitorIds = new BasicDBList();
289
+ for (Competitor suppressedCompetitor : leaderboard.getSuppressedCompetitors()) {
290
+ dbSuppressedCompetitorIds.add(suppressedCompetitor.getId());
291
+ }
292
+ dbLeaderboard.put(FieldNames.LEADERBOARD_SUPPRESSED_COMPETITOR_IDS.name(), dbSuppressedCompetitorIds);
293
+ if (leaderboard instanceof FlexibleLeaderboard) {
294
+ storeFlexibleLeaderboard((FlexibleLeaderboard) leaderboard, dbLeaderboard);
295
+ } else if (leaderboard instanceof RegattaLeaderboard) {
296
+ storeRegattaLeaderboard((RegattaLeaderboard) leaderboard, dbLeaderboard);
297
+ } else {
298
+ // at least store the scoring scheme
299
+ dbLeaderboard.put(FieldNames.SCORING_SCHEME_TYPE.name(), leaderboard.getScoringScheme().getType().name());
300
+ }
301
+ if (leaderboard.getDefaultCourseArea() != null) {
302
+ dbLeaderboard.put(FieldNames.COURSE_AREA_ID.name(), leaderboard.getDefaultCourseArea().getId().toString());
303
+ } else {
304
+ dbLeaderboard.put(FieldNames.COURSE_AREA_ID.name(), null);
305
+ }
306
+ storeColumnFactors(leaderboard, dbLeaderboard);
307
+ storeLeaderboardCorrectionsAndDiscards(leaderboard, dbLeaderboard);
308
+ leaderboardCollection.update(query, dbLeaderboard, /* upsrt */ true, /* multi */ false, WriteConcern.SAFE);
309
+ }
310
+
311
+ private void storeColumnFactors(Leaderboard leaderboard, BasicDBObject dbLeaderboard) {
312
+ DBObject raceColumnFactors = new BasicDBObject();
313
+ for (RaceColumn raceColumn : leaderboard.getRaceColumns()) {
314
+ Double explicitFactor = raceColumn.getExplicitFactor();
315
+ if (explicitFactor != null) {
316
+ raceColumnFactors.put(MongoUtils.escapeDollarAndDot(raceColumn.getName()), explicitFactor);
317
+ }
318
+ }
319
+ dbLeaderboard.put(FieldNames.LEADERBOARD_COLUMN_FACTORS.name(), raceColumnFactors);
320
+ }
321
+
322
+ private void storeRegattaLeaderboard(RegattaLeaderboard leaderboard, DBObject dbLeaderboard) {
323
+ dbLeaderboard.put(FieldNames.REGATTA_NAME.name(), leaderboard.getRegatta().getName());
324
+ }
325
+
326
+ private void storeFlexibleLeaderboard(FlexibleLeaderboard leaderboard, BasicDBObject dbLeaderboard) {
327
+ BasicDBList dbRaceColumns = new BasicDBList();
328
+ dbLeaderboard.put(FieldNames.SCORING_SCHEME_TYPE.name(), leaderboard.getScoringScheme().getType().name());
329
+ dbLeaderboard.put(FieldNames.LEADERBOARD_COLUMNS.name(), dbRaceColumns);
330
+ for (RaceColumn raceColumn : leaderboard.getRaceColumns()) {
331
+ BasicDBObject dbRaceColumn = storeRaceColumn(raceColumn);
332
+ dbRaceColumns.add(dbRaceColumn);
333
+ }
334
+ }
335
+
336
+ private void storeLeaderboardCorrectionsAndDiscards(Leaderboard leaderboard, BasicDBObject dbLeaderboard) {
337
+ if (leaderboard.hasCarriedPoints()) {
338
+ BasicDBList dbCarriedPoints = new BasicDBList();
339
+ dbLeaderboard.put(FieldNames.LEADERBOARD_CARRIED_POINTS_BY_ID.name(), dbCarriedPoints);
340
+ for (Entry<Competitor, Double> competitorWithCarriedPoints : leaderboard
341
+ .getCompetitorsForWhichThereAreCarriedPoints().entrySet()) {
342
+ double carriedPoints = competitorWithCarriedPoints.getValue();
343
+ Competitor competitor = competitorWithCarriedPoints.getKey();
344
+ DBObject dbCarriedPointsForCompetitor = new BasicDBObject();
345
+ dbCarriedPointsForCompetitor.put(FieldNames.COMPETITOR_ID.name(), competitor.getId());
346
+ dbCarriedPointsForCompetitor.put(FieldNames.LEADERBOARD_CARRIED_POINTS.name(), carriedPoints);
347
+ dbCarriedPoints.add(dbCarriedPointsForCompetitor);
348
+ }
349
+ }
350
+ BasicDBObject dbScoreCorrections = new BasicDBObject();
351
+ storeScoreCorrections(leaderboard, dbScoreCorrections);
352
+ dbLeaderboard.put(FieldNames.LEADERBOARD_SCORE_CORRECTIONS.name(), dbScoreCorrections);
353
+ final ResultDiscardingRule resultDiscardingRule = leaderboard.getResultDiscardingRule();
354
+ storeResultDiscardingRule(dbLeaderboard, resultDiscardingRule, FieldNames.LEADERBOARD_DISCARDING_THRESHOLDS);
355
+ BasicDBList competitorDisplayNames = new BasicDBList();
356
+ for (Competitor competitor : leaderboard.getCompetitors()) {
357
+ String displayNameForCompetitor = leaderboard.getDisplayName(competitor);
358
+ if (displayNameForCompetitor != null) {
359
+ DBObject dbDisplayName = new BasicDBObject();
360
+ dbDisplayName.put(FieldNames.COMPETITOR_ID.name(), competitor.getId());
361
+ dbDisplayName.put(FieldNames.COMPETITOR_DISPLAY_NAME.name(), displayNameForCompetitor);
362
+ competitorDisplayNames.add(dbDisplayName);
363
+ }
364
+ }
365
+ dbLeaderboard.put(FieldNames.LEADERBOARD_COMPETITOR_DISPLAY_NAMES.name(), competitorDisplayNames);
366
+ }
367
+
368
+ /**
369
+ * Stores the result discarding rule to <code>dbObject</code>'s field identified by <code>field</code> if the result discarding
370
+ * rule is not <code>null</code> and is of type {@link ThresholdBasedResultDiscardingRule}. Otherwise, it is assumed that the
371
+ * result discarding rule is otherwise implicitly obtained, e.g., from a definition of a regatta with its series, stored elsewhere.
372
+ */
373
+ private void storeResultDiscardingRule(DBObject dbObject,
374
+ final ResultDiscardingRule resultDiscardingRule, FieldNames field) {
375
+ if (resultDiscardingRule != null && resultDiscardingRule instanceof ThresholdBasedResultDiscardingRule) {
376
+ BasicDBList dbResultDiscardingThresholds = new BasicDBList();
377
+ for (int threshold : ((ThresholdBasedResultDiscardingRule) resultDiscardingRule).getDiscardIndexResultsStartingWithHowManyRaces()) {
378
+ dbResultDiscardingThresholds.add(threshold);
379
+ }
380
+ dbObject.put(field.name(), dbResultDiscardingThresholds);
381
+ }
382
+ }
383
+
384
+ private BasicDBObject storeRaceColumn(RaceColumn raceColumn) {
385
+ BasicDBObject dbRaceColumn = new BasicDBObject();
386
+ dbRaceColumn.put(FieldNames.LEADERBOARD_COLUMN_NAME.name(), raceColumn.getName());
387
+ dbRaceColumn.put(FieldNames.LEADERBOARD_IS_MEDAL_RACE_COLUMN.name(), raceColumn.isMedalRace());
388
+ storeRaceIdentifiers(raceColumn, dbRaceColumn);
389
+ return dbRaceColumn;
390
+ }
391
+
392
+ private void storeScoreCorrections(Leaderboard leaderboard, BasicDBObject dbScoreCorrections) {
393
+ TimePoint now = MillisecondsTimePoint.now();
394
+ SettableScoreCorrection scoreCorrection = leaderboard.getScoreCorrection();
395
+ for (RaceColumn raceColumn : scoreCorrection.getRaceColumnsThatHaveCorrections()) {
396
+ BasicDBList dbCorrectionForRace = new BasicDBList();
397
+ for (Competitor competitor : scoreCorrection.getCompetitorsThatHaveCorrectionsIn(raceColumn)) {
398
+ // TODO bug 655: make score corrections time dependent
399
+ if (scoreCorrection.isScoreCorrected(competitor, raceColumn, now)) {
400
+ BasicDBObject dbCorrectionForCompetitor = new BasicDBObject();
401
+ dbCorrectionForCompetitor.put(FieldNames.COMPETITOR_ID.name(), competitor.getId());
402
+ MaxPointsReason maxPointsReason = scoreCorrection.getMaxPointsReason(competitor, raceColumn, now);
403
+ if (maxPointsReason != MaxPointsReason.NONE) {
404
+ dbCorrectionForCompetitor.put(FieldNames.LEADERBOARD_SCORE_CORRECTION_MAX_POINTS_REASON.name(),
405
+ maxPointsReason.name());
406
+ }
407
+ Double explicitScoreCorrection = scoreCorrection
408
+ .getExplicitScoreCorrection(competitor, raceColumn);
409
+ if (explicitScoreCorrection != null) {
410
+ dbCorrectionForCompetitor.put(FieldNames.LEADERBOARD_CORRECTED_SCORE.name(),
411
+ explicitScoreCorrection);
412
+ }
413
+ dbCorrectionForRace.add(dbCorrectionForCompetitor);
414
+ }
415
+ }
416
+ if (!dbCorrectionForRace.isEmpty()) {
417
+ // using the column name as the key for the score corrections requires re-writing the score corrections
418
+ // of a meta-leaderboard if the name of one of its leaderboards changes
419
+ dbScoreCorrections.put(MongoUtils.escapeDollarAndDot(raceColumn.getName()), dbCorrectionForRace);
420
+ }
421
+ }
422
+ final TimePoint timePointOfLastCorrectionsValidity = scoreCorrection.getTimePointOfLastCorrectionsValidity();
423
+ if (timePointOfLastCorrectionsValidity != null) {
424
+ dbScoreCorrections.put(FieldNames.LEADERBOARD_SCORE_CORRECTION_TIMESTAMP.name(), timePointOfLastCorrectionsValidity.asMillis());
425
+ }
426
+ if (scoreCorrection.getComment() != null) {
427
+ dbScoreCorrections.put(FieldNames.LEADERBOARD_SCORE_CORRECTION_COMMENT.name(), scoreCorrection.getComment());
428
+ }
429
+ }
430
+
431
+ @Override
432
+ public void removeLeaderboard(String leaderboardName) {
433
+ DBCollection leaderboardCollection = database.getCollection(CollectionNames.LEADERBOARDS.name());
434
+ BasicDBObject query = new BasicDBObject(FieldNames.LEADERBOARD_NAME.name(), leaderboardName);
435
+ leaderboardCollection.remove(query);
436
+ }
437
+
438
+ @Override
439
+ public void renameLeaderboard(String oldName, String newName) {
440
+ DBCollection leaderboardCollection = database.getCollection(CollectionNames.LEADERBOARDS.name());
441
+ BasicDBObject query = new BasicDBObject(FieldNames.LEADERBOARD_NAME.name(), oldName);
442
+ BasicDBObject renameUpdate = new BasicDBObject("$set", new BasicDBObject(FieldNames.LEADERBOARD_NAME.name(), newName));
443
+ leaderboardCollection.update(query, renameUpdate, /* upsert */ true, /* multi */ false, WriteConcern.SAFE);
444
+ }
445
+
446
+ @Override
447
+ public void storeLeaderboardGroup(LeaderboardGroup leaderboardGroup) {
448
+ DBCollection leaderboardGroupCollection = database.getCollection(CollectionNames.LEADERBOARD_GROUPS.name());
449
+ DBCollection leaderboardCollection = database.getCollection(CollectionNames.LEADERBOARDS.name());
450
+
451
+ try {
452
+ leaderboardGroupCollection.createIndex(new BasicDBObject(FieldNames.LEADERBOARD_GROUP_NAME.name(), 1));
453
+ } catch (NullPointerException npe) {
454
+ // sometimes, for reasons yet to be clarified, ensuring an index on the name field causes an NPE
455
+ logger.log(Level.SEVERE, "storeLeaderboardGroup", npe);
456
+ }
457
+ BasicDBObject query = new BasicDBObject(FieldNames.LEADERBOARD_GROUP_NAME.name(), leaderboardGroup.getName());
458
+ BasicDBObject dbLeaderboardGroup = new BasicDBObject();
459
+ dbLeaderboardGroup.put(FieldNames.LEADERBOARD_GROUP_UUID.name(), leaderboardGroup.getId());
460
+ dbLeaderboardGroup.put(FieldNames.LEADERBOARD_GROUP_NAME.name(), leaderboardGroup.getName());
461
+ dbLeaderboardGroup.put(FieldNames.LEADERBOARD_GROUP_DESCRIPTION.name(), leaderboardGroup.getDescription());
462
+ dbLeaderboardGroup.put(FieldNames.LEADERBOARD_GROUP_DISPLAY_NAME.name(), leaderboardGroup.getDisplayName());
463
+ dbLeaderboardGroup.put(FieldNames.LEADERBOARD_GROUP_DISPLAY_IN_REVERSE_ORDER.name(), leaderboardGroup.isDisplayGroupsInReverseOrder());
464
+ final Leaderboard overallLeaderboard = leaderboardGroup.getOverallLeaderboard();
465
+ if (overallLeaderboard != null) {
466
+ BasicDBObject overallLeaderboardQuery = new BasicDBObject(FieldNames.LEADERBOARD_NAME.name(), overallLeaderboard.getName());
467
+ DBObject dbOverallLeaderboard = leaderboardCollection.findOne(overallLeaderboardQuery);
468
+ if (dbOverallLeaderboard == null) {
469
+ storeLeaderboard(overallLeaderboard);
470
+ dbOverallLeaderboard = leaderboardCollection.findOne(overallLeaderboardQuery);
471
+ }
472
+ ObjectId dbOverallLeaderboardId = (ObjectId) dbOverallLeaderboard.get("_id");
473
+ dbLeaderboardGroup.put(FieldNames.LEADERBOARD_GROUP_OVERALL_LEADERBOARD.name(), dbOverallLeaderboardId);
474
+ }
475
+ BasicDBList dbLeaderboardIds = new BasicDBList();
476
+ for (Leaderboard leaderboard : leaderboardGroup.getLeaderboards()) {
477
+ BasicDBObject leaderboardQuery = new BasicDBObject(FieldNames.LEADERBOARD_NAME.name(), leaderboard.getName());
478
+ DBObject dbLeaderboard = leaderboardCollection.findOne(leaderboardQuery);
479
+ if (dbLeaderboard == null) {
480
+ storeLeaderboard(leaderboard);
481
+ dbLeaderboard = leaderboardCollection.findOne(leaderboardQuery);
482
+ }
483
+ ObjectId dbLeaderboardId = (ObjectId) dbLeaderboard.get("_id");
484
+ dbLeaderboardIds.add(dbLeaderboardId);
485
+ }
486
+ dbLeaderboardGroup.put(FieldNames.LEADERBOARD_GROUP_LEADERBOARDS.name(), dbLeaderboardIds);
487
+ leaderboardGroupCollection.update(query, dbLeaderboardGroup, true, false, WriteConcern.SAFE);
488
+ }
489
+
490
+ @Override
491
+ public void removeLeaderboardGroup(String groupName) {
492
+ DBCollection leaderboardGroupCollection = database.getCollection(CollectionNames.LEADERBOARD_GROUPS.name());
493
+ BasicDBObject query = new BasicDBObject(FieldNames.LEADERBOARD_GROUP_NAME.name(), groupName);
494
+ leaderboardGroupCollection.remove(query);
495
+ }
496
+
497
+ @Override
498
+ public void renameLeaderboardGroup(String oldName, String newName) {
499
+ DBCollection leaderboardGroupCollection = database.getCollection(CollectionNames.LEADERBOARD_GROUPS.name());
500
+ BasicDBObject query = new BasicDBObject(FieldNames.LEADERBOARD_GROUP_NAME.name(), oldName);
501
+ BasicDBObject update = new BasicDBObject("$set", new BasicDBObject(FieldNames.LEADERBOARD_GROUP_NAME.name(), newName));
502
+ leaderboardGroupCollection.update(query, update, /* upsert */ true, /* multi */ false, WriteConcern.SAFE);
503
+ }
504
+
505
+ @Override
506
+ public void storeServerConfiguration(SailingServerConfiguration serverConfiguration) {
507
+ DBCollection serverCollection = database.getCollection(CollectionNames.SERVER_CONFIGURATION.name());
508
+ DBObject newServerConfig = new BasicDBObject();
509
+ newServerConfig.put(FieldNames.SERVER_IS_STANDALONE.name(), serverConfiguration.isStandaloneServer());
510
+ DBObject currentServerConfig = serverCollection.findOne();
511
+ if(currentServerConfig != null) {
512
+ serverCollection.update(currentServerConfig, newServerConfig, /* upsrt */ true, /* multi */ false, WriteConcern.SAFE);
513
+ } else {
514
+ serverCollection.save(newServerConfig);
515
+ }
516
+ }
517
+
518
+ @Override
519
+ public void storeSailingServer(RemoteSailingServerReference server) {
520
+ DBCollection serverCollection = database.getCollection(CollectionNames.SAILING_SERVERS.name());
521
+ serverCollection.createIndex(new BasicDBObject(FieldNames.SERVER_NAME.name(), 1));
522
+ DBObject query = new BasicDBObject();
523
+ query.put(FieldNames.SERVER_NAME.name(), server.getName());
524
+ DBObject serverDBObject = new BasicDBObject();
525
+ serverDBObject.put(FieldNames.SERVER_NAME.name(), server.getName());
526
+ serverDBObject.put(FieldNames.SERVER_URL.name(), server.getURL().toExternalForm());
527
+ serverCollection.update(query, serverDBObject, /* upsrt */ true, /* multi */ false, WriteConcern.SAFE);
528
+ }
529
+
530
+ @Override
531
+ public void removeSailingServer(String name) {
532
+ DBCollection serverCollection = database.getCollection(CollectionNames.SAILING_SERVERS.name());
533
+ BasicDBObject query = new BasicDBObject(FieldNames.SERVER_NAME.name(), name);
534
+ serverCollection.remove(query);
535
+ }
536
+
537
+ /**
538
+ * StoreEvent() uses some deprecated methods of event to keep backward compatibility.
539
+ */
540
+ @SuppressWarnings("deprecation")
541
+ @Override
542
+ public void storeEvent(Event event) {
543
+ DBCollection eventCollection = database.getCollection(CollectionNames.EVENTS.name());
544
+ eventCollection.createIndex(new BasicDBObject(FieldNames.EVENT_ID.name(), 1));
545
+ DBObject query = new BasicDBObject();
546
+ query.put(FieldNames.EVENT_ID.name(), event.getId());
547
+ DBObject eventDBObject = new BasicDBObject();
548
+ eventDBObject.put(FieldNames.EVENT_NAME.name(), event.getName());
549
+ eventDBObject.put(FieldNames.EVENT_DESCRIPTION.name(), event.getDescription());
550
+ eventDBObject.put(FieldNames.EVENT_ID.name(), event.getId());
551
+ eventDBObject.put(FieldNames.EVENT_LOGO_IMAGE_URL.name(), event.getLogoImageURL() != null ? event.getLogoImageURL().toString() : null);
552
+ eventDBObject.put(FieldNames.EVENT_OFFICIAL_WEBSITE_URL.name(), event.getOfficialWebsiteURL() != null ? event.getOfficialWebsiteURL().toString() : null);
553
+ eventDBObject.put(FieldNames.EVENT_SAILORS_INFO_WEBSITE_URL.name(), event.getSailorsInfoWebsiteURL() != null ? event.getSailorsInfoWebsiteURL().toString() : null);
554
+ storeTimePoint(event.getStartDate(), eventDBObject, FieldNames.EVENT_START_DATE);
555
+ storeTimePoint(event.getEndDate(), eventDBObject, FieldNames.EVENT_END_DATE);
556
+ eventDBObject.put(FieldNames.EVENT_IS_PUBLIC.name(), event.isPublic());
557
+ DBObject venueDBObject = getVenueAsDBObject(event.getVenue());
558
+ eventDBObject.put(FieldNames.VENUE.name(), venueDBObject);
559
+ BasicDBList imageURLs = new BasicDBList();
560
+ for (URL imageURL : event.getImageURLs()) {
561
+ imageURLs.add(imageURL.toString());
562
+ }
563
+ eventDBObject.put(FieldNames.EVENT_IMAGE_URLS.name(), imageURLs);
564
+ BasicDBList videoURLs = new BasicDBList();
565
+ for (URL videoURL : event.getVideoURLs()) {
566
+ videoURLs.add(videoURL.toString());
567
+ }
568
+ eventDBObject.put(FieldNames.EVENT_VIDEO_URLS.name(), videoURLs);
569
+ BasicDBList sponsorImageURLs = new BasicDBList();
570
+ for (URL sponsorImageURL : event.getSponsorImageURLs()) {
571
+ sponsorImageURLs.add(sponsorImageURL.toString());
572
+ }
573
+ eventDBObject.put(FieldNames.EVENT_SPONSOR_IMAGE_URLS.name(), sponsorImageURLs);
574
+ BasicDBList images = new BasicDBList();
575
+ for (ImageDescriptor image : event.getImages()) {
576
+ DBObject imageObject = createImageObject(image);
577
+ images.add(imageObject);
578
+ }
579
+ eventDBObject.put(FieldNames.EVENT_IMAGES.name(), images);
580
+ BasicDBList videos = new BasicDBList();
581
+ for (VideoDescriptor video: event.getVideos()) {
582
+ DBObject videoObject = createVideoObject(video);
583
+ videos.add(videoObject);
584
+ }
585
+ eventDBObject.put(FieldNames.EVENT_VIDEOS.name(), videos);
586
+ eventCollection.update(query, eventDBObject, /* upsrt */ true, /* multi */ false, WriteConcern.SAFE);
587
+ // now store the links to the leaderboard groups
588
+ DBCollection linksCollection = database.getCollection(CollectionNames.LEADERBOARD_GROUP_LINKS_FOR_EVENTS.name());
589
+ linksCollection.createIndex(new BasicDBObject(FieldNames.EVENT_ID.name(), 1));
590
+ BasicDBList lgUUIDs = new BasicDBList();
591
+ for (LeaderboardGroup lg : event.getLeaderboardGroups()) {
592
+ lgUUIDs.add(lg.getId());
593
+ }
594
+ DBObject dbLinks = new BasicDBObject();
595
+ dbLinks.put(FieldNames.EVENT_ID.name(), event.getId());
596
+ dbLinks.put(FieldNames.LEADERBOARD_GROUP_UUID.name(), lgUUIDs);
597
+ linksCollection.update(query, dbLinks, /* upsrt */ true, /* multi */ false, WriteConcern.SAFE);
598
+ }
599
+
600
+ @Override
601
+ public void renameEvent(Serializable id, String newName) {
602
+ DBCollection eventCollection = database.getCollection(CollectionNames.EVENTS.name());
603
+ BasicDBObject query = new BasicDBObject(FieldNames.EVENT_ID.name(), id);
604
+ BasicDBObject renameUpdate = new BasicDBObject("$set", new BasicDBObject(FieldNames.EVENT_NAME.name(), newName));
605
+ eventCollection.update(query, renameUpdate, /* upsert */ true, /* multi */ false, WriteConcern.SAFE);
606
+ }
607
+
608
+ @Override
609
+ public void removeEvent(Serializable id) {
610
+ DBCollection eventsCollection = database.getCollection(CollectionNames.EVENTS.name());
611
+ BasicDBObject query = new BasicDBObject(FieldNames.EVENT_ID.name(), id);
612
+ eventsCollection.remove(query);
613
+ }
614
+
615
+ private DBObject getVenueAsDBObject(Venue venue) {
616
+ DBObject result = new BasicDBObject();
617
+ result.put(FieldNames.VENUE_NAME.name(), venue.getName());
618
+ BasicDBList courseAreaList = new BasicDBList();
619
+ result.put(FieldNames.COURSE_AREAS.name(), courseAreaList);
620
+ for (CourseArea courseArea : venue.getCourseAreas()) {
621
+ DBObject dbCourseArea = new BasicDBObject();
622
+ courseAreaList.add(dbCourseArea);
623
+ dbCourseArea.put(FieldNames.COURSE_AREA_NAME.name(), courseArea.getName());
624
+ dbCourseArea.put(FieldNames.COURSE_AREA_ID.name(), courseArea.getId());
625
+ }
626
+ return result;
627
+ }
628
+
629
+ @Override
630
+ public void storeRegatta(Regatta regatta) {
631
+ DBCollection regattasCollection = database.getCollection(CollectionNames.REGATTAS.name());
632
+ regattasCollection.createIndex(new BasicDBObject(FieldNames.REGATTA_NAME.name(), 1));
633
+ regattasCollection.createIndex(new BasicDBObject(FieldNames.REGATTA_ID.name(), 1));
634
+ DBObject dbRegatta = new BasicDBObject();
635
+ DBObject query = new BasicDBObject(FieldNames.REGATTA_NAME.name(), regatta.getName());
636
+ dbRegatta.put(FieldNames.REGATTA_NAME.name(), regatta.getName());
637
+ dbRegatta.put(FieldNames.REGATTA_ID.name(), regatta.getId());
638
+ storeTimePoint(regatta.getStartDate(), dbRegatta, FieldNames.REGATTA_START_DATE);
639
+ storeTimePoint(regatta.getEndDate(), dbRegatta, FieldNames.REGATTA_END_DATE);
640
+ dbRegatta.put(FieldNames.SCORING_SCHEME_TYPE.name(), regatta.getScoringScheme().getType().name());
641
+ if (regatta.getBoatClass() != null) {
642
+ dbRegatta.put(FieldNames.BOAT_CLASS_NAME.name(), regatta.getBoatClass().getName());
643
+ dbRegatta.put(FieldNames.BOAT_CLASS_TYPICALLY_STARTS_UPWIND.name(), regatta.getBoatClass().typicallyStartsUpwind());
644
+ }
645
+ dbRegatta.put(FieldNames.REGATTA_SERIES.name(), storeSeries(regatta.getSeries()));
646
+
647
+ if (regatta.getDefaultCourseArea() != null) {
648
+ dbRegatta.put(FieldNames.COURSE_AREA_ID.name(), regatta.getDefaultCourseArea().getId().toString());
649
+ } else {
650
+ dbRegatta.put(FieldNames.COURSE_AREA_ID.name(), null);
651
+ }
652
+ if (regatta.getRegattaConfiguration() != null) {
653
+ JsonSerializer<RegattaConfiguration> serializer = RegattaConfigurationJsonSerializer.create();
654
+ JSONObject json = serializer.serialize(regatta.getRegattaConfiguration());
655
+ DBObject configurationObject = (DBObject) JSON.parse(json.toString());
656
+ dbRegatta.put(FieldNames.REGATTA_REGATTA_CONFIGURATION.name(), configurationObject);
657
+ }
658
+ dbRegatta.put(FieldNames.REGATTA_USE_START_TIME_INFERENCE.name(), regatta.useStartTimeInference());
659
+ dbRegatta.put(FieldNames.REGATTA_RANKING_METRIC.name(), storeRankingMetric(regatta));
660
+ regattasCollection.update(query, dbRegatta, /* upsrt */ true, /* multi */ false, WriteConcern.SAFE);
661
+ }
662
+
663
+ private DBObject storeRankingMetric(Regatta regatta) {
664
+ DBObject rankingMetricJson = new BasicDBObject();
665
+ final String rankingMetricTypeName = regatta.getRankingMetricType().name();
666
+ rankingMetricJson.put(FieldNames.REGATTA_RANKING_METRIC_TYPE.name(), rankingMetricTypeName);
667
+ return rankingMetricJson;
668
+ }
669
+
670
+ @Override
671
+ public void removeRegatta(Regatta regatta) {
672
+ DBCollection regattasCollection = database.getCollection(CollectionNames.REGATTAS.name());
673
+ DBObject query = new BasicDBObject(FieldNames.REGATTA_NAME.name(), regatta.getName());
674
+ regattasCollection.remove(query);
675
+ }
676
+
677
+ private BasicDBList storeSeries(Iterable<? extends Series> series) {
678
+ BasicDBList dbSeries = new BasicDBList();
679
+ for (Series s : series) {
680
+ dbSeries.add(storeSeries(s));
681
+ }
682
+ return dbSeries;
683
+ }
684
+
685
+ private DBObject storeSeries(Series s) {
686
+ DBObject dbSeries = new BasicDBObject();
687
+ dbSeries.put(FieldNames.SERIES_NAME.name(), s.getName());
688
+ dbSeries.put(FieldNames.SERIES_IS_MEDAL.name(), s.isMedal());
689
+ dbSeries.put(FieldNames.SERIES_HAS_SPLIT_FLEET_CONTIGUOUS_SCORING.name(), s.hasSplitFleetContiguousScoring());
690
+ dbSeries.put(FieldNames.SERIES_STARTS_WITH_ZERO_SCORE.name(), s.isStartsWithZeroScore());
691
+ dbSeries.put(FieldNames.SERIES_STARTS_WITH_NON_DISCARDABLE_CARRY_FORWARD.name(), s.isFirstColumnIsNonDiscardableCarryForward());
692
+ BasicDBList dbFleets = new BasicDBList();
693
+ for (Fleet fleet : s.getFleets()) {
694
+ dbFleets.add(storeFleet(fleet));
695
+ }
696
+ dbSeries.put(FieldNames.SERIES_FLEETS.name(), dbFleets);
697
+ BasicDBList dbRaceColumns = new BasicDBList();
698
+ for (RaceColumn raceColumn : s.getRaceColumns()) {
699
+ dbRaceColumns.add(storeRaceColumn(raceColumn));
700
+ }
701
+ dbSeries.put(FieldNames.SERIES_RACE_COLUMNS.name(), dbRaceColumns);
702
+ if (s.getResultDiscardingRule() != null) {
703
+ storeResultDiscardingRule(dbSeries, s.getResultDiscardingRule(), FieldNames.SERIES_DISCARDING_THRESHOLDS);
704
+ }
705
+ return dbSeries;
706
+ }
707
+
708
+ private DBObject storeFleet(Fleet fleet) {
709
+ DBObject dbFleet = new BasicDBObject(FieldNames.FLEET_NAME.name(), fleet.getName());
710
+ if (fleet instanceof FleetImpl) {
711
+ dbFleet.put(FieldNames.FLEET_ORDERING.name(), ((FleetImpl) fleet).getOrdering());
712
+ if(fleet.getColor() != null) {
713
+ com.sap.sse.common.Util.Triple<Integer, Integer, Integer> colorAsRGB = fleet.getColor().getAsRGB();
714
+ // we save the color as a integer value representing the RGB values
715
+ int colorAsInt = (256 * 256 * colorAsRGB.getC()) + colorAsRGB.getB() * 256 + colorAsRGB.getA();
716
+ dbFleet.put(FieldNames.FLEET_COLOR.name(), colorAsInt);
717
+ } else {
718
+ dbFleet.put(FieldNames.FLEET_COLOR.name(), null);
719
+ }
720
+ }
721
+ return dbFleet;
722
+ }
723
+
724
+ @Override
725
+ public void storeRegattaForRaceID(String raceIDAsString, Regatta regatta) {
726
+ DBCollection regattaForRaceIDCollection = database.getCollection(CollectionNames.REGATTA_FOR_RACE_ID.name());
727
+ DBObject query = new BasicDBObject(FieldNames.RACE_ID_AS_STRING.name(), raceIDAsString);
728
+ DBObject entry = new BasicDBObject(FieldNames.RACE_ID_AS_STRING.name(), raceIDAsString);
729
+ entry.put(FieldNames.REGATTA_NAME.name(), regatta.getName());
730
+ regattaForRaceIDCollection.update(query, entry, /* upsrt */ true, /* multi */ false, WriteConcern.SAFE);
731
+ }
732
+
733
+ @Override
734
+ public void removeRegattaForRaceID(String raceIDAsString, Regatta regatta) {
735
+ DBCollection regattaForRaceIDCollection = database.getCollection(CollectionNames.REGATTA_FOR_RACE_ID.name());
736
+ DBObject query = new BasicDBObject(FieldNames.RACE_ID_AS_STRING.name(), raceIDAsString);
737
+ regattaForRaceIDCollection.remove(query);
738
+ }
739
+
740
+ public DBCollection getRaceLogCollection() {
741
+ DBCollection result = database.getCollection(CollectionNames.RACE_LOGS.name());
742
+ result.createIndex(new BasicDBObject(FieldNames.RACE_LOG_IDENTIFIER.name(), null));
743
+ return result;
744
+ }
745
+
746
+ private void storeRaceLogEventAuthor(DBObject dbObject, AbstractLogEventAuthor author) {
747
+ if (author != null) {
748
+ dbObject.put(FieldNames.RACE_LOG_EVENT_AUTHOR_NAME.name(), author.getName());
749
+ dbObject.put(FieldNames.RACE_LOG_EVENT_AUTHOR_PRIORITY.name(), author.getPriority());
750
+ }
751
+ }
752
+
753
+ public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogFlagEvent flagEvent) {
754
+ BasicDBObject result = new BasicDBObject();
755
+ storeRaceLogIdentifier(raceLogIdentifier, result);
756
+ result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogFlagEvent(flagEvent));
757
+ return result;
758
+ }
759
+
760
+ private void storeRaceLogIdentifier(RaceLogIdentifier raceLogIdentifier, DBObject result) {
761
+ result.put(FieldNames.RACE_LOG_IDENTIFIER.name(), TripleSerializer.serialize(raceLogIdentifier.getIdentifier()));
762
+ }
763
+
764
+ public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogStartTimeEvent startTimeEvent) {
765
+ BasicDBObject result = new BasicDBObject();
766
+ storeRaceLogIdentifier(raceLogIdentifier, result);
767
+ result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogStartTimeEvent(startTimeEvent));
768
+ return result;
769
+ }
770
+
771
+ public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogPassChangeEvent passChangeEvent) {
772
+ BasicDBObject result = new BasicDBObject();
773
+ storeRaceLogIdentifier(raceLogIdentifier, result);
774
+ result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogPassChangeEvent(passChangeEvent));
775
+ return result;
776
+ }
777
+
778
+ public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogRaceStatusEvent raceStatusEvent) {
779
+ BasicDBObject result = new BasicDBObject();
780
+ storeRaceLogIdentifier(raceLogIdentifier, result);
781
+ result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogRaceStatusEvent(raceStatusEvent));
782
+ return result;
783
+ }
784
+
785
+ public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogCourseAreaChangedEvent courseAreaChangedEvent) {
786
+ BasicDBObject result = new BasicDBObject();
787
+ storeRaceLogIdentifier(raceLogIdentifier, result);
788
+ result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogCourseAreaChangedEvent(courseAreaChangedEvent));
789
+ return result;
790
+ }
791
+
792
+ public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogCourseDesignChangedEvent courseDesignChangedEvent) {
793
+ BasicDBObject result = new BasicDBObject();
794
+ storeRaceLogIdentifier(raceLogIdentifier, result);
795
+ result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogCourseDesignChangedEvent(courseDesignChangedEvent));
796
+ return result;
797
+ }
798
+
799
+ public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogFinishPositioningListChangedEvent finishPositioningListChangedEvent) {
800
+ BasicDBObject result = new BasicDBObject();
801
+ storeRaceLogIdentifier(raceLogIdentifier, result);
802
+ result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogFinishPositioningListChangedEvent(finishPositioningListChangedEvent));
803
+ return result;
804
+ }
805
+
806
+ public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogFinishPositioningConfirmedEvent finishPositioningConfirmedEvent) {
807
+ BasicDBObject result = new BasicDBObject();
808
+ storeRaceLogIdentifier(raceLogIdentifier, result);
809
+ result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogFinishPositioningConfirmedEvent(finishPositioningConfirmedEvent));
810
+ return result;
811
+ }
812
+
813
+ public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogPathfinderEvent pathfinderEvent) {
814
+ BasicDBObject result = new BasicDBObject();
815
+ storeRaceLogIdentifier(raceLogIdentifier, result);
816
+ result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogPathfinderEvent(pathfinderEvent));
817
+ return result;
818
+ }
819
+
820
+ public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogGateLineOpeningTimeEvent gateLineOpeningTimeEvent) {
821
+ BasicDBObject result = new BasicDBObject();
822
+ storeRaceLogIdentifier(raceLogIdentifier, result);
823
+ result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogGateLineOpeningTimeEvent(gateLineOpeningTimeEvent));
824
+ return result;
825
+ }
826
+
827
+ public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogStartProcedureChangedEvent event) {
828
+ DBObject result = new BasicDBObject();
829
+ storeRaceLogIdentifier(raceLogIdentifier, result);
830
+ result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogStartProcedureChangedEvent(event));
831
+ return result;
832
+ }
833
+
834
+ public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogProtestStartTimeEvent event) {
835
+ DBObject result = new BasicDBObject();
836
+ storeRaceLogIdentifier(raceLogIdentifier, result);
837
+ result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogProtestStartTimeEvent(event));
838
+ return result;
839
+ }
840
+
841
+ public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogWindFixEvent event) {
842
+ DBObject result = new BasicDBObject();
843
+ storeRaceLogIdentifier(raceLogIdentifier, result);
844
+ result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogWindFix(event));
845
+ return result;
846
+ }
847
+
848
+ public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogDeviceCompetitorMappingEvent event) {
849
+ BasicDBObject result = new BasicDBObject();
850
+ storeRaceLogIdentifier(raceLogIdentifier, result);
851
+ result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogDeviceCompetitorMappingEvent(event));
852
+ return result;
853
+ }
854
+
855
+ public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogDeviceMarkMappingEvent event) {
856
+ BasicDBObject result = new BasicDBObject();
857
+ storeRaceLogIdentifier(raceLogIdentifier, result);
858
+ result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogDeviceMarkMappingEvent(event));
859
+ return result;
860
+ }
861
+
862
+ public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogDenoteForTrackingEvent event) {
863
+ BasicDBObject result = new BasicDBObject();
864
+ storeRaceLogIdentifier(raceLogIdentifier, result);
865
+ result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogDenoteForTrackingEvent(event));
866
+ return result;
867
+ }
868
+
869
+ public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogStartTrackingEvent event) {
870
+ BasicDBObject result = new BasicDBObject();
871
+ storeRaceLogIdentifier(raceLogIdentifier, result);
872
+ result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogStartTrackingEvent(event));
873
+ return result;
874
+ }
875
+
876
+ public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogRevokeEvent event) {
877
+ BasicDBObject result = new BasicDBObject();
878
+ storeRaceLogIdentifier(raceLogIdentifier, result);
879
+ result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogRevokeEvent(event));
880
+ return result;
881
+ }
882
+
883
+ public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogRegisterCompetitorEvent event) {
884
+ BasicDBObject result = new BasicDBObject();
885
+ storeRaceLogIdentifier(raceLogIdentifier, result);
886
+ result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogRegisterCompetitorEvent(event));
887
+ return result;
888
+ }
889
+
890
+ public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogDefineMarkEvent event) {
891
+ BasicDBObject result = new BasicDBObject();
892
+ storeRaceLogIdentifier(raceLogIdentifier, result);
893
+ result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogDefineMarkEvent(event));
894
+ return result;
895
+ }
896
+
897
+ public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogCloseOpenEndedDeviceMappingEvent event) {
898
+ BasicDBObject result = new BasicDBObject();
899
+ storeRaceLogIdentifier(raceLogIdentifier, result);
900
+ result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogCloseOpenEndedDeviceMappingEvent(event));
901
+ return result;
902
+ }
903
+
904
+ public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogEndOfTrackingEvent event) {
905
+ BasicDBObject result = new BasicDBObject();
906
+ storeRaceLogIdentifier(raceLogIdentifier, result);
907
+ result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogEndOfTrackingEvent(event));
908
+ return result;
909
+ }
910
+
911
+ public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogStartOfTrackingEvent event) {
912
+ BasicDBObject result = new BasicDBObject();
913
+ storeRaceLogIdentifier(raceLogIdentifier, result);
914
+ result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogStartOfTrackingEvent(event));
915
+ return result;
916
+ }
917
+
918
+ public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogAdditionalScoringInformationEvent event) {
919
+ BasicDBObject result = new BasicDBObject();
920
+ storeRaceLogIdentifier(raceLogIdentifier, result);
921
+ result.put(FieldNames.RACE_LOG_EVENT.name(), storeAdditionalScoringInformation(event));
922
+ return result;
923
+ }
924
+
925
+ private Object storeAdditionalScoringInformation(RaceLogAdditionalScoringInformationEvent event) {
926
+ DBObject result = new BasicDBObject();
927
+ storeRaceLogEventProperties(event, result);
928
+ result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogAdditionalScoringInformationEvent.class.getSimpleName());
929
+ result.put(FieldNames.RACE_LOG_ADDITIONAL_SCORING_INFORMATION_TYPE.name(), event.getType().name());
930
+ return result;
931
+ }
932
+
933
+ public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogFixedMarkPassingEvent event) {
934
+ BasicDBObject result = new BasicDBObject();
935
+ storeRaceLogIdentifier(raceLogIdentifier, result);
936
+ result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogFixedMarkPassingEvent(event));
937
+ return result;
938
+ }
939
+
940
+ public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogSuppressedMarkPassingsEvent event) {
941
+ BasicDBObject result = new BasicDBObject();
942
+ storeRaceLogIdentifier(raceLogIdentifier, result);
943
+ result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogSuppressedMarkPassingsEvent(event));
944
+ return result;
945
+ }
946
+
947
+ private Object storeRaceLogWindFix(RaceLogWindFixEvent event) {
948
+ DBObject result = new BasicDBObject();
949
+ storeRaceLogEventProperties(event, result);
950
+ result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogWindFixEvent.class.getSimpleName());
951
+ result.put(FieldNames.WIND.name(), storeWind(event.getWindFix()));
952
+ result.put(FieldNames.IS_MAGNETIC.name(), event.isMagnetic());
953
+ return result;
954
+ }
955
+
956
+ private Object storeRaceLogProtestStartTimeEvent(RaceLogProtestStartTimeEvent event) {
957
+ DBObject result = new BasicDBObject();
958
+ storeRaceLogEventProperties(event, result);
959
+ result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogProtestStartTimeEvent.class.getSimpleName());
960
+ storeTimePoint(event.getProtestStartTime(), result, FieldNames.RACE_LOG_PROTEST_START_TIME);
961
+ return result;
962
+ }
963
+
964
+ private Object storeRaceLogEndOfTrackingEvent(RaceLogEndOfTrackingEvent event) {
965
+ DBObject result = new BasicDBObject();
966
+ storeRaceLogEventProperties(event, result);
967
+ result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogEndOfTrackingEvent.class.getSimpleName());
968
+ return result;
969
+ }
970
+
971
+ private Object storeRaceLogStartOfTrackingEvent(RaceLogStartOfTrackingEvent event) {
972
+ DBObject result = new BasicDBObject();
973
+ storeRaceLogEventProperties(event, result);
974
+ result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogStartOfTrackingEvent.class.getSimpleName());
975
+ return result;
976
+ }
977
+
978
+ private Object storeRaceLogStartProcedureChangedEvent(RaceLogStartProcedureChangedEvent event) {
979
+ DBObject result = new BasicDBObject();
980
+ storeRaceLogEventProperties(event, result);
981
+ result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogStartProcedureChangedEvent.class.getSimpleName());
982
+ result.put(FieldNames.RACE_LOG_START_PROCEDURE_TYPE.name(), event.getStartProcedureType().name());
983
+ return result;
984
+ }
985
+
986
+ private Object storeRaceLogPathfinderEvent(RaceLogPathfinderEvent pathfinderEvent) {
987
+ DBObject result = new BasicDBObject();
988
+ storeRaceLogEventProperties(pathfinderEvent, result);
989
+ result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogPathfinderEvent.class.getSimpleName());
990
+ result.put(FieldNames.RACE_LOG_PATHFINDER_ID.name(), pathfinderEvent.getPathfinderId());
991
+ return result;
992
+ }
993
+
994
+ private void storeDeviceMappingEvent(DeviceMappingEvent<?, ?> event, DBObject result, FieldNames fromField, FieldNames toField) {
995
+ try {
996
+ result.put(FieldNames.DEVICE_ID.name(), storeDeviceId(deviceIdentifierServiceFinder, event.getDevice()));
997
+ } catch (TransformationException | NoCorrespondingServiceRegisteredException e) {
998
+ logger.log(Level.WARNING, "Could not store device identifier for mappng event", e);
999
+ }
1000
+ if (event.getFrom() != null) {
1001
+ storeTimePoint(event.getFrom(), result, fromField);
1002
+ }
1003
+ if (event.getTo() != null) {
1004
+ storeTimePoint(event.getTo(), result, toField);
1005
+ }
1006
+ }
1007
+
1008
+ private Object storeRaceLogDeviceCompetitorMappingEvent(RaceLogDeviceCompetitorMappingEvent event) {
1009
+ DBObject result = new BasicDBObject();
1010
+ storeRaceLogEventProperties(event, result);
1011
+ result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogDeviceCompetitorMappingEvent.class.getSimpleName());
1012
+ storeDeviceMappingEvent(event, result, FieldNames.RACE_LOG_FROM, FieldNames.RACE_LOG_TO);
1013
+ result.put(FieldNames.COMPETITOR_ID.name(), event.getMappedTo().getId());
1014
+ return result;
1015
+ }
1016
+
1017
+ private Object storeRaceLogDeviceMarkMappingEvent(RaceLogDeviceMarkMappingEvent event) {
1018
+ DBObject result = new BasicDBObject();
1019
+ storeRaceLogEventProperties(event, result);
1020
+ result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogDeviceMarkMappingEvent.class.getSimpleName());
1021
+ storeDeviceMappingEvent(event, result, FieldNames.RACE_LOG_FROM, FieldNames.RACE_LOG_TO);
1022
+ result.put(FieldNames.MARK.name(), storeMark(event.getMappedTo()));
1023
+ return result;
1024
+ }
1025
+
1026
+ private Object storeRaceLogDenoteForTrackingEvent(RaceLogDenoteForTrackingEvent event) {
1027
+ DBObject result = new BasicDBObject();
1028
+ storeRaceLogEventProperties(event, result);
1029
+ result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogDenoteForTrackingEvent.class.getSimpleName());
1030
+ result.put(FieldNames.RACE_NAME.name(), event.getRaceName());
1031
+ result.put(FieldNames.BOAT_CLASS_NAME.name(), event.getBoatClass().getName());
1032
+ result.put(FieldNames.RACE_ID.name(), event.getRaceId());
1033
+ return result;
1034
+ }
1035
+
1036
+ private Object storeRaceLogStartTrackingEvent(RaceLogStartTrackingEvent event) {
1037
+ DBObject result = new BasicDBObject();
1038
+ storeRaceLogEventProperties(event, result);
1039
+ result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogStartTrackingEvent.class.getSimpleName());
1040
+ return result;
1041
+ }
1042
+
1043
+ private Object storeRaceLogRevokeEvent(RaceLogRevokeEvent event) {
1044
+ DBObject result = new BasicDBObject();
1045
+ storeRaceLogEventProperties(event, result);
1046
+ result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogRevokeEvent.class.getSimpleName());
1047
+ result.put(FieldNames.RACE_LOG_REVOKED_EVENT_ID.name(), event.getRevokedEventId());
1048
+ result.put(FieldNames.RACE_LOG_REVOKED_EVENT_TYPE.name(), event.getRevokedEventType());
1049
+ result.put(FieldNames.RACE_LOG_REVOKED_EVENT_SHORT_INFO.name(), event.getRevokedEventShortInfo());
1050
+ result.put(FieldNames.RACE_LOG_REVOKED_REASON.name(), event.getReason());
1051
+ return result;
1052
+ }
1053
+
1054
+ private Object storeRaceLogRegisterCompetitorEvent(RaceLogRegisterCompetitorEvent event) {
1055
+ DBObject result = new BasicDBObject();
1056
+ storeRaceLogEventProperties(event, result);
1057
+ result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogRegisterCompetitorEvent.class.getSimpleName());
1058
+ result.put(FieldNames.RACE_LOG_COMPETITOR_ID.name(), event.getCompetitor().getId());
1059
+ return result;
1060
+ }
1061
+
1062
+ private Object storeRaceLogDefineMarkEvent(RaceLogDefineMarkEvent event) {
1063
+ DBObject result = new BasicDBObject();
1064
+ storeRaceLogEventProperties(event, result);
1065
+ result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogDefineMarkEvent.class.getSimpleName());
1066
+ result.put(FieldNames.RACE_LOG_MARK.name(), storeMark(event.getMark()));
1067
+ return result;
1068
+ }
1069
+
1070
+ private Object storeRaceLogCloseOpenEndedDeviceMappingEvent(RaceLogCloseOpenEndedDeviceMappingEvent event) {
1071
+ DBObject result = new BasicDBObject();
1072
+ storeRaceLogEventProperties(event, result);
1073
+ result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogCloseOpenEndedDeviceMappingEvent.class.getSimpleName());
1074
+ result.put(FieldNames.RACE_LOG_DEVICE_MAPPING_EVENT_ID.name(), event.getDeviceMappingEventId());
1075
+ storeTimePoint(event.getClosingTimePoint(), result, FieldNames.RACE_LOG_CLOSING_TIMEPOINT);
1076
+ return result;
1077
+ }
1078
+
1079
+ private Object storeRaceLogFixedMarkPassingEvent(RaceLogFixedMarkPassingEvent event) {
1080
+ DBObject result = new BasicDBObject();
1081
+ storeRaceLogEventProperties(event, result);
1082
+ result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogFixedMarkPassingEvent.class.getSimpleName());
1083
+ result.put(FieldNames.INDEX_OF_PASSED_WAYPOINT.name(), event.getZeroBasedIndexOfPassedWaypoint());
1084
+ result.put(FieldNames.TIMEPOINT_OF_FIXED_MARKPASSING.name(), event.getTimePointOfFixedPassing().asMillis());
1085
+ return result;
1086
+ }
1087
+
1088
+ private Object storeRaceLogSuppressedMarkPassingsEvent(RaceLogSuppressedMarkPassingsEvent event) {
1089
+ DBObject result = new BasicDBObject();
1090
+ storeRaceLogEventProperties(event, result);
1091
+ result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogSuppressedMarkPassingsEvent.class.getSimpleName());
1092
+ result.put(FieldNames.INDEX_OF_FIRST_SUPPRESSED_WAYPOINT.name(), event.getZeroBasedIndexOfFirstSuppressedWaypoint());
1093
+ return result;
1094
+ }
1095
+
1096
+ public DBObject storeRaceLogFlagEvent(RaceLogFlagEvent flagEvent) {
1097
+ DBObject result = new BasicDBObject();
1098
+ storeRaceLogEventProperties(flagEvent, result);
1099
+ result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogFlagEvent.class.getSimpleName());
1100
+ result.put(FieldNames.RACE_LOG_EVENT_FLAG_UPPER.name(), flagEvent.getUpperFlag().name());
1101
+ result.put(FieldNames.RACE_LOG_EVENT_FLAG_LOWER.name(), flagEvent.getLowerFlag().name());
1102
+ result.put(FieldNames.RACE_LOG_EVENT_FLAG_DISPLAYED.name(), String.valueOf(flagEvent.isDisplayed()));
1103
+ return result;
1104
+ }
1105
+
1106
+ private DBObject storeRaceLogStartTimeEvent(RaceLogStartTimeEvent startTimeEvent) {
1107
+ DBObject result = new BasicDBObject();
1108
+ storeRaceLogEventProperties(startTimeEvent, result);
1109
+ result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogStartTimeEvent.class.getSimpleName());
1110
+ storeTimePoint(startTimeEvent.getStartTime(), result, FieldNames.RACE_LOG_EVENT_START_TIME);
1111
+ result.put(FieldNames.RACE_LOG_EVENT_NEXT_STATUS.name(), startTimeEvent.getNextStatus().name());
1112
+ return result;
1113
+ }
1114
+
1115
+ private void storeRaceLogEventProperties(RaceLogEvent event, DBObject result) {
1116
+ // for compatibility reasons we reuse the field name of Timed
1117
+ storeTimePoint(event.getLogicalTimePoint(), result, FieldNames.TIME_AS_MILLIS);
1118
+ storeTimePoint(event.getCreatedAt(), result, FieldNames.RACE_LOG_EVENT_CREATED_AT);
1119
+ result.put(FieldNames.RACE_LOG_EVENT_ID.name(), event.getId());
1120
+ result.put(FieldNames.RACE_LOG_EVENT_PASS_ID.name(), event.getPassId());
1121
+ result.put(FieldNames.RACE_LOG_EVENT_INVOLVED_BOATS.name(), storeInvolvedBoatsForRaceLogEvent(event.getInvolvedBoats()));
1122
+ storeRaceLogEventAuthor(result, event.getAuthor());
1123
+ }
1124
+
1125
+
1126
+ private BasicDBList storeInvolvedBoatsForRaceLogEvent(List<Competitor> competitors) {
1127
+ BasicDBList dbInvolvedCompetitorIds = new BasicDBList();
1128
+ for (Competitor competitor : competitors) {
1129
+ dbInvolvedCompetitorIds.add(competitor.getId());
1130
+ }
1131
+ return dbInvolvedCompetitorIds;
1132
+ }
1133
+
1134
+ private DBObject storeRaceLogPassChangeEvent(RaceLogPassChangeEvent passChangeEvent) {
1135
+ DBObject result = new BasicDBObject();
1136
+ storeRaceLogEventProperties(passChangeEvent, result);
1137
+ result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogPassChangeEvent.class.getSimpleName());
1138
+ return result;
1139
+ }
1140
+
1141
+ private DBObject storeRaceLogDependentStartTimeEvent(RaceLogDependentStartTimeEvent dependentStartTimeEvent) {
1142
+ DBObject result = new BasicDBObject();
1143
+ storeRaceLogEventProperties(dependentStartTimeEvent, result);
1144
+ result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogDependentStartTimeEvent.class.getSimpleName());
1145
+ result.put(FieldNames.RACE_LOG_DEPDENDENT_ON_REGATTALIKE.name(), dependentStartTimeEvent.getDependentOnRaceIdentifier().getRegattaLikeParentName());
1146
+ result.put(FieldNames.RACE_LOG_DEPDENDENT_ON_RACECOLUMN.name(), dependentStartTimeEvent.getDependentOnRaceIdentifier().getRaceColumnName());
1147
+ result.put(FieldNames.RACE_LOG_DEPDENDENT_ON_FLEET.name(), dependentStartTimeEvent.getDependentOnRaceIdentifier().getFleetName());
1148
+ storeDuration(dependentStartTimeEvent.getStartTimeDifference(), result, FieldNames.RACE_LOG_START_TIME_DIFFERENCE_IN_MS);
1149
+ result.put(FieldNames.RACE_LOG_EVENT_NEXT_STATUS.name(), dependentStartTimeEvent.getNextStatus().name());
1150
+ return result;
1151
+ }
1152
+
1153
+ private void storeDuration(Duration duration, DBObject result, FieldNames fieldName) {
1154
+ if (duration != null) {
1155
+ result.put(fieldName.name(), duration.asMillis());
1156
+ }
1157
+ }
1158
+
1159
+ private DBObject storeRaceLogRaceStatusEvent(RaceLogRaceStatusEvent raceStatusEvent) {
1160
+ DBObject result = new BasicDBObject();
1161
+ storeRaceLogEventProperties(raceStatusEvent, result);
1162
+ result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogRaceStatusEvent.class.getSimpleName());
1163
+ result.put(FieldNames.RACE_LOG_EVENT_NEXT_STATUS.name(), raceStatusEvent.getNextStatus().name());
1164
+ return result;
1165
+ }
1166
+
1167
+ private DBObject storeRaceLogCourseAreaChangedEvent(RaceLogCourseAreaChangedEvent courseAreaChangedEvent) {
1168
+ DBObject result = new BasicDBObject();
1169
+ storeRaceLogEventProperties(courseAreaChangedEvent, result);
1170
+ result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogCourseAreaChangedEvent.class.getSimpleName());
1171
+ result.put(FieldNames.COURSE_AREA_ID.name(), courseAreaChangedEvent.getCourseAreaId());
1172
+ return result;
1173
+ }
1174
+
1175
+ private DBObject storeRaceLogCourseDesignChangedEvent(RaceLogCourseDesignChangedEvent courseDesignChangedEvent) {
1176
+ DBObject result = new BasicDBObject();
1177
+ storeRaceLogEventProperties(courseDesignChangedEvent, result);
1178
+ result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogCourseDesignChangedEvent.class.getSimpleName());
1179
+ result.put(FieldNames.RACE_LOG_COURSE_DESIGN_NAME.name(), courseDesignChangedEvent.getCourseDesign().getName());
1180
+ result.put(FieldNames.RACE_LOG_COURSE_DESIGN.name(), storeCourseBase(courseDesignChangedEvent.getCourseDesign()));
1181
+ return result;
1182
+ }
1183
+
1184
+ private Object storeRaceLogFinishPositioningListChangedEvent(RaceLogFinishPositioningListChangedEvent finishPositioningListChangedEvent) {
1185
+ DBObject result = new BasicDBObject();
1186
+ storeRaceLogEventProperties(finishPositioningListChangedEvent, result);
1187
+ result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogFinishPositioningListChangedEvent.class.getSimpleName());
1188
+ result.put(FieldNames.RACE_LOG_POSITIONED_COMPETITORS.name(), storePositionedCompetitors(finishPositioningListChangedEvent.getPositionedCompetitorsIDsNamesMaxPointsReasons()));
1189
+
1190
+ return result;
1191
+ }
1192
+
1193
+ private Object storeRaceLogFinishPositioningConfirmedEvent(RaceLogFinishPositioningConfirmedEvent finishPositioningConfirmedEvent) {
1194
+ DBObject result = new BasicDBObject();
1195
+ storeRaceLogEventProperties(finishPositioningConfirmedEvent, result);
1196
+ result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogFinishPositioningConfirmedEvent.class.getSimpleName());
1197
+ result.put(FieldNames.RACE_LOG_POSITIONED_COMPETITORS.name(), storePositionedCompetitors(finishPositioningConfirmedEvent.getPositionedCompetitorsIDsNamesMaxPointsReasons()));
1198
+
1199
+ return result;
1200
+ }
1201
+
1202
+ private Object storeRaceLogGateLineOpeningTimeEvent(RaceLogGateLineOpeningTimeEvent gateLineOpeningTimeEvent){
1203
+ DBObject result = new BasicDBObject();
1204
+ storeRaceLogEventProperties(gateLineOpeningTimeEvent, result);
1205
+ result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogGateLineOpeningTimeEvent.class.getSimpleName());
1206
+ result.put(FieldNames.RACE_LOG_GATE_LINE_OPENING_TIME.name(), gateLineOpeningTimeEvent.getGateLineOpeningTimes().getGateLaunchStopTime());
1207
+ result.put(FieldNames.RACE_LOG_GOLF_DOWN_TIME.name(), gateLineOpeningTimeEvent.getGateLineOpeningTimes().getGolfDownTime());
1208
+ return result;
1209
+ }
1210
+
1211
+ private BasicDBList storePositionedCompetitors(List<com.sap.sse.common.Util.Triple<Serializable, String, MaxPointsReason>> positionedCompetitors) {
1212
+ BasicDBList dbList = new BasicDBList();
1213
+ if (positionedCompetitors != null) {
1214
+ for (com.sap.sse.common.Util.Triple<Serializable, String, MaxPointsReason> competitorPair : positionedCompetitors) {
1215
+ dbList.add(storePositionedCompetitor(competitorPair));
1216
+ }
1217
+ }
1218
+ return dbList;
1219
+ }
1220
+
1221
+ private DBObject storePositionedCompetitor(com.sap.sse.common.Util.Triple<Serializable, String, MaxPointsReason> competitorTriple) {
1222
+ DBObject result = new BasicDBObject();
1223
+ result.put(FieldNames.COMPETITOR_ID.name(), competitorTriple.getA());
1224
+ result.put(FieldNames.COMPETITOR_DISPLAY_NAME.name(), competitorTriple.getB());
1225
+ result.put(FieldNames.LEADERBOARD_SCORE_CORRECTION_MAX_POINTS_REASON.name(), competitorTriple.getC().name());
1226
+
1227
+ return result;
1228
+ }
1229
+
1230
+ private BasicDBList storeCourseBase(CourseBase courseData) {
1231
+ BasicDBList dbList = new BasicDBList();
1232
+
1233
+ for (Waypoint waypoint : courseData.getWaypoints()) {
1234
+ dbList.add(storeWaypoint(waypoint));
1235
+ }
1236
+ return dbList;
1237
+ }
1238
+
1239
+ private DBObject storeWaypoint(Waypoint waypoint) {
1240
+ DBObject result = new BasicDBObject();
1241
+ result.put(FieldNames.WAYPOINT_PASSINGINSTRUCTIONS.name(), getPassingInstructions(waypoint.getPassingInstructions()));
1242
+ result.put(FieldNames.CONTROLPOINT.name(), storeControlPoint(waypoint.getControlPoint()));
1243
+ return result;
1244
+ }
1245
+
1246
+ private DBObject storeControlPoint(ControlPoint controlPoint) {
1247
+ DBObject result = new BasicDBObject();
1248
+ if (controlPoint instanceof Mark) {
1249
+ result.put(FieldNames.CONTROLPOINT_CLASS.name(), Mark.class.getSimpleName());
1250
+ result.put(FieldNames.CONTROLPOINT_VALUE.name(), storeMark((Mark) controlPoint));
1251
+ } else if (controlPoint instanceof ControlPointWithTwoMarks) {
1252
+ result.put(FieldNames.CONTROLPOINT_CLASS.name(), ControlPointWithTwoMarks.class.getSimpleName());
1253
+ result.put(FieldNames.CONTROLPOINT_VALUE.name(), storeControlPointWithTwoMarks((ControlPointWithTwoMarks) controlPoint));
1254
+ }
1255
+ return result;
1256
+ }
1257
+
1258
+ private DBObject storeControlPointWithTwoMarks(ControlPointWithTwoMarks cpwtm) {
1259
+ DBObject result = new BasicDBObject();
1260
+ result.put(FieldNames.CONTROLPOINTWITHTWOMARKS_ID.name(), cpwtm.getId());
1261
+ result.put(FieldNames.CONTROLPOINTWITHTWOMARKS_NAME.name(), cpwtm.getName());
1262
+ result.put(FieldNames.CONTROLPOINTWITHTWOMARKS_LEFT.name(), storeMark(cpwtm.getLeft()));
1263
+ result.put(FieldNames.CONTROLPOINTWITHTWOMARKS_RIGHT.name(), storeMark(cpwtm.getRight()));
1264
+ return result;
1265
+ }
1266
+
1267
+ private DBObject storeMark(Mark mark) {
1268
+ DBObject result = new BasicDBObject();
1269
+ result.put(FieldNames.MARK_ID.name(), mark.getId());
1270
+ result.put(FieldNames.MARK_COLOR.name(), mark.getColor());
1271
+ result.put(FieldNames.MARK_NAME.name(), mark.getName());
1272
+ result.put(FieldNames.MARK_PATTERN.name(), mark.getPattern());
1273
+ result.put(FieldNames.MARK_SHAPE.name(), mark.getShape());
1274
+ result.put(FieldNames.MARK_TYPE.name(), mark.getType() == null ? null : mark.getType().name());
1275
+ return result;
1276
+ }
1277
+
1278
+ private String getPassingInstructions(PassingInstruction passingInstructions) {
1279
+ final String passing;
1280
+ if (passingInstructions != null) {
1281
+ passing = passingInstructions.name();
1282
+ } else {
1283
+ passing = null;
1284
+ }
1285
+ return passing;
1286
+ }
1287
+ @Override
1288
+ public void storeCompetitor(Competitor competitor) {
1289
+ DBCollection collection = database.getCollection(CollectionNames.COMPETITORS.name());
1290
+ JSONObject json = competitorSerializer.serialize(competitor);
1291
+ DBObject query = (DBObject) JSON.parse(CompetitorJsonSerializer.getCompetitorIdQuery(competitor).toString());
1292
+ DBObject entry = (DBObject) JSON.parse(json.toString());
1293
+ collection.update(query, entry, /* upsrt */true, /* multi */false, WriteConcern.SAFE);
1294
+ }
1295
+
1296
+ @Override
1297
+ public void removeAllCompetitors() {
1298
+ logger.info("Removing all persistent competitor info");
1299
+ DBCollection collection = database.getCollection(CollectionNames.COMPETITORS.name());
1300
+ collection.drop();
1301
+ }
1302
+
1303
+ @Override
1304
+ public void removeCompetitor(Competitor competitor) {
1305
+ logger.info("Removing persistent competitor info for competitor "+competitor.getName()+" with ID "+competitor.getId());
1306
+ DBCollection collection = database.getCollection(CollectionNames.COMPETITORS.name());
1307
+ DBObject query = (DBObject) JSON.parse(CompetitorJsonSerializer.getCompetitorIdQuery(competitor).toString());
1308
+ collection.remove(query, WriteConcern.SAFE);
1309
+ }
1310
+
1311
+ @Override
1312
+ public void storeDeviceConfiguration(DeviceConfigurationMatcher matcher, DeviceConfiguration configuration) {
1313
+ DBCollection configurationsCollections = database.getCollection(CollectionNames.CONFIGURATIONS.name());
1314
+
1315
+ DBObject query = new BasicDBObject();
1316
+ query.put(FieldNames.CONFIGURATION_MATCHER_ID.name(), matcher.getMatcherIdentifier());
1317
+
1318
+ DBObject entryObject = new BasicDBObject();
1319
+ entryObject.put(FieldNames.CONFIGURATION_MATCHER_ID.name(), matcher.getMatcherIdentifier());
1320
+ entryObject.put(FieldNames.CONFIGURATION_MATCHER.name(), createDeviceConfigurationMatcherObject(matcher));
1321
+ entryObject.put(FieldNames.CONFIGURATION_CONFIG.name(), createDeviceConfigurationObject(configuration));
1322
+
1323
+ configurationsCollections.update(query, entryObject, /* upsrt */ true, /* multi */ false, WriteConcern.SAFE);
1324
+ }
1325
+
1326
+ private DBObject createDeviceConfigurationMatcherObject(DeviceConfigurationMatcher matcher) {
1327
+ DBObject matcherObject = new BasicDBObject();
1328
+ matcherObject.put(FieldNames.CONFIGURATION_MATCHER_TYPE.name(), matcher.getMatcherType().name());
1329
+ if (matcher instanceof DeviceConfigurationMatcherSingle) {
1330
+ BasicDBList client = new BasicDBList();
1331
+ client.add(((DeviceConfigurationMatcherSingle)matcher).getClientIdentifier());
1332
+ matcherObject.put(FieldNames.CONFIGURATION_MATCHER_CLIENTS.name(), client);
1333
+ } else if (matcher instanceof DeviceConfigurationMatcherMulti) {
1334
+ BasicDBList clients = new BasicDBList();
1335
+ Util.addAll(((DeviceConfigurationMatcherMulti)matcher).getClientIdentifiers(), clients);
1336
+ matcherObject.put(FieldNames.CONFIGURATION_MATCHER_CLIENTS.name(), clients);
1337
+ }
1338
+ return matcherObject;
1339
+ }
1340
+
1341
+ private DBObject createDeviceConfigurationObject(DeviceConfiguration configuration) {
1342
+ JsonSerializer<DeviceConfiguration> serializer = DeviceConfigurationJsonSerializer.create();
1343
+ JSONObject json = serializer.serialize(configuration);
1344
+ DBObject entry = (DBObject) JSON.parse(json.toString());
1345
+ return entry;
1346
+ }
1347
+
1348
+ @Override
1349
+ public void removeDeviceConfiguration(DeviceConfigurationMatcher matcher) {
1350
+ DBCollection configurationsCollections = database.getCollection(CollectionNames.CONFIGURATIONS.name());
1351
+ DBObject query = new BasicDBObject();
1352
+ query.put(FieldNames.CONFIGURATION_MATCHER_ID.name(), matcher.getMatcherIdentifier());
1353
+ configurationsCollections.remove(query);
1354
+ }
1355
+
1356
+ public static DBObject storeDeviceId(
1357
+ TypeBasedServiceFinder<DeviceIdentifierMongoHandler> deviceIdentifierServiceFinder, DeviceIdentifier device)
1358
+ throws TransformationException, NoCorrespondingServiceRegisteredException {
1359
+ String type = device.getIdentifierType();
1360
+ DeviceIdentifierMongoHandler handler = deviceIdentifierServiceFinder.findService(type);
1361
+ com.sap.sse.common.Util.Pair<String, ? extends Object> pair = handler.serialize(device);
1362
+ type = pair.getA();
1363
+ Object deviceTypeSpecificId = pair.getB();
1364
+ return new BasicDBObjectBuilder()
1365
+ .add(FieldNames.DEVICE_TYPE.name(), type)
1366
+ .add(FieldNames.DEVICE_TYPE_SPECIFIC_ID.name(), deviceTypeSpecificId)
1367
+ .add(FieldNames.DEVICE_STRING_REPRESENTATION.name(), device.getStringRepresentation()).get();
1368
+ }
1369
+
1370
+ void storeRaceLogEventEvent(DBObject eventEntry) {
1371
+ getRaceLogCollection().insert(eventEntry);
1372
+ }
1373
+
1374
+ @Override
1375
+ public void removeRaceLog(RaceLogIdentifier identifier) {
1376
+ DBObject query = new BasicDBObject();
1377
+ storeRaceLogIdentifier(identifier, query);
1378
+ getRaceLogCollection().remove(query);
1379
+ }
1380
+
1381
+ @Override
1382
+ public void removeRegattaLog(RegattaLikeIdentifier identifier) {
1383
+ DBObject query = new BasicDBObject();
1384
+ addRegattaLikeIdentifier(identifier, query);
1385
+ getRegattaLogCollection().remove(query);
1386
+ }
1387
+
1388
+ @Override
1389
+ public void storeResultUrl(String resultProviderName, URL url) {
1390
+ DBCollection resultUrlsCollection = database.getCollection(CollectionNames.RESULT_URLS.name());
1391
+ DBObject query = new BasicDBObject(FieldNames.RESULT_PROVIDERNAME.name(), resultProviderName);
1392
+ DBObject entry = new BasicDBObject(FieldNames.RESULT_PROVIDERNAME.name(), resultProviderName);
1393
+ entry.put(FieldNames.RESULT_URL.name(), url.toString());
1394
+ resultUrlsCollection.update(query, entry, /* upsrt */true, /* multi */false, WriteConcern.SAFE);
1395
+ }
1396
+
1397
+ @Override
1398
+ public void removeResultUrl(String resultProviderName, URL url) {
1399
+ DBCollection resultUrlsCollection = database.getCollection(CollectionNames.RESULT_URLS.name());
1400
+ DBObject query = new BasicDBObjectBuilder().add(FieldNames.RESULT_PROVIDERNAME.name(), resultProviderName)
1401
+ .add(FieldNames.RESULT_URL.name(), url.toString()).get();
1402
+ resultUrlsCollection.remove(query);
1403
+ }
1404
+
1405
+ public DBCollection getRegattaLogCollection() {
1406
+ DBCollection result = database.getCollection(CollectionNames.REGATTA_LOGS.name());
1407
+ DBObject index = new BasicDBObject(FieldNames.REGATTA_LOG_IDENTIFIER_TYPE.name(), null);
1408
+ index.put(FieldNames.REGATTA_LOG_IDENTIFIER_NAME.name(), null);
1409
+ result.createIndex(index);
1410
+ return result;
1411
+ }
1412
+
1413
+ private DBObject createBasicRegattaLogEventDBObject(RegattaLogEvent event) {
1414
+ DBObject result = new BasicDBObject();
1415
+ storeTimed(event, result);
1416
+ storeTimePoint(event.getCreatedAt(), result, FieldNames.REGATTA_LOG_EVENT_CREATED_AT);
1417
+ result.put(FieldNames.REGATTA_LOG_EVENT_ID.name(), event.getId());
1418
+ result.put(FieldNames.REGATTA_LOG_EVENT_AUTHOR_NAME.name(), event.getAuthor().getName());
1419
+ result.put(FieldNames.REGATTA_LOG_EVENT_AUTHOR_PRIORITY.name(), event.getAuthor().getPriority());
1420
+ return result;
1421
+ }
1422
+
1423
+ private void addRegattaLikeIdentifier(RegattaLikeIdentifier regattaLikeId, DBObject toObject) {
1424
+ toObject.put(FieldNames.REGATTA_LOG_IDENTIFIER_TYPE.name(), regattaLikeId.getIdentifierType());
1425
+ toObject.put(FieldNames.REGATTA_LOG_IDENTIFIER_NAME.name(), regattaLikeId.getName());
1426
+ }
1427
+
1428
+ private void storeRegattaLogEvent(RegattaLikeIdentifier regattaLikeId, DBObject innerObject) {
1429
+ DBObject result = new BasicDBObject(FieldNames.REGATTA_LOG_EVENT.name(), innerObject);
1430
+ addRegattaLikeIdentifier(regattaLikeId, result);
1431
+ getRegattaLogCollection().insert(result);
1432
+ }
1433
+
1434
+ public void storeRegattaLogEvent(RegattaLikeIdentifier regattaLikeId, RegattaLogDeviceCompetitorMappingEvent event) {
1435
+ DBObject result = createBasicRegattaLogEventDBObject(event);
1436
+ result.put(FieldNames.REGATTA_LOG_EVENT_CLASS.name(), RegattaLogDeviceCompetitorMappingEvent.class.getSimpleName());
1437
+ storeDeviceMappingEvent(event, result, FieldNames.REGATTA_LOG_FROM, FieldNames.REGATTA_LOG_TO);
1438
+ result.put(FieldNames.COMPETITOR_ID.name(), event.getMappedTo().getId());
1439
+ storeRegattaLogEvent(regattaLikeId, result);
1440
+ }
1441
+
1442
+ public void storeRegattaLogEvent(RegattaLikeIdentifier regattaLikeId, RegattaLogDeviceMarkMappingEvent event) {
1443
+ DBObject result = createBasicRegattaLogEventDBObject(event);
1444
+ result.put(FieldNames.REGATTA_LOG_EVENT_CLASS.name(), RegattaLogDeviceMarkMappingEvent.class.getSimpleName());
1445
+ storeDeviceMappingEvent(event, result, FieldNames.REGATTA_LOG_FROM, FieldNames.REGATTA_LOG_TO);
1446
+ result.put(FieldNames.MARK.name(), storeMark(event.getMappedTo()));
1447
+ storeRegattaLogEvent(regattaLikeId, result);
1448
+ }
1449
+
1450
+ public void storeRegattaLogEvent(RegattaLikeIdentifier regattaLikeId, RegattaLogRevokeEvent event) {
1451
+ DBObject result = createBasicRegattaLogEventDBObject(event);
1452
+ result.put(FieldNames.REGATTA_LOG_EVENT_CLASS.name(), RegattaLogRevokeEvent.class.getSimpleName());
1453
+ result.put(FieldNames.REGATTA_LOG_REVOKED_EVENT_ID.name(), event.getRevokedEventId());
1454
+ result.put(FieldNames.REGATTA_LOG_REVOKED_EVENT_TYPE.name(), event.getRevokedEventType());
1455
+ result.put(FieldNames.REGATTA_LOG_REVOKED_EVENT_SHORT_INFO.name(), event.getRevokedEventShortInfo());
1456
+ result.put(FieldNames.REGATTA_LOG_REVOKED_REASON.name(), event.getReason());
1457
+ storeRegattaLogEvent(regattaLikeId, result);
1458
+ }
1459
+
1460
+ public void storeRegattaLogEvent(RegattaLikeIdentifier regattaLikeId, RegattaLogRegisterCompetitorEvent event) {
1461
+ DBObject result = createBasicRegattaLogEventDBObject(event);
1462
+ result.put(FieldNames.REGATTA_LOG_EVENT_CLASS.name(), RegattaLogRegisterCompetitorEvent.class.getSimpleName());
1463
+ result.put(FieldNames.REGATTA_LOG_COMPETITOR_ID.name(), event.getCompetitor().getId());
1464
+ storeRegattaLogEvent(regattaLikeId, result);
1465
+ }
1466
+
1467
+ public void storeRegattaLogEvent(RegattaLikeIdentifier regattaLikeId, RegattaLogCloseOpenEndedDeviceMappingEvent event) {
1468
+ DBObject result = createBasicRegattaLogEventDBObject(event);
1469
+ result.put(FieldNames.REGATTA_LOG_EVENT_CLASS.name(), RegattaLogCloseOpenEndedDeviceMappingEvent.class.getSimpleName());
1470
+ result.put(FieldNames.REGATTA_LOG_DEVICE_MAPPING_EVENT_ID.name(), event.getDeviceMappingEventId());
1471
+ storeTimePoint(event.getClosingTimePoint(), result, FieldNames.REGATTA_LOG_CLOSING_TIMEPOINT);
1472
+ storeRegattaLogEvent(regattaLikeId, result);
1473
+ }
1474
+
1475
+ public void storeRegattaLogEvent(RegattaLikeIdentifier regattaLikeId, RegattaLogSetCompetitorTimeOnTimeFactorEvent event) {
1476
+ DBObject result = createBasicRegattaLogEventDBObject(event);
1477
+ result.put(FieldNames.REGATTA_LOG_EVENT_CLASS.name(), RegattaLogCloseOpenEndedDeviceMappingEvent.class.getSimpleName());
1478
+ result.put(FieldNames.REGATTA_LOG_COMPETITOR_ID.name(), event.getCompetitor().getId());
1479
+ result.put(FieldNames.REGATTA_LOG_TIME_ON_TIME_FACTOR.name(), event.getTimeOnTimeFactor());
1480
+ }
1481
+
1482
+ public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogDependentStartTimeEvent event) {
1483
+ BasicDBObject result = new BasicDBObject();
1484
+ storeRaceLogIdentifier(raceLogIdentifier, result);
1485
+ result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogDependentStartTimeEvent(event));
1486
+ return result;
1487
+ }
1488
+
1489
+ public void storeRegattaLogEvent(RegattaLikeIdentifier regattaLikeId, RegattaLogSetCompetitorTimeOnDistanceAllowancePerNauticalMileEvent event) {
1490
+ DBObject result = createBasicRegattaLogEventDBObject(event);
1491
+ result.put(FieldNames.REGATTA_LOG_EVENT_CLASS.name(), RegattaLogCloseOpenEndedDeviceMappingEvent.class.getSimpleName());
1492
+ result.put(FieldNames.REGATTA_LOG_COMPETITOR_ID.name(), event.getCompetitor().getId());
1493
+ result.put(FieldNames.REGATTA_LOG_TIME_ON_DISTANCE_SECONDS_ALLOWANCE_PER_NAUTICAL_MILE.name(), event.getTimeOnDistanceAllowancePerNauticalMile().asSeconds());
1494
+ storeRegattaLogEvent(regattaLikeId, result);
1495
+ }
1496
+
1497
+ private DBObject createImageObject(ImageDescriptor image) {
1498
+ DBObject result = new BasicDBObject();
1499
+ result.put(FieldNames.IMAGE_URL.name(), image.getURL().toString());
1500
+ result.put(FieldNames.IMAGE_LOCALE.name(), image.getLocale() != null ? image.getLocale().toLanguageTag() : null);
1501
+ result.put(FieldNames.IMAGE_TITLE.name(), image.getTitle());
1502
+ result.put(FieldNames.IMAGE_SUBTITLE.name(), image.getSubtitle());
1503
+ result.put(FieldNames.IMAGE_COPYRIGHT.name(), image.getCopyright());
1504
+ result.put(FieldNames.IMAGE_WIDTH_IN_PX.name(), image.getWidthInPx());
1505
+ result.put(FieldNames.IMAGE_HEIGHT_IN_PX.name(), image.getHeightInPx());
1506
+ storeTimePoint(image.getCreatedAtDate(), result, FieldNames.IMAGE_CREATEDATDATE);
1507
+ BasicDBList tags = new BasicDBList();
1508
+ for (String tag : image.getTags()) {
1509
+ tags.add(tag);
1510
+ }
1511
+ result.put(FieldNames.IMAGE_TAGS.name(), tags);
1512
+ return result;
1513
+ }
1514
+
1515
+ private DBObject createVideoObject(VideoDescriptor video) {
1516
+ DBObject result = new BasicDBObject();
1517
+ result.put(FieldNames.VIDEO_URL.name(), video.getURL().toString());
1518
+ result.put(FieldNames.VIDEO_LOCALE.name(), video.getLocale() != null ? video.getLocale().toLanguageTag() : null);
1519
+ result.put(FieldNames.VIDEO_THUMBNAIL_URL.name(), video.getThumbnailURL() != null ? video.getThumbnailURL().toString() : null);
1520
+ result.put(FieldNames.VIDEO_TITLE.name(), video.getTitle());
1521
+ result.put(FieldNames.VIDEO_SUBTITLE.name(), video.getSubtitle());
1522
+ result.put(FieldNames.VIDEO_MIMETYPE.name(), video.getMimeType() != null ? video.getMimeType().name() : null);
1523
+ result.put(FieldNames.VIDEO_COPYRIGHT.name(), video.getCopyright());
1524
+ result.put(FieldNames.VIDEO_LENGTH_IN_SECONDS.name(), video.getLengthInSeconds());
1525
+ storeTimePoint(video.getCreatedAtDate(), result, FieldNames.VIDEO_CREATEDATDATE);
1526
+ BasicDBList tags = new BasicDBList();
1527
+ for (String tag : video.getTags()) {
1528
+ tags.add(tag);
1529
+ }
1530
+ result.put(FieldNames.VIDEO_TAGS.name(), tags);
1531
+ return result;
1532
+ }
1533
+}
java/com.sap.sailing.domain.shared.android/pom.xml
... ...
@@ -1,39 +1,39 @@
1
-<?xml version="1.0" encoding="UTF-8"?>
2
-<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
3
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
4
- <modelVersion>4.0.0</modelVersion>
5
- <parent>
6
- <artifactId>root</artifactId>
7
- <groupId>com.sap.sailing</groupId>
8
- <version>1.0.0-SNAPSHOT</version>
9
- </parent>
10
- <artifactId>com.sap.sailing.domain.shared.android</artifactId>
11
- <packaging>eclipse-plugin</packaging>
12
- <build>
13
- <plugins>
14
- <plugin>
15
- <groupId>org.eclipse.tycho</groupId>
16
- <artifactId>tycho-compiler-plugin</artifactId>
17
- <version>${tycho-version}</version>
18
- <configuration>
19
- <source>1.7</source>
20
- <target>1.7</target>
21
- </configuration>
22
- </plugin>
23
- <plugin>
24
- <groupId>org.eclipse.tycho</groupId>
25
- <artifactId>tycho-source-plugin</artifactId>
26
- <version>${tycho-version}</version>
27
- <executions>
28
- <execution>
29
- <id>plugin-source</id>
30
- <phase>generate-sources</phase>
31
- <goals>
32
- <goal>plugin-source</goal>
33
- </goals>
34
- </execution>
35
- </executions>
36
- </plugin>
37
- </plugins>
38
- </build>
39
-</project>
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
3
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
4
+ <modelVersion>4.0.0</modelVersion>
5
+ <parent>
6
+ <artifactId>root</artifactId>
7
+ <groupId>com.sap.sailing</groupId>
8
+ <version>1.0.0-SNAPSHOT</version>
9
+ </parent>
10
+ <artifactId>com.sap.sailing.domain.shared.android</artifactId>
11
+ <packaging>eclipse-plugin</packaging>
12
+ <build>
13
+ <plugins>
14
+ <plugin>
15
+ <groupId>org.eclipse.tycho</groupId>
16
+ <artifactId>tycho-compiler-plugin</artifactId>
17
+ <version>${tycho-version}</version>
18
+ <configuration>
19
+ <source>1.7</source>
20
+ <target>1.7</target>
21
+ </configuration>
22
+ </plugin>
23
+ <plugin>
24
+ <groupId>org.eclipse.tycho</groupId>
25
+ <artifactId>tycho-source-plugin</artifactId>
26
+ <version>${tycho-version}</version>
27
+ <executions>
28
+ <execution>
29
+ <id>plugin-source</id>
30
+ <phase>generate-sources</phase>
31
+ <goals>
32
+ <goal>plugin-source</goal>
33
+ </goals>
34
+ </execution>
35
+ </executions>
36
+ </plugin>
37
+ </plugins>
38
+ </build>
39
+</project>
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/EventBase.java
... ...
@@ -1,98 +1,98 @@
1
-package com.sap.sailing.domain.base;
2
-
3
-import java.net.URL;
4
-import java.util.Map;
5
-
6
-import com.sap.sailing.domain.common.Renamable;
7
-import com.sap.sse.common.Named;
8
-import com.sap.sse.common.TimePoint;
9
-import com.sap.sse.common.WithID;
10
-import com.sap.sse.common.media.ImageSize;
11
-import com.sap.sse.common.media.WithMedia;
12
-
13
-/**
14
- * Base interface for an Event consisting of all static information, which might be shared
15
- * by the server and an Android application.
16
- */
17
-public interface EventBase extends Named, WithDescription, Renamable, WithID, WithMedia {
18
-
19
- void setDescription(String description);
20
-
21
- /**
22
- * @return a non-<code>null</code> venue for this event
23
- */
24
- Venue getVenue();
25
-
26
- /**
27
- * @return the start date of the event
28
- */
29
- TimePoint getStartDate();
30
-
31
- void setStartDate(TimePoint startDate);
32
-
33
- /**
34
- * @return the end date of the event
35
- */
36
- TimePoint getEndDate();
37
-
38
- void setEndDate(TimePoint startDate);
39
-
40
- boolean isPublic();
41
-
42
- void setPublic(boolean isPublic);
43
-
44
- /**
45
- * @deprecated
46
- * Returns a non-<code>null</code> live but unmodifiable collection of URLs pointing to image resources that can be
47
- * used to represent the event, e.g., on a web page.
48
- *
49
- * @return a non-<code>null</code> value which may be empty
50
- */
51
- Iterable<URL> getImageURLs();
52
-
53
- /**
54
- * @deprecated
55
- * An event may have zero or more sponsors, each of which usually want to see their logo on the web page.
56
- *
57
- * @return the sponsors' logos; always non-<code>null</code> but possibly empty
58
- */
59
- Iterable<URL> getSponsorImageURLs();
60
-
61
- /**
62
- * @deprecated
63
- * An optional logo image; may return <code>null</code>.
64
- */
65
- URL getLogoImageURL();
66
-
67
- /**
68
- * @deprecated
69
- * Returns a non-<code>null</code> live but unmodifiable collection of URLs pointing to video resources that can be
70
- * used to represent the event, e.g., on a web page.
71
- *
72
- * @return a non-<code>null</code> value which may be empty
73
- */
74
- Iterable<URL> getVideoURLs();
75
-
76
- /**
77
- * @return the URL of the event's official web site, or <code>null</code> if such a site does not exist or its URL
78
- * is not known
79
- */
80
- URL getOfficialWebsiteURL();
81
-
82
- void setOfficialWebsiteURL(URL officialWebsiteURL);
83
-
84
- /**
85
- * @return the URL of an external web site containing sailor related information like protests, official results, etc.
86
- * or <code>null</code> if such a site does not exist or its URL is not known.
87
- */
88
- URL getSailorsInfoWebsiteURL();
89
-
90
- void setSailorsInfoWebsiteURL(URL sailorsInfoWebsiteURL);
91
-
92
- Iterable<? extends LeaderboardGroupBase> getLeaderboardGroups();
93
-
94
- /**
95
- * Sets and converts all event images and videos from the old URL based format to the new richer format
96
- * */
97
- boolean setMediaURLs(Iterable<URL> imageURLs, Iterable<URL> sponsorImageURLs, Iterable<URL> videoURLs, URL logoImageURL, Map<URL, ImageSize> imageSizes);
98
-}
1
+package com.sap.sailing.domain.base;
2
+
3
+import java.net.URL;
4
+import java.util.Map;
5
+
6
+import com.sap.sailing.domain.common.Renamable;
7
+import com.sap.sse.common.Named;
8
+import com.sap.sse.common.TimePoint;
9
+import com.sap.sse.common.WithID;
10
+import com.sap.sse.common.media.ImageSize;
11
+import com.sap.sse.shared.media.WithMedia;
12
+
13
+/**
14
+ * Base interface for an Event consisting of all static information, which might be shared
15
+ * by the server and an Android application.
16
+ */
17
+public interface EventBase extends Named, WithDescription, Renamable, WithID, WithMedia {
18
+
19
+ void setDescription(String description);
20
+
21
+ /**
22
+ * @return a non-<code>null</code> venue for this event
23
+ */
24
+ Venue getVenue();
25
+
26
+ /**
27
+ * @return the start date of the event
28
+ */
29
+ TimePoint getStartDate();
30
+
31
+ void setStartDate(TimePoint startDate);
32
+
33
+ /**
34
+ * @return the end date of the event
35
+ */
36
+ TimePoint getEndDate();
37
+
38
+ void setEndDate(TimePoint startDate);
39
+
40
+ boolean isPublic();
41
+
42
+ void setPublic(boolean isPublic);
43
+
44
+ /**
45
+ * @deprecated
46
+ * Returns a non-<code>null</code> live but unmodifiable collection of URLs pointing to image resources that can be
47
+ * used to represent the event, e.g., on a web page.
48
+ *
49
+ * @return a non-<code>null</code> value which may be empty
50
+ */
51
+ Iterable<URL> getImageURLs();
52
+
53
+ /**
54
+ * @deprecated
55
+ * An event may have zero or more sponsors, each of which usually want to see their logo on the web page.
56
+ *
57
+ * @return the sponsors' logos; always non-<code>null</code> but possibly empty
58
+ */
59
+ Iterable<URL> getSponsorImageURLs();
60
+
61
+ /**
62
+ * @deprecated
63
+ * An optional logo image; may return <code>null</code>.
64
+ */
65
+ URL getLogoImageURL();
66
+
67
+ /**
68
+ * @deprecated
69
+ * Returns a non-<code>null</code> live but unmodifiable collection of URLs pointing to video resources that can be
70
+ * used to represent the event, e.g., on a web page.
71
+ *
72
+ * @return a non-<code>null</code> value which may be empty
73
+ */
74
+ Iterable<URL> getVideoURLs();
75
+
76
+ /**
77
+ * @return the URL of the event's official web site, or <code>null</code> if such a site does not exist or its URL
78
+ * is not known
79
+ */
80
+ URL getOfficialWebsiteURL();
81
+
82
+ void setOfficialWebsiteURL(URL officialWebsiteURL);
83
+
84
+ /**
85
+ * @return the URL of an external web site containing sailor related information like protests, official results, etc.
86
+ * or <code>null</code> if such a site does not exist or its URL is not known.
87
+ */
88
+ URL getSailorsInfoWebsiteURL();
89
+
90
+ void setSailorsInfoWebsiteURL(URL sailorsInfoWebsiteURL);
91
+
92
+ Iterable<? extends LeaderboardGroupBase> getLeaderboardGroups();
93
+
94
+ /**
95
+ * Sets and converts all event images and videos from the old URL based format to the new richer format
96
+ * */
97
+ boolean setMediaURLs(Iterable<URL> imageURLs, Iterable<URL> sponsorImageURLs, Iterable<URL> videoURLs, URL logoImageURL, Map<URL, ImageSize> imageSizes);
98
+}
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/EventBaseImpl.java
... ...
@@ -15,15 +15,15 @@ import com.sap.sailing.domain.base.Venue;
15 15
import com.sap.sse.common.TimePoint;
16 16
import com.sap.sse.common.Util;
17 17
import com.sap.sse.common.Util.Pair;
18
-import com.sap.sse.common.media.ImageDescriptor;
19
-import com.sap.sse.common.media.ImageDescriptorImpl;
20 18
import com.sap.sse.common.media.ImageSize;
21
-import com.sap.sse.common.media.MediaDescriptor;
22 19
import com.sap.sse.common.media.MediaTagConstants;
23
-import com.sap.sse.common.media.MediaUtils;
24 20
import com.sap.sse.common.media.MimeType;
25
-import com.sap.sse.common.media.VideoDescriptor;
26
-import com.sap.sse.common.media.VideoDescriptorImpl;
21
+import com.sap.sse.shared.media.ImageDescriptor;
22
+import com.sap.sse.shared.media.MediaDescriptor;
23
+import com.sap.sse.shared.media.MediaUtils;
24
+import com.sap.sse.shared.media.VideoDescriptor;
25
+import com.sap.sse.shared.media.impl.ImageDescriptorImpl;
26
+import com.sap.sse.shared.media.impl.VideoDescriptorImpl;
27 27
28 28
public abstract class EventBaseImpl implements EventBase {
29 29
private static final long serialVersionUID = -5749964088848611074L;
java/com.sap.sailing.domain.test/resources/IncrementalLeaderboardDTO.ser
java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/MultipleClassesInRegattaTest.java
... ...
@@ -60,7 +60,7 @@ public class MultipleClassesInRegattaTest {
60 60
httpAndHost
61 61
+ "/events/event_20110505_SailingTea/clientparams.php?event=event_20110505_SailingTea&race=cce678c8-97e6-11e0-9aed-406186cbf87c"),
62 62
new URI(liveURI), new URI(storedURI), new URI(courseDesignUpdateURI),
63
- /* startOfTracking */null, /* endOfTracking */null, /* delayToLiveInMillis */0l, /* simulateWithStartTimeNow */ false, /* ignoreTracTracMarkPassings*/
63
+ /* startOfTracking */null, /* endOfTracking */null, /* delayToLiveInMillis */0l, /* offsetToStartTimeOfSimulatedRace */ null, /* ignoreTracTracMarkPassings*/
64 64
false, EmptyRaceLogStore.INSTANCE, EmptyRegattaLogStore.INSTANCE, EmptyWindStore.INSTANCE,
65 65
EmptyGPSFixStore.INSTANCE, tracTracUsername, tracTracPassword,
66 66
TracTracConnectionConstants.ONLINE_STATUS, TracTracConnectionConstants.ONLINE_VISIBILITY,
... ...
@@ -71,7 +71,7 @@ public class MultipleClassesInRegattaTest {
71 71
httpAndHost
72 72
+ "/events/event_20110505_SailingTea/clientparams.php?event=event_20110505_SailingTea&race=11290bd6-97e7-11e0-9aed-406186cbf87c"),
73 73
new URI(liveURI), new URI(storedURI), new URI(courseDesignUpdateURI),
74
- /* startOfTracking */null, /* endOfTracking */null, /* delayToLiveInMillis */0l, /* simulateWithStartTimeNow */ false, /* ignoreTracTracMarkPassings*/
74
+ /* startOfTracking */null, /* endOfTracking */null, /* delayToLiveInMillis */0l, /* offsetToStartTimeOfSimulatedRace */ null, /* ignoreTracTracMarkPassings*/
75 75
false, EmptyRaceLogStore.INSTANCE, EmptyRegattaLogStore.INSTANCE, EmptyWindStore.INSTANCE,
76 76
EmptyGPSFixStore.INSTANCE, tracTracUsername, tracTracPassword,
77 77
TracTracConnectionConstants.ONLINE_STATUS, TracTracConnectionConstants.ONLINE_VISIBILITY,
... ...
@@ -82,7 +82,7 @@ public class MultipleClassesInRegattaTest {
82 82
httpAndHost
83 83
+ "/events/event_20110505_SailingTea/clientparams.php?event=event_20110505_SailingTea&race=39635b24-97e7-11e0-9aed-406186cbf87c"),
84 84
new URI(liveURI), new URI(storedURI), new URI(courseDesignUpdateURI),
85
- /* startOfTracking */null, /* endOfTracking */null, /* delayToLiveInMillis */0l, /* simulateWithStartTimeNow */ false, /* ignoreTracTracMarkPassings*/
85
+ /* startOfTracking */null, /* endOfTracking */null, /* delayToLiveInMillis */0l, /* offsetToStartTimeOfSimulatedRace */ null, /* ignoreTracTracMarkPassings*/
86 86
false, EmptyRaceLogStore.INSTANCE, EmptyRegattaLogStore.INSTANCE, EmptyWindStore.INSTANCE,
87 87
EmptyGPSFixStore.INSTANCE, tracTracUsername, tracTracPassword,
88 88
TracTracConnectionConstants.ONLINE_STATUS, TracTracConnectionConstants.ONLINE_VISIBILITY,
... ...
@@ -93,7 +93,7 @@ public class MultipleClassesInRegattaTest {
93 93
httpAndHost
94 94
+ "/events/event_20110505_SailingTea/clientparams.php?event=event_20110505_SailingTea&race=04498426-7dfd-11e0-8236-406186cbf87c"),
95 95
new URI(liveURI), new URI(storedURI), new URI(courseDesignUpdateURI),
96
- /* startOfTracking */null, /* endOfTracking */null, /* delayToLiveInMillis */0l, /* simulateWithStartTimeNow */ false, /* ignoreTracTracMarkPassings*/
96
+ /* startOfTracking */null, /* endOfTracking */null, /* delayToLiveInMillis */0l, /* offsetToStartTimeOfSimulatedRace */ null, /* ignoreTracTracMarkPassings*/
97 97
false, EmptyRaceLogStore.INSTANCE, EmptyRegattaLogStore.INSTANCE, EmptyWindStore.INSTANCE,
98 98
EmptyGPSFixStore.INSTANCE, tracTracUsername, tracTracPassword,
99 99
TracTracConnectionConstants.ONLINE_STATUS, TracTracConnectionConstants.ONLINE_VISIBILITY,
java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/UnicodeCharactersInCompetitorNamesTest.java
... ...
@@ -67,7 +67,7 @@ public class UnicodeCharactersInCompetitorNamesTest {
67 67
+ TracTracConnectionConstants.PORT_STORED), new URI(
68 68
"http://tracms.traclive.dk/update_course"),
69 69
/* startOfTracking */null, /* endOfTracking */null, /* delayToLiveInMillis */0l,
70
- /* simulateWithStartTimeNow */ false, /* ignoreTracTracMarkPassings*/
70
+ /* offsetToStartTimeOfSimulatedRace */ null, /* ignoreTracTracMarkPassings*/
71 71
false, EmptyRaceLogStore.INSTANCE, EmptyRegattaLogStore.INSTANCE,
72 72
EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE, "tracTest", "tracTest", "", "",
73 73
new DummyTrackedRegattaRegistry(), mock(RaceLogResolver.class));
java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/DomainFactory.java
... ...
@@ -44,6 +44,7 @@ import com.sap.sailing.domain.tracking.impl.EmptyWindStore;
44 44
import com.sap.sailing.domain.tractracadapter.impl.DomainFactoryImpl;
45 45
import com.sap.sailing.domain.tractracadapter.impl.RaceCourseReceiver;
46 46
import com.sap.sailing.domain.tractracadapter.impl.Simulator;
47
+import com.sap.sse.common.Duration;
47 48
import com.sap.sse.common.TimePoint;
48 49
import com.sap.sse.common.Util;
49 50
import com.tractrac.model.lib.api.data.IPosition;
... ...
@@ -95,7 +96,6 @@ public interface DomainFactory {
95 96
* Fetch a race definition previously created by a call to {@link #getOrCreateRaceDefinitionAndTrackedRace}. If no such
96 97
* race definition was created so far, the call blocks until such a definition is provided by a call to
97 98
* {@link #getOrCreateRaceDefinitionAndTrackedRace}.
98
- * @param raceId TODO
99 99
*/
100 100
RaceDefinition getAndWaitForRaceDefinition(UUID raceId);
101 101
... ...
@@ -105,7 +105,6 @@ public interface DomainFactory {
105 105
* A new {@link com.sap.sailing.domain.base.Regatta} is created if no event by
106 106
* an equal name with a boat class with an equal name as the <code>event</code>'s
107 107
* boat class exists yet.
108
- * @param trackedRegattaRegistry TODO
109 108
*/
110 109
com.sap.sailing.domain.base.Regatta getOrCreateDefaultRegatta(RaceLogStore raceLogStore, RegattaLogStore regattaLogStore,
111 110
IRace race, TrackedRegattaRegistry trackedRegattaRegistry);
... ...
@@ -134,27 +133,30 @@ public interface DomainFactory {
134 133
* if <code>null</code>, all stored data until the "end of time" will be loaded that the event has
135 134
* to provide, particularly for the mark positions which are stored per event, not per race; otherwise,
136 135
* particularly the mark position loading will be constrained to this end time.
136
+ * @param offsetToStartTimeOfSimulatedRace
137
+ * if non-<code>null</code>, the {@link Simulator} will be used with this duration as start offset
137 138
* @param windStore
138 139
* Provides the capability to obtain the {@link WindTrack}s for the different wind sources. A trivial
139 140
* implementation is {@link EmptyWindStore} which simply provides new, empty tracks. This is always
140 141
* available but loses track of the wind, e.g., during server restarts.
141
- * @param raceLogResolver TODO
142 142
*/
143 143
TracTracRaceTracker createRaceTracker(URL paramURL, URI liveURI, URI storedURI, URI courseDesignUpdateURI,
144 144
TimePoint startOfTracking, TimePoint endOfTracking, long delayToLiveInMillis,
145
- boolean simulateWithStartTimeNow, boolean useInternalMarkPassingAlgorithm, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore,
145
+ Duration offsetToStartTimeOfSimulatedRace, boolean useInternalMarkPassingAlgorithm, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore,
146 146
WindStore windStore, GPSFixStore gpsFixStore, String tracTracUsername, String tracTracPassword,
147 147
String raceStatus, String raceVisibility, TrackedRegattaRegistry trackedRegattaRegistry, RaceLogResolver raceLogResolver)
148 148
throws MalformedURLException, FileNotFoundException, URISyntaxException, CreateModelException, SubscriberInitializationException;
149 149
150 150
/**
151
- * Same as {@link #createRaceTracker(URL, URI, URI, URI, TimePoint, TimePoint, WindStore, TrackedRegattaRegistry)}, only that
152
- * a predefined {@link Regatta} is used to hold the resulting races.
153
- * @param raceLogResolver TODO
151
+ * Same as {@link #createRaceTracker(URL, URI, URI, URI, TimePoint, TimePoint, WindStore, TrackedRegattaRegistry)},
152
+ * only that a predefined {@link Regatta} is used to hold the resulting races.
153
+ *
154
+ * @param offsetToStartTimeOfSimulatedRace
155
+ * if non-<code>null</code>, the {@link Simulator} will be used with this duration as start offset
154 156
*/
155 157
RaceTracker createRaceTracker(Regatta regatta, URL paramURL, URI liveURI, URI storedURI, URI courseDesignUpdateURI,
156 158
TimePoint startOfTracking, TimePoint endOfTracking, long delayToLiveInMillis,
157
- boolean simulateWithStartTimeNow, boolean useInternalMarkPassingAlgorithm, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore,
159
+ Duration offsetToStartTimeOfSimulatedRace, boolean useInternalMarkPassingAlgorithm, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore,
158 160
WindStore windStore, GPSFixStore gpsFixStore, String tracTracUsername, String tracTracPassword,
159 161
String raceStatus, String raceVisibility, TrackedRegattaRegistry trackedRegattaRegistry, RaceLogResolver raceLogResolver)
160 162
throws MalformedURLException, FileNotFoundException, URISyntaxException, CreateModelException,
... ...
@@ -262,9 +264,13 @@ public interface DomainFactory {
262 264
263 265
Util.Pair<Iterable<com.sap.sailing.domain.base.Competitor>, BoatClass> getCompetitorsAndDominantBoatClass(IRace race);
264 266
267
+ /**
268
+ * @param offsetToStartTimeOfSimulatedRace
269
+ * if non-<code>null</code>, the {@link Simulator} will be used with this duration as start offset
270
+ */
265 271
RaceTrackingConnectivityParameters createTrackingConnectivityParameters(URL paramURL, URI liveURI, URI storedURI,
266 272
URI courseDesignUpdateURI, TimePoint startOfTracking, TimePoint endOfTracking, long delayToLiveInMillis,
267
- boolean simulateWithStartTimeNow, boolean useInternalMarkPassingAlgorithm, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore,
273
+ Duration offsetToStartTimeOfSimulatedRace, boolean useInternalMarkPassingAlgorithm, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore,
268 274
String tracTracUsername, String tracTracPassword, String raceStatus, String raceVisibility);
269 275
/**
270 276
* Removes all knowledge about <code>tractracRace</code> which includes removing it from the race cache, from the
java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/TracTracAdapter.java
... ...
@@ -17,6 +17,8 @@ import com.sap.sailing.domain.regattalog.RegattaLogStore;
17 17
import com.sap.sailing.domain.tracking.RaceHandle;
18 18
import com.sap.sailing.domain.tracking.TrackerManager;
19 19
import com.sap.sailing.domain.tracking.WindStore;
20
+import com.sap.sailing.domain.tractracadapter.impl.Simulator;
21
+import com.sap.sse.common.Duration;
20 22
import com.sap.sse.common.TimePoint;
21 23
import com.sap.sse.common.Util;
22 24
... ...
@@ -65,12 +67,13 @@ public interface TracTracAdapter {
65 67
* created, with a single default series and a single default fleet. If a valid {@link RegattaIdentifier}
66 68
* is specified, a regatta lookup is performed with that identifier; if the regatta is found, it is used
67 69
* to add the races to. Otherwise, a default regatta as described above will be created and used.
68
- * @param raceStatus
70
+ * @param offsetToStartTimeOfSimulatedRace
71
+ * if non-<code>null</code>, the {@link Simulator} will be used with this duration as start offset
69 72
*/
70 73
RaceHandle addTracTracRace(TrackerManager trackerManager, RegattaIdentifier regattaToAddTo, URL paramURL,
71 74
URI liveURI, URI storedURI, URI courseDesignUpdateURI, TimePoint trackingStartTime,
72 75
TimePoint trackingEndTime, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore,
73
- long timeoutForReceivingRaceDefinitionInMilliseconds, boolean simulateWithStartTimeNow, boolean useInternalMarkPassingAlgorithm,
76
+ long timeoutForReceivingRaceDefinitionInMilliseconds, Duration offsetToStartTimeOfSimulatedRace, boolean useInternalMarkPassingAlgorithm,
74 77
String tracTracUsername, String tracTracPassword, String raceStatus, String raceVisibility)
75 78
throws MalformedURLException, FileNotFoundException, URISyntaxException, Exception;
76 79
java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/DomainFactoryImpl.java
... ...
@@ -74,6 +74,7 @@ import com.sap.sailing.domain.tractracadapter.ReceiverType;
74 74
import com.sap.sailing.domain.tractracadapter.TracTracConfiguration;
75 75
import com.sap.sailing.domain.tractracadapter.TracTracControlPoint;
76 76
import com.sap.sailing.domain.tractracadapter.TracTracRaceTracker;
77
+import com.sap.sse.common.Duration;
77 78
import com.sap.sse.common.TimePoint;
78 79
import com.sap.sse.common.Util;
79 80
import com.sap.sse.common.Util.Pair;
... ...
@@ -652,27 +653,27 @@ public class DomainFactoryImpl implements DomainFactory {
652 653
@Override
653 654
public TracTracRaceTracker createRaceTracker(URL paramURL, URI liveURI, URI storedURI, URI courseDesignUpdateURI,
654 655
TimePoint startOfTracking, TimePoint endOfTracking, long delayToLiveInMillis,
655
- boolean simulateWithStartTimeNow, boolean useInternalMarkPassingAlgorithm, RaceLogStore raceLogStore,
656
+ Duration offsetToStartTimeOfSimulatedRace, boolean useInternalMarkPassingAlgorithm, RaceLogStore raceLogStore,
656 657
RegattaLogStore regattaLogStore, WindStore windStore, GPSFixStore gpsFixStore, String tracTracUsername,
657 658
String tracTracPassword, String raceStatus, String raceVisibility,
658 659
TrackedRegattaRegistry trackedRegattaRegistry, RaceLogResolver raceLogResolver) throws MalformedURLException, FileNotFoundException,
659 660
URISyntaxException, CreateModelException, SubscriberInitializationException {
660 661
return new TracTracRaceTrackerImpl(this, paramURL, liveURI, storedURI, courseDesignUpdateURI, startOfTracking,
661
- endOfTracking, delayToLiveInMillis, simulateWithStartTimeNow, useInternalMarkPassingAlgorithm, raceLogStore, regattaLogStore, windStore,
662
+ endOfTracking, delayToLiveInMillis, offsetToStartTimeOfSimulatedRace, useInternalMarkPassingAlgorithm, raceLogStore, regattaLogStore, windStore,
662 663
gpsFixStore, tracTracUsername, tracTracPassword, raceStatus, raceVisibility, trackedRegattaRegistry, raceLogResolver);
663 664
}
664 665
665 666
@Override
666 667
public RaceTracker createRaceTracker(Regatta regatta, URL paramURL, URI liveURI, URI storedURI,
667 668
URI courseDesignUpdateURI, TimePoint startOfTracking, TimePoint endOfTracking, long delayToLiveInMillis,
668
- boolean simulateWithStartTimeNow, boolean useInternalMarkPassingAlgorithm, RaceLogStore raceLogStore,
669
+ Duration offsetToStartTimeOfSimulatedRace, boolean useInternalMarkPassingAlgorithm, RaceLogStore raceLogStore,
669 670
RegattaLogStore regattaLogStore, WindStore windStore, GPSFixStore gpsFixStore, String tracTracUsername,
670 671
String tracTracPassword, String raceStatus, String raceVisibility,
671 672
TrackedRegattaRegistry trackedRegattaRegistry, RaceLogResolver raceLogResolver)
672 673
throws MalformedURLException, FileNotFoundException, URISyntaxException, CreateModelException,
673 674
SubscriberInitializationException {
674 675
return new TracTracRaceTrackerImpl(regatta, this, paramURL, liveURI, storedURI, courseDesignUpdateURI,
675
- startOfTracking, endOfTracking, delayToLiveInMillis, simulateWithStartTimeNow, useInternalMarkPassingAlgorithm, raceLogStore,
676
+ startOfTracking, endOfTracking, delayToLiveInMillis, offsetToStartTimeOfSimulatedRace, useInternalMarkPassingAlgorithm, raceLogStore,
676 677
regattaLogStore, windStore, gpsFixStore, tracTracUsername, tracTracPassword, raceStatus,
677 678
raceVisibility, trackedRegattaRegistry, raceLogResolver);
678 679
}
... ...
@@ -690,11 +691,11 @@ public class DomainFactoryImpl implements DomainFactory {
690 691
@Override
691 692
public RaceTrackingConnectivityParameters createTrackingConnectivityParameters(URL paramURL, URI liveURI,
692 693
URI storedURI, URI courseDesignUpdateURI, TimePoint startOfTracking, TimePoint endOfTracking,
693
- long delayToLiveInMillis, boolean simulateWithStartTimeNow, boolean useInternalMarkPassingAlgorithm, RaceLogStore raceLogStore,
694
+ long delayToLiveInMillis, Duration offsetToStartTimeOfSimulatedRace, boolean useInternalMarkPassingAlgorithm, RaceLogStore raceLogStore,
694 695
RegattaLogStore regattaLogStore, String tracTracUsername, String tracTracPassword, String raceStatus,
695 696
String raceVisibility) {
696 697
return new RaceTrackingConnectivityParametersImpl(paramURL, liveURI, storedURI, courseDesignUpdateURI,
697
- startOfTracking, endOfTracking, delayToLiveInMillis, simulateWithStartTimeNow, useInternalMarkPassingAlgorithm, raceLogStore,
698
+ startOfTracking, endOfTracking, delayToLiveInMillis, offsetToStartTimeOfSimulatedRace, useInternalMarkPassingAlgorithm, raceLogStore,
698 699
regattaLogStore, this, tracTracUsername, tracTracPassword, raceStatus, raceVisibility);
699 700
}
700 701
java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/RaceTrackingConnectivityParametersImpl.java
... ...
@@ -16,6 +16,7 @@ import com.sap.sailing.domain.tracking.RaceTrackingConnectivityParameters;
16 16
import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
17 17
import com.sap.sailing.domain.tracking.WindStore;
18 18
import com.sap.sailing.domain.tractracadapter.DomainFactory;
19
+import com.sap.sse.common.Duration;
19 20
import com.sap.sse.common.TimePoint;
20 21
import com.tractrac.model.lib.api.event.CreateModelException;
21 22
import com.tractrac.subscription.lib.api.SubscriberInitializationException;
... ...
@@ -31,7 +32,7 @@ public class RaceTrackingConnectivityParametersImpl implements RaceTrackingConne
31 32
private final RegattaLogStore regattaLogStore;
32 33
private final DomainFactory domainFactory;
33 34
private final long delayToLiveInMillis;
34
- private final boolean simulateWithStartTimeNow;
35
+ private final Duration offsetToStartTimeOfSimulatedRace;
35 36
private final String tracTracUsername;
36 37
private final String tracTracPassword;
37 38
private final String raceStatus;
... ...
@@ -40,7 +41,7 @@ public class RaceTrackingConnectivityParametersImpl implements RaceTrackingConne
40 41
41 42
public RaceTrackingConnectivityParametersImpl(URL paramURL, URI liveURI, URI storedURI, URI courseDesignUpdateURI,
42 43
TimePoint startOfTracking, TimePoint endOfTracking, long delayToLiveInMillis,
43
- boolean simulateWithStartTimeNow, boolean useInternalMarkPassingAlgorithm, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore,
44
+ Duration offsetToStartTimeOfSimulatedRace, boolean useInternalMarkPassingAlgorithm, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore,
44 45
DomainFactory domainFactory, String tracTracUsername, String tracTracPassword, String raceStatus,
45 46
String raceVisibility) {
46 47
super();
... ...
@@ -52,7 +53,7 @@ public class RaceTrackingConnectivityParametersImpl implements RaceTrackingConne
52 53
this.endOfTracking = endOfTracking;
53 54
this.delayToLiveInMillis = delayToLiveInMillis;
54 55
this.domainFactory = domainFactory;
55
- this.simulateWithStartTimeNow = simulateWithStartTimeNow;
56
+ this.offsetToStartTimeOfSimulatedRace = offsetToStartTimeOfSimulatedRace;
56 57
this.raceLogStore = raceLogStore;
57 58
this.regattaLogStore = regattaLogStore;
58 59
this.tracTracUsername = tracTracUsername;
... ...
@@ -67,7 +68,7 @@ public class RaceTrackingConnectivityParametersImpl implements RaceTrackingConne
67 68
GPSFixStore gpsFixStore, RaceLogResolver raceLogResolver) throws MalformedURLException, FileNotFoundException, URISyntaxException,
68 69
CreateModelException, SubscriberInitializationException {
69 70
RaceTracker tracker = domainFactory.createRaceTracker(paramURL, liveURI, storedURI, courseDesignUpdateURI,
70
- startOfTracking, endOfTracking, delayToLiveInMillis, simulateWithStartTimeNow, useInternalMarkPassingAlgorithm, raceLogStore,
71
+ startOfTracking, endOfTracking, delayToLiveInMillis, offsetToStartTimeOfSimulatedRace, useInternalMarkPassingAlgorithm, raceLogStore,
71 72
regattaLogStore, windStore, gpsFixStore, tracTracUsername, tracTracPassword, raceStatus,
72 73
raceVisibility, trackedRegattaRegistry, raceLogResolver);
73 74
return tracker;
... ...
@@ -77,7 +78,7 @@ public class RaceTrackingConnectivityParametersImpl implements RaceTrackingConne
77 78
public RaceTracker createRaceTracker(Regatta regatta, TrackedRegattaRegistry trackedRegattaRegistry,
78 79
WindStore windStore, GPSFixStore gpsFixStore, RaceLogResolver raceLogResolver) throws Exception {
79 80
RaceTracker tracker = domainFactory.createRaceTracker(regatta, paramURL, liveURI, storedURI,
80
- courseDesignUpdateURI, startOfTracking, endOfTracking, delayToLiveInMillis, simulateWithStartTimeNow, useInternalMarkPassingAlgorithm,
81
+ courseDesignUpdateURI, startOfTracking, endOfTracking, delayToLiveInMillis, offsetToStartTimeOfSimulatedRace, useInternalMarkPassingAlgorithm,
81 82
raceLogStore, regattaLogStore, windStore, gpsFixStore, tracTracUsername, tracTracPassword, raceStatus,
82 83
raceVisibility, trackedRegattaRegistry, raceLogResolver);
83 84
return tracker;
java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/RawPositionReceiver.java
... ...
@@ -4,7 +4,6 @@ import java.util.logging.Logger;
4 4
5 5
import com.sap.sailing.domain.base.Competitor;
6 6
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
7
-import com.sap.sailing.domain.common.tracking.impl.GPSFixMovingImpl;
8 7
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
9 8
import com.sap.sailing.domain.tracking.DynamicTrackedRegatta;
10 9
import com.sap.sailing.domain.tractracadapter.DomainFactory;
... ...
@@ -57,13 +56,13 @@ public class RawPositionReceiver extends AbstractReceiverWithQueue<IRaceCompetit
57 56
IRace race = event.getA().getRace();
58 57
DynamicTrackedRace trackedRace = getTrackedRace(race);
59 58
if (trackedRace != null) {
60
- GPSFixMoving fix = getDomainFactory().createGPSFixMoving(event.getB());
59
+ final GPSFixMoving fix = getDomainFactory().createGPSFixMoving(event.getB());
60
+ Competitor competitor = getDomainFactory().getOrCreateCompetitor(event.getA().getCompetitor());
61 61
if (getSimulator() != null) {
62
- fix = new GPSFixMovingImpl(fix.getPosition(), getSimulator().delay(
63
- fix.getTimePoint()), fix.getSpeed());
62
+ getSimulator().scheduleCompetitorPosition(competitor, fix);
63
+ } else {
64
+ trackedRace.recordFix(competitor, fix);
64 65
}
65
- Competitor competitor = getDomainFactory().getOrCreateCompetitor(event.getA().getCompetitor());
66
- trackedRace.recordFix(competitor, fix);
67 66
} else {
68 67
logger.warning("Couldn't find tracked race for race " + race.getName()
69 68
+ ". Dropping raw position event " + event);
java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/Simulator.java
... ...
@@ -24,7 +24,9 @@ import com.sap.sailing.domain.tracking.WindStore;
24 24
import com.sap.sailing.domain.tracking.WindTrack;
25 25
import com.sap.sailing.domain.tracking.impl.EmptyWindStore;
26 26
import com.sap.sailing.domain.tracking.impl.MarkPassingImpl;
27
+import com.sap.sse.common.Duration;
27 28
import com.sap.sse.common.TimePoint;
29
+import com.sap.sse.common.impl.MillisecondsDurationImpl;
28 30
import com.sap.sse.common.impl.MillisecondsTimePoint;
29 31
30 32
public class Simulator {
... ...
@@ -33,13 +35,15 @@ public class Simulator {
33 35
private DynamicTrackedRace trackedRace;
34 36
private final WindStore windStore;
35 37
private boolean stopped;
36
- private long advanceInMillis = -1;
38
+ private Duration advanceInMillis = Duration.NULL.minus(1);
37 39
private Timer timer = new Timer("Timer for TracTrac Simulator");
40
+ private final Duration offsetToStart;
38 41
39
- public Simulator(WindStore windStore) {
42
+ public Simulator(WindStore windStore, Duration offsetToStart) {
40 43
super();
41 44
assert windStore != null;
42 45
this.windStore = windStore;
46
+ this.offsetToStart = offsetToStart;
43 47
}
44 48
45 49
/**
... ...
@@ -69,7 +73,7 @@ public class Simulator {
69 73
/**
70 74
* This is what everybody is waiting for :-). Notifies all waiters.
71 75
*/
72
- public synchronized void setAdvanceInMillis(long advanceInMillis) {
76
+ public synchronized void setAdvanceInMillis(Duration advanceInMillis) {
73 77
this.advanceInMillis = advanceInMillis;
74 78
notifyAll();
75 79
}
... ...
@@ -114,7 +118,7 @@ public class Simulator {
114 118
* Waits until {@link #advanceInMillis} is set to something not equal to -1 which is its initial value. Unblocked by
115 119
* {@link #setAdvanceInMillis(long)}.
116 120
*/
117
- private synchronized long getAdvanceInMillis() {
121
+ private synchronized Duration getAdvance() {
118 122
while (!isAdvanceInMillisSet()) {
119 123
try {
120 124
wait(2000); // wait for two seconds, then re-evaluate whether there is a start time
... ...
@@ -125,6 +129,16 @@ public class Simulator {
125 129
}
126 130
return advanceInMillis;
127 131
}
132
+
133
+ private Duration getOffsetToStart() {
134
+ final Duration result;
135
+ if (offsetToStart != null) {
136
+ result = offsetToStart;
137
+ } else {
138
+ result = Duration.NULL;
139
+ }
140
+ return result;
141
+ }
128 142
129 143
/**
130 144
* Transforms <code>timed</code>'s time point according to this simulator's delay and waits roughly until this time
... ...
@@ -157,12 +171,12 @@ public class Simulator {
157 171
* Like {@link #delay}, only that it doesn't wait until <code>timePoint</code> is reached in wall time.
158 172
*/
159 173
public TimePoint advance(TimePoint timePoint) {
160
- return new MillisecondsTimePoint(timePoint.asMillis()+getAdvanceInMillis());
174
+ return timePoint.plus(getAdvance());
161 175
}
162 176
163 177
/**
164 178
* If {@link #advanceInMillis} is already set to a non-negative value, it is left alone, and
165
- * {@link #delay(TimePoint)} is called. Otherwise, <code>time</code> is taken to be the original start time of the
179
+ * {@link #advance(TimePoint)} is called. Otherwise, <code>time</code> is taken to be the original start time of the
166 180
* race which is then used to compute {@link #advanceInMillis} such that
167 181
* <code>time.asMillis() + advanceInMillis == System.currentTimeMillis()</code>.
168 182
*/
... ...
@@ -183,13 +197,13 @@ public class Simulator {
183 197
if (isAdvanceInMillisSet()) {
184 198
return advance(time);
185 199
} else {
186
- setAdvanceInMillis(System.currentTimeMillis() - time.asMillis());
200
+ setAdvanceInMillis(new MillisecondsDurationImpl(MillisecondsTimePoint.now().minus(time.asMillis()).plus(getOffsetToStart()).asMillis()));
187 201
return advance(time);
188 202
}
189 203
}
190 204
191 205
private boolean isAdvanceInMillisSet() {
192
- return advanceInMillis != -1;
206
+ return !advanceInMillis.equals(Duration.NULL.minus(1));
193 207
}
194 208
195 209
/**
... ...
@@ -238,21 +252,35 @@ public class Simulator {
238 252
// deliver an empty list now
239 253
trackedRace.updateMarkPassings(competitor, markPassings);
240 254
}
241
-
255
+ }
256
+
257
+ public void scheduleCompetitorPosition(final Competitor competitor, GPSFixMoving competitorFix) {
258
+ final RecordGPSFix<Competitor> recorder = (c, f)->trackedRace.recordFix(c, f);
259
+ scheduleFixRecording(competitor, competitorFix, recorder);
260
+ }
261
+
262
+ @FunctionalInterface
263
+ private static interface RecordGPSFix<T> {
264
+ void recordFix(T objectForFix, GPSFixMoving fix);
242 265
}
243 266
244 267
public void scheduleMarkPosition(final Mark mark, GPSFixMoving markFix) {
245
- final TimePoint transformedTimepoint = advance(markFix.getTimePoint());
246
- final GPSFixMoving transformedMarkFix = new GPSFixMovingImpl(markFix.getPosition(), transformedTimepoint, markFix.getSpeed());
268
+ final RecordGPSFix<Mark> recorder = (m, f)->trackedRace.recordFix(mark, f);
269
+ scheduleFixRecording(mark, markFix, recorder);
270
+ }
271
+
272
+ private <T> void scheduleFixRecording(final T object, GPSFixMoving fix, final RecordGPSFix<T> recorder) {
273
+ final TimePoint transformedTimepoint = advance(fix.getTimePoint());
274
+ final GPSFixMoving transformedMarkFix = new GPSFixMovingImpl(fix.getPosition(), transformedTimepoint, fix.getSpeed());
247 275
long waitTime = getWaitTimeInMillisUntil(transformedMarkFix.getTimePoint());
248 276
if (waitTime <= 0) {
249
- trackedRace.recordFix(mark, transformedMarkFix);
277
+ recorder.recordFix(object, transformedMarkFix);
250 278
} else {
251 279
timer.schedule(new TimerTask() {
252 280
@Override
253 281
public void run() {
254 282
try {
255
- trackedRace.recordFix(mark, transformedMarkFix);
283
+ recorder.recordFix(object, transformedMarkFix);
256 284
} catch (Exception e) {
257 285
logger.throwing(Simulator.class.getName(), "scheduleMarkPosition", e);
258 286
}
java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/TracTracAdapterImpl.java
... ...
@@ -21,6 +21,7 @@ import com.sap.sailing.domain.tractracadapter.JSONService;
21 21
import com.sap.sailing.domain.tractracadapter.RaceRecord;
22 22
import com.sap.sailing.domain.tractracadapter.TracTracAdapter;
23 23
import com.sap.sailing.domain.tractracadapter.TracTracConfiguration;
24
+import com.sap.sse.common.Duration;
24 25
import com.sap.sse.common.TimePoint;
25 26
import com.sap.sse.common.Util;
26 27
... ...
@@ -56,7 +57,7 @@ public class TracTracAdapterImpl implements TracTracAdapter {
56 57
getTracTracDomainFactory().createTrackingConnectivityParameters(paramURL, liveURI, storedURI,
57 58
courseDesignUpdateURI,
58 59
/* startOfTracking */null,
59
- /* endOfTracking */null, delayToLiveInMillis, /* simulateWithStartTimeNow */false, /* ignoreTracTracMarkPassings */ false,
60
+ /* endOfTracking */null, delayToLiveInMillis, /* offsetToStartTimeOfSimulatedRace */null, /* ignoreTracTracMarkPassings */ false,
60 61
raceLogStore, regattaLogStore, tracTracUsername, tracTracPassword, raceStatus, raceVisibility),
61 62
timeoutInMilliseconds);
62 63
}
... ...
@@ -65,13 +66,13 @@ public class TracTracAdapterImpl implements TracTracAdapter {
65 66
public RaceHandle addTracTracRace(TrackerManager trackerManager, RegattaIdentifier regattaToAddTo, URL paramURL,
66 67
URI liveURI, URI storedURI, URI courseDesignUpdateURI, TimePoint startOfTracking, TimePoint endOfTracking,
67 68
RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, long timeoutInMilliseconds,
68
- boolean simulateWithStartTimeNow, boolean useInternalMarkPassingAlgorithm, String tracTracUsername, String tracTracPassword, String raceStatus,
69
+ Duration offsetToStartTimeOfSimulatedRace, boolean useInternalMarkPassingAlgorithm, String tracTracUsername, String tracTracPassword, String raceStatus,
69 70
String raceVisibility) throws Exception {
70 71
return trackerManager.addRace(
71 72
regattaToAddTo,
72 73
getTracTracDomainFactory().createTrackingConnectivityParameters(paramURL, liveURI, storedURI,
73 74
courseDesignUpdateURI, startOfTracking, endOfTracking, delayToLiveInMillis,
74
- simulateWithStartTimeNow, useInternalMarkPassingAlgorithm, raceLogStore, regattaLogStore, tracTracUsername, tracTracPassword,
75
+ offsetToStartTimeOfSimulatedRace, useInternalMarkPassingAlgorithm, raceLogStore, regattaLogStore, tracTracUsername, tracTracPassword,
75 76
raceStatus, raceVisibility), timeoutInMilliseconds);
76 77
}
77 78
java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/TracTracRaceTrackerImpl.java
... ...
@@ -49,6 +49,7 @@ import com.sap.sailing.domain.tractracadapter.DomainFactory;
49 49
import com.sap.sailing.domain.tractracadapter.Receiver;
50 50
import com.sap.sailing.domain.tractracadapter.TracTracConnectionConstants;
51 51
import com.sap.sailing.domain.tractracadapter.TracTracRaceTracker;
52
+import com.sap.sse.common.Duration;
52 53
import com.sap.sse.common.TimePoint;
53 54
import com.sap.sse.common.Util;
54 55
import com.tractrac.model.lib.api.ModelLocator;
... ...
@@ -253,26 +254,26 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl implements
253 254
*/
254 255
protected TracTracRaceTrackerImpl(DomainFactory domainFactory, URL paramURL, URI liveURI, URI storedURI,
255 256
URI courseDesignUpdateURI, TimePoint startOfTracking, TimePoint endOfTracking, long delayToLiveInMillis,
256
- boolean simulateWithStartTimeNow, boolean useInternalMarkPassingAlgorithm, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore,
257
+ Duration offsetToStartTimeOfSimulatedRace, boolean useInternalMarkPassingAlgorithm, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore,
257 258
WindStore windStore, GPSFixStore gpsFixStore, String tracTracUsername, String tracTracPassword,
258 259
String raceStatus, String raceVisibility, TrackedRegattaRegistry trackedRegattaRegistry, RaceLogResolver raceLogResolver)
259 260
throws URISyntaxException, MalformedURLException, FileNotFoundException, CreateModelException,
260 261
SubscriberInitializationException {
261 262
this(ModelLocator.getEventFactory().createRace(new URI(paramURL.toString())), domainFactory, paramURL, liveURI,
262 263
storedURI, courseDesignUpdateURI, startOfTracking, endOfTracking, delayToLiveInMillis,
263
- simulateWithStartTimeNow, useInternalMarkPassingAlgorithm, raceLogStore, regattaLogStore, windStore, gpsFixStore, tracTracUsername,
264
+ offsetToStartTimeOfSimulatedRace, useInternalMarkPassingAlgorithm, raceLogStore, regattaLogStore, windStore, gpsFixStore, tracTracUsername,
264 265
tracTracPassword, raceStatus, raceVisibility, trackedRegattaRegistry, raceLogResolver);
265 266
}
266 267
267 268
private TracTracRaceTrackerImpl(IRace tractracRace, DomainFactory domainFactory, URL paramURL, URI liveURI,
268 269
URI storedURI, URI courseDesignUpdateURI, TimePoint startOfTracking, TimePoint endOfTracking,
269
- long delayToLiveInMillis, boolean simulateWithStartTimeNow, boolean useInternalMarkPassingAlgorithm, RaceLogStore raceLogStore,
270
+ long delayToLiveInMillis, Duration offsetToStartTimeOfSimulatedRace, boolean useInternalMarkPassingAlgorithm, RaceLogStore raceLogStore,
270 271
RegattaLogStore regattaLogStore, WindStore windStore, GPSFixStore gpsFixStore, String tracTracUsername,
271 272
String tracTracPassword, String raceStatus, String raceVisibility,
272 273
TrackedRegattaRegistry trackedRegattaRegistry, RaceLogResolver raceLogResolver) throws URISyntaxException, MalformedURLException,
273 274
FileNotFoundException, SubscriberInitializationException {
274 275
this(tractracRace, null, domainFactory, paramURL, liveURI, storedURI, courseDesignUpdateURI, startOfTracking,
275
- endOfTracking, delayToLiveInMillis, simulateWithStartTimeNow, useInternalMarkPassingAlgorithm, raceLogStore, regattaLogStore, windStore,
276
+ endOfTracking, delayToLiveInMillis, offsetToStartTimeOfSimulatedRace, useInternalMarkPassingAlgorithm, raceLogStore, regattaLogStore, windStore,
276 277
gpsFixStore, tracTracUsername, tracTracPassword, raceStatus, raceVisibility, trackedRegattaRegistry, raceLogResolver);
277 278
}
278 279
... ...
@@ -284,14 +285,14 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl implements
284 285
*/
285 286
protected TracTracRaceTrackerImpl(Regatta regatta, DomainFactory domainFactory, URL paramURL, URI liveURI,
286 287
URI storedURI, URI courseDesignUpdateURI, TimePoint startOfTracking, TimePoint endOfTracking,
287
- long delayToLiveInMillis, boolean simulateWithStartTimeNow, boolean ignoreTracTracMarkPassings, RaceLogStore raceLogStore,
288
+ long delayToLiveInMillis, Duration offsetToStartTimeOfSimulatedRace, boolean ignoreTracTracMarkPassings, RaceLogStore raceLogStore,
288 289
RegattaLogStore regattaLogStore, WindStore windStore, GPSFixStore gpsFixStore, String tracTracUsername,
289 290
String tracTracPassword, String raceStatus, String raceVisibility,
290 291
TrackedRegattaRegistry trackedRegattaRegistry, RaceLogResolver raceLogResolver) throws URISyntaxException,
291 292
MalformedURLException, FileNotFoundException, CreateModelException, SubscriberInitializationException {
292 293
this(ModelLocator.getEventFactory().createRace(new URI(paramURL.toString())), regatta, domainFactory, paramURL,
293 294
liveURI, storedURI, courseDesignUpdateURI, startOfTracking, endOfTracking, delayToLiveInMillis,
294
- simulateWithStartTimeNow, ignoreTracTracMarkPassings, raceLogStore, regattaLogStore, windStore, gpsFixStore, tracTracUsername,
295
+ offsetToStartTimeOfSimulatedRace, ignoreTracTracMarkPassings, raceLogStore, regattaLogStore, windStore, gpsFixStore, tracTracUsername,
295 296
tracTracPassword, raceStatus, raceVisibility, trackedRegattaRegistry, raceLogResolver);
296 297
}
297 298
... ...
@@ -300,15 +301,15 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl implements
300 301
* @param regatta
301 302
* if <code>null</code>, then <code>domainFactory.getOrCreateRegatta(tractracEvent)</code> will be used
302 303
* to obtain a default regatta
303
- * @param simulateWithStartTimeNow
304
- * if <code>true</code>, the connector will adjust the time stamps of all events received such that the
304
+ * @param offsetToStartTimeOfSimulatedRace
305
+ * if not <code>null</code>, the connector will adjust the time stamps of all events received such that the
305 306
* first mark passing for the first waypoint will be set to "now." It will delay the forwarding of all
306
- * events received such that they seem to be sent in "real-time." So, more or less the time points
307
+ * events received such that they seem to be sent in "real-time" + <code>offsetToStartTimeOfSimulatedRace</code> So, more or less the time points
307 308
* attached to the events sent to the receivers will again approximate the wall time.
308 309
*/
309 310
private TracTracRaceTrackerImpl(IRace tractracRace, final Regatta regatta, DomainFactory domainFactory,
310 311
URL paramURL, URI liveURI, URI storedURI, URI tracTracUpdateURI, TimePoint startOfTracking,
311
- TimePoint endOfTracking, long delayToLiveInMillis, boolean simulateWithStartTimeNow, boolean useInternalMarkPassingAlgorithm,
312
+ TimePoint endOfTracking, long delayToLiveInMillis, Duration offsetToStartTimeOfSimulatedRace, boolean useInternalMarkPassingAlgorithm,
312 313
RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, WindStore windStore, GPSFixStore gpsFixStore,
313 314
String tracTracUsername, String tracTracPassword, String raceStatus, String raceVisibility,
314 315
TrackedRegattaRegistry trackedRegattaRegistry, RaceLogResolver raceLogResolver) throws URISyntaxException, MalformedURLException,
... ...
@@ -323,8 +324,8 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl implements
323 324
this.gpsFixStore = gpsFixStore;
324 325
this.domainFactory = domainFactory;
325 326
this.lastProgressPerID = new HashMap<>();
326
- if (simulateWithStartTimeNow) {
327
- simulator = new Simulator(windStore);
327
+ if (offsetToStartTimeOfSimulatedRace != null) {
328
+ simulator = new Simulator(windStore, offsetToStartTimeOfSimulatedRace);
328 329
// don't write the transformed wind fixes into the DB again... see also bug 1974
329 330
this.windStore = EmptyWindStore.INSTANCE;
330 331
} else {
java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/Course.java
... ...
@@ -43,4 +43,6 @@ public interface Course extends CourseBase {
43 43
void update(Iterable<Util.Pair<ControlPoint, PassingInstruction>> newControlPoints, DomainFactory baseDomainFactory) throws PatchFailedException;
44 44
45 45
int getNumberOfWaypoints();
46
+
47
+ Leg getLeg(int zeroBasedIndexOfWaypoint);
46 48
}
java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/CourseImpl.java
... ...
@@ -247,6 +247,11 @@ public class CourseImpl extends NamedImpl implements Course {
247 247
}
248 248
249 249
@Override
250
+ public Leg getLeg(int zeroBasedIndexOfWaypoint) {
251
+ return legs.get(zeroBasedIndexOfWaypoint);
252
+ }
253
+
254
+ @Override
250 255
public String toString() {
251 256
lockForRead();
252 257
try {
java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractSimpleLeaderboardImpl.java
... ...
@@ -1,1907 +1,1937 @@
1
-package com.sap.sailing.domain.leaderboard.impl;
2
-
3
-import java.io.IOException;
4
-import java.io.ObjectInputStream;
5
-import java.util.ArrayList;
6
-import java.util.Collection;
7
-import java.util.Collections;
8
-import java.util.Comparator;
9
-import java.util.Date;
10
-import java.util.HashMap;
11
-import java.util.HashSet;
12
-import java.util.Iterator;
13
-import java.util.LinkedHashMap;
14
-import java.util.List;
15
-import java.util.Map;
16
-import java.util.NavigableSet;
17
-import java.util.NoSuchElementException;
18
-import java.util.Set;
19
-import java.util.UUID;
20
-import java.util.concurrent.Callable;
21
-import java.util.concurrent.ExecutionException;
22
-import java.util.concurrent.Executor;
23
-import java.util.concurrent.Executors;
24
-import java.util.concurrent.Future;
25
-import java.util.concurrent.FutureTask;
26
-import java.util.concurrent.LinkedBlockingQueue;
27
-import java.util.concurrent.RunnableFuture;
28
-import java.util.concurrent.ThreadPoolExecutor;
29
-import java.util.concurrent.TimeUnit;
30
-import java.util.logging.Level;
31
-import java.util.logging.Logger;
32
-
33
-import com.sap.sailing.domain.abstractlog.race.InvalidatesLeaderboardCache;
34
-import com.sap.sailing.domain.abstractlog.race.RaceLogEvent;
35
-import com.sap.sailing.domain.base.Competitor;
36
-import com.sap.sailing.domain.base.Course;
37
-import com.sap.sailing.domain.base.DomainFactory;
38
-import com.sap.sailing.domain.base.Fleet;
39
-import com.sap.sailing.domain.base.Leg;
40
-import com.sap.sailing.domain.base.RaceColumn;
41
-import com.sap.sailing.domain.base.RaceColumnInSeries;
42
-import com.sap.sailing.domain.base.RaceColumnListener;
43
-import com.sap.sailing.domain.base.Waypoint;
44
-import com.sap.sailing.domain.common.Distance;
45
-import com.sap.sailing.domain.common.LegType;
46
-import com.sap.sailing.domain.common.ManeuverType;
47
-import com.sap.sailing.domain.common.MaxPointsReason;
48
-import com.sap.sailing.domain.common.NoWindError;
49
-import com.sap.sailing.domain.common.NoWindException;
50
-import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
51
-import com.sap.sailing.domain.common.RegattaNameAndRaceName;
52
-import com.sap.sailing.domain.common.Speed;
53
-import com.sap.sailing.domain.common.SpeedWithBearing;
54
-import com.sap.sailing.domain.common.dto.CompetitorDTO;
55
-import com.sap.sailing.domain.common.dto.FleetDTO;
56
-import com.sap.sailing.domain.common.dto.LeaderboardDTO;
57
-import com.sap.sailing.domain.common.dto.LeaderboardEntryDTO;
58
-import com.sap.sailing.domain.common.dto.LeaderboardRowDTO;
59
-import com.sap.sailing.domain.common.dto.LegEntryDTO;
60
-import com.sap.sailing.domain.common.dto.RaceDTO;
61
-import com.sap.sailing.domain.common.tracking.GPSFixMoving;
62
-import com.sap.sailing.domain.leaderboard.Leaderboard;
63
-import com.sap.sailing.domain.leaderboard.NumberOfCompetitorsInLeaderboardFetcher;
64
-import com.sap.sailing.domain.leaderboard.ResultDiscardingRule;
65
-import com.sap.sailing.domain.leaderboard.ScoreCorrection.Result;
66
-import com.sap.sailing.domain.leaderboard.SettableScoreCorrection;
67
-import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule;
68
-import com.sap.sailing.domain.leaderboard.caching.LeaderboardDTOCache;
69
-import com.sap.sailing.domain.leaderboard.caching.LeaderboardDTOCalculationReuseCache;
70
-import com.sap.sailing.domain.leaderboard.caching.LiveLeaderboardUpdater;
71
-import com.sap.sailing.domain.racelog.RaceLogIdentifier;
72
-import com.sap.sailing.domain.ranking.RankingMetric.CompetitorRankingInfo;
73
-import com.sap.sailing.domain.ranking.RankingMetric.RankingInfo;
74
-import com.sap.sailing.domain.tracking.GPSFixTrack;
75
-import com.sap.sailing.domain.tracking.Maneuver;
76
-import com.sap.sailing.domain.tracking.MarkPassing;
77
-import com.sap.sailing.domain.tracking.MarkPassingManeuver;
78
-import com.sap.sailing.domain.tracking.RaceChangeListener;
79
-import com.sap.sailing.domain.tracking.TrackedLeg;
80
-import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor;
81
-import com.sap.sailing.domain.tracking.TrackedRace;
82
-import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
83
-import com.sap.sailing.domain.tracking.WindLegTypeAndLegBearingCache;
84
-import com.sap.sailing.domain.tracking.WindPositionMode;
85
-import com.sap.sailing.domain.tracking.impl.AbstractRaceChangeListener;
86
-import com.sap.sailing.util.impl.RaceColumnListeners;
87
-import com.sap.sse.common.Duration;
88
-import com.sap.sse.common.TimePoint;
89
-import com.sap.sse.common.Util;
90
-import com.sap.sse.common.impl.MillisecondsTimePoint;
91
-import com.sap.sse.concurrent.LockUtil;
92
-import com.sap.sse.concurrent.NamedReentrantReadWriteLock;
93
-import com.sap.sse.util.impl.ThreadFactoryWithPriority;
94
-
95
-/**
96
- * Base implementation for various types of leaderboards. The {@link RaceColumnListener} implementation forwards events
97
- * received to all {@link RaceColumnListener} subscribed with this leaderboard. To which objects this leaderboard subscribes
98
- * as {@link RaceColumnListener} is left to the concrete subclasses to implement, but the race columns seem like useful
99
- * candidates.
100
- *
101
- * @author Axel Uhl (d043530)
102
- *
103
- */
104
-public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, RaceColumnListener {
105
- private static final long serialVersionUID = 330156778603279333L;
106
-
107
- private static final Logger logger = Logger.getLogger(AbstractSimpleLeaderboardImpl.class.getName());
108
-
109
- static final Double DOUBLE_0 = new Double(0);
110
-
111
- private final SettableScoreCorrection scoreCorrection;
112
-
113
- private ThresholdBasedResultDiscardingRule crossLeaderboardResultDiscardingRule;
114
-
115
- /**
116
- * The optional display name mappings for competitors. This allows a user to override the tracking-provided
117
- * competitor names for display in a leaderboard.
118
- */
119
- private final Map<Competitor, String> displayNames;
120
-
121
- /** the display name of the leaderboard */
122
- private String displayName;
123
-
124
- /**
125
- * Backs the {@link #getCarriedPoints(Competitor)} API with data. Can be used to prime this leaderboard
126
- * with aggregated results of races not tracked / displayed by this leaderboard in detail. The points
127
- * provided by this map are considered by {@link #getTotalPoints(Competitor, TimePoint)}.
128
- */
129
- private final Map<Competitor, Double> carriedPoints;
130
-
131
- private final RaceColumnListeners raceColumnListeners;
132
-
133
- /**
134
- * A set that manages the difference between {@link #getCompetitors()} and {@link #getAllCompetitors()}. Access
135
- * is controlled by the {@link #suppressedCompetitorsLock} lock.
136
- */
137
- private final Set<Competitor> suppressedCompetitors;
138
- private final NamedReentrantReadWriteLock suppressedCompetitorsLock;
139
-
140
- private transient Map<com.sap.sse.common.Util.Pair<TrackedRace, Competitor>, RunnableFuture<RaceDetails>> raceDetailsAtEndOfTrackingCache;
141
-
142
- /**
143
- * This executor needs to be a different one than {@link #executor} because the tasks run by {@link #executor}
144
- * can depend on the results of the tasks run by {@link #raceDetailsExecutor}, and an {@link Executor} doesn't
145
- * move a task that is blocked by waiting for another {@link FutureTask} to the side but blocks permanently,
146
- * ending in a deadlock (one that cannot easily be detected by the Eclipse debugger either).
147
- */
148
- private transient Executor raceDetailsExecutor;
149
-
150
- /**
151
- * Used to remove all these listeners from their tracked races when this servlet is {@link #destroy() destroyed}.
152
- */
153
- private transient Set<CacheInvalidationListener> cacheInvalidationListeners;
154
-
155
- private transient ThreadPoolExecutor executor;
156
-
157
- private transient LiveLeaderboardUpdater liveLeaderboardUpdater;
158
-
159
- private transient LeaderboardDTOCache leaderboardDTOCache;
160
-
161
- /**
162
- * A leaderboard entry representing a snapshot of a cell at a given time point for a single race/competitor.
163
- *
164
- * @author Axel Uhl (d043530)
165
- *
166
- */
167
- class EntryImpl implements Entry {
168
- private final Callable<Integer> trackedRankProvider;
169
- private final Double netPoints;
170
- private final Callable<Double> netPointsUncorrectedProvider;
171
- private final boolean isNetPointsCorrected;
172
- private final Double totalPoints;
173
- private final MaxPointsReason maxPointsReason;
174
- private final boolean discarded;
175
- private final Fleet fleet;
176
-
177
- private EntryImpl(Callable<Integer> trackedRankProvider, Double netPoints,
178
- Callable<Double> netPointsUncorrectedProvider, boolean isNetPointsCorrected,
179
- Double totalPoints, MaxPointsReason maxPointsReason, boolean discarded, Fleet fleet) {
180
- super();
181
- this.trackedRankProvider = trackedRankProvider;
182
- this.netPoints = netPoints;
183
- this.netPointsUncorrectedProvider = netPointsUncorrectedProvider;
184
- this.isNetPointsCorrected = isNetPointsCorrected;
185
- this.totalPoints = totalPoints;
186
- this.maxPointsReason = maxPointsReason;
187
- this.discarded = discarded;
188
- this.fleet = fleet;
189
- }
190
- @Override
191
- public int getTrackedRank() {
192
- try {
193
- return trackedRankProvider.call();
194
- } catch (Exception e) {
195
- throw new RuntimeException(e);
196
- }
197
- }
198
- @Override
199
- public Double getNetPoints() {
200
- return netPoints;
201
- }
202
- @Override
203
- public boolean isNetPointsCorrected() {
204
- return isNetPointsCorrected;
205
- }
206
- @Override
207
- public Double getTotalPoints() {
208
- return totalPoints;
209
- }
210
- @Override
211
- public MaxPointsReason getMaxPointsReason() {
212
- return maxPointsReason;
213
- }
214
- @Override
215
- public boolean isDiscarded() {
216
- return discarded;
217
- }
218
- @Override
219
- public Fleet getFleet() {
220
- return fleet;
221
- }
222
- @Override
223
- public Double getNetPointsUncorrected() {
224
- try {
225
- return netPointsUncorrectedProvider.call();
226
- } catch (Exception e) {
227
- throw new RuntimeException(e);
228
- }
229
- }
230
- }
231
-
232
- /**
233
- * Computing the competitors can be a bit expensive, particularly if the fleet is large and there may be suppressed
234
- * competitors, and the leaderboard may be a meta-leaderboard that refers to other leaderboards which each have
235
- * several tracked races attached from where the competitors need to be retrieved. Ideally, the competitors list
236
- * would be cached, but that is again difficult because we would have to monitor all changes in all dependent
237
- * leaderboards and columns and tracked races properly.
238
- * <p>
239
- *
240
- * As it turns out, one of the most frequent uses of the {@link AbstractSimpleLeaderboardImpl#getCompetitors}
241
- * competitors list is to determine their number which in turn is only required for high-point scoring systems and
242
- * for computing the default score for penalties. Again, the most frequently used low-point family of scoring schemes
243
- * does not require this number. Yet, the scoring scheme requires an argument for polymorphic use by those that
244
- * need it. Instead of computing it for each call, this interface lets us defer the actual calculation until the
245
- * point when it's really needed. Once asked, this object will cache the result. Therefore, a new one should be
246
- * constructed each time the number shall be computed.
247
- *
248
- * @author Axel Uhl (D043530)
249
- *
250
- */
251
- public class NumberOfCompetitorsFetcherImpl implements NumberOfCompetitorsInLeaderboardFetcher {
252
- private int numberOfCompetitors = -1;
253
-
254
- @Override
255
- public int getNumberOfCompetitorsInLeaderboard() {
256
- if (numberOfCompetitors == -1) {
257
- numberOfCompetitors = Util.size(getCompetitors());
258
- }
259
- return numberOfCompetitors;
260
- }
261
- }
262
-
263
- /**
264
- * Handles the invalidation of the {@link SailingServiceImpl#raceDetailsAtEndOfTrackingCache} entries if the tracked
265
- * race changes in any way. In particular, for {@link #statusChanged}, when the status changes away from LOADING,
266
- * calculations may start or resume, making it necessary to clear the cache.
267
- *
268
- * @author Axel Uhl (D043530)
269
- *
270
- */
271
- private class CacheInvalidationListener extends AbstractRaceChangeListener {
272
- private final TrackedRace trackedRace;
273
- private final Competitor competitor;
274
-
275
- public CacheInvalidationListener(TrackedRace trackedRace, Competitor competitor) {
276
- this.trackedRace = trackedRace;
277
- this.competitor = competitor;
278
- }
279
-
280
- public TrackedRace getTrackedRace() {
281
- return trackedRace;
282
- }
283
-
284
- public void removeFromTrackedRace() {
285
- trackedRace.removeListener(this);
286
- }
287
-
288
- private void invalidateCacheAndRemoveThisListenerFromTrackedRace() {
289
- synchronized (raceDetailsAtEndOfTrackingCache) {
290
- raceDetailsAtEndOfTrackingCache.remove(new com.sap.sse.common.Util.Pair<TrackedRace, Competitor>(trackedRace, competitor));
291
- removeFromTrackedRace();
292
- }
293
- }
294
-
295
- @Override
296
- protected void defaultAction() {
297
- invalidateCacheAndRemoveThisListenerFromTrackedRace();
298
- }
299
- }
300
-
301
- private static class UUIDGenerator implements LeaderboardDTO.UUIDGenerator {
302
- @Override
303
- public String generateRandomUUID() {
304
- return UUID.randomUUID().toString();
305
- }
306
- }
307
-
308
- public AbstractSimpleLeaderboardImpl(ThresholdBasedResultDiscardingRule resultDiscardingRule) {
309
- this.carriedPoints = new HashMap<Competitor, Double>();
310
- this.scoreCorrection = createScoreCorrection();
311
- this.displayNames = new HashMap<Competitor, String>();
312
- this.crossLeaderboardResultDiscardingRule = resultDiscardingRule;
313
- this.suppressedCompetitors = new HashSet<Competitor>();
314
- this.suppressedCompetitorsLock = new NamedReentrantReadWriteLock("suppressedCompetitorsLock", /* fair */ false);
315
- this.raceColumnListeners = new RaceColumnListeners();
316
- this.raceDetailsAtEndOfTrackingCache = new HashMap<com.sap.sse.common.Util.Pair<TrackedRace, Competitor>, RunnableFuture<RaceDetails>>();
317
- initTransientFields();
318
- }
319
-
320
- /**
321
- * Produces the score correction object to use in this leaderboard. Used by the constructor. Subclasses may override
322
- * this method to create a more specific type of score correction. This implementation produces an object of type
323
- * {@link ScoreCorrectionImpl}.
324
- */
325
- protected SettableScoreCorrection createScoreCorrection() {
326
- return new ScoreCorrectionImpl(this);
327
- }
328
-
329
- private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException {
330
- ois.defaultReadObject();
331
- initTransientFields();
332
- }
333
-
334
- private void initTransientFields() {
335
- this.raceDetailsAtEndOfTrackingCache = new HashMap<com.sap.sse.common.Util.Pair<TrackedRace,Competitor>, RunnableFuture<RaceDetails>>();
336
- this.raceDetailsExecutor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
337
- this.cacheInvalidationListeners = new HashSet<CacheInvalidationListener>();
338
- // When many updates are triggered in a short period of time by a single thread, ensure that the single thread
339
- // providing the updates is not outperformed by all the re-calculations happening here. Leave at least one
340
- // core to other things, but by using at least three threads ensure that no simplistic deadlocks may occur.
341
- final int THREAD_POOL_SIZE = Math.max(Runtime.getRuntime().availableProcessors(), 3);
342
- executor = new ThreadPoolExecutor(/* corePoolSize */ THREAD_POOL_SIZE,
343
- /* maximumPoolSize */ THREAD_POOL_SIZE,
344
- /* keepAliveTime */ 60, TimeUnit.SECONDS,
345
- /* workQueue */ new LinkedBlockingQueue<Runnable>(), new ThreadFactoryWithPriority(Thread.NORM_PRIORITY-1, /* daemon */ true));
346
- }
347
-
348
- @Override
349
- public void destroy() {
350
- for (CacheInvalidationListener cacheInvalidationListener : cacheInvalidationListeners) {
351
- cacheInvalidationListener.removeFromTrackedRace();
352
- }
353
- }
354
-
355
- @Override
356
- public SettableScoreCorrection getScoreCorrection() {
357
- return scoreCorrection;
358
- }
359
-
360
- @Override
361
- public String getDisplayName(Competitor competitor) {
362
- return displayNames.get(competitor);
363
- }
364
-
365
- @Override
366
- public String getDisplayName() {
367
- return displayName;
368
- }
369
-
370
- @Override
371
- public void setDisplayName(String displayName) {
372
- this.displayName = displayName;
373
- }
374
-
375
- @Override
376
- public ResultDiscardingRule getResultDiscardingRule() {
377
- return crossLeaderboardResultDiscardingRule;
378
- }
379
-
380
- @Override
381
- public void setCarriedPoints(Competitor competitor, double carriedPoints) {
382
- Double oldCarriedPoints = this.carriedPoints.put(competitor, carriedPoints);
383
- if (!Util.equalsWithNull(oldCarriedPoints, carriedPoints)) {
384
- getScoreCorrection().notifyListenersAboutCarriedPointsChange(competitor, oldCarriedPoints, carriedPoints);
385
- }
386
- }
387
-
388
- @Override
389
- public double getCarriedPoints(Competitor competitor) {
390
- Double result = carriedPoints.get(competitor);
391
- return result == null ? 0 : result;
392
- }
393
-
394
- @Override
395
- public Map<Competitor, Double> getCompetitorsForWhichThereAreCarriedPoints() {
396
- return Collections.unmodifiableMap(carriedPoints);
397
- }
398
-
399
- @Override
400
- public void unsetCarriedPoints(Competitor competitor) {
401
- Double oldCarriedPoints = carriedPoints.remove(competitor);
402
- if (oldCarriedPoints != null) {
403
- getScoreCorrection().notifyListenersAboutCarriedPointsChange(competitor, oldCarriedPoints, null);
404
- }
405
- }
406
-
407
- @Override
408
- public boolean hasCarriedPoints() {
409
- return !carriedPoints.isEmpty();
410
- }
411
-
412
- @Override
413
- public boolean hasCarriedPoints(Competitor competitor) {
414
- return carriedPoints.containsKey(competitor);
415
- }
416
-
417
- @Override
418
- public void setDisplayName(Competitor competitor, String displayName) {
419
- String oldDisplayName = displayNames.get(competitor);
420
- displayNames.put(competitor, displayName);
421
- if (!Util.equalsWithNull(oldDisplayName, displayName)) {
422
- getRaceColumnListeners().notifyListenersAboutCompetitorDisplayNameChanged(competitor, oldDisplayName, displayName);
423
- }
424
- }
425
-
426
- @Override
427
- public void setCrossLeaderboardResultDiscardingRule(ThresholdBasedResultDiscardingRule discardingRule) {
428
- ResultDiscardingRule oldDiscardingRule = getResultDiscardingRule();
429
- this.crossLeaderboardResultDiscardingRule = discardingRule;
430
- getRaceColumnListeners().notifyListenersAboutResultDiscardingRuleChanged(oldDiscardingRule, discardingRule);
431
- }
432
-
433
- @Override
434
- public Double getNetPoints(final Competitor competitor, final RaceColumn raceColumn, final TimePoint timePoint) {
435
- return getScoreCorrection().getCorrectedScore(
436
- new Callable<Integer>() {
437
- public Integer call() throws NoWindException {
438
- return getTrackedRank(competitor, raceColumn, timePoint);
439
- }
440
- }, competitor,
441
- raceColumn, timePoint, new NumberOfCompetitorsFetcherImpl(), getScoringScheme()).getCorrectedScore();
442
- }
443
-
444
- @Override
445
- public MaxPointsReason getMaxPointsReason(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint) {
446
- return getScoreCorrection().getMaxPointsReason(competitor, raceColumn, timePoint);
447
- }
448
-
449
- @Override
450
- public boolean isDiscarded(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint) {
451
- return isDiscarded(competitor, raceColumn, getRaceColumns(), timePoint);
452
- }
453
-
454
- private boolean isDiscarded(Competitor competitor, RaceColumn raceColumn,
455
- Iterable<RaceColumn> raceColumnsToConsider, TimePoint timePoint) {
456
- final Set<RaceColumn> discardedRaceColumns = getResultDiscardingRule()
457
- .getDiscardedRaceColumns(competitor, this, raceColumnsToConsider, timePoint);
458
- return isDiscarded(competitor, raceColumn, timePoint, discardedRaceColumns);
459
- }
460
-
461
- /**
462
- * Same as {@link #isDiscarded(Competitor, RaceColumn, TimePoint)}, only that the set of discarded race columns can
463
- * be specified which is useful when total points are to be computed for more than one column for the same
464
- * competitor because then the calculation of discards (which requires looking at all columns) only needs to be done
465
- * once and not again for each column (which would lead to quadratic effort).
466
- *
467
- * @param discardedRaceColumns
468
- * expected to be the result of what we would get if we called {@link #getResultDiscardingRule()}.
469
- * {@link ResultDiscardingRule#getDiscardedRaceColumns(Competitor, Leaderboard, Iterable, TimePoint)
470
- * getDiscardedRaceColumns(competitor, this, raceColumnsToConsider, timePoint)}.
471
- */
472
- private boolean isDiscarded(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint,
473
- final Set<RaceColumn> discardedRaceColumns) {
474
- return !raceColumn.isMedalRace()
475
- && getMaxPointsReason(competitor, raceColumn, timePoint).isDiscardable()
476
- && discardedRaceColumns.contains(raceColumn);
477
- }
478
-
479
- @Override
480
- public Double getTotalPoints(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint) throws NoWindException {
481
- return getTotalPoints(competitor, raceColumn, getRaceColumns(), timePoint);
482
- }
483
-
484
- @Override
485
- public Double getTotalPoints(Competitor competitor, RaceColumn raceColumn,
486
- Iterable<RaceColumn> raceColumnsToConsider, TimePoint timePoint) throws NoWindException {
487
- final Set<RaceColumn> discardedRaceColumns = getResultDiscardingRule()
488
- .getDiscardedRaceColumns(competitor, this, raceColumnsToConsider, timePoint);
489
- return getTotalPoints(competitor, raceColumn, timePoint, discardedRaceColumns);
490
- }
491
-
492
- /**
493
- * Same as {@link #getTotalPoints(Competitor, RaceColumn, Iterable, TimePoint)}, only that the set of discarded race columns can
494
- * be specified which is useful when total points are to be computed for more than one column for the same
495
- * competitor because then the calculation of discards (which requires looking at all columns) only needs to be done
496
- * once and not again for each column (which would lead to quadratic effort).
497
- *
498
- * @param discardedRaceColumns
499
- * expected to be the result of what we would get if we called {@link #getResultDiscardingRule()}.
500
- * {@link ResultDiscardingRule#getDiscardedRaceColumns(Competitor, Leaderboard, Iterable, TimePoint)
501
- * getDiscardedRaceColumns(competitor, this, raceColumnsToConsider, timePoint)}.
502
- */
503
- @Override
504
- public Double getTotalPoints(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint, Set<RaceColumn> discardedRaceColumns) {
505
- Double result;
506
- if (isDiscarded(competitor, raceColumn, timePoint, discardedRaceColumns)) {
507
- result = 0.0;
508
- } else {
509
- final Double netPoints = getNetPoints(competitor, raceColumn, timePoint);
510
- if (netPoints == null) {
511
- result = null;
512
- } else {
513
- result = raceColumn.getFactor() * netPoints;
514
- }
515
- }
516
- return result;
517
- }
518
-
519
- @Override
520
- public Double getTotalPoints(Competitor competitor, TimePoint timePoint) {
521
- return getTotalPoints(competitor, getRaceColumns(), timePoint);
522
- }
523
-
524
- @Override
525
- public Double getTotalPoints(Competitor competitor, final Iterable<RaceColumn> raceColumnsToConsider,
526
- TimePoint timePoint) {
527
- // when a column with isStartsWithZeroScore() is found, only reset score if the competitor scored in any race from there on
528
- boolean needToResetScoreUponNextNonEmptyEntry = false;
529
- double result = getCarriedPoints(competitor);
530
- final Set<RaceColumn> discardedRaceColumns = getResultDiscardingRule()
531
- .getDiscardedRaceColumns(competitor, this, raceColumnsToConsider, timePoint);
532
- for (RaceColumn raceColumn : raceColumnsToConsider) {
533
- if (raceColumn.isStartsWithZeroScore()) {
534
- needToResetScoreUponNextNonEmptyEntry = true;
535
- }
536
- if (getScoringScheme().isValidInTotalScore(this, raceColumn, competitor, timePoint)) {
537
- final Double totalPoints = getTotalPoints(competitor, raceColumn, timePoint, discardedRaceColumns);
538
- if (totalPoints != null) {
539
- if (needToResetScoreUponNextNonEmptyEntry) {
540
- result = 0;
541
- needToResetScoreUponNextNonEmptyEntry = false;
542
- }
543
- result += totalPoints;
544
- }
545
- }
546
- }
547
- return result;
548
- }
549
-
550
- /**
551
- * All competitors with non-<code>null</code> net points are added to the result which is then sorted by net points in ascending
552
- * order. The fleet, if ordered, is the primary ordering criterion, followed by the net points.
553
- */
554
- @Override
555
- public List<Competitor> getCompetitorsFromBestToWorst(final RaceColumn raceColumn, TimePoint timePoint) throws NoWindException {
556
- final Map<Competitor, com.sap.sse.common.Util.Pair<Double, Fleet>> netPointsAndFleet = new HashMap<Competitor, com.sap.sse.common.Util.Pair<Double, Fleet>>();
557
- for (Competitor competitor : getCompetitors()) {
558
- Double netPoints = getNetPoints(competitor, raceColumn, timePoint);
559
- if (netPoints != null) {
560
- netPointsAndFleet.put(competitor, new com.sap.sse.common.Util.Pair<Double, Fleet>(netPoints, raceColumn.getFleetOfCompetitor(competitor)));
561
- }
562
- }
563
- List<Competitor> result = new ArrayList<Competitor>(netPointsAndFleet.keySet());
564
- Collections.sort(result, new Comparator<Competitor>() {
565
- @Override
566
- public int compare(Competitor o1, Competitor o2) {
567
- int comparisonResult;
568
- if (o1 == o2) {
569
- comparisonResult = 0;
570
- } else {
571
- if (raceColumn.hasSplitFleets() && !raceColumn.hasSplitFleetContiguousScoring()) {
572
- // only check fleets if there are more than one and the column is not to be contiguously scored even in case
573
- // of split fleets
574
- final Fleet o1Fleet = netPointsAndFleet.get(o1).getB();
575
- final Fleet o2Fleet = netPointsAndFleet.get(o2).getB();
576
- if (o1Fleet == null) {
577
- if (o2Fleet == null) {
578
- comparisonResult = 0;
579
- } else {
580
- comparisonResult = 1; // o1 ranks "worse" because it doesn't have a fleet set while o2
581
- // has
582
- }
583
- } else {
584
- if (o2Fleet == null) {
585
- comparisonResult = -1; // o1 ranks "better" because it has a fleet set while o2 hasn't
586
- } else {
587
- comparisonResult = o1Fleet.compareTo(o2Fleet);
588
- }
589
- }
590
- } else {
591
- // either there are no split fleets or the split isn't relevant for scoring as for ordered fleets
592
- // the scoring runs contiguously from top to bottom
593
- comparisonResult = 0;
594
- }
595
- if (comparisonResult == 0) {
596
- comparisonResult = getScoringScheme().getScoreComparator(/* nullScoresAreBetter */ false).compare(
597
- netPointsAndFleet.get(o1).getA(), netPointsAndFleet.get(o2).getA());
598
- }
599
- }
600
- return comparisonResult;
601
- }
602
- });
603
- return result;
604
- }
605
-
606
- @Override
607
- public List<Competitor> getCompetitorsFromBestToWorst(TimePoint timePoint) {
608
- return getCompetitorsFromBestToWorst(getRaceColumns(), timePoint);
609
- }
610
-
611
- private List<Competitor> getCompetitorsFromBestToWorst(Iterable<RaceColumn> raceColumnsToConsider, TimePoint timePoint) {
612
- List<Competitor> result = new ArrayList<Competitor>();
613
- for (Competitor competitor : getCompetitors()) {
614
- result.add(competitor);
615
- }
616
- Collections.sort(result, getTotalRankComparator(raceColumnsToConsider, timePoint));
617
- return result;
618
- }
619
-
620
- @Override
621
- public int getTotalRankOfCompetitor(Competitor competitor, TimePoint timePoint) throws NoWindException {
622
- List<Competitor> competitorsFromBestToWorst = getCompetitorsFromBestToWorst(timePoint);
623
- return competitorsFromBestToWorst.indexOf(competitor) + 1;
624
- }
625
-
626
- protected Comparator<? super Competitor> getTotalRankComparator(Iterable<RaceColumn> raceColumnsToConsider, TimePoint timePoint) {
627
- return new LeaderboardTotalRankComparator(this, timePoint, getScoringScheme(), /* nullScoresAreBetter */ false, raceColumnsToConsider);
628
- }
629
-
630
- @Override
631
- public RaceColumn getRaceColumnByName(String columnName) {
632
- RaceColumn result = null;
633
- for (RaceColumn r : getRaceColumns()) {
634
- if (r.getName().equals(columnName)) {
635
- result = r;
636
- break;
637
- }
638
- }
639
- return result;
640
- }
641
-
642
- @Override
643
- public Competitor getCompetitorByName(String competitorName) {
644
- for (Competitor competitor : getAllCompetitors()) {
645
- if (competitor.getName().equals(competitorName)) {
646
- return competitor;
647
- }
648
- }
649
- return null;
650
- }
651
-
652
- @Override
653
- public boolean countRaceForComparisonWithDiscardingThresholds(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint) {
654
- TrackedRace trackedRaceForCompetitorInColumn;
655
- return getScoringScheme().isValidInTotalScore(this, raceColumn, competitor, timePoint) &&
656
- (getScoreCorrection().isScoreCorrected(competitor, raceColumn, timePoint) ||
657
- ((trackedRaceForCompetitorInColumn=raceColumn.getTrackedRace(competitor)) != null &&
658
- trackedRaceForCompetitorInColumn.hasStarted(timePoint) &&
659
- trackedRaceForCompetitorInColumn.getRank(competitor, timePoint) != 0));
660
- }
661
-
662
- @Override
663
- public void addRaceColumnListener(RaceColumnListener listener) {
664
- getRaceColumnListeners().addRaceColumnListener(listener);
665
- }
666
-
667
- @Override
668
- public void removeRaceColumnListener(RaceColumnListener listener) {
669
- getRaceColumnListeners().removeRaceColumnListener(listener);
670
- }
671
-
672
- @Override
673
- public Entry getEntry(final Competitor competitor, final RaceColumn race, final TimePoint timePoint) throws NoWindException {
674
- final Set<RaceColumn> discardedRaceColumns = getResultDiscardingRule().getDiscardedRaceColumns(competitor, this, getRaceColumns(), timePoint);
675
- return getEntry(competitor, race, timePoint, discardedRaceColumns);
676
- }
677
-
678
- @Override
679
- public Entry getEntry(final Competitor competitor, final RaceColumn race, final TimePoint timePoint,
680
- Set<RaceColumn> discardedRaceColumns) throws NoWindException {
681
- Callable<Integer> trackedRankProvider = new Callable<Integer>() {
682
- public Integer call() throws NoWindException {
683
- return getTrackedRank(competitor, race, timePoint);
684
- }
685
- };
686
- final Result correctedResults = getScoreCorrection().getCorrectedScore(trackedRankProvider, competitor, race,
687
- timePoint, new NumberOfCompetitorsFetcherImpl(), getScoringScheme());
688
- boolean discarded = isDiscarded(competitor, race, timePoint, discardedRaceColumns);
689
- final Double correctedScore = correctedResults.getCorrectedScore();
690
- return new EntryImpl(trackedRankProvider, correctedScore, new Callable<Double>() {
691
- @Override
692
- public Double call() {
693
- return correctedResults.getUncorrectedScore();
694
- }
695
- }, correctedResults.isCorrected(), discarded ? DOUBLE_0 : correctedScore == null ? null
696
- : Double.valueOf(correctedScore * race.getFactor()), correctedResults.getMaxPointsReason(), discarded,
697
- race.getFleetOfCompetitor(competitor));
698
- }
699
-
700
- @Override
701
- public Map<RaceColumn, List<Competitor>> getRankedCompetitorsFromBestToWorstAfterEachRaceColumn(TimePoint timePoint) throws NoWindException {
702
- Map<RaceColumn, List<Competitor>> result = new LinkedHashMap<>();
703
- List<RaceColumn> raceColumnsToConsider = new ArrayList<>();
704
- for (RaceColumn raceColumn : getRaceColumns()) {
705
- raceColumnsToConsider.add(raceColumn);
706
- result.put(raceColumn, getCompetitorsFromBestToWorst(raceColumnsToConsider, timePoint));
707
- }
708
- return result;
709
- }
710
-
711
- @Override
712
- public Map<RaceColumn, Map<Competitor, Double>> getTotalPointsSumAfterRaceColumn(final TimePoint timePoint)
713
- throws NoWindException {
714
- final Map<RaceColumn, Map<Competitor, Double>> result = new LinkedHashMap<>();
715
- List<RaceColumn> raceColumnsToConsider = new ArrayList<>();
716
- Map<RaceColumn, Future<Map<Competitor, Double>>> futures = new HashMap<>();
717
- for (final RaceColumn raceColumn : getRaceColumns()) {
718
- raceColumnsToConsider.add(raceColumn);
719
- final Iterable<RaceColumn> finalRaceColumnsToConsider = new ArrayList<>(raceColumnsToConsider);
720
- futures.put(raceColumn, executor.submit(new Callable<Map<Competitor, Double>>() {
721
- @Override
722
- public Map<Competitor, Double> call() {
723
- Map<Competitor, Double> totalPointsSumPerCompetitorInColumn = new HashMap<>();
724
- for (Competitor competitor : getCompetitors()) {
725
- totalPointsSumPerCompetitorInColumn.put(competitor, getTotalPoints(competitor, finalRaceColumnsToConsider, timePoint));
726
- }
727
- synchronized (result) {
728
- return totalPointsSumPerCompetitorInColumn;
729
- }
730
- }
731
- }));
732
- }
733
- for (RaceColumn raceColumn : getRaceColumns()) {
734
- try {
735
- result.put(raceColumn, futures.get(raceColumn).get());
736
- } catch (InterruptedException | ExecutionException e) {
737
- if (e.getCause() instanceof NoWindError) {
738
- throw ((NoWindError) e.getCause()).getCause();
739
- } else {
740
- throw new RuntimeException(e); // no caught exceptions occur in the futures executed
741
- }
742
- }
743
- }
744
- return result;
745
- }
746
-
747
- @Override
748
- public Map<com.sap.sse.common.Util.Pair<Competitor, RaceColumn>, Entry> getContent(final TimePoint timePoint) throws NoWindException {
749
- Map<com.sap.sse.common.Util.Pair<Competitor, RaceColumn>, Entry> result = new HashMap<com.sap.sse.common.Util.Pair<Competitor, RaceColumn>, Entry>();
750
- Map<Competitor, Set<RaceColumn>> discardedRaces = new HashMap<Competitor, Set<RaceColumn>>();
751
- for (final RaceColumn raceColumn : getRaceColumns()) {
752
- for (final Competitor competitor : getCompetitors()) {
753
- Callable<Integer> trackedRankProvider = new Callable<Integer>() {
754
- @Override
755
- public Integer call() throws Exception {
756
- return getTrackedRank(competitor, raceColumn, timePoint);
757
- }
758
- };
759
- final Result correctedResults = getScoreCorrection().getCorrectedScore(trackedRankProvider, competitor, raceColumn,
760
- timePoint, new NumberOfCompetitorsFetcherImpl(), getScoringScheme());
761
- Set<RaceColumn> discardedRacesForCompetitor = discardedRaces.get(competitor);
762
- if (discardedRacesForCompetitor == null) {
763
- discardedRacesForCompetitor = getResultDiscardingRule().getDiscardedRaceColumns(competitor, this, getRaceColumns(), timePoint);
764
- discardedRaces.put(competitor, discardedRacesForCompetitor);
765
- }
766
- boolean discarded = discardedRacesForCompetitor.contains(raceColumn);
767
- final Double correctedScore = correctedResults.getCorrectedScore();
768
- Entry entry = new EntryImpl(trackedRankProvider, correctedScore,
769
- new Callable<Double>() { @Override public Double call() { return correctedResults.getUncorrectedScore(); } },
770
- correctedResults.isCorrected(),
771
- discarded ? DOUBLE_0 : (correctedScore==null?null:
772
- Double.valueOf((correctedScore * raceColumn.getFactor()))), correctedResults.getMaxPointsReason(),
773
- discarded, raceColumn.getFleetOfCompetitor(competitor));
774
- result.put(new com.sap.sse.common.Util.Pair<Competitor, RaceColumn>(competitor, raceColumn), entry);
775
- }
776
- }
777
- return result;
778
- }
779
-
780
- @Override
781
- public void trackedRaceLinked(RaceColumn raceColumn, Fleet fleet, TrackedRace trackedRace) {
782
- getRaceColumnListeners().notifyListenersAboutTrackedRaceLinked(raceColumn, fleet, trackedRace);
783
- }
784
-
785
- @Override
786
- public void trackedRaceUnlinked(RaceColumn raceColumn, Fleet fleet, TrackedRace trackedRace) {
787
- getRaceColumnListeners().notifyListenersAboutTrackedRaceUnlinked(raceColumn, fleet, trackedRace);
788
- // It's generally possible that a leaderboard links to the same tracked race in multiple columns / fleets;
789
- // only if it no longer references the trackedRace currently unlinked from one column/fleet, also unlink
790
- // all cache invalidation listeners for said trackedRace
791
- if (!Util.contains(getTrackedRaces(), trackedRace)) {
792
- synchronized (cacheInvalidationListeners) {
793
- for (Iterator<CacheInvalidationListener> cacheInvalidationListenerIter=cacheInvalidationListeners.iterator();
794
- cacheInvalidationListenerIter.hasNext(); ) {
795
- CacheInvalidationListener cacheInvalidationListener = cacheInvalidationListenerIter.next();
796
- if (cacheInvalidationListener.getTrackedRace() == trackedRace) {
797
- cacheInvalidationListener.removeFromTrackedRace();
798
- cacheInvalidationListenerIter.remove();
799
- }
800
- }
801
- }
802
- }
803
- }
804
-
805
- @Override
806
- public void isMedalRaceChanged(RaceColumn raceColumn, boolean newIsMedalRace) {
807
- getRaceColumnListeners().notifyListenersAboutIsMedalRaceChanged(raceColumn, newIsMedalRace);
808
- }
809
-
810
- @Override
811
- public void isStartsWithZeroScoreChanged(RaceColumn raceColumn, boolean newIsStartsWithZeroScore) {
812
- getRaceColumnListeners().notifyListenersAboutIsStartsWithZeroScoreChanged(raceColumn, newIsStartsWithZeroScore);
813
- }
814
-
815
- @Override
816
- public void isFirstColumnIsNonDiscardableCarryForwardChanged(RaceColumn raceColumn, boolean firstColumnIsNonDiscardableCarryForward) {
817
- getRaceColumnListeners().notifyListenersAboutIsFirstColumnIsNonDiscardableCarryForwardChanged(raceColumn, firstColumnIsNonDiscardableCarryForward);
818
- }
819
-
820
- @Override
821
- public void hasSplitFleetContiguousScoringChanged(RaceColumn raceColumn, boolean hasSplitFleetContiguousScoring) {
822
- getRaceColumnListeners().notifyListenersAboutHasSplitFleetContiguousScoringChanged(raceColumn, hasSplitFleetContiguousScoring);
823
- }
824
-
825
- @Override
826
- public void raceColumnMoved(RaceColumn raceColumn, int newIndex) {
827
- getRaceColumnListeners().notifyListenersAboutRaceColumnMoved(raceColumn, newIndex);
828
- }
829
-
830
- @Override
831
- public void factorChanged(RaceColumn raceColumn, Double oldFactor, Double newFactor) {
832
- getRaceColumnListeners().notifyListenersAboutFactorChanged(raceColumn, oldFactor, newFactor);
833
- }
834
-
835
- /**
836
- * A leaderboard will only accept the addition of a race column if the column's name is unique across the leaderboard.
837
- */
838
- @Override
839
- public boolean canAddRaceColumnToContainer(RaceColumn newRaceColumn) {
840
- boolean result = true;
841
- for (RaceColumn raceColumn : getRaceColumns()) {
842
- if (raceColumn.getName().equals(newRaceColumn.getName())) {
843
- result = false;
844
- break;
845
- }
846
- }
847
- return result;
848
- }
849
-
850
- @Override
851
- public void raceColumnAddedToContainer(RaceColumn raceColumn) {
852
- getRaceColumnListeners().notifyListenersAboutRaceColumnAddedToContainer(raceColumn);
853
- }
854
-
855
- @Override
856
- public void raceColumnRemovedFromContainer(RaceColumn raceColumn) {
857
- getRaceColumnListeners().notifyListenersAboutRaceColumnRemovedFromContainer(raceColumn);
858
- }
859
-
860
- @Override
861
- public void competitorDisplayNameChanged(Competitor competitor, String oldDisplayName, String displayName) {
862
- getRaceColumnListeners().notifyListenersAboutCompetitorDisplayNameChanged(competitor, oldDisplayName, displayName);
863
- }
864
-
865
- @Override
866
- public void resultDiscardingRuleChanged(ResultDiscardingRule oldDiscardingRule, ResultDiscardingRule newDiscardingRule) {
867
- getRaceColumnListeners().notifyListenersAboutResultDiscardingRuleChanged(oldDiscardingRule, newDiscardingRule);
868
- }
869
-
870
- @Override
871
- public boolean isTransient() {
872
- return false;
873
- }
874
-
875
- /**
876
- * Finds out the time point when any of the {@link Leaderboard#getTrackedRaces() tracked races currently attached to
877
- * the <code>leaderboard</code>} and the {@link Leaderboard#getScoreCorrection() score corrections} have last been
878
- * modified. If no tracked race is attached and no time-stamped score corrections have been applied to the leaderboard,
879
- * <code>null</code> is returned. The time point computed this way is a good choice for normalizing queries for later time
880
- * points in an attempt to achieve more cache hits.<p>
881
- *
882
- * Note, however, that the result does not tell about structural changes to the leaderboard and therefore cannot be used
883
- * to determine the need for cache invalidation. For example, if a column is added to a leaderboard after the time point
884
- * returned by this method but that column's attached tracked race has finished before the time point returned by this method,
885
- * the result of this method won't change. Still, the contents of the leaderboard will change by a change in column structure.
886
- * A different means to determine the possibility of changes that happened to this leaderboard must be used for cache
887
- * management. Such a facility has to listen for score correction changes, tracked races being attached or detached and
888
- * the column structure changing.
889
- *
890
- * @see TrackedRace#getTimePointOfNewestEvent()
891
- * @see SettableScoreCorrection#getTimePointOfLastCorrectionsValidity()
892
- */
893
- @Override
894
- public TimePoint getTimePointOfLatestModification() {
895
- TimePoint result = null;
896
- for (TrackedRace trackedRace : getTrackedRaces()) {
897
- if (result == null || (trackedRace.getTimePointOfNewestEvent() != null && trackedRace.getTimePointOfNewestEvent().after(result))) {
898
- result = trackedRace.getTimePointOfNewestEvent();
899
- }
900
- }
901
- TimePoint timePointOfLastScoreCorrection = getScoreCorrection().getTimePointOfLastCorrectionsValidity();
902
- if (timePointOfLastScoreCorrection != null && (result == null || timePointOfLastScoreCorrection.after(result))) {
903
- result = timePointOfLastScoreCorrection;
904
- }
905
- return result;
906
- }
907
-
908
- @Override
909
- public com.sap.sse.common.Util.Pair<GPSFixMoving, Speed> getMaximumSpeedOverGround(Competitor competitor, TimePoint timePoint) {
910
- com.sap.sse.common.Util.Pair<GPSFixMoving, Speed> result = null;
911
- // TODO should we ensure that competitor participated in all race columns?
912
- for (TrackedRace trackedRace : getTrackedRaces()) {
913
- if (Util.contains(trackedRace.getRace().getCompetitors(), competitor)) {
914
- NavigableSet<MarkPassing> markPassings = trackedRace.getMarkPassings(competitor);
915
- if (!markPassings.isEmpty()) {
916
- TimePoint from = markPassings.first().getTimePoint();
917
- TimePoint to;
918
- if (timePoint.after(markPassings.last().getTimePoint()) &&
919
- markPassings.last().getWaypoint() == trackedRace.getRace().getCourse().getLastWaypoint()) {
920
- // stop counting when competitor finished the race
921
- to = markPassings.last().getTimePoint();
922
- } else {
923
- to = timePoint;
924
- }
925
- com.sap.sse.common.Util.Pair<GPSFixMoving, Speed> maxSpeed = trackedRace.getTrack(competitor).getMaximumSpeedOverGround(from, to);
926
- if (result == null || result.getB() == null ||
927
- (maxSpeed != null && maxSpeed.getB() != null && maxSpeed.getB().compareTo(result.getB()) > 0)) {
928
- result = maxSpeed;
929
- }
930
- }
931
- }
932
- }
933
- return result;
934
- }
935
-
936
- @Override
937
- public Speed getAverageSpeedOverGround(Competitor competitor, TimePoint timePoint) {
938
- Speed result = null;
939
- for (TrackedRace trackedRace : getTrackedRaces()) {
940
- if (Util.contains(trackedRace.getRace().getCompetitors(), competitor)) {
941
- NavigableSet<MarkPassing> markPassings = trackedRace.getMarkPassings(competitor);
942
- if (!markPassings.isEmpty()) {
943
- TimePoint from = markPassings.first().getTimePoint();
944
- TimePoint to;
945
- if (timePoint.after(markPassings.last().getTimePoint()) &&
946
- markPassings.last().getWaypoint() == trackedRace.getRace().getCourse().getLastWaypoint()) {
947
- // stop counting when competitor finished the race
948
- to = markPassings.last().getTimePoint();
949
- } else {
950
- if (markPassings.last().getWaypoint() != trackedRace.getRace().getCourse().getLastWaypoint() &&
951
- timePoint.after(markPassings.last().getTimePoint())) {
952
- result = null;
953
- break;
954
- }
955
- to = timePoint;
956
- }
957
- Distance distanceTraveled = trackedRace.getDistanceTraveled(competitor, timePoint);
958
- if (distanceTraveled != null) {
959
- result = distanceTraveled.inTime(to.asMillis()-from.asMillis());
960
- }
961
- }
962
- }
963
- }
964
- return result;
965
- }
966
-
967
- @Override
968
- public Duration getTotalTimeSailedInLegType(Competitor competitor, LegType legType, TimePoint timePoint) throws NoWindException {
969
- return getTotalTimeSailedInLegType(competitor, legType, timePoint, new HashMap<TrackedLeg, LegType>());
970
- }
971
-
972
- private Duration getTotalTimeSailedInLegType(Competitor competitor, LegType legType, TimePoint timePoint, Map<TrackedLeg, LegType> legTypeCache) throws NoWindException {
973
- Duration result = null;
974
- // TODO should we ensure that competitor participated in all race columns?
975
- outerLoop:
976
- for (TrackedRace trackedRace : getTrackedRaces()) {
977
- if (Util.contains(trackedRace.getRace().getCompetitors(), competitor)) {
978
- trackedRace.getRace().getCourse().lockForRead();
979
- try {
980
- for (Leg leg : trackedRace.getRace().getCourse().getLegs()) {
981
- TrackedLegOfCompetitor trackedLegOfCompetitor = trackedRace.getTrackedLeg(competitor, leg);
982
- if (trackedLegOfCompetitor.hasStartedLeg(timePoint)) {
983
- // find out leg type at the time the competitor started the leg
984
- try {
985
- final TrackedLeg trackedLeg = trackedRace.getTrackedLeg(leg);
986
- LegType trackedLegType = legTypeCache.get(trackedLeg);
987
- if (trackedLegType == null) {
988
- final TimePoint startTime = trackedLegOfCompetitor.getStartTime();
989
- TimePoint finishTime = trackedLegOfCompetitor.getFinishTime();
990
- if (finishTime == null) {
991
- finishTime = timePoint;
992
- }
993
- trackedLegType = trackedLeg.getLegType(startTime.plus(startTime.until(finishTime).divide(2))); // middle of the leg
994
- legTypeCache.put(trackedLeg, trackedLegType);
995
- }
996
- if (legType == trackedLegType) {
997
- Duration timeSpentOnDownwind = trackedLegOfCompetitor.getTime(timePoint);
998
- if (timeSpentOnDownwind != null) {
999
- if (result == null) {
1000
- result = timeSpentOnDownwind;
1001
- } else {
1002
- result = result.plus(timeSpentOnDownwind);
1003
- }
1004
- } else {
1005
- // Although the competitor has started the leg, no value was produced. This
1006
- // means that the competitor didn't finish the leg before tracking ended. No useful value
1007
- // can be obtained for this competitor anymore.
1008
- result = null;
1009
- break outerLoop;
1010
- }
1011
- }
1012
- } catch (NoWindException nwe) {
1013
- // without wind there is no leg type and hence there is no reasonable value for this:
1014
- result = null;
1015
- break outerLoop;
1016
- }
1017
- }
1018
- }
1019
- } finally {
1020
- trackedRace.getRace().getCourse().unlockAfterRead();
1021
- }
1022
- }
1023
- }
1024
- return result;
1025
- }
1026
-
1027
- @Override
1028
- public Duration getTotalTimeSailed(Competitor competitor, TimePoint timePoint) {
1029
- Duration result = null;
1030
- for (TrackedRace trackedRace : getTrackedRaces()) {
1031
- if (Util.contains(trackedRace.getRace().getCompetitors(), competitor)) {
1032
- NavigableSet<MarkPassing> markPassings = trackedRace.getMarkPassings(competitor);
1033
- if (!markPassings.isEmpty()) {
1034
- TimePoint from = trackedRace.getStartOfRace(); // start counting at race start, not when the competitor passed the line
1035
- if (from != null && !timePoint.before(from)) { // but only if the race started after timePoint
1036
- TimePoint to;
1037
- if (timePoint.after(markPassings.last().getTimePoint())
1038
- && markPassings.last().getWaypoint() == trackedRace.getRace().getCourse()
1039
- .getLastWaypoint()) {
1040
- // stop counting when competitor finished the race
1041
- to = markPassings.last().getTimePoint();
1042
- } else {
1043
- if (trackedRace.getEndOfTracking() != null
1044
- && timePoint.after(trackedRace.getEndOfTracking())) {
1045
- result = null; // race not finished until end of tracking; no reasonable value can be
1046
- // computed for competitor
1047
- break;
1048
- } else {
1049
- to = timePoint;
1050
- }
1051
- }
1052
- Duration timeSpent = from.until(to);
1053
- if (result == null) {
1054
- result = timeSpent;
1055
- } else {
1056
- result=result.plus(timeSpent);
1057
- }
1058
- }
1059
- }
1060
- }
1061
- }
1062
- return result;
1063
- }
1064
-
1065
- @Override
1066
- public Distance getTotalDistanceTraveled(Competitor competitor, TimePoint timePoint) {
1067
- Distance result = null;
1068
- for (TrackedRace trackedRace : getTrackedRaces()) {
1069
- TimePoint startOfRace;
1070
- if (Util.contains(trackedRace.getRace().getCompetitors(), competitor) &&
1071
- (startOfRace=trackedRace.getStartOfRace()) != null &&
1072
- !startOfRace.after(timePoint)) {
1073
- Distance distanceSailedInRace = trackedRace.getDistanceTraveled(competitor, timePoint);
1074
- if (distanceSailedInRace != null) {
1075
- if (result == null) {
1076
- result = distanceSailedInRace;
1077
- } else {
1078
- result = result.add(distanceSailedInRace);
1079
- }
1080
- } else {
1081
- // if competitor has not finished one single race in the whole
1082
- // series then we can not return a meaningful value for all
1083
- // all races
1084
- return null;
1085
- }
1086
- }
1087
- }
1088
- return result;
1089
- }
1090
-
1091
- protected RaceColumnListeners getRaceColumnListeners() {
1092
- return raceColumnListeners;
1093
- }
1094
-
1095
- @Override
1096
- public Iterable<Competitor> getCompetitors() {
1097
- final Iterable<Competitor> result;
1098
- // mostly the set of suppressed competitors is empty; in this case, avoid having to loop over the
1099
- // potentially large set of competitors
1100
- if (Util.isEmpty(getSuppressedCompetitors())) {
1101
- result = getAllCompetitors();
1102
- } else {
1103
- final Iterable<Competitor> allCompetitors = getAllCompetitors();
1104
- final Set<Competitor> suppressed = new HashSet<>();
1105
- Util.addAll(getSuppressedCompetitors(), suppressed);
1106
- result = getCompetitorIterableSkippingSuppressed(allCompetitors, suppressed);
1107
- }
1108
- return result;
1109
- }
1110
-
1111
- @Override
1112
- public Iterable<Competitor> getCompetitors(RaceColumn raceColumn, Fleet fleet) {
1113
- return getCompetitorIterableSkippingSuppressed(getAllCompetitors(raceColumn, fleet), getSuppressedCompetitors());
1114
- }
1115
-
1116
- /**
1117
- * return an iterable with a smart iterator that filters out the suppressed elements on demand
1118
- */
1119
- protected Iterable<Competitor> getCompetitorIterableSkippingSuppressed(final Iterable<Competitor> allCompetitors,
1120
- final Iterable<Competitor> suppressed) {
1121
- final Iterable<Competitor> result;
1122
- result = new Iterable<Competitor>() {
1123
- @Override
1124
- public Iterator<Competitor> iterator() {
1125
- return new Iterator<Competitor>() {
1126
- private final Iterator<Competitor> allIter = allCompetitors.iterator();
1127
- private Competitor next = advance();
1128
-
1129
- private Competitor advance() {
1130
- next = null;
1131
- while (allIter.hasNext() && next == null) {
1132
- next = allIter.next();
1133
- if (Util.contains(suppressed, next)) {
1134
- next = null;
1135
- }
1136
- }
1137
- return next;
1138
- }
1139
-
1140
- @Override
1141
- public boolean hasNext() {
1142
- return next != null;
1143
- }
1144
-
1145
- @Override
1146
- public Competitor next() {
1147
- if (next == null) {
1148
- throw new NoSuchElementException();
1149
- }
1150
- final Competitor result = next;
1151
- advance();
1152
- return result;
1153
- }
1154
- };
1155
- }
1156
- };
1157
- return result;
1158
- }
1159
-
1160
- @Override
1161
- public Iterable<Competitor> getSuppressedCompetitors() {
1162
- LockUtil.lockForRead(suppressedCompetitorsLock);
1163
- try {
1164
- return new HashSet<Competitor>(suppressedCompetitors);
1165
- } finally {
1166
- LockUtil.unlockAfterRead(suppressedCompetitorsLock);
1167
- }
1168
- }
1169
-
1170
- @Override
1171
- public void setSuppressed(Competitor competitor, boolean suppressed) {
1172
- LockUtil.lockForWrite(suppressedCompetitorsLock);
1173
- try {
1174
- if (suppressed) {
1175
- suppressedCompetitors.add(competitor);
1176
- } else {
1177
- suppressedCompetitors.remove(competitor);
1178
- }
1179
- } finally {
1180
- LockUtil.unlockAfterWrite(suppressedCompetitorsLock);
1181
- }
1182
- getScoreCorrection().notifyListenersAboutIsSuppressedChange(competitor, suppressed);
1183
- }
1184
-
1185
- @Override
1186
- public TimePoint getNowMinusDelay() {
1187
- final TimePoint now = MillisecondsTimePoint.now();
1188
- final Long delayToLiveInMillis = getDelayToLiveInMillis();
1189
- TimePoint timePoint = delayToLiveInMillis == null ? now : now.minus(delayToLiveInMillis);
1190
- return timePoint;
1191
- }
1192
-
1193
- @Override
1194
- public void raceLogEventAdded(RaceColumn raceColumn, RaceLogIdentifier raceLogIdentifier, RaceLogEvent event) {
1195
- getRaceColumnListeners().notifyListenersAboutRaceLogEventAdded(raceColumn, raceLogIdentifier, event);
1196
- if (event instanceof InvalidatesLeaderboardCache) {
1197
- // make sure to invalidate the cache as this event indicates that
1198
- // it changes values the cache could still hold
1199
- if (leaderboardDTOCache != null) {
1200
- leaderboardDTOCache.invalidate(this);
1201
- }
1202
- }
1203
- }
1204
-
1205
- @Override
1206
- public LeaderboardDTO computeDTO(final TimePoint timePoint,
1207
- final Collection<String> namesOfRaceColumnsForWhichToLoadLegDetails, boolean addOverallDetails,
1208
- final boolean waitForLatestAnalyses, TrackedRegattaRegistry trackedRegattaRegistry, final DomainFactory baseDomainFactory,
1209
- final boolean fillNetPointsUncorrected)
1210
- throws NoWindException {
1211
- long startOfRequestHandling = System.currentTimeMillis();
1212
- final LeaderboardDTOCalculationReuseCache cache = new LeaderboardDTOCalculationReuseCache(timePoint);
1213
- final LeaderboardDTO result = new LeaderboardDTO(this.getScoreCorrection().getTimePointOfLastCorrectionsValidity() == null ? null
1214
- : this.getScoreCorrection().getTimePointOfLastCorrectionsValidity().asDate(),
1215
- this.getScoreCorrection() == null ? null : this.getScoreCorrection().getComment(),
1216
- this.getScoringScheme() == null ? null : this.getScoringScheme().getType(), this
1217
- .getScoringScheme().isHigherBetter(), new UUIDGenerator(), addOverallDetails);
1218
- result.competitors = new ArrayList<CompetitorDTO>();
1219
- result.name = this.getName();
1220
- result.competitorDisplayNames = new HashMap<CompetitorDTO, String>();
1221
- for (Competitor suppressedCompetitor : this.getSuppressedCompetitors()) {
1222
- result.setSuppressed(baseDomainFactory.convertToCompetitorDTO(suppressedCompetitor), true);
1223
- }
1224
- // Now create the race columns and, as a future task, set their competitorsFromBestToWorst, then wait for all these
1225
- // futures to finish:
1226
- Map<RaceColumn, FutureTask<List<CompetitorDTO>>> competitorsFromBestToWorstTasks = new HashMap<>();
1227
- for (final RaceColumn raceColumn : this.getRaceColumns()) {
1228
- result.createEmptyRaceColumn(raceColumn.getName(), raceColumn.isMedalRace(),
1229
- raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getRegatta().getName() : null,
1230
- raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getSeries().getName() : null);
1231
- for (Fleet fleet : raceColumn.getFleets()) {
1232
- RegattaAndRaceIdentifier raceIdentifier = null;
1233
- RaceDTO race = null;
1234
- TrackedRace trackedRace = raceColumn.getTrackedRace(fleet);
1235
- final FleetDTO fleetDTO = baseDomainFactory.convertToFleetDTO(fleet);
1236
- if (trackedRace != null) {
1237
- raceIdentifier = new RegattaNameAndRaceName(trackedRace.getTrackedRegatta().getRegatta().getName(),
1238
- trackedRace.getRace().getName());
1239
- race = baseDomainFactory.createRaceDTO(trackedRegattaRegistry, /* withGeoLocationData */ false, raceIdentifier, trackedRace);
1240
- }
1241
- // Note: the RaceColumnDTO won't be created by the following addRace call because it has been created
1242
- // above by the result.createEmptyRaceColumn call
1243
- result.addRace(raceColumn.getName(), raceColumn.getExplicitFactor(), raceColumn.getFactor(),
1244
- raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getRegatta().getName() : null,
1245
- raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getSeries().getName() : null,
1246
- fleetDTO, raceColumn.isMedalRace(), raceIdentifier, race);
1247
- }
1248
- FutureTask<List<CompetitorDTO>> task = new FutureTask<List<CompetitorDTO>>(
1249
- () -> baseDomainFactory.getCompetitorDTOList(AbstractSimpleLeaderboardImpl.this.getCompetitorsFromBestToWorst(raceColumn, timePoint)));
1250
- executor.execute(task);
1251
- competitorsFromBestToWorstTasks.put(raceColumn, task);
1252
- }
1253
- // wait for the competitor orderings to have been computed for all race columns before continuing; subsequent tasks may depend on these data
1254
- for (Map.Entry<RaceColumn, FutureTask<List<CompetitorDTO>>> raceColumnAndTaskToJoin : competitorsFromBestToWorstTasks.entrySet()) {
1255
- try {
1256
- result.setCompetitorsFromBestToWorst(raceColumnAndTaskToJoin.getKey().getName(), raceColumnAndTaskToJoin.getValue().get());
1257
- } catch (InterruptedException e) {
1258
- throw new RuntimeException(e);
1259
- } catch (ExecutionException e) {
1260
- // See also bug 1371: for stability reasons, don't let the exception percolate but rather accept
1261
- // null values.
1262
- // If new evidence is provided, a re-calculation of the leaderboard will be triggered anyway. So
1263
- // this helps robustness from a user's perspective.
1264
- logger.log(
1265
- Level.SEVERE,
1266
- AbstractSimpleLeaderboardImpl.class.getName() + ".computeDTO(" + this.getName() + ", "
1267
- + timePoint + ", " + namesOfRaceColumnsForWhichToLoadLegDetails+", addOverallDetails="+addOverallDetails
1268
- + "): exception during computing competitor ordering for race column "+raceColumnAndTaskToJoin.getKey().getName(), e);
1269
- }
1270
- }
1271
- result.setDelayToLiveInMillisForLatestRace(this.getDelayToLiveInMillis());
1272
- result.rows = new HashMap<CompetitorDTO, LeaderboardRowDTO>();
1273
- result.hasCarriedPoints = this.hasCarriedPoints();
1274
- if (this.getResultDiscardingRule() instanceof ThresholdBasedResultDiscardingRule) {
1275
- result.discardThresholds = ((ThresholdBasedResultDiscardingRule) this.getResultDiscardingRule())
1276
- .getDiscardIndexResultsStartingWithHowManyRaces();
1277
- } else {
1278
- result.discardThresholds = null;
1279
- }
1280
- // Computing the competitor leg ranks is expensive, especially in live mode, in case new events keep
1281
- // invalidating the ranks cache in TrackedLegImpl. The problem then is that the sorting based on wind data is repeated for
1282
- // each competitor, leading to square effort. We therefore need to compute the leg ranks for those races where leg
1283
- // details are requested only once and pass them into getLeaderboardEntryDTO
1284
- final Map<Leg, LinkedHashMap<Competitor, Integer>> legRanksCache = new HashMap<Leg, LinkedHashMap<Competitor, Integer>>();
1285
- for (final RaceColumn raceColumn : this.getRaceColumns()) {
1286
- // if details for the column are requested, cache the leg's ranks
1287
- if (namesOfRaceColumnsForWhichToLoadLegDetails != null
1288
- && namesOfRaceColumnsForWhichToLoadLegDetails.contains(raceColumn.getName())) {
1289
- for (Fleet fleet : raceColumn.getFleets()) {
1290
- TrackedRace trackedRace = raceColumn.getTrackedRace(fleet);
1291
- if (trackedRace != null) {
1292
- trackedRace.getRace().getCourse().lockForRead();
1293
- try {
1294
- for (TrackedLeg trackedLeg : trackedRace.getTrackedLegs()) {
1295
- legRanksCache.put(trackedLeg.getLeg(), trackedLeg.getRanks(timePoint, cache));
1296
- }
1297
- } finally {
1298
- trackedRace.getRace().getCourse().unlockAfterRead();
1299
- }
1300
- }
1301
- }
1302
- }
1303
- }
1304
- for (final Competitor competitor : this.getCompetitorsFromBestToWorst(timePoint)) {
1305
- CompetitorDTO competitorDTO = baseDomainFactory.convertToCompetitorDTO(competitor);
1306
- LeaderboardRowDTO row = new LeaderboardRowDTO();
1307
- row.competitor = competitorDTO;
1308
- row.fieldsByRaceColumnName = new HashMap<String, LeaderboardEntryDTO>();
1309
- row.carriedPoints = this.hasCarriedPoints(competitor) ? this.getCarriedPoints(competitor) : null;
1310
- row.totalPoints = this.getTotalPoints(competitor, timePoint);
1311
- if (addOverallDetails) {
1312
- addOverallDetailsToRow(timePoint, competitor, row);
1313
- }
1314
- result.competitors.add(competitorDTO);
1315
- Map<String, Future<LeaderboardEntryDTO>> futuresForColumnName = new HashMap<String, Future<LeaderboardEntryDTO>>();
1316
- final Set<RaceColumn> discardedRaceColumns = getResultDiscardingRule().getDiscardedRaceColumns(competitor, this, getRaceColumns(), timePoint);
1317
- for (final RaceColumn raceColumn : this.getRaceColumns()) {
1318
- RunnableFuture<LeaderboardEntryDTO> future = new FutureTask<LeaderboardEntryDTO>(() -> {
1319
- Entry entry = AbstractSimpleLeaderboardImpl.this.getEntry(competitor, raceColumn, timePoint, discardedRaceColumns);
1320
- return getLeaderboardEntryDTO(entry, raceColumn, competitor, timePoint,
1321
- namesOfRaceColumnsForWhichToLoadLegDetails != null
1322
- && namesOfRaceColumnsForWhichToLoadLegDetails.contains(raceColumn
1323
- .getName()), waitForLatestAnalyses, legRanksCache, baseDomainFactory,
1324
- fillNetPointsUncorrected, cache);
1325
- });
1326
- executor.execute(future);
1327
- futuresForColumnName.put(raceColumn.getName(), future);
1328
- }
1329
- for (Map.Entry<String, Future<LeaderboardEntryDTO>> raceColumnNameAndFuture : futuresForColumnName.entrySet()) {
1330
- try {
1331
- row.fieldsByRaceColumnName.put(raceColumnNameAndFuture.getKey(), raceColumnNameAndFuture.getValue().get());
1332
- } catch (InterruptedException e) {
1333
- throw new RuntimeException(e);
1334
- } catch (ExecutionException e) {
1335
- // See also bug 1371: for stability reasons, don't let the exception percolate but rather accept
1336
- // null values.
1337
- // If new evidence is provided, a re-calculation of the leaderboard will be triggered anyway. So
1338
- // this helps robustness from a user's perspective.
1339
- logger.log(
1340
- Level.SEVERE,
1341
- AbstractSimpleLeaderboardImpl.class.getName() + ".computeDTO(" + this.getName() + ", "
1342
- + timePoint + ", " + namesOfRaceColumnsForWhichToLoadLegDetails+", addOverallDetails="+addOverallDetails
1343
- + "): exception during computing leaderboard entry for competitor "
1344
- + competitor.getName() + " in race column " + raceColumnNameAndFuture.getKey()
1345
- + ". Leaving empty.", e);
1346
- }
1347
- }
1348
- result.rows.put(competitorDTO, row);
1349
- String displayName = this.getDisplayName(competitor);
1350
- if (displayName != null) {
1351
- result.competitorDisplayNames.put(competitorDTO, displayName);
1352
- }
1353
- }
1354
- logger.info("computeLeaderboardByName(" + this.getName() + ", " + timePoint + ", "
1355
- + namesOfRaceColumnsForWhichToLoadLegDetails + ", addOverallDetails=" + addOverallDetails + ") took "
1356
- + (System.currentTimeMillis() - startOfRequestHandling) + "ms");
1357
- return result;
1358
- }
1359
-
1360
- private void addOverallDetailsToRow(final TimePoint timePoint,
1361
- final Competitor competitor, LeaderboardRowDTO row) throws NoWindException {
1362
- final com.sap.sse.common.Util.Pair<GPSFixMoving, Speed> maximumSpeedOverGround = this.getMaximumSpeedOverGround(competitor, timePoint);
1363
- if (maximumSpeedOverGround != null && maximumSpeedOverGround.getB() != null) {
1364
- row.maximumSpeedOverGroundInKnots = maximumSpeedOverGround.getB().getKnots();
1365
- row.whenMaximumSpeedOverGroundWasAchieved = maximumSpeedOverGround.getA().getTimePoint().asDate();
1366
- }
1367
- Map<TrackedLeg, LegType> legTypeCache = new HashMap<>();
1368
- final Duration totalTimeSailedDownwind = this.getTotalTimeSailedInLegType(competitor, LegType.DOWNWIND, timePoint, legTypeCache);
1369
- row.totalTimeSailedDownwindInSeconds = totalTimeSailedDownwind==null?null:totalTimeSailedDownwind.asSeconds();
1370
- final Duration totalTimeSailedUpwind = this.getTotalTimeSailedInLegType(competitor, LegType.UPWIND, timePoint, legTypeCache);
1371
- row.totalTimeSailedUpwindInSeconds = totalTimeSailedUpwind==null?null:totalTimeSailedUpwind.asSeconds();
1372
- final Duration totalTimeSailedReaching = this.getTotalTimeSailedInLegType(competitor, LegType.REACHING, timePoint, legTypeCache);
1373
- row.totalTimeSailedReachingInSeconds = totalTimeSailedReaching==null?null:totalTimeSailedReaching.asSeconds();
1374
- final Duration totalTimeSailed = this.getTotalTimeSailed(competitor, timePoint);
1375
- row.totalTimeSailedInSeconds = totalTimeSailed==null?null:totalTimeSailed.asSeconds();
1376
- final Distance totalDistanceTraveledInMeters = this.getTotalDistanceTraveled(competitor, timePoint);
1377
- row.totalDistanceTraveledInMeters = totalDistanceTraveledInMeters==null?null:totalDistanceTraveledInMeters.getMeters();
1378
- }
1379
-
1380
- /**
1381
- * @param waitForLatestAnalyses
1382
- * if <code>false</code>, this method is allowed to read the maneuver analysis results from a cache that
1383
- * may not reflect all data already received; otherwise, the method will always block for the latest
1384
- * cache updates to have happened before returning.
1385
- * @param fillNetPointsUncorrected
1386
- * tells if {@link LeaderboardEntryDTO#netPointsUncorrected} shall be filled; filling it is rather
1387
- * expensive, especially when compared to simply retrieving a score correction, and particularly if in a
1388
- * larger fleet a number of competitors haven't properly finished the race. This should only be used for
1389
- * leaderboard editing where a user needs to see what the uncorrected score was that would be used when
1390
- * the correction was removed.
1391
- */
1392
- private LeaderboardEntryDTO getLeaderboardEntryDTO(Entry entry, RaceColumn raceColumn, Competitor competitor,
1393
- TimePoint timePoint, boolean addLegDetails, boolean waitForLatestAnalyses,
1394
- Map<Leg, LinkedHashMap<Competitor, Integer>> legRanksCache, DomainFactory baseDomainFactory,
1395
- boolean fillNetPointsUncorrected, WindLegTypeAndLegBearingCache cache) {
1396
- LeaderboardEntryDTO entryDTO = new LeaderboardEntryDTO();
1397
- TrackedRace trackedRace = raceColumn.getTrackedRace(competitor);
1398
- entryDTO.race = trackedRace == null ? null : trackedRace.getRaceIdentifier();
1399
- entryDTO.netPoints = entry.getNetPoints();
1400
- if (fillNetPointsUncorrected) {
1401
- entryDTO.netPointsUncorrected = entry.getNetPointsUncorrected();
1402
- }
1403
- entryDTO.netPointsCorrected = entry.isNetPointsCorrected();
1404
- entryDTO.totalPoints = entry.getTotalPoints();
1405
- entryDTO.reasonForMaxPoints = entry.getMaxPointsReason();
1406
- entryDTO.discarded = entry.isDiscarded();
1407
- final GPSFixTrack<Competitor, GPSFixMoving> track = trackedRace == null ? null : trackedRace.getTrack(competitor);
1408
- if (trackedRace != null) {
1409
- Date timePointOfLastPositionFixAtOrBeforeQueryTimePoint = getTimePointOfLastFixAtOrBefore(competitor, trackedRace, timePoint);
1410
- if (track != null) {
1411
- entryDTO.averageSamplingInterval = track.getAverageIntervalBetweenRawFixes();
1412
- }
1413
- if (timePointOfLastPositionFixAtOrBeforeQueryTimePoint != null) {
1414
- long timeDifferenceInMs = timePoint.asMillis() - timePointOfLastPositionFixAtOrBeforeQueryTimePoint.getTime();
1415
- entryDTO.timeSinceLastPositionFixInSeconds = timeDifferenceInMs == 0 ? 0.0 : timeDifferenceInMs / 1000.0;
1416
- } else {
1417
- entryDTO.timeSinceLastPositionFixInSeconds = null;
1418
- }
1419
- }
1420
- if (addLegDetails && trackedRace != null) {
1421
- try {
1422
- final RankingInfo rankingInfo = trackedRace.getRankingMetric().getRankingInfo(timePoint, cache);
1423
- RaceDetails raceDetails = getRaceDetails(trackedRace, competitor, timePoint, waitForLatestAnalyses,
1424
- legRanksCache, rankingInfo, cache);
1425
- entryDTO.legDetails = raceDetails.getLegDetails();
1426
- entryDTO.windwardDistanceToCompetitorFarthestAheadInMeters = raceDetails.getWindwardDistanceToCompetitorFarthestAhead() == null ? null
1427
- : raceDetails.getWindwardDistanceToCompetitorFarthestAhead().getMeters();
1428
- entryDTO.gapToLeaderInOwnTime = trackedRace.getRankingMetric().getGapToLeaderInOwnTime(rankingInfo, competitor, cache);
1429
- entryDTO.averageAbsoluteCrossTrackErrorInMeters = raceDetails.getAverageAbsoluteCrossTrackError() == null ? null
1430
- : raceDetails.getAverageAbsoluteCrossTrackError().getMeters();
1431
- entryDTO.averageSignedCrossTrackErrorInMeters = raceDetails.getAverageSignedCrossTrackError() == null ? null
1432
- : raceDetails.getAverageSignedCrossTrackError().getMeters();
1433
- entryDTO.calculatedTime = raceDetails.getCorrectedTime();
1434
- entryDTO.calculatedTimeAtEstimatedArrivalAtCompetitorFarthestAhead = raceDetails.getCorrectedTimeAtEstimatedArrivalAtCompetitorFarthestAhead();
1435
- entryDTO.gapToLeaderInOwnTime = raceDetails.getGapToLeaderInOwnTime();
1436
- final TimePoint startOfRace = trackedRace.getStartOfRace();
1437
- if (startOfRace != null) {
1438
- Waypoint startWaypoint = trackedRace.getRace().getCourse().getFirstWaypoint();
1439
- NavigableSet<MarkPassing> competitorMarkPassings = trackedRace.getMarkPassings(competitor);
1440
- trackedRace.lockForRead(competitorMarkPassings);
1441
- try {
1442
- if (!competitorMarkPassings.isEmpty()) {
1443
- final MarkPassing firstMarkPassing = competitorMarkPassings.iterator().next();
1444
- if (firstMarkPassing.getWaypoint() == startWaypoint) {
1445
- Distance distanceToStartLineFiveSecondsBeforeStartOfRace = trackedRace.getDistanceToStartLine(competitor, /*milliseconds before start*/ 5000);
1446
- entryDTO.distanceToStartLineFiveSecondsBeforeStartInMeters = distanceToStartLineFiveSecondsBeforeStartOfRace == null ? null
1447
- : distanceToStartLineFiveSecondsBeforeStartOfRace.getMeters();
1448
- Speed speedFiveSecondsBeforeStartOfRace = trackedRace.getSpeed(competitor, /*milliseconds before start*/ 5000);
1449
- entryDTO.speedOverGroundFiveSecondsBeforeStartInKnots = speedFiveSecondsBeforeStartOfRace == null ? null
1450
- : speedFiveSecondsBeforeStartOfRace.getKnots();
1451
- Distance distanceToStartLineAtStartOfRace = trackedRace.getDistanceToStartLine(
1452
- competitor, startOfRace);
1453
- entryDTO.distanceToStartLineAtStartOfRaceInMeters = distanceToStartLineAtStartOfRace == null ? null
1454
- : distanceToStartLineAtStartOfRace.getMeters();
1455
- Speed speedAtStartTime = track == null ? null : track.getEstimatedSpeed(startOfRace);
1456
- entryDTO.speedOverGroundAtStartOfRaceInKnots = speedAtStartTime == null ? null
1457
- : speedAtStartTime.getKnots();
1458
- TimePoint competitorStartTime = firstMarkPassing.getTimePoint();
1459
- entryDTO.timeBetweenRaceStartAndCompetitorStartInSeconds = startOfRace.until(competitorStartTime).asSeconds();
1460
- Speed competitorSpeedWhenPassingStart = track == null ? null : track
1461
- .getEstimatedSpeed(competitorStartTime);
1462
- entryDTO.speedOverGroundAtPassingStartWaypointInKnots = competitorSpeedWhenPassingStart == null ? null
1463
- : competitorSpeedWhenPassingStart.getKnots();
1464
- try {
1465
- entryDTO.startTack = trackedRace.getTack(competitor, competitorStartTime);
1466
- } catch (NoWindException nwe) {
1467
- entryDTO.startTack = null; // leave empty in case no wind information is available
1468
- }
1469
- Distance distanceFromStarboardSideOfStartLineWhenPassingStart = trackedRace
1470
- .getDistanceFromStarboardSideOfStartLineWhenPassingStart(competitor);
1471
- entryDTO.distanceToStarboardSideOfStartLineInMeters = distanceFromStarboardSideOfStartLineWhenPassingStart == null ? null
1472
- : distanceFromStarboardSideOfStartLineWhenPassingStart.getMeters();
1473
- }
1474
- }
1475
- } finally {
1476
- trackedRace.unlockAfterRead(competitorMarkPassings);
1477
- }
1478
- }
1479
- } catch (InterruptedException e) {
1480
- throw new RuntimeException(e);
1481
- } catch (ExecutionException e) {
1482
- throw new RuntimeException(e); // the future used to calculate the leg details was interrupted; escalate as runtime exception
1483
- }
1484
- }
1485
- final Fleet fleet = entry.getFleet();
1486
- entryDTO.fleet = fleet == null ? null : baseDomainFactory.convertToFleetDTO(fleet);
1487
- return entryDTO;
1488
- }
1489
-
1490
- /**
1491
- * Determines the time point of the last raw fix (with outliers not removed) for <code>competitor</code> in
1492
- * <code>trackedRace</code>. If the competitor's track is <code>null</code> or empty, <code>null</code> is returned.
1493
- * @param trackedRace must not be <code>null</code>
1494
- * @param atOrBefore find the last fix at or before the time point specified
1495
- */
1496
- private Date getTimePointOfLastFixAtOrBefore(Competitor competitor, TrackedRace trackedRace, TimePoint atOrBefore) {
1497
- assert trackedRace != null;
1498
- final Date timePointOfLastPositionFix;
1499
- GPSFixTrack<Competitor, GPSFixMoving> track = trackedRace.getTrack(competitor);
1500
- if (track == null) {
1501
- timePointOfLastPositionFix = null;
1502
- } else {
1503
- GPSFixMoving lastFix = track.getLastFixAtOrBefore(atOrBefore);
1504
- if (lastFix == null) {
1505
- timePointOfLastPositionFix = null;
1506
- } else {
1507
- timePointOfLastPositionFix = lastFix.getTimePoint().asDate();
1508
- }
1509
- }
1510
- return timePointOfLastPositionFix;
1511
- }
1512
-
1513
- private static class RaceDetails {
1514
- private final List<LegEntryDTO> legDetails;
1515
- private final Distance windwardDistanceToCompetitorFarthestAhead;
1516
- private final Distance averageAbsoluteCrossTrackError;
1517
- private final Distance averageSignedCrossTrackError;
1518
- private final Duration gapToLeaderInOwnTime;
1519
- private final Duration correctedTime;
1520
- private final Duration correctedTimeAtEstimatedArrivalAtCompetitorFarthestAhead;
1521
-
1522
- public RaceDetails(List<LegEntryDTO> legDetails, Distance windwardDistanceToCompetitorFarthestAhead,
1523
- Distance averageAbsoluteCrossTrackError, Distance averageSignedCrossTrackError,
1524
- Duration gapToLeaderInOwnTime, Duration correctedTime,
1525
- Duration correctedTimeAtEstimatedArrivalAtCompetitorFarthestAhead) {
1526
- super();
1527
- this.legDetails = legDetails;
1528
- this.windwardDistanceToCompetitorFarthestAhead = windwardDistanceToCompetitorFarthestAhead;
1529
- this.averageAbsoluteCrossTrackError = averageAbsoluteCrossTrackError;
1530
- this.averageSignedCrossTrackError = averageSignedCrossTrackError;
1531
- this.gapToLeaderInOwnTime = gapToLeaderInOwnTime;
1532
- this.correctedTime = correctedTime;
1533
- this.correctedTimeAtEstimatedArrivalAtCompetitorFarthestAhead = correctedTimeAtEstimatedArrivalAtCompetitorFarthestAhead;
1534
- }
1535
- public List<LegEntryDTO> getLegDetails() {
1536
- return legDetails;
1537
- }
1538
- public Distance getWindwardDistanceToCompetitorFarthestAhead() {
1539
- return windwardDistanceToCompetitorFarthestAhead;
1540
- }
1541
- public Distance getAverageAbsoluteCrossTrackError() {
1542
- return averageAbsoluteCrossTrackError;
1543
- }
1544
- public Distance getAverageSignedCrossTrackError() {
1545
- return averageSignedCrossTrackError;
1546
- }
1547
- public Duration getGapToLeaderInOwnTime() {
1548
- return gapToLeaderInOwnTime;
1549
- }
1550
- public Duration getCorrectedTime() {
1551
- return correctedTime;
1552
- }
1553
- public Duration getCorrectedTimeAtEstimatedArrivalAtCompetitorFarthestAhead() {
1554
- return correctedTimeAtEstimatedArrivalAtCompetitorFarthestAhead;
1555
- }
1556
- }
1557
-
1558
- /**
1559
- * If <code>timePoint</code> is after the end of the race's tracking the query will be adjusted to obtain the values
1560
- * at the end of the {@link TrackedRace#getEndOfTracking() race's tracking time}. If the time point adjusted this
1561
- * way equals the end of the tracking time, the query results will be looked up in a cache first and if not found,
1562
- * they will be stored to the cache after calculating them. A cache invalidation {@link RaceChangeListener listener}
1563
- * will be registered with the race which will be triggered for any event received by the race.
1564
- * @param waitForLatestAnalyses
1565
- * if <code>false</code>, this method is allowed to read the maneuver analysis results from a cache that
1566
- * may not reflect all data already received; otherwise, the method will always block for the latest
1567
- * cache updates to have happened before returning.
1568
- */
1569
- private RaceDetails getRaceDetails(TrackedRace trackedRace, Competitor competitor, TimePoint timePoint,
1570
- boolean waitForLatestAnalyses, Map<Leg, LinkedHashMap<Competitor, Integer>> legRanksCache,
1571
- RankingInfo rankingInfo, WindLegTypeAndLegBearingCache cache) throws InterruptedException, ExecutionException {
1572
- final RaceDetails raceDetails;
1573
- if (trackedRace.getEndOfTracking() != null && trackedRace.getEndOfTracking().compareTo(timePoint) < 0) {
1574
- raceDetails = getRaceDetailsForEndOfTrackingFromCacheOrCalculateAndCache(trackedRace, competitor, legRanksCache, rankingInfo, cache);
1575
- } else {
1576
- raceDetails = calculateRaceDetails(trackedRace, competitor, timePoint, waitForLatestAnalyses, legRanksCache, cache, rankingInfo);
1577
- }
1578
- return raceDetails;
1579
- }
1580
-
1581
- private RaceDetails getRaceDetailsForEndOfTrackingFromCacheOrCalculateAndCache(final TrackedRace trackedRace,
1582
- final Competitor competitor, final Map<Leg, LinkedHashMap<Competitor, Integer>> legRanksCache,
1583
- RankingInfo rankingInfo, final WindLegTypeAndLegBearingCache cache) throws InterruptedException, ExecutionException {
1584
- final com.sap.sse.common.Util.Pair<TrackedRace, Competitor> key = new com.sap.sse.common.Util.Pair<TrackedRace, Competitor>(trackedRace, competitor);
1585
- RunnableFuture<RaceDetails> raceDetails;
1586
- synchronized (raceDetailsAtEndOfTrackingCache) {
1587
- raceDetails = raceDetailsAtEndOfTrackingCache.get(key);
1588
- if (raceDetails == null) {
1589
- raceDetails = new FutureTask<RaceDetails>(new Callable<RaceDetails>() {
1590
- @Override
1591
- public RaceDetails call() throws Exception {
1592
- TimePoint end = trackedRace.getEndOfRace();
1593
- if (end == null) {
1594
- end = trackedRace.getEndOfTracking();
1595
- }
1596
- return calculateRaceDetails(trackedRace, competitor, end,
1597
- // TODO see bug 1358: for now, use waitForLatest==false until we've switched to optimistic locking for the course read lock
1598
- /* TODO old comment when it was still true: "because this is done only once after end of tracking" */
1599
- /* waitForLatestAnalyses (maneuver and cross track error) */ false,
1600
- legRanksCache, cache, rankingInfo);
1601
- }
1602
- });
1603
- raceDetailsExecutor.execute(raceDetails);
1604
- raceDetailsAtEndOfTrackingCache.put(key, raceDetails);
1605
- final CacheInvalidationListener cacheInvalidationListener = new CacheInvalidationListener(trackedRace, competitor);
1606
- trackedRace.addListener(cacheInvalidationListener);
1607
- cacheInvalidationListeners.add(cacheInvalidationListener);
1608
- }
1609
- }
1610
- return raceDetails.get();
1611
- }
1612
-
1613
- private RaceDetails calculateRaceDetails(TrackedRace trackedRace, Competitor competitor, TimePoint timePoint,
1614
- boolean waitForLatestAnalyses, Map<Leg, LinkedHashMap<Competitor, Integer>> legRanksCache,
1615
- WindLegTypeAndLegBearingCache cache, final RankingInfo rankingInfo) {
1616
- final List<LegEntryDTO> legDetails = new ArrayList<LegEntryDTO>();
1617
- final Course course = trackedRace.getRace().getCourse();
1618
- course.lockForRead(); // hold back any course re-configurations while looping over the legs
1619
- try {
1620
- for (Leg leg : course.getLegs()) {
1621
- LegEntryDTO legEntry;
1622
- // We loop over a copy of the course's legs; during a course change, legs may become "stale," even with
1623
- // regard to the leg/trackedLeg structures inside the tracked race which is updated by the course change
1624
- // immediately. That's why we've acquired a read lock for the course above.
1625
- TrackedLegOfCompetitor trackedLeg = trackedRace.getTrackedLeg(competitor, leg);
1626
- if (trackedLeg != null && trackedLeg.hasStartedLeg(timePoint)) {
1627
- legEntry = createLegEntry(trackedLeg, timePoint, waitForLatestAnalyses, legRanksCache, rankingInfo, cache);
1628
- } else {
1629
- legEntry = null;
1630
- }
1631
- legDetails.add(legEntry);
1632
- }
1633
- final Distance windwardDistanceToCompetitorFarthestAhead = trackedRace == null ? null : trackedRace
1634
- .getWindwardDistanceToCompetitorFarthestAhead(competitor, timePoint, WindPositionMode.LEG_MIDDLE, rankingInfo, cache);
1635
- Distance averageAbsoluteCrossTrackError;
1636
- try {
1637
- averageAbsoluteCrossTrackError = trackedRace == null ? null : trackedRace.getAverageAbsoluteCrossTrackError(
1638
- competitor, timePoint, waitForLatestAnalyses, cache);
1639
- } catch (NoWindException nwe) {
1640
- // without wind information, use null meaning "unknown"
1641
- averageAbsoluteCrossTrackError = null;
1642
- }
1643
- Distance averageSignedCrossTrackError;
1644
- try {
1645
- averageSignedCrossTrackError = trackedRace == null ? null : trackedRace.getAverageSignedCrossTrackError(
1646
- competitor, timePoint, waitForLatestAnalyses, cache);
1647
- } catch (NoWindException nwe) {
1648
- // without wind information, use null meaning "unknown"
1649
- averageSignedCrossTrackError = null;
1650
- }
1651
- final CompetitorRankingInfo competitorRankingInfo = rankingInfo.getCompetitorRankingInfo().apply(competitor);
1652
- return new RaceDetails(legDetails, windwardDistanceToCompetitorFarthestAhead, averageAbsoluteCrossTrackError, averageSignedCrossTrackError,
1653
- trackedRace.getRankingMetric().getGapToLeaderInOwnTime(rankingInfo, competitor, cache),
1654
- trackedRace.getRankingMetric().getCorrectedTime(competitor, timePoint),
1655
- competitorRankingInfo == null ? null : competitorRankingInfo.getCorrectedTimeAtEstimatedArrivalAtCompetitorFarthestAhead());
1656
- } finally {
1657
- course.unlockAfterRead();
1658
- }
1659
- }
1660
-
1661
- private LegEntryDTO createLegEntry(TrackedLegOfCompetitor trackedLeg, TimePoint timePoint,
1662
- boolean waitForLatestAnalyses, Map<Leg, LinkedHashMap<Competitor, Integer>> legRanksCache,
1663
- RankingInfo rankingInfo, WindLegTypeAndLegBearingCache cache) {
1664
- LegEntryDTO result;
1665
- final Duration time = trackedLeg.getTime(timePoint);
1666
- if (trackedLeg == null || time == null) {
1667
- result = null;
1668
- } else {
1669
- result = new LegEntryDTO();
1670
- try {
1671
- result.legType = trackedLeg.getTrackedLeg().getLegType(timePoint);
1672
- } catch (NoWindException nwe) {
1673
- result.legType = null; // can't determine leg type without wind data
1674
- }
1675
- final Speed averageSpeedOverGround = trackedLeg.getAverageSpeedOverGround(timePoint);
1676
- result.averageSpeedOverGroundInKnots = averageSpeedOverGround == null ? null : averageSpeedOverGround.getKnots();
1677
- Distance averageAbsoluteCrossTrackError;
1678
- try {
1679
- averageAbsoluteCrossTrackError = trackedLeg.getAverageAbsoluteCrossTrackError(timePoint, waitForLatestAnalyses);
1680
- } catch (NoWindException nwe) {
1681
- // leave averageAbsoluteCrossTrackError as null, meaning "unknown"
1682
- averageAbsoluteCrossTrackError = null;
1683
- }
1684
- result.averageAbsoluteCrossTrackErrorInMeters = averageAbsoluteCrossTrackError == null ? null : averageAbsoluteCrossTrackError.getMeters();
1685
- Distance averageSignedCrossTrackError;
1686
- try {
1687
- averageSignedCrossTrackError = trackedLeg.getAverageSignedCrossTrackError(timePoint, waitForLatestAnalyses);
1688
- } catch (NoWindException nwe) {
1689
- // leave averageSignedCrossTrackError as null, meaning "unknown"
1690
- averageSignedCrossTrackError = null;
1691
- }
1692
- result.averageSignedCrossTrackErrorInMeters = averageSignedCrossTrackError == null ? null : averageSignedCrossTrackError.getMeters();
1693
- Double speedOverGroundInKnots;
1694
- if (trackedLeg.hasFinishedLeg(timePoint)) {
1695
- speedOverGroundInKnots = averageSpeedOverGround == null ? null : averageSpeedOverGround.getKnots();
1696
- } else {
1697
- final SpeedWithBearing speedOverGround = trackedLeg.getSpeedOverGround(timePoint);
1698
- speedOverGroundInKnots = speedOverGround == null ? null : speedOverGround.getKnots();
1699
- }
1700
- result.currentSpeedOverGroundInKnots = speedOverGroundInKnots == null ? null : speedOverGroundInKnots;
1701
- Distance distanceTraveled = trackedLeg.getDistanceTraveled(timePoint);
1702
- result.distanceTraveledInMeters = distanceTraveled == null ? null : distanceTraveled.getMeters();
1703
- Distance distanceTraveledConsideringGateStart = trackedLeg.getDistanceTraveledConsideringGateStart(timePoint);
1704
- result.distanceTraveledIncludingGateStartInMeters = distanceTraveledConsideringGateStart == null ? null : distanceTraveledConsideringGateStart.getMeters();
1705
- final Duration estimatedTimeToNextMarkInSeconds = trackedLeg.getEstimatedTimeToNextMark(timePoint, WindPositionMode.EXACT, cache);
1706
- result.estimatedTimeToNextWaypointInSeconds = estimatedTimeToNextMarkInSeconds==null?null:estimatedTimeToNextMarkInSeconds.asSeconds();
1707
- result.timeInMilliseconds = time.asMillis();
1708
- result.finished = trackedLeg.hasFinishedLeg(timePoint);
1709
- final TimePoint legFinishTime = trackedLeg.getFinishTime();
1710
- result.correctedTotalTime = trackedLeg.hasStartedLeg(timePoint) ? trackedLeg.getTrackedLeg().getTrackedRace().getRankingMetric().getCorrectedTime(trackedLeg.getCompetitor(),
1711
- trackedLeg.hasFinishedLeg(timePoint) ? legFinishTime : timePoint, cache) : null;
1712
- // fetch the leg gap in own corrected time from the ranking metric
1713
- final Duration gapToLeaderInOwnTime = trackedLeg.getTrackedLeg().getTrackedRace().getRankingMetric().
1714
- getLegGapToLegLeaderInOwnTime(trackedLeg, timePoint, rankingInfo, cache);
1715
- result.gapToLeaderInSeconds = gapToLeaderInOwnTime == null ? null : gapToLeaderInOwnTime.asSeconds();
1716
- if (result.gapToLeaderInSeconds != null) {
1717
- final Duration gapAtEndOfPreviousLeg = getGapAtEndOfPreviousLeg(trackedLeg, rankingInfo, cache);
1718
- if (gapAtEndOfPreviousLeg != null) {
1719
- result.gapChangeSinceLegStartInSeconds = result.gapToLeaderInSeconds - gapAtEndOfPreviousLeg.asSeconds();
1720
- }
1721
- }
1722
- LinkedHashMap<Competitor, Integer> legRanks = legRanksCache.get(trackedLeg.getLeg());
1723
- if (legRanks != null) {
1724
- result.rank = legRanks.get(trackedLeg.getCompetitor());
1725
- } else {
1726
- result.rank = trackedLeg.getRank(timePoint, cache);
1727
- }
1728
- result.started = trackedLeg.hasStartedLeg(timePoint);
1729
- Speed velocityMadeGood;
1730
- if (trackedLeg.hasFinishedLeg(timePoint)) {
1731
- velocityMadeGood = trackedLeg.getAverageVelocityMadeGood(timePoint);
1732
- } else {
1733
- velocityMadeGood = trackedLeg.getVelocityMadeGood(timePoint, WindPositionMode.EXACT);
1734
- }
1735
- result.velocityMadeGoodInKnots = velocityMadeGood == null ? null : velocityMadeGood.getKnots();
1736
- Distance windwardDistanceToGo = trackedLeg.getWindwardDistanceToGo(timePoint, WindPositionMode.LEG_MIDDLE);
1737
- result.windwardDistanceToGoInMeters = windwardDistanceToGo == null ? null : windwardDistanceToGo
1738
- .getMeters();
1739
- final TimePoint startOfRace = trackedLeg.getTrackedLeg().getTrackedRace().getStartOfRace();
1740
- if (startOfRace != null && trackedLeg.hasStartedLeg(timePoint)) {
1741
- // not using trackedLeg.getManeuvers(...) because it may not catch the mark passing maneuver starting this leg
1742
- // because that may have been detected as slightly before the mark passing time, hence associated with the previous leg
1743
- List<Maneuver> maneuvers = trackedLeg.getTrackedLeg().getTrackedRace()
1744
- .getManeuvers(trackedLeg.getCompetitor(), startOfRace, timePoint, waitForLatestAnalyses);
1745
- if (maneuvers != null) {
1746
- result.numberOfManeuvers = new HashMap<ManeuverType, Integer>();
1747
- result.numberOfManeuvers.put(ManeuverType.TACK, 0);
1748
- result.numberOfManeuvers.put(ManeuverType.JIBE, 0);
1749
- result.numberOfManeuvers.put(ManeuverType.PENALTY_CIRCLE, 0);
1750
- Map<ManeuverType, Double> totalManeuverLossInMeters = new HashMap<ManeuverType, Double>();
1751
- totalManeuverLossInMeters.put(ManeuverType.TACK, 0.0);
1752
- totalManeuverLossInMeters.put(ManeuverType.JIBE, 0.0);
1753
- totalManeuverLossInMeters.put(ManeuverType.PENALTY_CIRCLE, 0.0);
1754
- TimePoint startOfLeg = trackedLeg.getStartTime();
1755
- for (Maneuver maneuver : maneuvers) {
1756
- // don't count maneuvers that were in previous legs
1757
- switch (maneuver.getType()) {
1758
- case TACK:
1759
- case JIBE:
1760
- case PENALTY_CIRCLE:
1761
- if (!maneuver.getTimePoint().before(startOfLeg) && (legFinishTime == null || legFinishTime.after(timePoint) ||
1762
- maneuver.getTimePoint().before(legFinishTime))) {
1763
- if (maneuver.getManeuverLoss() != null) {
1764
- result.numberOfManeuvers.put(maneuver.getType(),
1765
- result.numberOfManeuvers.get(maneuver.getType()) + 1);
1766
- totalManeuverLossInMeters.put(maneuver.getType(),
1767
- totalManeuverLossInMeters.get(maneuver.getType())
1768
- + maneuver.getManeuverLoss().getMeters());
1769
- }
1770
- }
1771
- break;
1772
- case MARK_PASSING:
1773
- // analyze all mark passings, not only those after this leg's start, to catch the mark passing
1774
- // maneuver starting this leg, even if its time point is slightly before the mark passing starting this leg
1775
- MarkPassingManeuver mpm = (MarkPassingManeuver) maneuver;
1776
- if (mpm.getWaypointPassed() == trackedLeg.getLeg().getFrom()) {
1777
- result.sideToWhichMarkAtLegStartWasRounded = mpm.getSide();
1778
- }
1779
- break;
1780
- default:
1781
- /* Do nothing here.
1782
- * Throwing an exception destroys the toggling (and maybe other behaviour) of the leaderboard.
1783
- */
1784
- }
1785
- }
1786
- result.averageManeuverLossInMeters = new HashMap<ManeuverType, Double>();
1787
- for (ManeuverType maneuverType : new ManeuverType[] { ManeuverType.TACK, ManeuverType.JIBE,
1788
- ManeuverType.PENALTY_CIRCLE }) {
1789
- if (result.numberOfManeuvers.get(maneuverType) != 0) {
1790
- result.averageManeuverLossInMeters.put(
1791
- maneuverType,
1792
- totalManeuverLossInMeters.get(maneuverType)
1793
- / result.numberOfManeuvers.get(maneuverType));
1794
- }
1795
- }
1796
- }
1797
- }
1798
- }
1799
- return result;
1800
- }
1801
-
1802
- private Duration getGapAtEndOfPreviousLeg(TrackedLegOfCompetitor trackedLeg, final RankingInfo rankingInfo, WindLegTypeAndLegBearingCache cache) {
1803
- final Duration result;
1804
- final Course course = trackedLeg.getTrackedLeg().getTrackedRace().getRace().getCourse();
1805
- // if trackedLeg is the first leg, compute the gap at the start of this leg; otherwise, compute gap
1806
- // at the end of the previous leg
1807
- final TimePoint timePoint = trackedLeg.getStartTime();
1808
- final TrackedLegOfCompetitor tloc;
1809
- if (course.getFirstWaypoint() == trackedLeg.getLeg().getFrom()) {
1810
- tloc = trackedLeg;
1811
- } else {
1812
- tloc = trackedLeg.getTrackedLeg().getTrackedRace().getTrackedLegFinishingAt(trackedLeg.getLeg().getFrom())
1813
- .getTrackedLeg(trackedLeg.getCompetitor());
1814
- }
1815
- result = trackedLeg.getTrackedLeg().getTrackedRace().getRankingMetric().getLegGapToLegLeaderInOwnTime(tloc, timePoint, rankingInfo, cache);
1816
- return result;
1817
- }
1818
-
1819
- private LeaderboardDTO getLiveLeaderboard(Collection<String> namesOfRaceColumnsForWhichToLoadLegDetails,
1820
- boolean addOverallDetails, TrackedRegattaRegistry trackedRegattaRegistry, DomainFactory baseDomainFactory) throws NoWindException, ExecutionException {
1821
- LiveLeaderboardUpdater liveLeaderboardUpdater = getLiveLeaderboardUpdater(trackedRegattaRegistry,
1822
- baseDomainFactory);
1823
- return liveLeaderboardUpdater.getLiveLeaderboard(namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails);
1824
- }
1825
-
1826
- private LiveLeaderboardUpdater getLiveLeaderboardUpdater(TrackedRegattaRegistry trackedRegattaRegistry,
1827
- DomainFactory baseDomainFactory) {
1828
- LiveLeaderboardUpdater result = this.liveLeaderboardUpdater;
1829
- if (result == null) {
1830
- synchronized (this) {
1831
- result = this.liveLeaderboardUpdater;
1832
- if (result == null) {
1833
- this.liveLeaderboardUpdater = new LiveLeaderboardUpdater(this, trackedRegattaRegistry, baseDomainFactory);
1834
- result = this.liveLeaderboardUpdater;
1835
- }
1836
- }
1837
- }
1838
- return result;
1839
- }
1840
-
1841
- private LeaderboardDTOCache getLeaderboardDTOCache() {
1842
- LeaderboardDTOCache result = this.leaderboardDTOCache;
1843
- if (result == null) {
1844
- synchronized (this) {
1845
- result = this.leaderboardDTOCache;
1846
- if (result == null) {
1847
- // The leaderboard cache is invalidated upon all competitor and mark position changes; some analyzes
1848
- // are pretty expensive, such as the maneuver re-calculation. Waiting for the latest analysis after only a
1849
- // single fix was updated is too expensive if users use the replay feature while a race is still running.
1850
- // Therefore, using waitForLatestAnalyses==false seems appropriate here.
1851
- this.leaderboardDTOCache = new LeaderboardDTOCache(/* waitForLatestAnalyses */false, this);
1852
- result = this.leaderboardDTOCache;
1853
- }
1854
- }
1855
- }
1856
- return result;
1857
- }
1858
-
1859
- @Override
1860
- public LeaderboardDTO getLeaderboardDTO(TimePoint timePoint,
1861
- Collection<String> namesOfRaceColumnsForWhichToLoadLegDetails, boolean addOverallDetails,
1862
- TrackedRegattaRegistry trackedRegattaRegistry, DomainFactory baseDomainFactory, boolean fillNetPointsUncorrected) throws NoWindException,
1863
- InterruptedException, ExecutionException {
1864
- LeaderboardDTO result = null;
1865
- if (timePoint == null) {
1866
- // timePoint==null means live mode; however, if we're after the end of all races and after all score
1867
- // corrections, don't use the live leaderboard updater which would keep re-calculating over and over again, but map
1868
- // this to a usual non-live call which uses the regular LeaderboardDTOCache which is invalidated properly
1869
- // when the tracked race associations or score corrections or tracked race contents changes:
1870
- final TimePoint nowMinusDelay = this.getNowMinusDelay();
1871
- final TimePoint timePointOfLatestModification = this.getTimePointOfLatestModification();
1872
- if (fillNetPointsUncorrected || (timePointOfLatestModification != null && !nowMinusDelay.before(timePointOfLatestModification))) {
1873
- // if there hasn't been any modification to the leaderboard since nowMinusDelay, use non-live mode
1874
- // and pull the result from the regular leaderboard cache:
1875
- timePoint = timePointOfLatestModification;
1876
- } else {
1877
- // don't use the regular leaderboard cache; the race still seems to be on; use the live leaderboard updater instead:
1878
- timePoint = null;
1879
- result = this.getLiveLeaderboard(namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails, trackedRegattaRegistry, baseDomainFactory);
1880
- }
1881
- }
1882
- if (timePoint != null) {
1883
- if (fillNetPointsUncorrected) {
1884
- // explicitly filling the uncorrected net points requires uncached recalculation
1885
- result = computeDTO(timePoint, namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails, /* waitForLatestAnalyses */ true,
1886
- trackedRegattaRegistry, baseDomainFactory, fillNetPointsUncorrected);
1887
- } else {
1888
- // in replay we'd like up-to-date results; they are still cached
1889
- // which is OK because the cache is invalidated whenever any of the tracked races attached to the
1890
- // leaderboard changes.
1891
- result = getLeaderboardDTOCache().getLeaderboardByName(timePoint,
1892
- namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails, baseDomainFactory,
1893
- trackedRegattaRegistry);
1894
- }
1895
- }
1896
- return result;
1897
- }
1898
-
1899
- public String toString() {
1900
- return getName() + " " + (getDefaultCourseArea() != null ? getDefaultCourseArea().getName() : "<No course area defined>") + " " + (getScoringScheme() != null ? getScoringScheme().getType().name() : "<No scoring scheme set>");
1901
- }
1902
-
1903
- @Override
1904
- public NumberOfCompetitorsInLeaderboardFetcher getNumberOfCompetitorsInLeaderboardFetcher() {
1905
- return new NumberOfCompetitorsFetcherImpl();
1906
- }
1907
-}
1
+package com.sap.sailing.domain.leaderboard.impl;
2
+
3
+import java.io.IOException;
4
+import java.io.ObjectInputStream;
5
+import java.util.ArrayList;
6
+import java.util.Collection;
7
+import java.util.Collections;
8
+import java.util.Comparator;
9
+import java.util.Date;
10
+import java.util.HashMap;
11
+import java.util.HashSet;
12
+import java.util.Iterator;
13
+import java.util.LinkedHashMap;
14
+import java.util.List;
15
+import java.util.Map;
16
+import java.util.NavigableSet;
17
+import java.util.NoSuchElementException;
18
+import java.util.Set;
19
+import java.util.UUID;
20
+import java.util.concurrent.Callable;
21
+import java.util.concurrent.ExecutionException;
22
+import java.util.concurrent.Executor;
23
+import java.util.concurrent.Executors;
24
+import java.util.concurrent.Future;
25
+import java.util.concurrent.FutureTask;
26
+import java.util.concurrent.LinkedBlockingQueue;
27
+import java.util.concurrent.RunnableFuture;
28
+import java.util.concurrent.ThreadPoolExecutor;
29
+import java.util.concurrent.TimeUnit;
30
+import java.util.logging.Level;
31
+import java.util.logging.Logger;
32
+
33
+import com.sap.sailing.domain.abstractlog.race.InvalidatesLeaderboardCache;
34
+import com.sap.sailing.domain.abstractlog.race.RaceLogEvent;
35
+import com.sap.sailing.domain.base.Competitor;
36
+import com.sap.sailing.domain.base.Course;
37
+import com.sap.sailing.domain.base.DomainFactory;
38
+import com.sap.sailing.domain.base.Fleet;
39
+import com.sap.sailing.domain.base.Leg;
40
+import com.sap.sailing.domain.base.RaceColumn;
41
+import com.sap.sailing.domain.base.RaceColumnInSeries;
42
+import com.sap.sailing.domain.base.RaceColumnListener;
43
+import com.sap.sailing.domain.base.Waypoint;
44
+import com.sap.sailing.domain.common.Distance;
45
+import com.sap.sailing.domain.common.LeaderboardType;
46
+import com.sap.sailing.domain.common.LegType;
47
+import com.sap.sailing.domain.common.ManeuverType;
48
+import com.sap.sailing.domain.common.MaxPointsReason;
49
+import com.sap.sailing.domain.common.NoWindError;
50
+import com.sap.sailing.domain.common.NoWindException;
51
+import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
52
+import com.sap.sailing.domain.common.RegattaNameAndRaceName;
53
+import com.sap.sailing.domain.common.Speed;
54
+import com.sap.sailing.domain.common.SpeedWithBearing;
55
+import com.sap.sailing.domain.common.dto.BasicRaceDTO;
56
+import com.sap.sailing.domain.common.dto.CompetitorDTO;
57
+import com.sap.sailing.domain.common.dto.FleetDTO;
58
+import com.sap.sailing.domain.common.dto.LeaderboardDTO;
59
+import com.sap.sailing.domain.common.dto.LeaderboardEntryDTO;
60
+import com.sap.sailing.domain.common.dto.LeaderboardRowDTO;
61
+import com.sap.sailing.domain.common.dto.LegEntryDTO;
62
+import com.sap.sailing.domain.common.dto.MetaLeaderboardRaceColumnDTO;
63
+import com.sap.sailing.domain.common.dto.RaceColumnDTO;
64
+import com.sap.sailing.domain.common.dto.RaceDTO;
65
+import com.sap.sailing.domain.common.tracking.GPSFixMoving;
66
+import com.sap.sailing.domain.leaderboard.Leaderboard;
67
+import com.sap.sailing.domain.leaderboard.NumberOfCompetitorsInLeaderboardFetcher;
68
+import com.sap.sailing.domain.leaderboard.ResultDiscardingRule;
69
+import com.sap.sailing.domain.leaderboard.ScoreCorrection.Result;
70
+import com.sap.sailing.domain.leaderboard.SettableScoreCorrection;
71
+import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule;
72
+import com.sap.sailing.domain.leaderboard.caching.LeaderboardDTOCache;
73
+import com.sap.sailing.domain.leaderboard.caching.LeaderboardDTOCalculationReuseCache;
74
+import com.sap.sailing.domain.leaderboard.caching.LiveLeaderboardUpdater;
75
+import com.sap.sailing.domain.leaderboard.meta.MetaLeaderboardColumn;
76
+import com.sap.sailing.domain.racelog.RaceLogIdentifier;
77
+import com.sap.sailing.domain.ranking.RankingMetric.CompetitorRankingInfo;
78
+import com.sap.sailing.domain.ranking.RankingMetric.RankingInfo;
79
+import com.sap.sailing.domain.tracking.GPSFixTrack;
80
+import com.sap.sailing.domain.tracking.Maneuver;
81
+import com.sap.sailing.domain.tracking.MarkPassing;
82
+import com.sap.sailing.domain.tracking.MarkPassingManeuver;
83
+import com.sap.sailing.domain.tracking.RaceChangeListener;
84
+import com.sap.sailing.domain.tracking.TrackedLeg;
85
+import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor;
86
+import com.sap.sailing.domain.tracking.TrackedRace;
87
+import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
88
+import com.sap.sailing.domain.tracking.WindLegTypeAndLegBearingCache;
89
+import com.sap.sailing.domain.tracking.WindPositionMode;
90
+import com.sap.sailing.domain.tracking.impl.AbstractRaceChangeListener;
91
+import com.sap.sailing.util.impl.RaceColumnListeners;
92
+import com.sap.sse.common.Duration;
93
+import com.sap.sse.common.TimePoint;
94
+import com.sap.sse.common.Util;
95
+import com.sap.sse.common.impl.MillisecondsTimePoint;
96
+import com.sap.sse.concurrent.LockUtil;
97
+import com.sap.sse.concurrent.NamedReentrantReadWriteLock;
98
+import com.sap.sse.util.impl.ThreadFactoryWithPriority;
99
+
100
+/**
101
+ * Base implementation for various types of leaderboards. The {@link RaceColumnListener} implementation forwards events
102
+ * received to all {@link RaceColumnListener} subscribed with this leaderboard. To which objects this leaderboard subscribes
103
+ * as {@link RaceColumnListener} is left to the concrete subclasses to implement, but the race columns seem like useful
104
+ * candidates.
105
+ *
106
+ * @author Axel Uhl (d043530)
107
+ *
108
+ */
109
+public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, RaceColumnListener {
110
+ private static final long serialVersionUID = 330156778603279333L;
111
+
112
+ private static final Logger logger = Logger.getLogger(AbstractSimpleLeaderboardImpl.class.getName());
113
+
114
+ static final Double DOUBLE_0 = new Double(0);
115
+
116
+ private final SettableScoreCorrection scoreCorrection;
117
+
118
+ private ThresholdBasedResultDiscardingRule crossLeaderboardResultDiscardingRule;
119
+
120
+ /**
121
+ * The optional display name mappings for competitors. This allows a user to override the tracking-provided
122
+ * competitor names for display in a leaderboard.
123
+ */
124
+ private final Map<Competitor, String> displayNames;
125
+
126
+ /** the display name of the leaderboard */
127
+ private String displayName;
128
+
129
+ /**
130
+ * Backs the {@link #getCarriedPoints(Competitor)} API with data. Can be used to prime this leaderboard
131
+ * with aggregated results of races not tracked / displayed by this leaderboard in detail. The points
132
+ * provided by this map are considered by {@link #getTotalPoints(Competitor, TimePoint)}.
133
+ */
134
+ private final Map<Competitor, Double> carriedPoints;
135
+
136
+ private final RaceColumnListeners raceColumnListeners;
137
+
138
+ /**
139
+ * A set that manages the difference between {@link #getCompetitors()} and {@link #getAllCompetitors()}. Access
140
+ * is controlled by the {@link #suppressedCompetitorsLock} lock.
141
+ */
142
+ private final Set<Competitor> suppressedCompetitors;
143
+ private final NamedReentrantReadWriteLock suppressedCompetitorsLock;
144
+
145
+ private transient Map<com.sap.sse.common.Util.Pair<TrackedRace, Competitor>, RunnableFuture<RaceDetails>> raceDetailsAtEndOfTrackingCache;
146
+
147
+ /**
148
+ * This executor needs to be a different one than {@link #executor} because the tasks run by {@link #executor}
149
+ * can depend on the results of the tasks run by {@link #raceDetailsExecutor}, and an {@link Executor} doesn't
150
+ * move a task that is blocked by waiting for another {@link FutureTask} to the side but blocks permanently,
151
+ * ending in a deadlock (one that cannot easily be detected by the Eclipse debugger either).
152
+ */
153
+ private transient Executor raceDetailsExecutor;
154
+
155
+ /**
156
+ * Used to remove all these listeners from their tracked races when this servlet is {@link #destroy() destroyed}.
157
+ */
158
+ private transient Set<CacheInvalidationListener> cacheInvalidationListeners;
159
+
160
+ private transient ThreadPoolExecutor executor;
161
+
162
+ private transient LiveLeaderboardUpdater liveLeaderboardUpdater;
163
+
164
+ private transient LeaderboardDTOCache leaderboardDTOCache;
165
+
166
+ /**
167
+ * A leaderboard entry representing a snapshot of a cell at a given time point for a single race/competitor.
168
+ *
169
+ * @author Axel Uhl (d043530)
170
+ *
171
+ */
172
+ class EntryImpl implements Entry {
173
+ private final Callable<Integer> trackedRankProvider;
174
+ private final Double netPoints;
175
+ private final Callable<Double> netPointsUncorrectedProvider;
176
+ private final boolean isNetPointsCorrected;
177
+ private final Double totalPoints;
178
+ private final MaxPointsReason maxPointsReason;
179
+ private final boolean discarded;
180
+ private final Fleet fleet;
181
+
182
+ private EntryImpl(Callable<Integer> trackedRankProvider, Double netPoints,
183
+ Callable<Double> netPointsUncorrectedProvider, boolean isNetPointsCorrected,
184
+ Double totalPoints, MaxPointsReason maxPointsReason, boolean discarded, Fleet fleet) {
185
+ super();
186
+ this.trackedRankProvider = trackedRankProvider;
187
+ this.netPoints = netPoints;
188
+ this.netPointsUncorrectedProvider = netPointsUncorrectedProvider;
189
+ this.isNetPointsCorrected = isNetPointsCorrected;
190
+ this.totalPoints = totalPoints;
191
+ this.maxPointsReason = maxPointsReason;
192
+ this.discarded = discarded;
193
+ this.fleet = fleet;
194
+ }
195
+ @Override
196
+ public int getTrackedRank() {
197
+ try {
198
+ return trackedRankProvider.call();
199
+ } catch (Exception e) {
200
+ throw new RuntimeException(e);
201
+ }
202
+ }
203
+ @Override
204
+ public Double getNetPoints() {
205
+ return netPoints;
206
+ }
207
+ @Override
208
+ public boolean isNetPointsCorrected() {
209
+ return isNetPointsCorrected;
210
+ }
211
+ @Override
212
+ public Double getTotalPoints() {
213
+ return totalPoints;
214
+ }
215
+ @Override
216
+ public MaxPointsReason getMaxPointsReason() {
217
+ return maxPointsReason;
218
+ }
219
+ @Override
220
+ public boolean isDiscarded() {
221
+ return discarded;
222
+ }
223
+ @Override
224
+ public Fleet getFleet() {
225
+ return fleet;
226
+ }
227
+ @Override
228
+ public Double getNetPointsUncorrected() {
229
+ try {
230
+ return netPointsUncorrectedProvider.call();
231
+ } catch (Exception e) {
232
+ throw new RuntimeException(e);
233
+ }
234
+ }
235
+ }
236
+
237
+ /**
238
+ * Computing the competitors can be a bit expensive, particularly if the fleet is large and there may be suppressed
239
+ * competitors, and the leaderboard may be a meta-leaderboard that refers to other leaderboards which each have
240
+ * several tracked races attached from where the competitors need to be retrieved. Ideally, the competitors list
241
+ * would be cached, but that is again difficult because we would have to monitor all changes in all dependent
242
+ * leaderboards and columns and tracked races properly.
243
+ * <p>
244
+ *
245
+ * As it turns out, one of the most frequent uses of the {@link AbstractSimpleLeaderboardImpl#getCompetitors}
246
+ * competitors list is to determine their number which in turn is only required for high-point scoring systems and
247
+ * for computing the default score for penalties. Again, the most frequently used low-point family of scoring schemes
248
+ * does not require this number. Yet, the scoring scheme requires an argument for polymorphic use by those that
249
+ * need it. Instead of computing it for each call, this interface lets us defer the actual calculation until the
250
+ * point when it's really needed. Once asked, this object will cache the result. Therefore, a new one should be
251
+ * constructed each time the number shall be computed.
252
+ *
253
+ * @author Axel Uhl (D043530)
254
+ *
255
+ */
256
+ public class NumberOfCompetitorsFetcherImpl implements NumberOfCompetitorsInLeaderboardFetcher {
257
+ private int numberOfCompetitors = -1;
258
+
259
+ @Override
260
+ public int getNumberOfCompetitorsInLeaderboard() {
261
+ if (numberOfCompetitors == -1) {
262
+ numberOfCompetitors = Util.size(getCompetitors());
263
+ }
264
+ return numberOfCompetitors;
265
+ }
266
+ }
267
+
268
+ /**
269
+ * Handles the invalidation of the {@link SailingServiceImpl#raceDetailsAtEndOfTrackingCache} entries if the tracked
270
+ * race changes in any way. In particular, for {@link #statusChanged}, when the status changes away from LOADING,
271
+ * calculations may start or resume, making it necessary to clear the cache.
272
+ *
273
+ * @author Axel Uhl (D043530)
274
+ *
275
+ */
276
+ private class CacheInvalidationListener extends AbstractRaceChangeListener {
277
+ private final TrackedRace trackedRace;
278
+ private final Competitor competitor;
279
+
280
+ public CacheInvalidationListener(TrackedRace trackedRace, Competitor competitor) {
281
+ this.trackedRace = trackedRace;
282
+ this.competitor = competitor;
283
+ }
284
+
285
+ public TrackedRace getTrackedRace() {
286
+ return trackedRace;
287
+ }
288
+
289
+ public void removeFromTrackedRace() {
290
+ trackedRace.removeListener(this);
291
+ }
292
+
293
+ private void invalidateCacheAndRemoveThisListenerFromTrackedRace() {
294
+ synchronized (raceDetailsAtEndOfTrackingCache) {
295
+ raceDetailsAtEndOfTrackingCache.remove(new com.sap.sse.common.Util.Pair<TrackedRace, Competitor>(trackedRace, competitor));
296
+ removeFromTrackedRace();
297
+ }
298
+ }
299
+
300
+ @Override
301
+ protected void defaultAction() {
302
+ invalidateCacheAndRemoveThisListenerFromTrackedRace();
303
+ }
304
+ }
305
+
306
+ private static class UUIDGenerator implements LeaderboardDTO.UUIDGenerator {
307
+ @Override
308
+ public String generateRandomUUID() {
309
+ return UUID.randomUUID().toString();
310
+ }
311
+ }
312
+
313
+ public AbstractSimpleLeaderboardImpl(ThresholdBasedResultDiscardingRule resultDiscardingRule) {
314
+ this.carriedPoints = new HashMap<Competitor, Double>();
315
+ this.scoreCorrection = createScoreCorrection();
316
+ this.displayNames = new HashMap<Competitor, String>();
317
+ this.crossLeaderboardResultDiscardingRule = resultDiscardingRule;
318
+ this.suppressedCompetitors = new HashSet<Competitor>();
319
+ this.suppressedCompetitorsLock = new NamedReentrantReadWriteLock("suppressedCompetitorsLock", /* fair */ false);
320
+ this.raceColumnListeners = new RaceColumnListeners();
321
+ this.raceDetailsAtEndOfTrackingCache = new HashMap<com.sap.sse.common.Util.Pair<TrackedRace, Competitor>, RunnableFuture<RaceDetails>>();
322
+ initTransientFields();
323
+ }
324
+
325
+ /**
326
+ * Produces the score correction object to use in this leaderboard. Used by the constructor. Subclasses may override
327
+ * this method to create a more specific type of score correction. This implementation produces an object of type
328
+ * {@link ScoreCorrectionImpl}.
329
+ */
330
+ protected SettableScoreCorrection createScoreCorrection() {
331
+ return new ScoreCorrectionImpl(this);
332
+ }
333
+
334
+ private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException {
335
+ ois.defaultReadObject();
336
+ initTransientFields();
337
+ }
338
+
339
+ private void initTransientFields() {
340
+ this.raceDetailsAtEndOfTrackingCache = new HashMap<com.sap.sse.common.Util.Pair<TrackedRace,Competitor>, RunnableFuture<RaceDetails>>();
341
+ this.raceDetailsExecutor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
342
+ this.cacheInvalidationListeners = new HashSet<CacheInvalidationListener>();
343
+ // When many updates are triggered in a short period of time by a single thread, ensure that the single thread
344
+ // providing the updates is not outperformed by all the re-calculations happening here. Leave at least one
345
+ // core to other things, but by using at least three threads ensure that no simplistic deadlocks may occur.
346
+ final int THREAD_POOL_SIZE = Math.max(Runtime.getRuntime().availableProcessors(), 3);
347
+ executor = new ThreadPoolExecutor(/* corePoolSize */ THREAD_POOL_SIZE,
348
+ /* maximumPoolSize */ THREAD_POOL_SIZE,
349
+ /* keepAliveTime */ 60, TimeUnit.SECONDS,
350
+ /* workQueue */ new LinkedBlockingQueue<Runnable>(), new ThreadFactoryWithPriority(Thread.NORM_PRIORITY-1, /* daemon */ true));
351
+ }
352
+
353
+ @Override
354
+ public void destroy() {
355
+ for (CacheInvalidationListener cacheInvalidationListener : cacheInvalidationListeners) {
356
+ cacheInvalidationListener.removeFromTrackedRace();
357
+ }
358
+ }
359
+
360
+ @Override
361
+ public SettableScoreCorrection getScoreCorrection() {
362
+ return scoreCorrection;
363
+ }
364
+
365
+ @Override
366
+ public String getDisplayName(Competitor competitor) {
367
+ return displayNames.get(competitor);
368
+ }
369
+
370
+ @Override
371
+ public String getDisplayName() {
372
+ return displayName;
373
+ }
374
+
375
+ @Override
376
+ public void setDisplayName(String displayName) {
377
+ this.displayName = displayName;
378
+ }
379
+
380
+ @Override
381
+ public ResultDiscardingRule getResultDiscardingRule() {
382
+ return crossLeaderboardResultDiscardingRule;
383
+ }
384
+
385
+ @Override
386
+ public void setCarriedPoints(Competitor competitor, double carriedPoints) {
387
+ Double oldCarriedPoints = this.carriedPoints.put(competitor, carriedPoints);
388
+ if (!Util.equalsWithNull(oldCarriedPoints, carriedPoints)) {
389
+ getScoreCorrection().notifyListenersAboutCarriedPointsChange(competitor, oldCarriedPoints, carriedPoints);
390
+ }
391
+ }
392
+
393
+ @Override
394
+ public double getCarriedPoints(Competitor competitor) {
395
+ Double result = carriedPoints.get(competitor);
396
+ return result == null ? 0 : result;
397
+ }
398
+
399
+ @Override
400
+ public Map<Competitor, Double> getCompetitorsForWhichThereAreCarriedPoints() {
401
+ return Collections.unmodifiableMap(carriedPoints);
402
+ }
403
+
404
+ @Override
405
+ public void unsetCarriedPoints(Competitor competitor) {
406
+ Double oldCarriedPoints = carriedPoints.remove(competitor);
407
+ if (oldCarriedPoints != null) {
408
+ getScoreCorrection().notifyListenersAboutCarriedPointsChange(competitor, oldCarriedPoints, null);
409
+ }
410
+ }
411
+
412
+ @Override
413
+ public boolean hasCarriedPoints() {
414
+ return !carriedPoints.isEmpty();
415
+ }
416
+
417
+ @Override
418
+ public boolean hasCarriedPoints(Competitor competitor) {
419
+ return carriedPoints.containsKey(competitor);
420
+ }
421
+
422
+ @Override
423
+ public void setDisplayName(Competitor competitor, String displayName) {
424
+ String oldDisplayName = displayNames.get(competitor);
425
+ displayNames.put(competitor, displayName);
426
+ if (!Util.equalsWithNull(oldDisplayName, displayName)) {
427
+ getRaceColumnListeners().notifyListenersAboutCompetitorDisplayNameChanged(competitor, oldDisplayName, displayName);
428
+ }
429
+ }
430
+
431
+ @Override
432
+ public void setCrossLeaderboardResultDiscardingRule(ThresholdBasedResultDiscardingRule discardingRule) {
433
+ ResultDiscardingRule oldDiscardingRule = getResultDiscardingRule();
434
+ this.crossLeaderboardResultDiscardingRule = discardingRule;
435
+ getRaceColumnListeners().notifyListenersAboutResultDiscardingRuleChanged(oldDiscardingRule, discardingRule);
436
+ }
437
+
438
+ @Override
439
+ public Double getNetPoints(final Competitor competitor, final RaceColumn raceColumn, final TimePoint timePoint) {
440
+ return getScoreCorrection().getCorrectedScore(
441
+ new Callable<Integer>() {
442
+ public Integer call() throws NoWindException {
443
+ return getTrackedRank(competitor, raceColumn, timePoint);
444
+ }
445
+ }, competitor,
446
+ raceColumn, timePoint, new NumberOfCompetitorsFetcherImpl(), getScoringScheme()).getCorrectedScore();
447
+ }
448
+
449
+ @Override
450
+ public MaxPointsReason getMaxPointsReason(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint) {
451
+ return getScoreCorrection().getMaxPointsReason(competitor, raceColumn, timePoint);
452
+ }
453
+
454
+ @Override
455
+ public boolean isDiscarded(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint) {
456
+ return isDiscarded(competitor, raceColumn, getRaceColumns(), timePoint);
457
+ }
458
+
459
+ private boolean isDiscarded(Competitor competitor, RaceColumn raceColumn,
460
+ Iterable<RaceColumn> raceColumnsToConsider, TimePoint timePoint) {
461
+ final Set<RaceColumn> discardedRaceColumns = getResultDiscardingRule()
462
+ .getDiscardedRaceColumns(competitor, this, raceColumnsToConsider, timePoint);
463
+ return isDiscarded(competitor, raceColumn, timePoint, discardedRaceColumns);
464
+ }
465
+
466
+ /**
467
+ * Same as {@link #isDiscarded(Competitor, RaceColumn, TimePoint)}, only that the set of discarded race columns can
468
+ * be specified which is useful when total points are to be computed for more than one column for the same
469
+ * competitor because then the calculation of discards (which requires looking at all columns) only needs to be done
470
+ * once and not again for each column (which would lead to quadratic effort).
471
+ *
472
+ * @param discardedRaceColumns
473
+ * expected to be the result of what we would get if we called {@link #getResultDiscardingRule()}.
474
+ * {@link ResultDiscardingRule#getDiscardedRaceColumns(Competitor, Leaderboard, Iterable, TimePoint)
475
+ * getDiscardedRaceColumns(competitor, this, raceColumnsToConsider, timePoint)}.
476
+ */
477
+ private boolean isDiscarded(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint,
478
+ final Set<RaceColumn> discardedRaceColumns) {
479
+ return !raceColumn.isMedalRace()
480
+ && getMaxPointsReason(competitor, raceColumn, timePoint).isDiscardable()
481
+ && discardedRaceColumns.contains(raceColumn);
482
+ }
483
+
484
+ @Override
485
+ public Double getTotalPoints(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint) throws NoWindException {
486
+ return getTotalPoints(competitor, raceColumn, getRaceColumns(), timePoint);
487
+ }
488
+
489
+ @Override
490
+ public Double getTotalPoints(Competitor competitor, RaceColumn raceColumn,
491
+ Iterable<RaceColumn> raceColumnsToConsider, TimePoint timePoint) throws NoWindException {
492
+ final Set<RaceColumn> discardedRaceColumns = getResultDiscardingRule()
493
+ .getDiscardedRaceColumns(competitor, this, raceColumnsToConsider, timePoint);
494
+ return getTotalPoints(competitor, raceColumn, timePoint, discardedRaceColumns);
495
+ }
496
+
497
+ /**
498
+ * Same as {@link #getTotalPoints(Competitor, RaceColumn, Iterable, TimePoint)}, only that the set of discarded race columns can
499
+ * be specified which is useful when total points are to be computed for more than one column for the same
500
+ * competitor because then the calculation of discards (which requires looking at all columns) only needs to be done
501
+ * once and not again for each column (which would lead to quadratic effort).
502
+ *
503
+ * @param discardedRaceColumns
504
+ * expected to be the result of what we would get if we called {@link #getResultDiscardingRule()}.
505
+ * {@link ResultDiscardingRule#getDiscardedRaceColumns(Competitor, Leaderboard, Iterable, TimePoint)
506
+ * getDiscardedRaceColumns(competitor, this, raceColumnsToConsider, timePoint)}.
507
+ */
508
+ @Override
509
+ public Double getTotalPoints(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint, Set<RaceColumn> discardedRaceColumns) {
510
+ Double result;
511
+ if (isDiscarded(competitor, raceColumn, timePoint, discardedRaceColumns)) {
512
+ result = 0.0;
513
+ } else {
514
+ final Double netPoints = getNetPoints(competitor, raceColumn, timePoint);
515
+ if (netPoints == null) {
516
+ result = null;
517
+ } else {
518
+ result = raceColumn.getFactor() * netPoints;
519
+ }
520
+ }
521
+ return result;
522
+ }
523
+
524
+ @Override
525
+ public Double getTotalPoints(Competitor competitor, TimePoint timePoint) {
526
+ return getTotalPoints(competitor, getRaceColumns(), timePoint);
527
+ }
528
+
529
+ @Override
530
+ public Double getTotalPoints(Competitor competitor, final Iterable<RaceColumn> raceColumnsToConsider,
531
+ TimePoint timePoint) {
532
+ // when a column with isStartsWithZeroScore() is found, only reset score if the competitor scored in any race from there on
533
+ boolean needToResetScoreUponNextNonEmptyEntry = false;
534
+ double result = getCarriedPoints(competitor);
535
+ final Set<RaceColumn> discardedRaceColumns = getResultDiscardingRule()
536
+ .getDiscardedRaceColumns(competitor, this, raceColumnsToConsider, timePoint);
537
+ for (RaceColumn raceColumn : raceColumnsToConsider) {
538
+ if (raceColumn.isStartsWithZeroScore()) {
539
+ needToResetScoreUponNextNonEmptyEntry = true;
540
+ }
541
+ if (getScoringScheme().isValidInTotalScore(this, raceColumn, competitor, timePoint)) {
542
+ final Double totalPoints = getTotalPoints(competitor, raceColumn, timePoint, discardedRaceColumns);
543
+ if (totalPoints != null) {
544
+ if (needToResetScoreUponNextNonEmptyEntry) {
545
+ result = 0;
546
+ needToResetScoreUponNextNonEmptyEntry = false;
547
+ }
548
+ result += totalPoints;
549
+ }
550
+ }
551
+ }
552
+ return result;
553
+ }
554
+
555
+ /**
556
+ * All competitors with non-<code>null</code> net points are added to the result which is then sorted by net points in ascending
557
+ * order. The fleet, if ordered, is the primary ordering criterion, followed by the net points.
558
+ */
559
+ @Override
560
+ public List<Competitor> getCompetitorsFromBestToWorst(final RaceColumn raceColumn, TimePoint timePoint) throws NoWindException {
561
+ final Map<Competitor, com.sap.sse.common.Util.Pair<Double, Fleet>> netPointsAndFleet = new HashMap<Competitor, com.sap.sse.common.Util.Pair<Double, Fleet>>();
562
+ for (Competitor competitor : getCompetitors()) {
563
+ Double netPoints = getNetPoints(competitor, raceColumn, timePoint);
564
+ if (netPoints != null) {
565
+ netPointsAndFleet.put(competitor, new com.sap.sse.common.Util.Pair<Double, Fleet>(netPoints, raceColumn.getFleetOfCompetitor(competitor)));
566
+ }
567
+ }
568
+ List<Competitor> result = new ArrayList<Competitor>(netPointsAndFleet.keySet());
569
+ Collections.sort(result, new Comparator<Competitor>() {
570
+ @Override
571
+ public int compare(Competitor o1, Competitor o2) {
572
+ int comparisonResult;
573
+ if (o1 == o2) {
574
+ comparisonResult = 0;
575
+ } else {
576
+ if (raceColumn.hasSplitFleets() && !raceColumn.hasSplitFleetContiguousScoring()) {
577
+ // only check fleets if there are more than one and the column is not to be contiguously scored even in case
578
+ // of split fleets
579
+ final Fleet o1Fleet = netPointsAndFleet.get(o1).getB();
580
+ final Fleet o2Fleet = netPointsAndFleet.get(o2).getB();
581
+ if (o1Fleet == null) {
582
+ if (o2Fleet == null) {
583
+ comparisonResult = 0;
584
+ } else {
585
+ comparisonResult = 1; // o1 ranks "worse" because it doesn't have a fleet set while o2
586
+ // has
587
+ }
588
+ } else {
589
+ if (o2Fleet == null) {
590
+ comparisonResult = -1; // o1 ranks "better" because it has a fleet set while o2 hasn't
591
+ } else {
592
+ comparisonResult = o1Fleet.compareTo(o2Fleet);
593
+ }
594
+ }
595
+ } else {
596
+ // either there are no split fleets or the split isn't relevant for scoring as for ordered fleets
597
+ // the scoring runs contiguously from top to bottom
598
+ comparisonResult = 0;
599
+ }
600
+ if (comparisonResult == 0) {
601
+ comparisonResult = getScoringScheme().getScoreComparator(/* nullScoresAreBetter */ false).compare(
602
+ netPointsAndFleet.get(o1).getA(), netPointsAndFleet.get(o2).getA());
603
+ }
604
+ }
605
+ return comparisonResult;
606
+ }
607
+ });
608
+ return result;
609
+ }
610
+
611
+ @Override
612
+ public List<Competitor> getCompetitorsFromBestToWorst(TimePoint timePoint) {
613
+ return getCompetitorsFromBestToWorst(getRaceColumns(), timePoint);
614
+ }
615
+
616
+ private List<Competitor> getCompetitorsFromBestToWorst(Iterable<RaceColumn> raceColumnsToConsider, TimePoint timePoint) {
617
+ List<Competitor> result = new ArrayList<Competitor>();
618
+ for (Competitor competitor : getCompetitors()) {
619
+ result.add(competitor);
620
+ }
621
+ Collections.sort(result, getTotalRankComparator(raceColumnsToConsider, timePoint));
622
+ return result;
623
+ }
624
+
625
+ @Override
626
+ public int getTotalRankOfCompetitor(Competitor competitor, TimePoint timePoint) throws NoWindException {
627
+ List<Competitor> competitorsFromBestToWorst = getCompetitorsFromBestToWorst(timePoint);
628
+ return competitorsFromBestToWorst.indexOf(competitor) + 1;
629
+ }
630
+
631
+ protected Comparator<? super Competitor> getTotalRankComparator(Iterable<RaceColumn> raceColumnsToConsider, TimePoint timePoint) {
632
+ return new LeaderboardTotalRankComparator(this, timePoint, getScoringScheme(), /* nullScoresAreBetter */ false, raceColumnsToConsider);
633
+ }
634
+
635
+ @Override
636
+ public RaceColumn getRaceColumnByName(String columnName) {
637
+ RaceColumn result = null;
638
+ for (RaceColumn r : getRaceColumns()) {
639
+ if (r.getName().equals(columnName)) {
640
+ result = r;
641
+ break;
642
+ }
643
+ }
644
+ return result;
645
+ }
646
+
647
+ @Override
648
+ public Competitor getCompetitorByName(String competitorName) {
649
+ for (Competitor competitor : getAllCompetitors()) {
650
+ if (competitor.getName().equals(competitorName)) {
651
+ return competitor;
652
+ }
653
+ }
654
+ return null;
655
+ }
656
+
657
+ @Override
658
+ public boolean countRaceForComparisonWithDiscardingThresholds(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint) {
659
+ TrackedRace trackedRaceForCompetitorInColumn;
660
+ return getScoringScheme().isValidInTotalScore(this, raceColumn, competitor, timePoint) &&
661
+ (getScoreCorrection().isScoreCorrected(competitor, raceColumn, timePoint) ||
662
+ ((trackedRaceForCompetitorInColumn=raceColumn.getTrackedRace(competitor)) != null &&
663
+ trackedRaceForCompetitorInColumn.hasStarted(timePoint) &&
664
+ trackedRaceForCompetitorInColumn.getRank(competitor, timePoint) != 0));
665
+ }
666
+
667
+ @Override
668
+ public void addRaceColumnListener(RaceColumnListener listener) {
669
+ getRaceColumnListeners().addRaceColumnListener(listener);
670
+ }
671
+
672
+ @Override
673
+ public void removeRaceColumnListener(RaceColumnListener listener) {
674
+ getRaceColumnListeners().removeRaceColumnListener(listener);
675
+ }
676
+
677
+ @Override
678
+ public Entry getEntry(final Competitor competitor, final RaceColumn race, final TimePoint timePoint) throws NoWindException {
679
+ final Set<RaceColumn> discardedRaceColumns = getResultDiscardingRule().getDiscardedRaceColumns(competitor, this, getRaceColumns(), timePoint);
680
+ return getEntry(competitor, race, timePoint, discardedRaceColumns);
681
+ }
682
+
683
+ @Override
684
+ public Entry getEntry(final Competitor competitor, final RaceColumn race, final TimePoint timePoint,
685
+ Set<RaceColumn> discardedRaceColumns) throws NoWindException {
686
+ Callable<Integer> trackedRankProvider = new Callable<Integer>() {
687
+ public Integer call() throws NoWindException {
688
+ return getTrackedRank(competitor, race, timePoint);
689
+ }
690
+ };
691
+ final Result correctedResults = getScoreCorrection().getCorrectedScore(trackedRankProvider, competitor, race,
692
+ timePoint, new NumberOfCompetitorsFetcherImpl(), getScoringScheme());
693
+ boolean discarded = isDiscarded(competitor, race, timePoint, discardedRaceColumns);
694
+ final Double correctedScore = correctedResults.getCorrectedScore();
695
+ return new EntryImpl(trackedRankProvider, correctedScore, new Callable<Double>() {
696
+ @Override
697
+ public Double call() {
698
+ return correctedResults.getUncorrectedScore();
699
+ }
700
+ }, correctedResults.isCorrected(), discarded ? DOUBLE_0 : correctedScore == null ? null
701
+ : Double.valueOf(correctedScore * race.getFactor()), correctedResults.getMaxPointsReason(), discarded,
702
+ race.getFleetOfCompetitor(competitor));
703
+ }
704
+
705
+ @Override
706
+ public Map<RaceColumn, List<Competitor>> getRankedCompetitorsFromBestToWorstAfterEachRaceColumn(TimePoint timePoint) throws NoWindException {
707
+ Map<RaceColumn, List<Competitor>> result = new LinkedHashMap<>();
708
+ List<RaceColumn> raceColumnsToConsider = new ArrayList<>();
709
+ for (RaceColumn raceColumn : getRaceColumns()) {
710
+ raceColumnsToConsider.add(raceColumn);
711
+ result.put(raceColumn, getCompetitorsFromBestToWorst(raceColumnsToConsider, timePoint));
712
+ }
713
+ return result;
714
+ }
715
+
716
+ @Override
717
+ public Map<RaceColumn, Map<Competitor, Double>> getTotalPointsSumAfterRaceColumn(final TimePoint timePoint)
718
+ throws NoWindException {
719
+ final Map<RaceColumn, Map<Competitor, Double>> result = new LinkedHashMap<>();
720
+ List<RaceColumn> raceColumnsToConsider = new ArrayList<>();
721
+ Map<RaceColumn, Future<Map<Competitor, Double>>> futures = new HashMap<>();
722
+ for (final RaceColumn raceColumn : getRaceColumns()) {
723
+ raceColumnsToConsider.add(raceColumn);
724
+ final Iterable<RaceColumn> finalRaceColumnsToConsider = new ArrayList<>(raceColumnsToConsider);
725
+ futures.put(raceColumn, executor.submit(new Callable<Map<Competitor, Double>>() {
726
+ @Override
727
+ public Map<Competitor, Double> call() {
728
+ Map<Competitor, Double> totalPointsSumPerCompetitorInColumn = new HashMap<>();
729
+ for (Competitor competitor : getCompetitors()) {
730
+ totalPointsSumPerCompetitorInColumn.put(competitor, getTotalPoints(competitor, finalRaceColumnsToConsider, timePoint));
731
+ }
732
+ synchronized (result) {
733
+ return totalPointsSumPerCompetitorInColumn;
734
+ }
735
+ }
736
+ }));
737
+ }
738
+ for (RaceColumn raceColumn : getRaceColumns()) {
739
+ try {
740
+ result.put(raceColumn, futures.get(raceColumn).get());
741
+ } catch (InterruptedException | ExecutionException e) {
742
+ if (e.getCause() instanceof NoWindError) {
743
+ throw ((NoWindError) e.getCause()).getCause();
744
+ } else {
745
+ throw new RuntimeException(e); // no caught exceptions occur in the futures executed
746
+ }
747
+ }
748
+ }
749
+ return result;
750
+ }
751
+
752
+ @Override
753
+ public Map<com.sap.sse.common.Util.Pair<Competitor, RaceColumn>, Entry> getContent(final TimePoint timePoint) throws NoWindException {
754
+ Map<com.sap.sse.common.Util.Pair<Competitor, RaceColumn>, Entry> result = new HashMap<com.sap.sse.common.Util.Pair<Competitor, RaceColumn>, Entry>();
755
+ Map<Competitor, Set<RaceColumn>> discardedRaces = new HashMap<Competitor, Set<RaceColumn>>();
756
+ for (final RaceColumn raceColumn : getRaceColumns()) {
757
+ for (final Competitor competitor : getCompetitors()) {
758
+ Callable<Integer> trackedRankProvider = new Callable<Integer>() {
759
+ @Override
760
+ public Integer call() throws Exception {
761
+ return getTrackedRank(competitor, raceColumn, timePoint);
762
+ }
763
+ };
764
+ final Result correctedResults = getScoreCorrection().getCorrectedScore(trackedRankProvider, competitor, raceColumn,
765
+ timePoint, new NumberOfCompetitorsFetcherImpl(), getScoringScheme());
766
+ Set<RaceColumn> discardedRacesForCompetitor = discardedRaces.get(competitor);
767
+ if (discardedRacesForCompetitor == null) {
768
+ discardedRacesForCompetitor = getResultDiscardingRule().getDiscardedRaceColumns(competitor, this, getRaceColumns(), timePoint);
769
+ discardedRaces.put(competitor, discardedRacesForCompetitor);
770
+ }
771
+ boolean discarded = discardedRacesForCompetitor.contains(raceColumn);
772
+ final Double correctedScore = correctedResults.getCorrectedScore();
773
+ Entry entry = new EntryImpl(trackedRankProvider, correctedScore,
774
+ new Callable<Double>() { @Override public Double call() { return correctedResults.getUncorrectedScore(); } },
775
+ correctedResults.isCorrected(),
776
+ discarded ? DOUBLE_0 : (correctedScore==null?null:
777
+ Double.valueOf((correctedScore * raceColumn.getFactor()))), correctedResults.getMaxPointsReason(),
778
+ discarded, raceColumn.getFleetOfCompetitor(competitor));
779
+ result.put(new com.sap.sse.common.Util.Pair<Competitor, RaceColumn>(competitor, raceColumn), entry);
780
+ }
781
+ }
782
+ return result;
783
+ }
784
+
785
+ @Override
786
+ public void trackedRaceLinked(RaceColumn raceColumn, Fleet fleet, TrackedRace trackedRace) {
787
+ getRaceColumnListeners().notifyListenersAboutTrackedRaceLinked(raceColumn, fleet, trackedRace);
788
+ }
789
+
790
+ @Override
791
+ public void trackedRaceUnlinked(RaceColumn raceColumn, Fleet fleet, TrackedRace trackedRace) {
792
+ getRaceColumnListeners().notifyListenersAboutTrackedRaceUnlinked(raceColumn, fleet, trackedRace);
793
+ // It's generally possible that a leaderboard links to the same tracked race in multiple columns / fleets;
794
+ // only if it no longer references the trackedRace currently unlinked from one column/fleet, also unlink
795
+ // all cache invalidation listeners for said trackedRace
796
+ if (!Util.contains(getTrackedRaces(), trackedRace)) {
797
+ synchronized (cacheInvalidationListeners) {
798
+ for (Iterator<CacheInvalidationListener> cacheInvalidationListenerIter=cacheInvalidationListeners.iterator();
799
+ cacheInvalidationListenerIter.hasNext(); ) {
800
+ CacheInvalidationListener cacheInvalidationListener = cacheInvalidationListenerIter.next();
801
+ if (cacheInvalidationListener.getTrackedRace() == trackedRace) {
802
+ cacheInvalidationListener.removeFromTrackedRace();
803
+ cacheInvalidationListenerIter.remove();
804
+ }
805
+ }
806
+ }
807
+ }
808
+ }
809
+
810
+ @Override
811
+ public void isMedalRaceChanged(RaceColumn raceColumn, boolean newIsMedalRace) {
812
+ getRaceColumnListeners().notifyListenersAboutIsMedalRaceChanged(raceColumn, newIsMedalRace);
813
+ }
814
+
815
+ @Override
816
+ public void isStartsWithZeroScoreChanged(RaceColumn raceColumn, boolean newIsStartsWithZeroScore) {
817
+ getRaceColumnListeners().notifyListenersAboutIsStartsWithZeroScoreChanged(raceColumn, newIsStartsWithZeroScore);
818
+ }
819
+
820
+ @Override
821
+ public void isFirstColumnIsNonDiscardableCarryForwardChanged(RaceColumn raceColumn, boolean firstColumnIsNonDiscardableCarryForward) {
822
+ getRaceColumnListeners().notifyListenersAboutIsFirstColumnIsNonDiscardableCarryForwardChanged(raceColumn, firstColumnIsNonDiscardableCarryForward);
823
+ }
824
+
825
+ @Override
826
+ public void hasSplitFleetContiguousScoringChanged(RaceColumn raceColumn, boolean hasSplitFleetContiguousScoring) {
827
+ getRaceColumnListeners().notifyListenersAboutHasSplitFleetContiguousScoringChanged(raceColumn, hasSplitFleetContiguousScoring);
828
+ }
829
+
830
+ @Override
831
+ public void raceColumnMoved(RaceColumn raceColumn, int newIndex) {
832
+ getRaceColumnListeners().notifyListenersAboutRaceColumnMoved(raceColumn, newIndex);
833
+ }
834
+
835
+ @Override
836
+ public void factorChanged(RaceColumn raceColumn, Double oldFactor, Double newFactor) {
837
+ getRaceColumnListeners().notifyListenersAboutFactorChanged(raceColumn, oldFactor, newFactor);
838
+ }
839
+
840
+ /**
841
+ * A leaderboard will only accept the addition of a race column if the column's name is unique across the leaderboard.
842
+ */
843
+ @Override
844
+ public boolean canAddRaceColumnToContainer(RaceColumn newRaceColumn) {
845
+ boolean result = true;
846
+ for (RaceColumn raceColumn : getRaceColumns()) {
847
+ if (raceColumn.getName().equals(newRaceColumn.getName())) {
848
+ result = false;
849
+ break;
850
+ }
851
+ }
852
+ return result;
853
+ }
854
+
855
+ @Override
856
+ public void raceColumnAddedToContainer(RaceColumn raceColumn) {
857
+ getRaceColumnListeners().notifyListenersAboutRaceColumnAddedToContainer(raceColumn);
858
+ }
859
+
860
+ @Override
861
+ public void raceColumnRemovedFromContainer(RaceColumn raceColumn) {
862
+ getRaceColumnListeners().notifyListenersAboutRaceColumnRemovedFromContainer(raceColumn);
863
+ }
864
+
865
+ @Override
866
+ public void competitorDisplayNameChanged(Competitor competitor, String oldDisplayName, String displayName) {
867
+ getRaceColumnListeners().notifyListenersAboutCompetitorDisplayNameChanged(competitor, oldDisplayName, displayName);
868
+ }
869
+
870
+ @Override
871
+ public void resultDiscardingRuleChanged(ResultDiscardingRule oldDiscardingRule, ResultDiscardingRule newDiscardingRule) {
872
+ getRaceColumnListeners().notifyListenersAboutResultDiscardingRuleChanged(oldDiscardingRule, newDiscardingRule);
873
+ }
874
+
875
+ @Override
876
+ public boolean isTransient() {
877
+ return false;
878
+ }
879
+
880
+ /**
881
+ * Finds out the time point when any of the {@link Leaderboard#getTrackedRaces() tracked races currently attached to
882
+ * the <code>leaderboard</code>} and the {@link Leaderboard#getScoreCorrection() score corrections} have last been
883
+ * modified. If no tracked race is attached and no time-stamped score corrections have been applied to the leaderboard,
884
+ * <code>null</code> is returned. The time point computed this way is a good choice for normalizing queries for later time
885
+ * points in an attempt to achieve more cache hits.<p>
886
+ *
887
+ * Note, however, that the result does not tell about structural changes to the leaderboard and therefore cannot be used
888
+ * to determine the need for cache invalidation. For example, if a column is added to a leaderboard after the time point
889
+ * returned by this method but that column's attached tracked race has finished before the time point returned by this method,
890
+ * the result of this method won't change. Still, the contents of the leaderboard will change by a change in column structure.
891
+ * A different means to determine the possibility of changes that happened to this leaderboard must be used for cache
892
+ * management. Such a facility has to listen for score correction changes, tracked races being attached or detached and
893
+ * the column structure changing.
894
+ *
895
+ * @see TrackedRace#getTimePointOfNewestEvent()
896
+ * @see SettableScoreCorrection#getTimePointOfLastCorrectionsValidity()
897
+ */
898
+ @Override
899
+ public TimePoint getTimePointOfLatestModification() {
900
+ TimePoint result = null;
901
+ for (TrackedRace trackedRace : getTrackedRaces()) {
902
+ if (result == null || (trackedRace.getTimePointOfNewestEvent() != null && trackedRace.getTimePointOfNewestEvent().after(result))) {
903
+ result = trackedRace.getTimePointOfNewestEvent();
904
+ }
905
+ }
906
+ TimePoint timePointOfLastScoreCorrection = getScoreCorrection().getTimePointOfLastCorrectionsValidity();
907
+ if (timePointOfLastScoreCorrection != null && (result == null || timePointOfLastScoreCorrection.after(result))) {
908
+ result = timePointOfLastScoreCorrection;
909
+ }
910
+ return result;
911
+ }
912
+
913
+ @Override
914
+ public com.sap.sse.common.Util.Pair<GPSFixMoving, Speed> getMaximumSpeedOverGround(Competitor competitor, TimePoint timePoint) {
915
+ com.sap.sse.common.Util.Pair<GPSFixMoving, Speed> result = null;
916
+ // TODO should we ensure that competitor participated in all race columns?
917
+ for (TrackedRace trackedRace : getTrackedRaces()) {
918
+ if (Util.contains(trackedRace.getRace().getCompetitors(), competitor)) {
919
+ NavigableSet<MarkPassing> markPassings = trackedRace.getMarkPassings(competitor);
920
+ if (!markPassings.isEmpty()) {
921
+ TimePoint from = markPassings.first().getTimePoint();
922
+ TimePoint to;
923
+ if (timePoint.after(markPassings.last().getTimePoint()) &&
924
+ markPassings.last().getWaypoint() == trackedRace.getRace().getCourse().getLastWaypoint()) {
925
+ // stop counting when competitor finished the race
926
+ to = markPassings.last().getTimePoint();
927
+ } else {
928
+ to = timePoint;
929
+ }
930
+ com.sap.sse.common.Util.Pair<GPSFixMoving, Speed> maxSpeed = trackedRace.getTrack(competitor).getMaximumSpeedOverGround(from, to);
931
+ if (result == null || result.getB() == null ||
932
+ (maxSpeed != null && maxSpeed.getB() != null && maxSpeed.getB().compareTo(result.getB()) > 0)) {
933
+ result = maxSpeed;
934
+ }
935
+ }
936
+ }
937
+ }
938
+ return result;
939
+ }
940
+
941
+ @Override
942
+ public Speed getAverageSpeedOverGround(Competitor competitor, TimePoint timePoint) {
943
+ Speed result = null;
944
+ for (TrackedRace trackedRace : getTrackedRaces()) {
945
+ if (Util.contains(trackedRace.getRace().getCompetitors(), competitor)) {
946
+ NavigableSet<MarkPassing> markPassings = trackedRace.getMarkPassings(competitor);
947
+ if (!markPassings.isEmpty()) {
948
+ TimePoint from = markPassings.first().getTimePoint();
949
+ TimePoint to;
950
+ if (timePoint.after(markPassings.last().getTimePoint()) &&
951
+ markPassings.last().getWaypoint() == trackedRace.getRace().getCourse().getLastWaypoint()) {
952
+ // stop counting when competitor finished the race
953
+ to = markPassings.last().getTimePoint();
954
+ } else {
955
+ if (markPassings.last().getWaypoint() != trackedRace.getRace().getCourse().getLastWaypoint() &&
956
+ timePoint.after(markPassings.last().getTimePoint())) {
957
+ result = null;
958
+ break;
959
+ }
960
+ to = timePoint;
961
+ }
962
+ Distance distanceTraveled = trackedRace.getDistanceTraveled(competitor, timePoint);
963
+ if (distanceTraveled != null) {
964
+ result = distanceTraveled.inTime(to.asMillis()-from.asMillis());
965
+ }
966
+ }
967
+ }
968
+ }
969
+ return result;
970
+ }
971
+
972
+ @Override
973
+ public Duration getTotalTimeSailedInLegType(Competitor competitor, LegType legType, TimePoint timePoint) throws NoWindException {
974
+ return getTotalTimeSailedInLegType(competitor, legType, timePoint, new HashMap<TrackedLeg, LegType>());
975
+ }
976
+
977
+ private Duration getTotalTimeSailedInLegType(Competitor competitor, LegType legType, TimePoint timePoint, Map<TrackedLeg, LegType> legTypeCache) throws NoWindException {
978
+ Duration result = null;
979
+ // TODO should we ensure that competitor participated in all race columns?
980
+ outerLoop:
981
+ for (TrackedRace trackedRace : getTrackedRaces()) {
982
+ if (Util.contains(trackedRace.getRace().getCompetitors(), competitor)) {
983
+ trackedRace.getRace().getCourse().lockForRead();
984
+ try {
985
+ for (Leg leg : trackedRace.getRace().getCourse().getLegs()) {
986
+ TrackedLegOfCompetitor trackedLegOfCompetitor = trackedRace.getTrackedLeg(competitor, leg);
987
+ if (trackedLegOfCompetitor.hasStartedLeg(timePoint)) {
988
+ // find out leg type at the time the competitor started the leg
989
+ try {
990
+ final TrackedLeg trackedLeg = trackedRace.getTrackedLeg(leg);
991
+ LegType trackedLegType = legTypeCache.get(trackedLeg);
992
+ if (trackedLegType == null) {
993
+ final TimePoint startTime = trackedLegOfCompetitor.getStartTime();
994
+ TimePoint finishTime = trackedLegOfCompetitor.getFinishTime();
995
+ if (finishTime == null) {
996
+ finishTime = timePoint;
997
+ }
998
+ trackedLegType = trackedLeg.getLegType(startTime.plus(startTime.until(finishTime).divide(2))); // middle of the leg
999
+ legTypeCache.put(trackedLeg, trackedLegType);
1000
+ }
1001
+ if (legType == trackedLegType) {
1002
+ Duration timeSpentOnDownwind = trackedLegOfCompetitor.getTime(timePoint);
1003
+ if (timeSpentOnDownwind != null) {
1004
+ if (result == null) {
1005
+ result = timeSpentOnDownwind;
1006
+ } else {
1007
+ result = result.plus(timeSpentOnDownwind);
1008
+ }
1009
+ } else {
1010
+ // Although the competitor has started the leg, no value was produced. This
1011
+ // means that the competitor didn't finish the leg before tracking ended. No useful value
1012
+ // can be obtained for this competitor anymore.
1013
+ result = null;
1014
+ break outerLoop;
1015
+ }
1016
+ }
1017
+ } catch (NoWindException nwe) {
1018
+ // without wind there is no leg type and hence there is no reasonable value for this:
1019
+ result = null;
1020
+ break outerLoop;
1021
+ }
1022
+ }
1023
+ }
1024
+ } finally {
1025
+ trackedRace.getRace().getCourse().unlockAfterRead();
1026
+ }
1027
+ }
1028
+ }
1029
+ return result;
1030
+ }
1031
+
1032
+ @Override
1033
+ public Duration getTotalTimeSailed(Competitor competitor, TimePoint timePoint) {
1034
+ Duration result = null;
1035
+ for (TrackedRace trackedRace : getTrackedRaces()) {
1036
+ if (Util.contains(trackedRace.getRace().getCompetitors(), competitor)) {
1037
+ NavigableSet<MarkPassing> markPassings = trackedRace.getMarkPassings(competitor);
1038
+ if (!markPassings.isEmpty()) {
1039
+ TimePoint from = trackedRace.getStartOfRace(); // start counting at race start, not when the competitor passed the line
1040
+ if (from != null && !timePoint.before(from)) { // but only if the race started after timePoint
1041
+ TimePoint to;
1042
+ if (timePoint.after(markPassings.last().getTimePoint())
1043
+ && markPassings.last().getWaypoint() == trackedRace.getRace().getCourse()
1044
+ .getLastWaypoint()) {
1045
+ // stop counting when competitor finished the race
1046
+ to = markPassings.last().getTimePoint();
1047
+ } else {
1048
+ if (trackedRace.getEndOfTracking() != null
1049
+ && timePoint.after(trackedRace.getEndOfTracking())) {
1050
+ result = null; // race not finished until end of tracking; no reasonable value can be
1051
+ // computed for competitor
1052
+ break;
1053
+ } else {
1054
+ to = timePoint;
1055
+ }
1056
+ }
1057
+ Duration timeSpent = from.until(to);
1058
+ if (result == null) {
1059
+ result = timeSpent;
1060
+ } else {
1061
+ result=result.plus(timeSpent);
1062
+ }
1063
+ }
1064
+ }
1065
+ }
1066
+ }
1067
+ return result;
1068
+ }
1069
+
1070
+ @Override
1071
+ public Distance getTotalDistanceTraveled(Competitor competitor, TimePoint timePoint) {
1072
+ Distance result = null;
1073
+ for (TrackedRace trackedRace : getTrackedRaces()) {
1074
+ TimePoint startOfRace;
1075
+ if (Util.contains(trackedRace.getRace().getCompetitors(), competitor) &&
1076
+ (startOfRace=trackedRace.getStartOfRace()) != null &&
1077
+ !startOfRace.after(timePoint)) {
1078
+ Distance distanceSailedInRace = trackedRace.getDistanceTraveled(competitor, timePoint);
1079
+ if (distanceSailedInRace != null) {
1080
+ if (result == null) {
1081
+ result = distanceSailedInRace;
1082
+ } else {
1083
+ result = result.add(distanceSailedInRace);
1084
+ }
1085
+ } else {
1086
+ // if competitor has not finished one single race in the whole
1087
+ // series then we can not return a meaningful value for all
1088
+ // all races
1089
+ return null;
1090
+ }
1091
+ }
1092
+ }
1093
+ return result;
1094
+ }
1095
+
1096
+ protected RaceColumnListeners getRaceColumnListeners() {
1097
+ return raceColumnListeners;
1098
+ }
1099
+
1100
+ @Override
1101
+ public Iterable<Competitor> getCompetitors() {
1102
+ final Iterable<Competitor> result;
1103
+ // mostly the set of suppressed competitors is empty; in this case, avoid having to loop over the
1104
+ // potentially large set of competitors
1105
+ if (Util.isEmpty(getSuppressedCompetitors())) {
1106
+ result = getAllCompetitors();
1107
+ } else {
1108
+ final Iterable<Competitor> allCompetitors = getAllCompetitors();
1109
+ final Set<Competitor> suppressed = new HashSet<>();
1110
+ Util.addAll(getSuppressedCompetitors(), suppressed);
1111
+ result = getCompetitorIterableSkippingSuppressed(allCompetitors, suppressed);
1112
+ }
1113
+ return result;
1114
+ }
1115
+
1116
+ @Override
1117
+ public Iterable<Competitor> getCompetitors(RaceColumn raceColumn, Fleet fleet) {
1118
+ return getCompetitorIterableSkippingSuppressed(getAllCompetitors(raceColumn, fleet), getSuppressedCompetitors());
1119
+ }
1120
+
1121
+ /**
1122
+ * return an iterable with a smart iterator that filters out the suppressed elements on demand
1123
+ */
1124
+ protected Iterable<Competitor> getCompetitorIterableSkippingSuppressed(final Iterable<Competitor> allCompetitors,
1125
+ final Iterable<Competitor> suppressed) {
1126
+ final Iterable<Competitor> result;
1127
+ result = new Iterable<Competitor>() {
1128
+ @Override
1129
+ public Iterator<Competitor> iterator() {
1130
+ return new Iterator<Competitor>() {
1131
+ private final Iterator<Competitor> allIter = allCompetitors.iterator();
1132
+ private Competitor next = advance();
1133
+
1134
+ private Competitor advance() {
1135
+ next = null;
1136
+ while (allIter.hasNext() && next == null) {
1137
+ next = allIter.next();
1138
+ if (Util.contains(suppressed, next)) {
1139
+ next = null;
1140
+ }
1141
+ }
1142
+ return next;
1143
+ }
1144
+
1145
+ @Override
1146
+ public boolean hasNext() {
1147
+ return next != null;
1148
+ }
1149
+
1150
+ @Override
1151
+ public Competitor next() {
1152
+ if (next == null) {
1153
+ throw new NoSuchElementException();
1154
+ }
1155
+ final Competitor result = next;
1156
+ advance();
1157
+ return result;
1158
+ }
1159
+ };
1160
+ }
1161
+ };
1162
+ return result;
1163
+ }
1164
+
1165
+ @Override
1166
+ public Iterable<Competitor> getSuppressedCompetitors() {
1167
+ LockUtil.lockForRead(suppressedCompetitorsLock);
1168
+ try {
1169
+ return new HashSet<Competitor>(suppressedCompetitors);
1170
+ } finally {
1171
+ LockUtil.unlockAfterRead(suppressedCompetitorsLock);
1172
+ }
1173
+ }
1174
+
1175
+ @Override
1176
+ public void setSuppressed(Competitor competitor, boolean suppressed) {
1177
+ LockUtil.lockForWrite(suppressedCompetitorsLock);
1178
+ try {
1179
+ if (suppressed) {
1180
+ suppressedCompetitors.add(competitor);
1181
+ } else {
1182
+ suppressedCompetitors.remove(competitor);
1183
+ }
1184
+ } finally {
1185
+ LockUtil.unlockAfterWrite(suppressedCompetitorsLock);
1186
+ }
1187
+ getScoreCorrection().notifyListenersAboutIsSuppressedChange(competitor, suppressed);
1188
+ }
1189
+
1190
+ @Override
1191
+ public TimePoint getNowMinusDelay() {
1192
+ final TimePoint now = MillisecondsTimePoint.now();
1193
+ final Long delayToLiveInMillis = getDelayToLiveInMillis();
1194
+ TimePoint timePoint = delayToLiveInMillis == null ? now : now.minus(delayToLiveInMillis);
1195
+ return timePoint;
1196
+ }
1197
+
1198
+ @Override
1199
+ public void raceLogEventAdded(RaceColumn raceColumn, RaceLogIdentifier raceLogIdentifier, RaceLogEvent event) {
1200
+ getRaceColumnListeners().notifyListenersAboutRaceLogEventAdded(raceColumn, raceLogIdentifier, event);
1201
+ if (event instanceof InvalidatesLeaderboardCache) {
1202
+ // make sure to invalidate the cache as this event indicates that
1203
+ // it changes values the cache could still hold
1204
+ if (leaderboardDTOCache != null) {
1205
+ leaderboardDTOCache.invalidate(this);
1206
+ }
1207
+ }
1208
+ }
1209
+
1210
+ @Override
1211
+ public LeaderboardDTO computeDTO(final TimePoint timePoint,
1212
+ final Collection<String> namesOfRaceColumnsForWhichToLoadLegDetails, boolean addOverallDetails,
1213
+ final boolean waitForLatestAnalyses, TrackedRegattaRegistry trackedRegattaRegistry, final DomainFactory baseDomainFactory,
1214
+ final boolean fillNetPointsUncorrected)
1215
+ throws NoWindException {
1216
+ long startOfRequestHandling = System.currentTimeMillis();
1217
+ final LeaderboardDTOCalculationReuseCache cache = new LeaderboardDTOCalculationReuseCache(timePoint);
1218
+ final LeaderboardDTO result = new LeaderboardDTO(this.getScoreCorrection().getTimePointOfLastCorrectionsValidity() == null ? null
1219
+ : this.getScoreCorrection().getTimePointOfLastCorrectionsValidity().asDate(),
1220
+ this.getScoreCorrection() == null ? null : this.getScoreCorrection().getComment(),
1221
+ this.getScoringScheme() == null ? null : this.getScoringScheme().getType(), this
1222
+ .getScoringScheme().isHigherBetter(), new UUIDGenerator(), addOverallDetails);
1223
+ result.type = getLeaderboardType();
1224
+ result.competitors = new ArrayList<CompetitorDTO>();
1225
+ result.name = this.getName();
1226
+ result.competitorDisplayNames = new HashMap<CompetitorDTO, String>();
1227
+ for (Competitor suppressedCompetitor : this.getSuppressedCompetitors()) {
1228
+ result.setSuppressed(baseDomainFactory.convertToCompetitorDTO(suppressedCompetitor), true);
1229
+ }
1230
+ // Now create the race columns and, as a future task, set their competitorsFromBestToWorst, then wait for all these
1231
+ // futures to finish:
1232
+ Map<RaceColumn, FutureTask<List<CompetitorDTO>>> competitorsFromBestToWorstTasks = new HashMap<>();
1233
+ for (final RaceColumn raceColumn : this.getRaceColumns()) {
1234
+ boolean isMetaLeaderboardColumn = raceColumn instanceof MetaLeaderboardColumn;
1235
+ RaceColumnDTO raceColumnDTO = result.createEmptyRaceColumn(raceColumn.getName(), raceColumn.isMedalRace(),
1236
+ raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getRegatta().getName() : null,
1237
+ raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getSeries().getName() : null,
1238
+ isMetaLeaderboardColumn);
1239
+ if (isMetaLeaderboardColumn && raceColumnDTO instanceof MetaLeaderboardRaceColumnDTO) {
1240
+ calculateRacesMetadata((MetaLeaderboardColumn) raceColumn, (MetaLeaderboardRaceColumnDTO) raceColumnDTO,
1241
+ trackedRegattaRegistry, baseDomainFactory);
1242
+ }
1243
+ for (Fleet fleet : raceColumn.getFleets()) {
1244
+ RegattaAndRaceIdentifier raceIdentifier = null;
1245
+ RaceDTO race = null;
1246
+ TrackedRace trackedRace = raceColumn.getTrackedRace(fleet);
1247
+ final FleetDTO fleetDTO = baseDomainFactory.convertToFleetDTO(fleet);
1248
+ if (trackedRace != null) {
1249
+ raceIdentifier = new RegattaNameAndRaceName(trackedRace.getTrackedRegatta().getRegatta().getName(),
1250
+ trackedRace.getRace().getName());
1251
+ race = baseDomainFactory.createRaceDTO(trackedRegattaRegistry, /* withGeoLocationData */ false, raceIdentifier, trackedRace);
1252
+ }
1253
+ // Note: the RaceColumnDTO won't be created by the following addRace call because it has been created
1254
+ // above by the result.createEmptyRaceColumn call
1255
+ result.addRace(raceColumn.getName(), raceColumn.getExplicitFactor(), raceColumn.getFactor(),
1256
+ raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getRegatta().getName() : null,
1257
+ raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getSeries().getName() : null,
1258
+ fleetDTO, raceColumn.isMedalRace(), raceIdentifier, race, isMetaLeaderboardColumn);
1259
+ }
1260
+ FutureTask<List<CompetitorDTO>> task = new FutureTask<List<CompetitorDTO>>(
1261
+ () -> baseDomainFactory.getCompetitorDTOList(AbstractSimpleLeaderboardImpl.this.getCompetitorsFromBestToWorst(raceColumn, timePoint)));
1262
+ executor.execute(task);
1263
+ competitorsFromBestToWorstTasks.put(raceColumn, task);
1264
+ }
1265
+ // wait for the competitor orderings to have been computed for all race columns before continuing; subsequent tasks may depend on these data
1266
+ for (Map.Entry<RaceColumn, FutureTask<List<CompetitorDTO>>> raceColumnAndTaskToJoin : competitorsFromBestToWorstTasks.entrySet()) {
1267
+ try {
1268
+ result.setCompetitorsFromBestToWorst(raceColumnAndTaskToJoin.getKey().getName(), raceColumnAndTaskToJoin.getValue().get());
1269
+ } catch (InterruptedException e) {
1270
+ throw new RuntimeException(e);
1271
+ } catch (ExecutionException e) {
1272
+ // See also bug 1371: for stability reasons, don't let the exception percolate but rather accept
1273
+ // null values.
1274
+ // If new evidence is provided, a re-calculation of the leaderboard will be triggered anyway. So
1275
+ // this helps robustness from a user's perspective.
1276
+ logger.log(
1277
+ Level.SEVERE,
1278
+ AbstractSimpleLeaderboardImpl.class.getName() + ".computeDTO(" + this.getName() + ", "
1279
+ + timePoint + ", " + namesOfRaceColumnsForWhichToLoadLegDetails+", addOverallDetails="+addOverallDetails
1280
+ + "): exception during computing competitor ordering for race column "+raceColumnAndTaskToJoin.getKey().getName(), e);
1281
+ }
1282
+ }
1283
+ result.setDelayToLiveInMillisForLatestRace(this.getDelayToLiveInMillis());
1284
+ result.rows = new HashMap<CompetitorDTO, LeaderboardRowDTO>();
1285
+ result.hasCarriedPoints = this.hasCarriedPoints();
1286
+ if (this.getResultDiscardingRule() instanceof ThresholdBasedResultDiscardingRule) {
1287
+ result.discardThresholds = ((ThresholdBasedResultDiscardingRule) this.getResultDiscardingRule())
1288
+ .getDiscardIndexResultsStartingWithHowManyRaces();
1289
+ } else {
1290
+ result.discardThresholds = null;
1291
+ }
1292
+ // Computing the competitor leg ranks is expensive, especially in live mode, in case new events keep
1293
+ // invalidating the ranks cache in TrackedLegImpl. The problem then is that the sorting based on wind data is repeated for
1294
+ // each competitor, leading to square effort. We therefore need to compute the leg ranks for those races where leg
1295
+ // details are requested only once and pass them into getLeaderboardEntryDTO
1296
+ final Map<Leg, LinkedHashMap<Competitor, Integer>> legRanksCache = new HashMap<Leg, LinkedHashMap<Competitor, Integer>>();
1297
+ for (final RaceColumn raceColumn : this.getRaceColumns()) {
1298
+ // if details for the column are requested, cache the leg's ranks
1299
+ if (namesOfRaceColumnsForWhichToLoadLegDetails != null
1300
+ && namesOfRaceColumnsForWhichToLoadLegDetails.contains(raceColumn.getName())) {
1301
+ for (Fleet fleet : raceColumn.getFleets()) {
1302
+ TrackedRace trackedRace = raceColumn.getTrackedRace(fleet);
1303
+ if (trackedRace != null) {
1304
+ trackedRace.getRace().getCourse().lockForRead();
1305
+ try {
1306
+ for (TrackedLeg trackedLeg : trackedRace.getTrackedLegs()) {
1307
+ legRanksCache.put(trackedLeg.getLeg(), trackedLeg.getRanks(timePoint, cache));
1308
+ }
1309
+ } finally {
1310
+ trackedRace.getRace().getCourse().unlockAfterRead();
1311
+ }
1312
+ }
1313
+ }
1314
+ }
1315
+ }
1316
+ for (final Competitor competitor : this.getCompetitorsFromBestToWorst(timePoint)) {
1317
+ CompetitorDTO competitorDTO = baseDomainFactory.convertToCompetitorDTO(competitor);
1318
+ LeaderboardRowDTO row = new LeaderboardRowDTO();
1319
+ row.competitor = competitorDTO;
1320
+ row.fieldsByRaceColumnName = new HashMap<String, LeaderboardEntryDTO>();
1321
+ row.carriedPoints = this.hasCarriedPoints(competitor) ? this.getCarriedPoints(competitor) : null;
1322
+ row.totalPoints = this.getTotalPoints(competitor, timePoint);
1323
+ if (addOverallDetails) {
1324
+ addOverallDetailsToRow(timePoint, competitor, row);
1325
+ }
1326
+ result.competitors.add(competitorDTO);
1327
+ Map<String, Future<LeaderboardEntryDTO>> futuresForColumnName = new HashMap<String, Future<LeaderboardEntryDTO>>();
1328
+ final Set<RaceColumn> discardedRaceColumns = getResultDiscardingRule().getDiscardedRaceColumns(competitor, this, getRaceColumns(), timePoint);
1329
+ for (final RaceColumn raceColumn : this.getRaceColumns()) {
1330
+ RunnableFuture<LeaderboardEntryDTO> future = new FutureTask<LeaderboardEntryDTO>(() -> {
1331
+ Entry entry = AbstractSimpleLeaderboardImpl.this.getEntry(competitor, raceColumn, timePoint, discardedRaceColumns);
1332
+ return getLeaderboardEntryDTO(entry, raceColumn, competitor, timePoint,
1333
+ namesOfRaceColumnsForWhichToLoadLegDetails != null
1334
+ && namesOfRaceColumnsForWhichToLoadLegDetails.contains(raceColumn
1335
+ .getName()), waitForLatestAnalyses, legRanksCache, baseDomainFactory,
1336
+ fillNetPointsUncorrected, cache);
1337
+ });
1338
+ executor.execute(future);
1339
+ futuresForColumnName.put(raceColumn.getName(), future);
1340
+ }
1341
+ for (Map.Entry<String, Future<LeaderboardEntryDTO>> raceColumnNameAndFuture : futuresForColumnName.entrySet()) {
1342
+ try {
1343
+ row.fieldsByRaceColumnName.put(raceColumnNameAndFuture.getKey(), raceColumnNameAndFuture.getValue().get());
1344
+ } catch (InterruptedException e) {
1345
+ throw new RuntimeException(e);
1346
+ } catch (ExecutionException e) {
1347
+ // See also bug 1371: for stability reasons, don't let the exception percolate but rather accept
1348
+ // null values.
1349
+ // If new evidence is provided, a re-calculation of the leaderboard will be triggered anyway. So
1350
+ // this helps robustness from a user's perspective.
1351
+ logger.log(
1352
+ Level.SEVERE,
1353
+ AbstractSimpleLeaderboardImpl.class.getName() + ".computeDTO(" + this.getName() + ", "
1354
+ + timePoint + ", " + namesOfRaceColumnsForWhichToLoadLegDetails+", addOverallDetails="+addOverallDetails
1355
+ + "): exception during computing leaderboard entry for competitor "
1356
+ + competitor.getName() + " in race column " + raceColumnNameAndFuture.getKey()
1357
+ + ". Leaving empty.", e);
1358
+ }
1359
+ }
1360
+ result.rows.put(competitorDTO, row);
1361
+ String displayName = this.getDisplayName(competitor);
1362
+ if (displayName != null) {
1363
+ result.competitorDisplayNames.put(competitorDTO, displayName);
1364
+ }
1365
+ }
1366
+ logger.info("computeLeaderboardByName(" + this.getName() + ", " + timePoint + ", "
1367
+ + namesOfRaceColumnsForWhichToLoadLegDetails + ", addOverallDetails=" + addOverallDetails + ") took "
1368
+ + (System.currentTimeMillis() - startOfRequestHandling) + "ms");
1369
+ return result;
1370
+ }
1371
+
1372
+ private void calculateRacesMetadata(MetaLeaderboardColumn metaLeaderboardColumn, MetaLeaderboardRaceColumnDTO columnDTO,
1373
+ TrackedRegattaRegistry trackedRegattaRegistry, final DomainFactory baseDomainFactory) {
1374
+ for (final RaceColumn raceColumn : metaLeaderboardColumn.getLeaderboard().getRaceColumns()) {
1375
+ for (Fleet fleet : raceColumn.getFleets()) {
1376
+ TrackedRace trackedRace = raceColumn.getTrackedRace(fleet);
1377
+ if (trackedRace != null) {
1378
+ String regattaName = trackedRace.getTrackedRegatta().getRegatta().getName();
1379
+ String raceName = trackedRace.getRace().getName();
1380
+ RegattaAndRaceIdentifier raceIdentifier = new RegattaNameAndRaceName(regattaName, raceName);
1381
+ columnDTO.addRace(new BasicRaceDTO(raceIdentifier, baseDomainFactory.createTrackedRaceDTO(trackedRace)));
1382
+ }
1383
+ }
1384
+ }
1385
+ }
1386
+
1387
+ private void addOverallDetailsToRow(final TimePoint timePoint,
1388
+ final Competitor competitor, LeaderboardRowDTO row) throws NoWindException {
1389
+ final com.sap.sse.common.Util.Pair<GPSFixMoving, Speed> maximumSpeedOverGround = this.getMaximumSpeedOverGround(competitor, timePoint);
1390
+ if (maximumSpeedOverGround != null && maximumSpeedOverGround.getB() != null) {
1391
+ row.maximumSpeedOverGroundInKnots = maximumSpeedOverGround.getB().getKnots();
1392
+ row.whenMaximumSpeedOverGroundWasAchieved = maximumSpeedOverGround.getA().getTimePoint().asDate();
1393
+ }
1394
+ Map<TrackedLeg, LegType> legTypeCache = new HashMap<>();
1395
+ final Duration totalTimeSailedDownwind = this.getTotalTimeSailedInLegType(competitor, LegType.DOWNWIND, timePoint, legTypeCache);
1396
+ row.totalTimeSailedDownwindInSeconds = totalTimeSailedDownwind==null?null:totalTimeSailedDownwind.asSeconds();
1397
+ final Duration totalTimeSailedUpwind = this.getTotalTimeSailedInLegType(competitor, LegType.UPWIND, timePoint, legTypeCache);
1398
+ row.totalTimeSailedUpwindInSeconds = totalTimeSailedUpwind==null?null:totalTimeSailedUpwind.asSeconds();
1399
+ final Duration totalTimeSailedReaching = this.getTotalTimeSailedInLegType(competitor, LegType.REACHING, timePoint, legTypeCache);
1400
+ row.totalTimeSailedReachingInSeconds = totalTimeSailedReaching==null?null:totalTimeSailedReaching.asSeconds();
1401
+ final Duration totalTimeSailed = this.getTotalTimeSailed(competitor, timePoint);
1402
+ row.totalTimeSailedInSeconds = totalTimeSailed==null?null:totalTimeSailed.asSeconds();
1403
+ final Distance totalDistanceTraveledInMeters = this.getTotalDistanceTraveled(competitor, timePoint);
1404
+ row.totalDistanceTraveledInMeters = totalDistanceTraveledInMeters==null?null:totalDistanceTraveledInMeters.getMeters();
1405
+ }
1406
+
1407
+ /**
1408
+ * @param waitForLatestAnalyses
1409
+ * if <code>false</code>, this method is allowed to read the maneuver analysis results from a cache that
1410
+ * may not reflect all data already received; otherwise, the method will always block for the latest
1411
+ * cache updates to have happened before returning.
1412
+ * @param fillNetPointsUncorrected
1413
+ * tells if {@link LeaderboardEntryDTO#netPointsUncorrected} shall be filled; filling it is rather
1414
+ * expensive, especially when compared to simply retrieving a score correction, and particularly if in a
1415
+ * larger fleet a number of competitors haven't properly finished the race. This should only be used for
1416
+ * leaderboard editing where a user needs to see what the uncorrected score was that would be used when
1417
+ * the correction was removed.
1418
+ */
1419
+ private LeaderboardEntryDTO getLeaderboardEntryDTO(Entry entry, RaceColumn raceColumn, Competitor competitor,
1420
+ TimePoint timePoint, boolean addLegDetails, boolean waitForLatestAnalyses,
1421
+ Map<Leg, LinkedHashMap<Competitor, Integer>> legRanksCache, DomainFactory baseDomainFactory,
1422
+ boolean fillNetPointsUncorrected, WindLegTypeAndLegBearingCache cache) {
1423
+ LeaderboardEntryDTO entryDTO = new LeaderboardEntryDTO();
1424
+ TrackedRace trackedRace = raceColumn.getTrackedRace(competitor);
1425
+ entryDTO.race = trackedRace == null ? null : trackedRace.getRaceIdentifier();
1426
+ entryDTO.netPoints = entry.getNetPoints();
1427
+ if (fillNetPointsUncorrected) {
1428
+ entryDTO.netPointsUncorrected = entry.getNetPointsUncorrected();
1429
+ }
1430
+ entryDTO.netPointsCorrected = entry.isNetPointsCorrected();
1431
+ entryDTO.totalPoints = entry.getTotalPoints();
1432
+ entryDTO.reasonForMaxPoints = entry.getMaxPointsReason();
1433
+ entryDTO.discarded = entry.isDiscarded();
1434
+ final GPSFixTrack<Competitor, GPSFixMoving> track = trackedRace == null ? null : trackedRace.getTrack(competitor);
1435
+ if (trackedRace != null) {
1436
+ Date timePointOfLastPositionFixAtOrBeforeQueryTimePoint = getTimePointOfLastFixAtOrBefore(competitor, trackedRace, timePoint);
1437
+ if (track != null) {
1438
+ entryDTO.averageSamplingInterval = track.getAverageIntervalBetweenRawFixes();
1439
+ }
1440
+ if (timePointOfLastPositionFixAtOrBeforeQueryTimePoint != null) {
1441
+ long timeDifferenceInMs = timePoint.asMillis() - timePointOfLastPositionFixAtOrBeforeQueryTimePoint.getTime();
1442
+ entryDTO.timeSinceLastPositionFixInSeconds = timeDifferenceInMs == 0 ? 0.0 : timeDifferenceInMs / 1000.0;
1443
+ } else {
1444
+ entryDTO.timeSinceLastPositionFixInSeconds = null;
1445
+ }
1446
+ }
1447
+ if (addLegDetails && trackedRace != null) {
1448
+ try {
1449
+ final RankingInfo rankingInfo = trackedRace.getRankingMetric().getRankingInfo(timePoint, cache);
1450
+ RaceDetails raceDetails = getRaceDetails(trackedRace, competitor, timePoint, waitForLatestAnalyses,
1451
+ legRanksCache, rankingInfo, cache);
1452
+ entryDTO.legDetails = raceDetails.getLegDetails();
1453
+ entryDTO.windwardDistanceToCompetitorFarthestAheadInMeters = raceDetails.getWindwardDistanceToCompetitorFarthestAhead() == null ? null
1454
+ : raceDetails.getWindwardDistanceToCompetitorFarthestAhead().getMeters();
1455
+ entryDTO.gapToLeaderInOwnTime = trackedRace.getRankingMetric().getGapToLeaderInOwnTime(rankingInfo, competitor, cache);
1456
+ entryDTO.averageAbsoluteCrossTrackErrorInMeters = raceDetails.getAverageAbsoluteCrossTrackError() == null ? null
1457
+ : raceDetails.getAverageAbsoluteCrossTrackError().getMeters();
1458
+ entryDTO.averageSignedCrossTrackErrorInMeters = raceDetails.getAverageSignedCrossTrackError() == null ? null
1459
+ : raceDetails.getAverageSignedCrossTrackError().getMeters();
1460
+ entryDTO.calculatedTime = raceDetails.getCorrectedTime();
1461
+ entryDTO.calculatedTimeAtEstimatedArrivalAtCompetitorFarthestAhead = raceDetails.getCorrectedTimeAtEstimatedArrivalAtCompetitorFarthestAhead();
1462
+ entryDTO.gapToLeaderInOwnTime = raceDetails.getGapToLeaderInOwnTime();
1463
+ final TimePoint startOfRace = trackedRace.getStartOfRace();
1464
+ if (startOfRace != null) {
1465
+ Waypoint startWaypoint = trackedRace.getRace().getCourse().getFirstWaypoint();
1466
+ NavigableSet<MarkPassing> competitorMarkPassings = trackedRace.getMarkPassings(competitor);
1467
+ trackedRace.lockForRead(competitorMarkPassings);
1468
+ try {
1469
+ if (!competitorMarkPassings.isEmpty()) {
1470
+ final MarkPassing firstMarkPassing = competitorMarkPassings.iterator().next();
1471
+ if (firstMarkPassing.getWaypoint() == startWaypoint) {
1472
+ Distance distanceToStartLineFiveSecondsBeforeStartOfRace = trackedRace.getDistanceToStartLine(competitor, /*milliseconds before start*/ 5000);
1473
+ entryDTO.distanceToStartLineFiveSecondsBeforeStartInMeters = distanceToStartLineFiveSecondsBeforeStartOfRace == null ? null
1474
+ : distanceToStartLineFiveSecondsBeforeStartOfRace.getMeters();
1475
+ Speed speedFiveSecondsBeforeStartOfRace = trackedRace.getSpeed(competitor, /*milliseconds before start*/ 5000);
1476
+ entryDTO.speedOverGroundFiveSecondsBeforeStartInKnots = speedFiveSecondsBeforeStartOfRace == null ? null
1477
+ : speedFiveSecondsBeforeStartOfRace.getKnots();
1478
+ Distance distanceToStartLineAtStartOfRace = trackedRace.getDistanceToStartLine(
1479
+ competitor, startOfRace);
1480
+ entryDTO.distanceToStartLineAtStartOfRaceInMeters = distanceToStartLineAtStartOfRace == null ? null
1481
+ : distanceToStartLineAtStartOfRace.getMeters();
1482
+ Speed speedAtStartTime = track == null ? null : track.getEstimatedSpeed(startOfRace);
1483
+ entryDTO.speedOverGroundAtStartOfRaceInKnots = speedAtStartTime == null ? null
1484
+ : speedAtStartTime.getKnots();
1485
+ TimePoint competitorStartTime = firstMarkPassing.getTimePoint();
1486
+ entryDTO.timeBetweenRaceStartAndCompetitorStartInSeconds = startOfRace.until(competitorStartTime).asSeconds();
1487
+ Speed competitorSpeedWhenPassingStart = track == null ? null : track
1488
+ .getEstimatedSpeed(competitorStartTime);
1489
+ entryDTO.speedOverGroundAtPassingStartWaypointInKnots = competitorSpeedWhenPassingStart == null ? null
1490
+ : competitorSpeedWhenPassingStart.getKnots();
1491
+ try {
1492
+ entryDTO.startTack = trackedRace.getTack(competitor, competitorStartTime);
1493
+ } catch (NoWindException nwe) {
1494
+ entryDTO.startTack = null; // leave empty in case no wind information is available
1495
+ }
1496
+ Distance distanceFromStarboardSideOfStartLineWhenPassingStart = trackedRace
1497
+ .getDistanceFromStarboardSideOfStartLineWhenPassingStart(competitor);
1498
+ entryDTO.distanceToStarboardSideOfStartLineInMeters = distanceFromStarboardSideOfStartLineWhenPassingStart == null ? null
1499
+ : distanceFromStarboardSideOfStartLineWhenPassingStart.getMeters();
1500
+ }
1501
+ }
1502
+ } finally {
1503
+ trackedRace.unlockAfterRead(competitorMarkPassings);
1504
+ }
1505
+ }
1506
+ } catch (InterruptedException e) {
1507
+ throw new RuntimeException(e);
1508
+ } catch (ExecutionException e) {
1509
+ throw new RuntimeException(e); // the future used to calculate the leg details was interrupted; escalate as runtime exception
1510
+ }
1511
+ }
1512
+ final Fleet fleet = entry.getFleet();
1513
+ entryDTO.fleet = fleet == null ? null : baseDomainFactory.convertToFleetDTO(fleet);
1514
+ return entryDTO;
1515
+ }
1516
+
1517
+ /**
1518
+ * Determines the time point of the last raw fix (with outliers not removed) for <code>competitor</code> in
1519
+ * <code>trackedRace</code>. If the competitor's track is <code>null</code> or empty, <code>null</code> is returned.
1520
+ * @param trackedRace must not be <code>null</code>
1521
+ * @param atOrBefore find the last fix at or before the time point specified
1522
+ */
1523
+ private Date getTimePointOfLastFixAtOrBefore(Competitor competitor, TrackedRace trackedRace, TimePoint atOrBefore) {
1524
+ assert trackedRace != null;
1525
+ final Date timePointOfLastPositionFix;
1526
+ GPSFixTrack<Competitor, GPSFixMoving> track = trackedRace.getTrack(competitor);
1527
+ if (track == null) {
1528
+ timePointOfLastPositionFix = null;
1529
+ } else {
1530
+ GPSFixMoving lastFix = track.getLastFixAtOrBefore(atOrBefore);
1531
+ if (lastFix == null) {
1532
+ timePointOfLastPositionFix = null;
1533
+ } else {
1534
+ timePointOfLastPositionFix = lastFix.getTimePoint().asDate();
1535
+ }
1536
+ }
1537
+ return timePointOfLastPositionFix;
1538
+ }
1539
+
1540
+ private static class RaceDetails {
1541
+ private final List<LegEntryDTO> legDetails;
1542
+ private final Distance windwardDistanceToCompetitorFarthestAhead;
1543
+ private final Distance averageAbsoluteCrossTrackError;
1544
+ private final Distance averageSignedCrossTrackError;
1545
+ private final Duration gapToLeaderInOwnTime;
1546
+ private final Duration correctedTime;
1547
+ private final Duration correctedTimeAtEstimatedArrivalAtCompetitorFarthestAhead;
1548
+
1549
+ public RaceDetails(List<LegEntryDTO> legDetails, Distance windwardDistanceToCompetitorFarthestAhead,
1550
+ Distance averageAbsoluteCrossTrackError, Distance averageSignedCrossTrackError,
1551
+ Duration gapToLeaderInOwnTime, Duration correctedTime,
1552
+ Duration correctedTimeAtEstimatedArrivalAtCompetitorFarthestAhead) {
1553
+ super();
1554
+ this.legDetails = legDetails;
1555
+ this.windwardDistanceToCompetitorFarthestAhead = windwardDistanceToCompetitorFarthestAhead;
1556
+ this.averageAbsoluteCrossTrackError = averageAbsoluteCrossTrackError;
1557
+ this.averageSignedCrossTrackError = averageSignedCrossTrackError;
1558
+ this.gapToLeaderInOwnTime = gapToLeaderInOwnTime;
1559
+ this.correctedTime = correctedTime;
1560
+ this.correctedTimeAtEstimatedArrivalAtCompetitorFarthestAhead = correctedTimeAtEstimatedArrivalAtCompetitorFarthestAhead;
1561
+ }
1562
+ public List<LegEntryDTO> getLegDetails() {
1563
+ return legDetails;
1564
+ }
1565
+ public Distance getWindwardDistanceToCompetitorFarthestAhead() {
1566
+ return windwardDistanceToCompetitorFarthestAhead;
1567
+ }
1568
+ public Distance getAverageAbsoluteCrossTrackError() {
1569
+ return averageAbsoluteCrossTrackError;
1570
+ }
1571
+ public Distance getAverageSignedCrossTrackError() {
1572
+ return averageSignedCrossTrackError;
1573
+ }
1574
+ public Duration getGapToLeaderInOwnTime() {
1575
+ return gapToLeaderInOwnTime;
1576
+ }
1577
+ public Duration getCorrectedTime() {
1578
+ return correctedTime;
1579
+ }
1580
+ public Duration getCorrectedTimeAtEstimatedArrivalAtCompetitorFarthestAhead() {
1581
+ return correctedTimeAtEstimatedArrivalAtCompetitorFarthestAhead;
1582
+ }
1583
+ }
1584
+
1585
+ /**
1586
+ * If <code>timePoint</code> is after the end of the race's tracking the query will be adjusted to obtain the values
1587
+ * at the end of the {@link TrackedRace#getEndOfTracking() race's tracking time}. If the time point adjusted this
1588
+ * way equals the end of the tracking time, the query results will be looked up in a cache first and if not found,
1589
+ * they will be stored to the cache after calculating them. A cache invalidation {@link RaceChangeListener listener}
1590
+ * will be registered with the race which will be triggered for any event received by the race.
1591
+ * @param waitForLatestAnalyses
1592
+ * if <code>false</code>, this method is allowed to read the maneuver analysis results from a cache that
1593
+ * may not reflect all data already received; otherwise, the method will always block for the latest
1594
+ * cache updates to have happened before returning.
1595
+ */
1596
+ private RaceDetails getRaceDetails(TrackedRace trackedRace, Competitor competitor, TimePoint timePoint,
1597
+ boolean waitForLatestAnalyses, Map<Leg, LinkedHashMap<Competitor, Integer>> legRanksCache,
1598
+ RankingInfo rankingInfo, WindLegTypeAndLegBearingCache cache) throws InterruptedException, ExecutionException {
1599
+ final RaceDetails raceDetails;
1600
+ if (trackedRace.getEndOfTracking() != null && trackedRace.getEndOfTracking().compareTo(timePoint) < 0) {
1601
+ raceDetails = getRaceDetailsForEndOfTrackingFromCacheOrCalculateAndCache(trackedRace, competitor, legRanksCache, rankingInfo, cache);
1602
+ } else {
1603
+ raceDetails = calculateRaceDetails(trackedRace, competitor, timePoint, waitForLatestAnalyses, legRanksCache, cache, rankingInfo);
1604
+ }
1605
+ return raceDetails;
1606
+ }
1607
+
1608
+ private RaceDetails getRaceDetailsForEndOfTrackingFromCacheOrCalculateAndCache(final TrackedRace trackedRace,
1609
+ final Competitor competitor, final Map<Leg, LinkedHashMap<Competitor, Integer>> legRanksCache,
1610
+ RankingInfo rankingInfo, final WindLegTypeAndLegBearingCache cache) throws InterruptedException, ExecutionException {
1611
+ final com.sap.sse.common.Util.Pair<TrackedRace, Competitor> key = new com.sap.sse.common.Util.Pair<TrackedRace, Competitor>(trackedRace, competitor);
1612
+ RunnableFuture<RaceDetails> raceDetails;
1613
+ synchronized (raceDetailsAtEndOfTrackingCache) {
1614
+ raceDetails = raceDetailsAtEndOfTrackingCache.get(key);
1615
+ if (raceDetails == null) {
1616
+ raceDetails = new FutureTask<RaceDetails>(new Callable<RaceDetails>() {
1617
+ @Override
1618
+ public RaceDetails call() throws Exception {
1619
+ TimePoint end = trackedRace.getEndOfRace();
1620
+ if (end == null) {
1621
+ end = trackedRace.getEndOfTracking();
1622
+ }
1623
+ return calculateRaceDetails(trackedRace, competitor, end,
1624
+ // TODO see bug 1358: for now, use waitForLatest==false until we've switched to optimistic locking for the course read lock
1625
+ /* TODO old comment when it was still true: "because this is done only once after end of tracking" */
1626
+ /* waitForLatestAnalyses (maneuver and cross track error) */ false,
1627
+ legRanksCache, cache, rankingInfo);
1628
+ }
1629
+ });
1630
+ raceDetailsExecutor.execute(raceDetails);
1631
+ raceDetailsAtEndOfTrackingCache.put(key, raceDetails);
1632
+ final CacheInvalidationListener cacheInvalidationListener = new CacheInvalidationListener(trackedRace, competitor);
1633
+ trackedRace.addListener(cacheInvalidationListener);
1634
+ cacheInvalidationListeners.add(cacheInvalidationListener);
1635
+ }
1636
+ }
1637
+ return raceDetails.get();
1638
+ }
1639
+
1640
+ private RaceDetails calculateRaceDetails(TrackedRace trackedRace, Competitor competitor, TimePoint timePoint,
1641
+ boolean waitForLatestAnalyses, Map<Leg, LinkedHashMap<Competitor, Integer>> legRanksCache,
1642
+ WindLegTypeAndLegBearingCache cache, final RankingInfo rankingInfo) {
1643
+ final List<LegEntryDTO> legDetails = new ArrayList<LegEntryDTO>();
1644
+ final Course course = trackedRace.getRace().getCourse();
1645
+ course.lockForRead(); // hold back any course re-configurations while looping over the legs
1646
+ try {
1647
+ for (Leg leg : course.getLegs()) {
1648
+ LegEntryDTO legEntry;
1649
+ // We loop over a copy of the course's legs; during a course change, legs may become "stale," even with
1650
+ // regard to the leg/trackedLeg structures inside the tracked race which is updated by the course change
1651
+ // immediately. That's why we've acquired a read lock for the course above.
1652
+ TrackedLegOfCompetitor trackedLeg = trackedRace.getTrackedLeg(competitor, leg);
1653
+ if (trackedLeg != null && trackedLeg.hasStartedLeg(timePoint)) {
1654
+ legEntry = createLegEntry(trackedLeg, timePoint, waitForLatestAnalyses, legRanksCache, rankingInfo, cache);
1655
+ } else {
1656
+ legEntry = null;
1657
+ }
1658
+ legDetails.add(legEntry);
1659
+ }
1660
+ final Distance windwardDistanceToCompetitorFarthestAhead = trackedRace == null ? null : trackedRace
1661
+ .getWindwardDistanceToCompetitorFarthestAhead(competitor, timePoint, WindPositionMode.LEG_MIDDLE, rankingInfo, cache);
1662
+ Distance averageAbsoluteCrossTrackError;
1663
+ try {
1664
+ averageAbsoluteCrossTrackError = trackedRace == null ? null : trackedRace.getAverageAbsoluteCrossTrackError(
1665
+ competitor, timePoint, waitForLatestAnalyses, cache);
1666
+ } catch (NoWindException nwe) {
1667
+ // without wind information, use null meaning "unknown"
1668
+ averageAbsoluteCrossTrackError = null;
1669
+ }
1670
+ Distance averageSignedCrossTrackError;
1671
+ try {
1672
+ averageSignedCrossTrackError = trackedRace == null ? null : trackedRace.getAverageSignedCrossTrackError(
1673
+ competitor, timePoint, waitForLatestAnalyses, cache);
1674
+ } catch (NoWindException nwe) {
1675
+ // without wind information, use null meaning "unknown"
1676
+ averageSignedCrossTrackError = null;
1677
+ }
1678
+ final CompetitorRankingInfo competitorRankingInfo = rankingInfo.getCompetitorRankingInfo().apply(competitor);
1679
+ return new RaceDetails(legDetails, windwardDistanceToCompetitorFarthestAhead, averageAbsoluteCrossTrackError, averageSignedCrossTrackError,
1680
+ trackedRace.getRankingMetric().getGapToLeaderInOwnTime(rankingInfo, competitor, cache),
1681
+ trackedRace.getRankingMetric().getCorrectedTime(competitor, timePoint),
1682
+ competitorRankingInfo == null ? null : competitorRankingInfo.getCorrectedTimeAtEstimatedArrivalAtCompetitorFarthestAhead());
1683
+ } finally {
1684
+ course.unlockAfterRead();
1685
+ }
1686
+ }
1687
+
1688
+ private LegEntryDTO createLegEntry(TrackedLegOfCompetitor trackedLeg, TimePoint timePoint,
1689
+ boolean waitForLatestAnalyses, Map<Leg, LinkedHashMap<Competitor, Integer>> legRanksCache,
1690
+ RankingInfo rankingInfo, WindLegTypeAndLegBearingCache cache) {
1691
+ LegEntryDTO result;
1692
+ final Duration time = trackedLeg.getTime(timePoint);
1693
+ if (trackedLeg == null || time == null) {
1694
+ result = null;
1695
+ } else {
1696
+ result = new LegEntryDTO();
1697
+ try {
1698
+ result.legType = trackedLeg.getTrackedLeg().getLegType(timePoint);
1699
+ } catch (NoWindException nwe) {
1700
+ result.legType = null; // can't determine leg type without wind data
1701
+ }
1702
+ final Speed averageSpeedOverGround = trackedLeg.getAverageSpeedOverGround(timePoint);
1703
+ result.averageSpeedOverGroundInKnots = averageSpeedOverGround == null ? null : averageSpeedOverGround.getKnots();
1704
+ Distance averageAbsoluteCrossTrackError;
1705
+ try {
1706
+ averageAbsoluteCrossTrackError = trackedLeg.getAverageAbsoluteCrossTrackError(timePoint, waitForLatestAnalyses);
1707
+ } catch (NoWindException nwe) {
1708
+ // leave averageAbsoluteCrossTrackError as null, meaning "unknown"
1709
+ averageAbsoluteCrossTrackError = null;
1710
+ }
1711
+ result.averageAbsoluteCrossTrackErrorInMeters = averageAbsoluteCrossTrackError == null ? null : averageAbsoluteCrossTrackError.getMeters();
1712
+ Distance averageSignedCrossTrackError;
1713
+ try {
1714
+ averageSignedCrossTrackError = trackedLeg.getAverageSignedCrossTrackError(timePoint, waitForLatestAnalyses);
1715
+ } catch (NoWindException nwe) {
1716
+ // leave averageSignedCrossTrackError as null, meaning "unknown"
1717
+ averageSignedCrossTrackError = null;
1718
+ }
1719
+ result.averageSignedCrossTrackErrorInMeters = averageSignedCrossTrackError == null ? null : averageSignedCrossTrackError.getMeters();
1720
+ Double speedOverGroundInKnots;
1721
+ if (trackedLeg.hasFinishedLeg(timePoint)) {
1722
+ speedOverGroundInKnots = averageSpeedOverGround == null ? null : averageSpeedOverGround.getKnots();
1723
+ } else {
1724
+ final SpeedWithBearing speedOverGround = trackedLeg.getSpeedOverGround(timePoint);
1725
+ speedOverGroundInKnots = speedOverGround == null ? null : speedOverGround.getKnots();
1726
+ }
1727
+ result.currentSpeedOverGroundInKnots = speedOverGroundInKnots == null ? null : speedOverGroundInKnots;
1728
+ Distance distanceTraveled = trackedLeg.getDistanceTraveled(timePoint);
1729
+ result.distanceTraveledInMeters = distanceTraveled == null ? null : distanceTraveled.getMeters();
1730
+ Distance distanceTraveledConsideringGateStart = trackedLeg.getDistanceTraveledConsideringGateStart(timePoint);
1731
+ result.distanceTraveledIncludingGateStartInMeters = distanceTraveledConsideringGateStart == null ? null : distanceTraveledConsideringGateStart.getMeters();
1732
+ final Duration estimatedTimeToNextMarkInSeconds = trackedLeg.getEstimatedTimeToNextMark(timePoint, WindPositionMode.EXACT, cache);
1733
+ result.estimatedTimeToNextWaypointInSeconds = estimatedTimeToNextMarkInSeconds==null?null:estimatedTimeToNextMarkInSeconds.asSeconds();
1734
+ result.timeInMilliseconds = time.asMillis();
1735
+ result.finished = trackedLeg.hasFinishedLeg(timePoint);
1736
+ final TimePoint legFinishTime = trackedLeg.getFinishTime();
1737
+ result.correctedTotalTime = trackedLeg.hasStartedLeg(timePoint) ? trackedLeg.getTrackedLeg().getTrackedRace().getRankingMetric().getCorrectedTime(trackedLeg.getCompetitor(),
1738
+ trackedLeg.hasFinishedLeg(timePoint) ? legFinishTime : timePoint, cache) : null;
1739
+ // fetch the leg gap in own corrected time from the ranking metric
1740
+ final Duration gapToLeaderInOwnTime = trackedLeg.getTrackedLeg().getTrackedRace().getRankingMetric().
1741
+ getLegGapToLegLeaderInOwnTime(trackedLeg, timePoint, rankingInfo, cache);
1742
+ result.gapToLeaderInSeconds = gapToLeaderInOwnTime == null ? null : gapToLeaderInOwnTime.asSeconds();
1743
+ if (result.gapToLeaderInSeconds != null) {
1744
+ final Duration gapAtEndOfPreviousLeg = getGapAtEndOfPreviousLeg(trackedLeg, rankingInfo, cache);
1745
+ if (gapAtEndOfPreviousLeg != null) {
1746
+ result.gapChangeSinceLegStartInSeconds = result.gapToLeaderInSeconds - gapAtEndOfPreviousLeg.asSeconds();
1747
+ }
1748
+ }
1749
+ LinkedHashMap<Competitor, Integer> legRanks = legRanksCache.get(trackedLeg.getLeg());
1750
+ if (legRanks != null) {
1751
+ result.rank = legRanks.get(trackedLeg.getCompetitor());
1752
+ } else {
1753
+ result.rank = trackedLeg.getRank(timePoint, cache);
1754
+ }
1755
+ result.started = trackedLeg.hasStartedLeg(timePoint);
1756
+ Speed velocityMadeGood;
1757
+ if (trackedLeg.hasFinishedLeg(timePoint)) {
1758
+ velocityMadeGood = trackedLeg.getAverageVelocityMadeGood(timePoint);
1759
+ } else {
1760
+ velocityMadeGood = trackedLeg.getVelocityMadeGood(timePoint, WindPositionMode.EXACT);
1761
+ }
1762
+ result.velocityMadeGoodInKnots = velocityMadeGood == null ? null : velocityMadeGood.getKnots();
1763
+ Distance windwardDistanceToGo = trackedLeg.getWindwardDistanceToGo(timePoint, WindPositionMode.LEG_MIDDLE);
1764
+ result.windwardDistanceToGoInMeters = windwardDistanceToGo == null ? null : windwardDistanceToGo
1765
+ .getMeters();
1766
+ final TimePoint startOfRace = trackedLeg.getTrackedLeg().getTrackedRace().getStartOfRace();
1767
+ if (startOfRace != null && trackedLeg.hasStartedLeg(timePoint)) {
1768
+ // not using trackedLeg.getManeuvers(...) because it may not catch the mark passing maneuver starting this leg
1769
+ // because that may have been detected as slightly before the mark passing time, hence associated with the previous leg
1770
+ List<Maneuver> maneuvers = trackedLeg.getTrackedLeg().getTrackedRace()
1771
+ .getManeuvers(trackedLeg.getCompetitor(), startOfRace, timePoint, waitForLatestAnalyses);
1772
+ if (maneuvers != null) {
1773
+ result.numberOfManeuvers = new HashMap<ManeuverType, Integer>();
1774
+ result.numberOfManeuvers.put(ManeuverType.TACK, 0);
1775
+ result.numberOfManeuvers.put(ManeuverType.JIBE, 0);
1776
+ result.numberOfManeuvers.put(ManeuverType.PENALTY_CIRCLE, 0);
1777
+ Map<ManeuverType, Double> totalManeuverLossInMeters = new HashMap<ManeuverType, Double>();
1778
+ totalManeuverLossInMeters.put(ManeuverType.TACK, 0.0);
1779
+ totalManeuverLossInMeters.put(ManeuverType.JIBE, 0.0);
1780
+ totalManeuverLossInMeters.put(ManeuverType.PENALTY_CIRCLE, 0.0);
1781
+ TimePoint startOfLeg = trackedLeg.getStartTime();
1782
+ for (Maneuver maneuver : maneuvers) {
1783
+ // don't count maneuvers that were in previous legs
1784
+ switch (maneuver.getType()) {
1785
+ case TACK:
1786
+ case JIBE:
1787
+ case PENALTY_CIRCLE:
1788
+ if (!maneuver.getTimePoint().before(startOfLeg) && (legFinishTime == null || legFinishTime.after(timePoint) ||
1789
+ maneuver.getTimePoint().before(legFinishTime))) {
1790
+ if (maneuver.getManeuverLoss() != null) {
1791
+ result.numberOfManeuvers.put(maneuver.getType(),
1792
+ result.numberOfManeuvers.get(maneuver.getType()) + 1);
1793
+ totalManeuverLossInMeters.put(maneuver.getType(),
1794
+ totalManeuverLossInMeters.get(maneuver.getType())
1795
+ + maneuver.getManeuverLoss().getMeters());
1796
+ }
1797
+ }
1798
+ break;
1799
+ case MARK_PASSING:
1800
+ // analyze all mark passings, not only those after this leg's start, to catch the mark passing
1801
+ // maneuver starting this leg, even if its time point is slightly before the mark passing starting this leg
1802
+ MarkPassingManeuver mpm = (MarkPassingManeuver) maneuver;
1803
+ if (mpm.getWaypointPassed() == trackedLeg.getLeg().getFrom()) {
1804
+ result.sideToWhichMarkAtLegStartWasRounded = mpm.getSide();
1805
+ }
1806
+ break;
1807
+ default:
1808
+ /* Do nothing here.
1809
+ * Throwing an exception destroys the toggling (and maybe other behaviour) of the leaderboard.
1810
+ */
1811
+ }
1812
+ }
1813
+ result.averageManeuverLossInMeters = new HashMap<ManeuverType, Double>();
1814
+ for (ManeuverType maneuverType : new ManeuverType[] { ManeuverType.TACK, ManeuverType.JIBE,
1815
+ ManeuverType.PENALTY_CIRCLE }) {
1816
+ if (result.numberOfManeuvers.get(maneuverType) != 0) {
1817
+ result.averageManeuverLossInMeters.put(
1818
+ maneuverType,
1819
+ totalManeuverLossInMeters.get(maneuverType)
1820
+ / result.numberOfManeuvers.get(maneuverType));
1821
+ }
1822
+ }
1823
+ }
1824
+ }
1825
+ }
1826
+ return result;
1827
+ }
1828
+
1829
+ private Duration getGapAtEndOfPreviousLeg(TrackedLegOfCompetitor trackedLeg, final RankingInfo rankingInfo, WindLegTypeAndLegBearingCache cache) {
1830
+ final Duration result;
1831
+ final Course course = trackedLeg.getTrackedLeg().getTrackedRace().getRace().getCourse();
1832
+ // if trackedLeg is the first leg, compute the gap at the start of this leg; otherwise, compute gap
1833
+ // at the end of the previous leg
1834
+ final TimePoint timePoint = trackedLeg.getStartTime();
1835
+ final TrackedLegOfCompetitor tloc;
1836
+ if (course.getFirstWaypoint() == trackedLeg.getLeg().getFrom()) {
1837
+ tloc = trackedLeg;
1838
+ } else {
1839
+ tloc = trackedLeg.getTrackedLeg().getTrackedRace().getTrackedLegFinishingAt(trackedLeg.getLeg().getFrom())
1840
+ .getTrackedLeg(trackedLeg.getCompetitor());
1841
+ }
1842
+ result = trackedLeg.getTrackedLeg().getTrackedRace().getRankingMetric().getLegGapToLegLeaderInOwnTime(tloc, timePoint, rankingInfo, cache);
1843
+ return result;
1844
+ }
1845
+
1846
+ private LeaderboardDTO getLiveLeaderboard(Collection<String> namesOfRaceColumnsForWhichToLoadLegDetails,
1847
+ boolean addOverallDetails, TrackedRegattaRegistry trackedRegattaRegistry, DomainFactory baseDomainFactory) throws NoWindException, ExecutionException {
1848
+ LiveLeaderboardUpdater liveLeaderboardUpdater = getLiveLeaderboardUpdater(trackedRegattaRegistry,
1849
+ baseDomainFactory);
1850
+ return liveLeaderboardUpdater.getLiveLeaderboard(namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails);
1851
+ }
1852
+
1853
+ private LiveLeaderboardUpdater getLiveLeaderboardUpdater(TrackedRegattaRegistry trackedRegattaRegistry,
1854
+ DomainFactory baseDomainFactory) {
1855
+ LiveLeaderboardUpdater result = this.liveLeaderboardUpdater;
1856
+ if (result == null) {
1857
+ synchronized (this) {
1858
+ result = this.liveLeaderboardUpdater;
1859
+ if (result == null) {
1860
+ this.liveLeaderboardUpdater = new LiveLeaderboardUpdater(this, trackedRegattaRegistry, baseDomainFactory);
1861
+ result = this.liveLeaderboardUpdater;
1862
+ }
1863
+ }
1864
+ }
1865
+ return result;
1866
+ }
1867
+
1868
+ private LeaderboardDTOCache getLeaderboardDTOCache() {
1869
+ LeaderboardDTOCache result = this.leaderboardDTOCache;
1870
+ if (result == null) {
1871
+ synchronized (this) {
1872
+ result = this.leaderboardDTOCache;
1873
+ if (result == null) {
1874
+ // The leaderboard cache is invalidated upon all competitor and mark position changes; some analyzes
1875
+ // are pretty expensive, such as the maneuver re-calculation. Waiting for the latest analysis after only a
1876
+ // single fix was updated is too expensive if users use the replay feature while a race is still running.
1877
+ // Therefore, using waitForLatestAnalyses==false seems appropriate here.
1878
+ this.leaderboardDTOCache = new LeaderboardDTOCache(/* waitForLatestAnalyses */false, this);
1879
+ result = this.leaderboardDTOCache;
1880
+ }
1881
+ }
1882
+ }
1883
+ return result;
1884
+ }
1885
+
1886
+ @Override
1887
+ public LeaderboardDTO getLeaderboardDTO(TimePoint timePoint,
1888
+ Collection<String> namesOfRaceColumnsForWhichToLoadLegDetails, boolean addOverallDetails,
1889
+ TrackedRegattaRegistry trackedRegattaRegistry, DomainFactory baseDomainFactory, boolean fillNetPointsUncorrected) throws NoWindException,
1890
+ InterruptedException, ExecutionException {
1891
+ LeaderboardDTO result = null;
1892
+ if (timePoint == null) {
1893
+ // timePoint==null means live mode; however, if we're after the end of all races and after all score
1894
+ // corrections, don't use the live leaderboard updater which would keep re-calculating over and over again, but map
1895
+ // this to a usual non-live call which uses the regular LeaderboardDTOCache which is invalidated properly
1896
+ // when the tracked race associations or score corrections or tracked race contents changes:
1897
+ final TimePoint nowMinusDelay = this.getNowMinusDelay();
1898
+ final TimePoint timePointOfLatestModification = this.getTimePointOfLatestModification();
1899
+ if (fillNetPointsUncorrected || (timePointOfLatestModification != null && !nowMinusDelay.before(timePointOfLatestModification))) {
1900
+ // if there hasn't been any modification to the leaderboard since nowMinusDelay, use non-live mode
1901
+ // and pull the result from the regular leaderboard cache:
1902
+ timePoint = timePointOfLatestModification;
1903
+ } else {
1904
+ // don't use the regular leaderboard cache; the race still seems to be on; use the live leaderboard updater instead:
1905
+ timePoint = null;
1906
+ result = this.getLiveLeaderboard(namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails, trackedRegattaRegistry, baseDomainFactory);
1907
+ }
1908
+ }
1909
+ if (timePoint != null) {
1910
+ if (fillNetPointsUncorrected) {
1911
+ // explicitly filling the uncorrected net points requires uncached recalculation
1912
+ result = computeDTO(timePoint, namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails, /* waitForLatestAnalyses */ true,
1913
+ trackedRegattaRegistry, baseDomainFactory, fillNetPointsUncorrected);
1914
+ } else {
1915
+ // in replay we'd like up-to-date results; they are still cached
1916
+ // which is OK because the cache is invalidated whenever any of the tracked races attached to the
1917
+ // leaderboard changes.
1918
+ result = getLeaderboardDTOCache().getLeaderboardByName(timePoint,
1919
+ namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails, baseDomainFactory,
1920
+ trackedRegattaRegistry);
1921
+ }
1922
+ }
1923
+ return result;
1924
+ }
1925
+
1926
+ public String toString() {
1927
+ return getName() + " " + (getDefaultCourseArea() != null ? getDefaultCourseArea().getName() : "<No course area defined>") + " " + (getScoringScheme() != null ? getScoringScheme().getType().name() : "<No scoring scheme set>");
1928
+ }
1929
+
1930
+ @Override
1931
+ public NumberOfCompetitorsInLeaderboardFetcher getNumberOfCompetitorsInLeaderboardFetcher() {
1932
+ return new NumberOfCompetitorsFetcherImpl();
1933
+ }
1934
+
1935
+ protected abstract LeaderboardType getLeaderboardType();
1936
+
1937
+}
java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/FlexibleLeaderboardImpl.java
... ...
@@ -1,336 +1,342 @@
1
-package com.sap.sailing.domain.leaderboard.impl;
2
-
3
-import java.io.IOException;
4
-import java.io.ObjectInputStream;
5
-import java.io.ObjectOutputStream;
6
-import java.io.ObjectStreamException;
7
-import java.util.ArrayList;
8
-import java.util.Arrays;
9
-import java.util.Collections;
10
-import java.util.HashMap;
11
-import java.util.List;
12
-import java.util.Map;
13
-import java.util.logging.Logger;
14
-
15
-import com.sap.sailing.domain.abstractlog.regatta.RegattaLog;
16
-import com.sap.sailing.domain.base.Competitor;
17
-import com.sap.sailing.domain.base.CourseArea;
18
-import com.sap.sailing.domain.base.Fleet;
19
-import com.sap.sailing.domain.base.RaceColumn;
20
-import com.sap.sailing.domain.base.RaceColumnListener;
21
-import com.sap.sailing.domain.base.impl.AbstractRaceExecutionOrderProvider;
22
-import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard;
23
-import com.sap.sailing.domain.leaderboard.FlexibleRaceColumn;
24
-import com.sap.sailing.domain.leaderboard.ScoringScheme;
25
-import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule;
26
-import com.sap.sailing.domain.racelog.RaceLogStore;
27
-import com.sap.sailing.domain.racelog.impl.EmptyRaceLogStore;
28
-import com.sap.sailing.domain.regattalike.BaseRegattaLikeImpl;
29
-import com.sap.sailing.domain.regattalike.FlexibleLeaderboardAsRegattaLikeIdentifier;
30
-import com.sap.sailing.domain.regattalike.IsRegattaLike;
31
-import com.sap.sailing.domain.regattalike.RegattaLikeIdentifier;
32
-import com.sap.sailing.domain.regattalike.RegattaLikeListener;
33
-import com.sap.sailing.domain.regattalog.RegattaLogStore;
34
-import com.sap.sailing.domain.regattalog.impl.EmptyRegattaLogStore;
35
-import com.sap.sailing.domain.tracking.RaceExecutionOrderProvider;
36
-import com.sap.sailing.domain.tracking.TrackedRace;
37
-import com.sap.sse.common.Duration;
38
-
39
-/**
40
- * A leaderboard implementation that allows users to flexibly configure which columns exist. No constraints need to be observed regarding
41
- * the columns belonging to the same regatta or even boat class.<p>
42
- *
43
- * The flexible leaderboard listens as {@link RaceColumnListener} on all its {@link RaceColumn}s and forwards all events to
44
- * all {@link RaceColumnListener}s subscribed with this leaderboard.
45
- *
46
- * @author Axel Uhl (D043530)
47
- *
48
- */
49
-public class FlexibleLeaderboardImpl extends AbstractLeaderboardImpl implements FlexibleLeaderboard {
50
- private static Logger logger = Logger.getLogger(FlexibleLeaderboardImpl.class.getName());
51
-
52
- protected static final DefaultFleetImpl defaultFleet = new DefaultFleetImpl();
53
- private static final long serialVersionUID = -5708971849158747846L;
54
- private final List<FlexibleRaceColumn> races;
55
- private final ScoringScheme scoringScheme;
56
- private String name;
57
- private transient RaceLogStore raceLogStore;
58
- private CourseArea courseArea;
59
- private RaceExecutionOrderProvider raceExecutionOrderProvider;
60
-
61
- /**
62
- * @see RegattaLog for the reason why the leaderboard manages a {@code RegattaLog}
63
- */
64
- private final IsRegattaLike regattaLikeHelper;
65
-
66
- public FlexibleLeaderboardImpl(String name, ThresholdBasedResultDiscardingRule resultDiscardingRule,
67
- ScoringScheme scoringScheme, CourseArea courseArea) {
68
- this(EmptyRaceLogStore.INSTANCE, EmptyRegattaLogStore.INSTANCE,
69
- name, resultDiscardingRule, scoringScheme, courseArea);
70
- }
71
-
72
- public FlexibleLeaderboardImpl(RaceLogStore raceLogStore, RegattaLogStore regattaLogStore,
73
- String name, ThresholdBasedResultDiscardingRule resultDiscardingRule,
74
- ScoringScheme scoringScheme, CourseArea courseArea) {
75
- super(resultDiscardingRule);
76
- this.scoringScheme = scoringScheme;
77
- if (name == null) {
78
- throw new IllegalArgumentException("A leaderboard's name must not be null");
79
- }
80
- this.name = name;
81
- this.races = new ArrayList<>();
82
- this.raceLogStore = raceLogStore;
83
- this.courseArea = courseArea;
84
- this.regattaLikeHelper = new BaseRegattaLikeImpl(new FlexibleLeaderboardAsRegattaLikeIdentifier(this), regattaLogStore) {
85
- private static final long serialVersionUID = 4082392360832548953L;
86
-
87
- @Override
88
- public RaceColumn getRaceColumnByName(String raceColumnName) {
89
- return getRaceColumnByName(raceColumnName);
90
- }
91
- };
92
- this.raceExecutionOrderProvider = new RaceExecutionOrderCache();
93
- }
94
-
95
- /**
96
- * Deserialization has to be maintained in lock-step with {@link #writeObject(ObjectOutputStream) serialization}.
97
- * When de-serializing, a possibly remote {@link #raceLogStore} is ignored because it is transient. Instead, an
98
- * {@link EmptyRaceLogStore} is used for the de-serialized instance. A new {@link RaceLogInformation} is
99
- * assembled for this empty race log and applied to all columns.
100
- */
101
- private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException {
102
- ois.defaultReadObject();
103
- raceLogStore = EmptyRaceLogStore.INSTANCE;
104
- for (RaceColumn column : getRaceColumns()) {
105
- column.setRaceLogInformation(raceLogStore, new FlexibleLeaderboardAsRegattaLikeIdentifier(this));
106
- }
107
- }
108
-
109
- protected Object readResolve() throws ObjectStreamException {
110
- raceExecutionOrderProvider.triggerUpdate();
111
- return this;
112
- }
113
-
114
- @Override
115
- public String getName() {
116
- return name;
117
- }
118
-
119
- /**
120
- * @param newName must not be <code>null</code>
121
- */
122
- public void setName(String newName) {
123
- if (newName == null) {
124
- throw new IllegalArgumentException("A leaderboard's name must not be null");
125
- }
126
- this.name = newName;
127
- }
128
-
129
- @Override
130
- public RaceColumn addRace(TrackedRace race, String columnName, boolean medalRace) {
131
- FlexibleRaceColumn column = addRaceColumn(columnName, medalRace, /* logAlreadyExistingColumn */false);
132
- column.setTrackedRace(defaultFleet, race); // triggers listeners because this object was registered above as
133
- // race column listener on the column
134
- return column;
135
- }
136
-
137
- @Override
138
- public FlexibleRaceColumn addRaceColumn(String name, boolean medalRace) {
139
- return addRaceColumn(name, medalRace, /* logAlreadyExistingColumn */true);
140
- }
141
-
142
- private FlexibleRaceColumn addRaceColumn(String name, boolean medalRace, boolean logAlreadyExistingColumn) {
143
- FlexibleRaceColumn column = getRaceColumnByName(name);
144
- if (column != null) {
145
- if (logAlreadyExistingColumn) {
146
- final String msg = "Trying to create race column with duplicate name " + name + " in leaderboard " + getName();
147
- logger.severe(msg);
148
- }
149
- } else {
150
- column = createRaceColumn(name, medalRace);
151
- column.addRaceColumnListener(this);
152
- races.add(column);
153
- column.setRaceLogInformation(raceLogStore, new FlexibleLeaderboardAsRegattaLikeIdentifier(this));
154
- getRaceColumnListeners().notifyListenersAboutRaceColumnAddedToContainer(column);
155
- }
156
- return column;
157
- }
158
-
159
- @Override
160
- public FlexibleRaceColumn getRaceColumnByName(String columnName) {
161
- return (FlexibleRaceColumn) super.getRaceColumnByName(columnName);
162
- }
163
-
164
- @Override
165
- public Fleet getFleet(String fleetName) {
166
- Fleet result;
167
- if (fleetName == null) {
168
- result = defaultFleet;
169
- } else {
170
- result = super.getFleet(fleetName);
171
- }
172
- return result;
173
- }
174
-
175
- @Override
176
- public void removeRaceColumn(String columnName) {
177
- final FlexibleRaceColumn raceColumn = getRaceColumnByName(columnName);
178
- if (raceColumn != null) {
179
- for (Fleet fleet : raceColumn.getFleets()) {
180
- raceLogStore.removeRaceLog(raceColumn.getRaceLogIdentifier(fleet));
181
- if (raceColumn.getTrackedRace(fleet) != null) {
182
- raceColumn.getTrackedRace(fleet).detachRaceExecutionOrderProvider(raceExecutionOrderProvider);
183
- }
184
- }
185
- races.remove(raceColumn);
186
- getRaceColumnListeners().notifyListenersAboutRaceColumnRemovedFromContainer(raceColumn);
187
- raceColumn.removeRaceColumnListener(this);
188
- }
189
- }
190
-
191
- @Override
192
- public Iterable<RaceColumn> getRaceColumns() {
193
- final Iterable<RaceColumn> result;
194
- if (races != null) {
195
- result = Collections.unmodifiableCollection(new ArrayList<RaceColumn>(races));
196
- } else {
197
- result = null;
198
- }
199
- return result;
200
- }
201
-
202
- protected RaceColumnImpl createRaceColumn(String column, boolean medalRace) {
203
- return new RaceColumnImpl(column, medalRace, raceExecutionOrderProvider);
204
- }
205
-
206
- protected Iterable<Fleet> turnNullOrEmptyFleetsIntoDefaultFleet(Fleet... fleets) {
207
- Iterable<Fleet> theFleets;
208
- if (fleets == null || fleets.length == 0) {
209
- Fleet defaultfleetCasted = defaultFleet;
210
- theFleets = Collections.singleton(defaultfleetCasted);
211
- } else {
212
- theFleets = Arrays.asList(fleets);
213
- }
214
- return theFleets;
215
- }
216
-
217
- @Override
218
- public void moveRaceColumnUp(String name) {
219
- FlexibleRaceColumn race = null;
220
- for (FlexibleRaceColumn r : races) {
221
- if (r.getName().equals(name)) {
222
- race = r;
223
- }
224
- }
225
- if (race != null) {
226
- int index = 0;
227
- index = races.lastIndexOf(race);
228
- index--;
229
- if (index >= 0) {
230
- races.remove(race);
231
- races.add(index, race);
232
- getRaceColumnListeners().notifyListenersAboutRaceColumnMoved(race, index);
233
- }
234
- }
235
- }
236
-
237
- @Override
238
- public void moveRaceColumnDown(String name) {
239
- FlexibleRaceColumn race = null;
240
- for (FlexibleRaceColumn r : races) {
241
- if (r.getName().equals(name)) {
242
- race = r;
243
- }
244
- }
245
- if (race != null) {
246
- int index = 0;
247
- index = races.lastIndexOf(race);
248
- if (index != -1) {
249
- index++;
250
- if (index < races.size()) {
251
- races.remove(race);
252
- races.add(index, race);
253
- getRaceColumnListeners().notifyListenersAboutRaceColumnMoved(race, index);
254
- }
255
- }
256
- }
257
- }
258
-
259
- @Override
260
- public void updateIsMedalRace(String raceName, boolean isMedalRace) {
261
- FlexibleRaceColumn race = null;
262
- for (FlexibleRaceColumn r : races) {
263
- if (r.getName().equals(raceName))
264
- race = r;
265
- }
266
- if (race != null) {
267
- race.setIsMedalRace(isMedalRace);
268
- }
269
- }
270
-
271
- @Override
272
- public ScoringScheme getScoringScheme() {
273
- return scoringScheme;
274
- }
275
-
276
- @Override
277
- public CourseArea getDefaultCourseArea() {
278
- return courseArea;
279
- }
280
-
281
- @Override
282
- public void setDefaultCourseArea(CourseArea newCourseArea) {
283
- this.courseArea = newCourseArea;
284
- }
285
-
286
- @Override
287
- public IsRegattaLike getRegattaLike() {
288
- return regattaLikeHelper;
289
- }
290
-
291
- @Override
292
- public RegattaLog getRegattaLog() {
293
- return regattaLikeHelper.getRegattaLog();
294
- }
295
-
296
- @Override
297
- public RegattaLikeIdentifier getRegattaLikeIdentifier() {
298
- return regattaLikeHelper.getRegattaLikeIdentifier();
299
- }
300
-
301
- @Override
302
- public void addListener(RegattaLikeListener listener) {
303
- regattaLikeHelper.addListener(listener);
304
- }
305
-
306
- @Override
307
- public void removeListener(RegattaLikeListener listener) {
308
- regattaLikeHelper.removeListener(listener);
309
- }
310
-
311
- @Override
312
- public Double getTimeOnTimeFactor(Competitor competitor) {
313
- return regattaLikeHelper.getTimeOnTimeFactor(competitor);
314
- }
315
-
316
- @Override
317
- public Duration getTimeOnDistanceAllowancePerNauticalMile(Competitor competitor) {
318
- return regattaLikeHelper.getTimeOnDistanceAllowancePerNauticalMile(competitor);
319
- }
320
-
321
- private class RaceExecutionOrderCache extends AbstractRaceExecutionOrderProvider {
322
- private static final long serialVersionUID = 652833386555762661L;
323
-
324
- public RaceExecutionOrderCache() {
325
- super();
326
- addRaceColumnListener(this);
327
- }
328
-
329
- @Override
330
- protected Map<Fleet, Iterable<? extends RaceColumn>> getRaceColumnsOfSeries() {
331
- final Map<Fleet, Iterable<? extends RaceColumn>> result = new HashMap<>();
332
- result.put(FlexibleLeaderboardImpl.defaultFleet, getRaceColumns());
333
- return result;
334
- }
335
- }
336
-}
1
+package com.sap.sailing.domain.leaderboard.impl;
2
+
3
+import java.io.IOException;
4
+import java.io.ObjectInputStream;
5
+import java.io.ObjectOutputStream;
6
+import java.io.ObjectStreamException;
7
+import java.util.ArrayList;
8
+import java.util.Arrays;
9
+import java.util.Collections;
10
+import java.util.HashMap;
11
+import java.util.List;
12
+import java.util.Map;
13
+import java.util.logging.Logger;
14
+
15
+import com.sap.sailing.domain.abstractlog.regatta.RegattaLog;
16
+import com.sap.sailing.domain.base.Competitor;
17
+import com.sap.sailing.domain.base.CourseArea;
18
+import com.sap.sailing.domain.base.Fleet;
19
+import com.sap.sailing.domain.base.RaceColumn;
20
+import com.sap.sailing.domain.base.RaceColumnListener;
21
+import com.sap.sailing.domain.base.impl.AbstractRaceExecutionOrderProvider;
22
+import com.sap.sailing.domain.common.LeaderboardType;
23
+import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard;
24
+import com.sap.sailing.domain.leaderboard.FlexibleRaceColumn;
25
+import com.sap.sailing.domain.leaderboard.ScoringScheme;
26
+import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule;
27
+import com.sap.sailing.domain.racelog.RaceLogStore;
28
+import com.sap.sailing.domain.racelog.impl.EmptyRaceLogStore;
29
+import com.sap.sailing.domain.regattalike.BaseRegattaLikeImpl;
30
+import com.sap.sailing.domain.regattalike.FlexibleLeaderboardAsRegattaLikeIdentifier;
31
+import com.sap.sailing.domain.regattalike.IsRegattaLike;
32
+import com.sap.sailing.domain.regattalike.RegattaLikeIdentifier;
33
+import com.sap.sailing.domain.regattalike.RegattaLikeListener;
34
+import com.sap.sailing.domain.regattalog.RegattaLogStore;
35
+import com.sap.sailing.domain.regattalog.impl.EmptyRegattaLogStore;
36
+import com.sap.sailing.domain.tracking.RaceExecutionOrderProvider;
37
+import com.sap.sailing.domain.tracking.TrackedRace;
38
+import com.sap.sse.common.Duration;
39
+
40
+/**
41
+ * A leaderboard implementation that allows users to flexibly configure which columns exist. No constraints need to be observed regarding
42
+ * the columns belonging to the same regatta or even boat class.<p>
43
+ *
44
+ * The flexible leaderboard listens as {@link RaceColumnListener} on all its {@link RaceColumn}s and forwards all events to
45
+ * all {@link RaceColumnListener}s subscribed with this leaderboard.
46
+ *
47
+ * @author Axel Uhl (D043530)
48
+ *
49
+ */
50
+public class FlexibleLeaderboardImpl extends AbstractLeaderboardImpl implements FlexibleLeaderboard {
51
+ private static Logger logger = Logger.getLogger(FlexibleLeaderboardImpl.class.getName());
52
+
53
+ protected static final DefaultFleetImpl defaultFleet = new DefaultFleetImpl();
54
+ private static final long serialVersionUID = -5708971849158747846L;
55
+ private final List<FlexibleRaceColumn> races;
56
+ private final ScoringScheme scoringScheme;
57
+ private String name;
58
+ private transient RaceLogStore raceLogStore;
59
+ private CourseArea courseArea;
60
+ private RaceExecutionOrderProvider raceExecutionOrderProvider;
61
+
62
+ /**
63
+ * @see RegattaLog for the reason why the leaderboard manages a {@code RegattaLog}
64
+ */
65
+ private final IsRegattaLike regattaLikeHelper;
66
+
67
+ public FlexibleLeaderboardImpl(String name, ThresholdBasedResultDiscardingRule resultDiscardingRule,
68
+ ScoringScheme scoringScheme, CourseArea courseArea) {
69
+ this(EmptyRaceLogStore.INSTANCE, EmptyRegattaLogStore.INSTANCE,
70
+ name, resultDiscardingRule, scoringScheme, courseArea);
71
+ }
72
+
73
+ public FlexibleLeaderboardImpl(RaceLogStore raceLogStore, RegattaLogStore regattaLogStore,
74
+ String name, ThresholdBasedResultDiscardingRule resultDiscardingRule,
75
+ ScoringScheme scoringScheme, CourseArea courseArea) {
76
+ super(resultDiscardingRule);
77
+ this.scoringScheme = scoringScheme;
78
+ if (name == null) {
79
+ throw new IllegalArgumentException("A leaderboard's name must not be null");
80
+ }
81
+ this.name = name;
82
+ this.races = new ArrayList<>();
83
+ this.raceLogStore = raceLogStore;
84
+ this.courseArea = courseArea;
85
+ this.regattaLikeHelper = new BaseRegattaLikeImpl(new FlexibleLeaderboardAsRegattaLikeIdentifier(this), regattaLogStore) {
86
+ private static final long serialVersionUID = 4082392360832548953L;
87
+
88
+ @Override
89
+ public RaceColumn getRaceColumnByName(String raceColumnName) {
90
+ return getRaceColumnByName(raceColumnName);
91
+ }
92
+ };
93
+ this.raceExecutionOrderProvider = new RaceExecutionOrderCache();
94
+ }
95
+
96
+ /**
97
+ * Deserialization has to be maintained in lock-step with {@link #writeObject(ObjectOutputStream) serialization}.
98
+ * When de-serializing, a possibly remote {@link #raceLogStore} is ignored because it is transient. Instead, an
99
+ * {@link EmptyRaceLogStore} is used for the de-serialized instance. A new {@link RaceLogInformation} is
100
+ * assembled for this empty race log and applied to all columns.
101
+ */
102
+ private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException {
103
+ ois.defaultReadObject();
104
+ raceLogStore = EmptyRaceLogStore.INSTANCE;
105
+ for (RaceColumn column : getRaceColumns()) {
106
+ column.setRaceLogInformation(raceLogStore, new FlexibleLeaderboardAsRegattaLikeIdentifier(this));
107
+ }
108
+ }
109
+
110
+ protected Object readResolve() throws ObjectStreamException {
111
+ raceExecutionOrderProvider.triggerUpdate();
112
+ return this;
113
+ }
114
+
115
+ @Override
116
+ public String getName() {
117
+ return name;
118
+ }
119
+
120
+ /**
121
+ * @param newName must not be <code>null</code>
122
+ */
123
+ public void setName(String newName) {
124
+ if (newName == null) {
125
+ throw new IllegalArgumentException("A leaderboard's name must not be null");
126
+ }
127
+ this.name = newName;
128
+ }
129
+
130
+ @Override
131
+ public RaceColumn addRace(TrackedRace race, String columnName, boolean medalRace) {
132
+ FlexibleRaceColumn column = addRaceColumn(columnName, medalRace, /* logAlreadyExistingColumn */false);
133
+ column.setTrackedRace(defaultFleet, race); // triggers listeners because this object was registered above as
134
+ // race column listener on the column
135
+ return column;
136
+ }
137
+
138
+ @Override
139
+ public FlexibleRaceColumn addRaceColumn(String name, boolean medalRace) {
140
+ return addRaceColumn(name, medalRace, /* logAlreadyExistingColumn */true);
141
+ }
142
+
143
+ private FlexibleRaceColumn addRaceColumn(String name, boolean medalRace, boolean logAlreadyExistingColumn) {
144
+ FlexibleRaceColumn column = getRaceColumnByName(name);
145
+ if (column != null) {
146
+ if (logAlreadyExistingColumn) {
147
+ final String msg = "Trying to create race column with duplicate name " + name + " in leaderboard " + getName();
148
+ logger.severe(msg);
149
+ }
150
+ } else {
151
+ column = createRaceColumn(name, medalRace);
152
+ column.addRaceColumnListener(this);
153
+ races.add(column);
154
+ column.setRaceLogInformation(raceLogStore, new FlexibleLeaderboardAsRegattaLikeIdentifier(this));
155
+ getRaceColumnListeners().notifyListenersAboutRaceColumnAddedToContainer(column);
156
+ }
157
+ return column;
158
+ }
159
+
160
+ @Override
161
+ public FlexibleRaceColumn getRaceColumnByName(String columnName) {
162
+ return (FlexibleRaceColumn) super.getRaceColumnByName(columnName);
163
+ }
164
+
165
+ @Override
166
+ public Fleet getFleet(String fleetName) {
167
+ Fleet result;
168
+ if (fleetName == null) {
169
+ result = defaultFleet;
170
+ } else {
171
+ result = super.getFleet(fleetName);
172
+ }
173
+ return result;
174
+ }
175
+
176
+ @Override
177
+ public void removeRaceColumn(String columnName) {
178
+ final FlexibleRaceColumn raceColumn = getRaceColumnByName(columnName);
179
+ if (raceColumn != null) {
180
+ for (Fleet fleet : raceColumn.getFleets()) {
181
+ raceLogStore.removeRaceLog(raceColumn.getRaceLogIdentifier(fleet));
182
+ if (raceColumn.getTrackedRace(fleet) != null) {
183
+ raceColumn.getTrackedRace(fleet).detachRaceExecutionOrderProvider(raceExecutionOrderProvider);
184
+ }
185
+ }
186
+ races.remove(raceColumn);
187
+ getRaceColumnListeners().notifyListenersAboutRaceColumnRemovedFromContainer(raceColumn);
188
+ raceColumn.removeRaceColumnListener(this);
189
+ }
190
+ }
191
+
192
+ @Override
193
+ public Iterable<RaceColumn> getRaceColumns() {
194
+ final Iterable<RaceColumn> result;
195
+ if (races != null) {
196
+ result = Collections.unmodifiableCollection(new ArrayList<RaceColumn>(races));
197
+ } else {
198
+ result = null;
199
+ }
200
+ return result;
201
+ }
202
+
203
+ protected RaceColumnImpl createRaceColumn(String column, boolean medalRace) {
204
+ return new RaceColumnImpl(column, medalRace, raceExecutionOrderProvider);
205
+ }
206
+
207
+ protected Iterable<Fleet> turnNullOrEmptyFleetsIntoDefaultFleet(Fleet... fleets) {
208
+ Iterable<Fleet> theFleets;
209
+ if (fleets == null || fleets.length == 0) {
210
+ Fleet defaultfleetCasted = defaultFleet;
211
+ theFleets = Collections.singleton(defaultfleetCasted);
212
+ } else {
213
+ theFleets = Arrays.asList(fleets);
214
+ }
215
+ return theFleets;
216
+ }
217
+
218
+ @Override
219
+ public void moveRaceColumnUp(String name) {
220
+ FlexibleRaceColumn race = null;
221
+ for (FlexibleRaceColumn r : races) {
222
+ if (r.getName().equals(name)) {
223
+ race = r;
224
+ }
225
+ }
226
+ if (race != null) {
227
+ int index = 0;
228
+ index = races.lastIndexOf(race);
229
+ index--;
230
+ if (index >= 0) {
231
+ races.remove(race);
232
+ races.add(index, race);
233
+ getRaceColumnListeners().notifyListenersAboutRaceColumnMoved(race, index);
234
+ }
235
+ }
236
+ }
237
+
238
+ @Override
239
+ public void moveRaceColumnDown(String name) {
240
+ FlexibleRaceColumn race = null;
241
+ for (FlexibleRaceColumn r : races) {
242
+ if (r.getName().equals(name)) {
243
+ race = r;
244
+ }
245
+ }
246
+ if (race != null) {
247
+ int index = 0;
248
+ index = races.lastIndexOf(race);
249
+ if (index != -1) {
250
+ index++;
251
+ if (index < races.size()) {
252
+ races.remove(race);
253
+ races.add(index, race);
254
+ getRaceColumnListeners().notifyListenersAboutRaceColumnMoved(race, index);
255
+ }
256
+ }
257
+ }
258
+ }
259
+
260
+ @Override
261
+ public void updateIsMedalRace(String raceName, boolean isMedalRace) {
262
+ FlexibleRaceColumn race = null;
263
+ for (FlexibleRaceColumn r : races) {
264
+ if (r.getName().equals(raceName))
265
+ race = r;
266
+ }
267
+ if (race != null) {
268
+ race.setIsMedalRace(isMedalRace);
269
+ }
270
+ }
271
+
272
+ @Override
273
+ public ScoringScheme getScoringScheme() {
274
+ return scoringScheme;
275
+ }
276
+
277
+ @Override
278
+ public CourseArea getDefaultCourseArea() {
279
+ return courseArea;
280
+ }
281
+
282
+ @Override
283
+ public void setDefaultCourseArea(CourseArea newCourseArea) {
284
+ this.courseArea = newCourseArea;
285
+ }
286
+
287
+ @Override
288
+ public IsRegattaLike getRegattaLike() {
289
+ return regattaLikeHelper;
290
+ }
291
+
292
+ @Override
293
+ public RegattaLog getRegattaLog() {
294
+ return regattaLikeHelper.getRegattaLog();
295
+ }
296
+
297
+ @Override
298
+ public RegattaLikeIdentifier getRegattaLikeIdentifier() {
299
+ return regattaLikeHelper.getRegattaLikeIdentifier();
300
+ }
301
+
302
+ @Override
303
+ public void addListener(RegattaLikeListener listener) {
304
+ regattaLikeHelper.addListener(listener);
305
+ }
306
+
307
+ @Override
308
+ public void removeListener(RegattaLikeListener listener) {
309
+ regattaLikeHelper.removeListener(listener);
310
+ }
311
+
312
+ @Override
313
+ public Double getTimeOnTimeFactor(Competitor competitor) {
314
+ return regattaLikeHelper.getTimeOnTimeFactor(competitor);
315
+ }
316
+
317
+ @Override
318
+ public Duration getTimeOnDistanceAllowancePerNauticalMile(Competitor competitor) {
319
+ return regattaLikeHelper.getTimeOnDistanceAllowancePerNauticalMile(competitor);
320
+ }
321
+
322
+ private class RaceExecutionOrderCache extends AbstractRaceExecutionOrderProvider {
323
+ private static final long serialVersionUID = 652833386555762661L;
324
+
325
+ public RaceExecutionOrderCache() {
326
+ super();
327
+ addRaceColumnListener(this);
328
+ }
329
+
330
+ @Override
331
+ protected Map<Fleet, Iterable<? extends RaceColumn>> getRaceColumnsOfSeries() {
332
+ final Map<Fleet, Iterable<? extends RaceColumn>> result = new HashMap<>();
333
+ result.put(FlexibleLeaderboardImpl.defaultFleet, getRaceColumns());
334
+ return result;
335
+ }
336
+ }
337
+
338
+ @Override
339
+ protected LeaderboardType getLeaderboardType() {
340
+ return LeaderboardType.FlexibleLeaderboard;
341
+ }
342
+}
java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/RegattaLeaderboardImpl.java
... ...
@@ -1,117 +1,123 @@
1
-package com.sap.sailing.domain.leaderboard.impl;
2
-
3
-import java.util.ArrayList;
4
-import java.util.List;
5
-
6
-import com.sap.sailing.domain.abstractlog.regatta.RegattaLog;
7
-import com.sap.sailing.domain.base.Competitor;
8
-import com.sap.sailing.domain.base.CourseArea;
9
-import com.sap.sailing.domain.base.Fleet;
10
-import com.sap.sailing.domain.base.RaceColumn;
11
-import com.sap.sailing.domain.base.RaceColumnInSeries;
12
-import com.sap.sailing.domain.base.RaceColumnListener;
13
-import com.sap.sailing.domain.base.RaceDefinition;
14
-import com.sap.sailing.domain.base.Regatta;
15
-import com.sap.sailing.domain.base.Series;
16
-import com.sap.sailing.domain.base.impl.RaceColumnInSeriesImpl;
17
-import com.sap.sailing.domain.leaderboard.RegattaLeaderboard;
18
-import com.sap.sailing.domain.leaderboard.ResultDiscardingRule;
19
-import com.sap.sailing.domain.leaderboard.ScoringScheme;
20
-import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule;
21
-import com.sap.sailing.domain.regattalike.IsRegattaLike;
22
-import com.sap.sailing.domain.tracking.TrackedRace;
23
-
24
-/**
25
- * A leaderboard that is based on the definition of a {@link Regatta} with its {@link Series} and {@link Fleet}. The regatta
26
- * leaderboard listens to its {@link Regatta} as a {@link RaceColumnListener} and forwards all link/unlink events for
27
- * {@link TrackedRace}s being linked to / unlinked from race columns to all {@link RaceColumnListener}s subscribed with
28
- * this leaderboard.
29
- *
30
- * @author Axel Uhl (D043530)
31
- *
32
- */
33
-public class RegattaLeaderboardImpl extends AbstractLeaderboardImpl implements RegattaLeaderboard {
34
- private static final long serialVersionUID = 2370461218294770084L;
35
- private final Regatta regatta;
36
-
37
- public RegattaLeaderboardImpl(Regatta regatta, ThresholdBasedResultDiscardingRule resultDiscardingRule) {
38
- super(resultDiscardingRule);
39
- this.regatta = regatta;
40
- regatta.addRaceColumnListener(this);
41
- }
42
-
43
- /**
44
- * Updates the display name of this regatta leaderboard so that the regatta's name is no longer used as the default name.
45
- */
46
- @Override
47
- public void setName(String newName) {
48
- setDisplayName(newName);
49
- }
50
-
51
- @Override
52
- public Regatta getRegatta() {
53
- return regatta;
54
- }
55
-
56
- @Override
57
- public String getName() {
58
- return getRegatta().getName();
59
- }
60
-
61
- @Override
62
- public Iterable<RaceColumn> getRaceColumns() {
63
- List<RaceColumn> result = new ArrayList<RaceColumn>();
64
- for (Series series : getRegatta().getSeries()) {
65
- for (RaceColumn raceColumn : series.getRaceColumns()) {
66
- result.add(raceColumn);
67
- }
68
- }
69
- return result;
70
- }
71
-
72
- @Override
73
- public RaceColumnInSeries getRaceColumnByName(String columnName) {
74
- return (RaceColumnInSeriesImpl) super.getRaceColumnByName(columnName);
75
- }
76
-
77
- @Override
78
- public ScoringScheme getScoringScheme() {
79
- return regatta.getScoringScheme();
80
- }
81
-
82
- @Override
83
- public CourseArea getDefaultCourseArea() {
84
- return regatta.getDefaultCourseArea();
85
- }
86
-
87
- /**
88
- * If the regatta' series {@link Regatta#definesSeriesDiscardThresholds() define} their own result discarding rules, this leaderboard uses
89
- * a composite result discarding rule of type {@link PerSeriesResultDiscardingRuleImpl} that contains discards within each series according
90
- * to the series' discarding rules. Otherwise, the default cross-leaderboard result discarding rule is obtained from the super class
91
- * implementation.
92
- */
93
- @Override
94
- public ResultDiscardingRule getResultDiscardingRule() {
95
- if (regatta.definesSeriesDiscardThresholds()) {
96
- return new PerSeriesResultDiscardingRuleImpl(regatta);
97
- } else {
98
- return super.getResultDiscardingRule();
99
- }
100
- }
101
-
102
- /**
103
- * Delegates to {@link Regatta#getAllCompetitors()} which is expected to deliver all competitors from this
104
- * leaderboard's regatta which includes those belonging to {@link RaceDefinition races}
105
- * {@link Regatta#getAllRaces() belonging to the regatta} as well as competitors listed on the regatta's
106
- * {@link RegattaLog}.
107
- */
108
- @Override
109
- public Iterable<Competitor> getAllCompetitors() {
110
- return regatta.getAllCompetitors();
111
- }
112
-
113
- @Override
114
- public IsRegattaLike getRegattaLike() {
115
- return regatta;
116
- }
117
-}
1
+package com.sap.sailing.domain.leaderboard.impl;
2
+
3
+import java.util.ArrayList;
4
+import java.util.List;
5
+
6
+import com.sap.sailing.domain.abstractlog.regatta.RegattaLog;
7
+import com.sap.sailing.domain.base.Competitor;
8
+import com.sap.sailing.domain.base.CourseArea;
9
+import com.sap.sailing.domain.base.Fleet;
10
+import com.sap.sailing.domain.base.RaceColumn;
11
+import com.sap.sailing.domain.base.RaceColumnInSeries;
12
+import com.sap.sailing.domain.base.RaceColumnListener;
13
+import com.sap.sailing.domain.base.RaceDefinition;
14
+import com.sap.sailing.domain.base.Regatta;
15
+import com.sap.sailing.domain.base.Series;
16
+import com.sap.sailing.domain.base.impl.RaceColumnInSeriesImpl;
17
+import com.sap.sailing.domain.common.LeaderboardType;
18
+import com.sap.sailing.domain.leaderboard.RegattaLeaderboard;
19
+import com.sap.sailing.domain.leaderboard.ResultDiscardingRule;
20
+import com.sap.sailing.domain.leaderboard.ScoringScheme;
21
+import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule;
22
+import com.sap.sailing.domain.regattalike.IsRegattaLike;
23
+import com.sap.sailing.domain.tracking.TrackedRace;
24
+
25
+/**
26
+ * A leaderboard that is based on the definition of a {@link Regatta} with its {@link Series} and {@link Fleet}. The regatta
27
+ * leaderboard listens to its {@link Regatta} as a {@link RaceColumnListener} and forwards all link/unlink events for
28
+ * {@link TrackedRace}s being linked to / unlinked from race columns to all {@link RaceColumnListener}s subscribed with
29
+ * this leaderboard.
30
+ *
31
+ * @author Axel Uhl (D043530)
32
+ *
33
+ */
34
+public class RegattaLeaderboardImpl extends AbstractLeaderboardImpl implements RegattaLeaderboard {
35
+ private static final long serialVersionUID = 2370461218294770084L;
36
+ private final Regatta regatta;
37
+
38
+ public RegattaLeaderboardImpl(Regatta regatta, ThresholdBasedResultDiscardingRule resultDiscardingRule) {
39
+ super(resultDiscardingRule);
40
+ this.regatta = regatta;
41
+ regatta.addRaceColumnListener(this);
42
+ }
43
+
44
+ /**
45
+ * Updates the display name of this regatta leaderboard so that the regatta's name is no longer used as the default name.
46
+ */
47
+ @Override
48
+ public void setName(String newName) {
49
+ setDisplayName(newName);
50
+ }
51
+
52
+ @Override
53
+ public Regatta getRegatta() {
54
+ return regatta;
55
+ }
56
+
57
+ @Override
58
+ public String getName() {
59
+ return getRegatta().getName();
60
+ }
61
+
62
+ @Override
63
+ public Iterable<RaceColumn> getRaceColumns() {
64
+ List<RaceColumn> result = new ArrayList<RaceColumn>();
65
+ for (Series series : getRegatta().getSeries()) {
66
+ for (RaceColumn raceColumn : series.getRaceColumns()) {
67
+ result.add(raceColumn);
68
+ }
69
+ }
70
+ return result;
71
+ }
72
+
73
+ @Override
74
+ public RaceColumnInSeries getRaceColumnByName(String columnName) {
75
+ return (RaceColumnInSeriesImpl) super.getRaceColumnByName(columnName);
76
+ }
77
+
78
+ @Override
79
+ public ScoringScheme getScoringScheme() {
80
+ return regatta.getScoringScheme();
81
+ }
82
+
83
+ @Override
84
+ public CourseArea getDefaultCourseArea() {
85
+ return regatta.getDefaultCourseArea();
86
+ }
87
+
88
+ /**
89
+ * If the regatta' series {@link Regatta#definesSeriesDiscardThresholds() define} their own result discarding rules, this leaderboard uses
90
+ * a composite result discarding rule of type {@link PerSeriesResultDiscardingRuleImpl} that contains discards within each series according
91
+ * to the series' discarding rules. Otherwise, the default cross-leaderboard result discarding rule is obtained from the super class
92
+ * implementation.
93
+ */
94
+ @Override
95
+ public ResultDiscardingRule getResultDiscardingRule() {
96
+ if (regatta.definesSeriesDiscardThresholds()) {
97
+ return new PerSeriesResultDiscardingRuleImpl(regatta);
98
+ } else {
99
+ return super.getResultDiscardingRule();
100
+ }
101
+ }
102
+
103
+ /**
104
+ * Delegates to {@link Regatta#getAllCompetitors()} which is expected to deliver all competitors from this
105
+ * leaderboard's regatta which includes those belonging to {@link RaceDefinition races}
106
+ * {@link Regatta#getAllRaces() belonging to the regatta} as well as competitors listed on the regatta's
107
+ * {@link RegattaLog}.
108
+ */
109
+ @Override
110
+ public Iterable<Competitor> getAllCompetitors() {
111
+ return regatta.getAllCompetitors();
112
+ }
113
+
114
+ @Override
115
+ public IsRegattaLike getRegattaLike() {
116
+ return regatta;
117
+ }
118
+
119
+ @Override
120
+ protected LeaderboardType getLeaderboardType() {
121
+ return LeaderboardType.RegattaLeaderboard;
122
+ }
123
+}
java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/meta/FlexibleMetaLeaderboard.java
... ...
@@ -1,64 +1,70 @@
1
-package com.sap.sailing.domain.leaderboard.meta;
2
-
3
-import java.util.ArrayList;
4
-import java.util.List;
5
-
6
-import com.sap.sailing.domain.base.CourseArea;
7
-import com.sap.sailing.domain.leaderboard.Leaderboard;
8
-import com.sap.sailing.domain.leaderboard.ScoringScheme;
9
-import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule;
10
-import com.sap.sse.concurrent.LockUtil;
11
-import com.sap.sse.concurrent.NamedReentrantReadWriteLock;
12
-
13
-public class FlexibleMetaLeaderboard extends AbstractMetaLeaderboard {
14
- private static final long serialVersionUID = 789076326144062944L;
15
-
16
- private final List<Leaderboard> leaderboards;
17
-
18
- private final NamedReentrantReadWriteLock leaderboardsLock;
19
-
20
- public FlexibleMetaLeaderboard(String name, ScoringScheme scoringScheme,
21
- ThresholdBasedResultDiscardingRule resultDiscardingRule) {
22
- super(name, scoringScheme, resultDiscardingRule);
23
- leaderboards = new ArrayList<Leaderboard>();
24
- leaderboardsLock = new NamedReentrantReadWriteLock("leaderboards collection of "+FlexibleMetaLeaderboard.class.getSimpleName()+" "+getName(),
25
- /* fair */ false);
26
- }
27
-
28
- @Override
29
- public Iterable<Leaderboard> getLeaderboards() {
30
- LockUtil.lockForRead(leaderboardsLock);
31
- try {
32
- return new ArrayList<Leaderboard>(leaderboards);
33
- } finally {
34
- LockUtil.unlockAfterRead(leaderboardsLock);
35
- }
36
- }
37
-
38
- public void addLeaderboard(Leaderboard leaderboard) {
39
- LockUtil.lockForWrite(leaderboardsLock);
40
- try {
41
- leaderboards.add(leaderboard);
42
- registerScoreCorrectionAndRaceColumnChangeForwarder(leaderboard);
43
- getRaceColumnListeners().notifyListenersAboutRaceColumnAddedToContainer(getColumnForLeaderboard(leaderboard));
44
- } finally {
45
- LockUtil.unlockAfterWrite(leaderboardsLock);
46
- }
47
- }
48
-
49
- public void removeLeaderboard(Leaderboard leaderboard) {
50
- LockUtil.lockForWrite(leaderboardsLock);
51
- try {
52
- leaderboards.remove(leaderboard);
53
- getRaceColumnListeners().notifyListenersAboutRaceColumnRemovedFromContainer(getColumnForLeaderboard(leaderboard));
54
- unregisterScoreCorrectionChangeForwarder(leaderboard);
55
- } finally {
56
- LockUtil.unlockAfterWrite(leaderboardsLock);
57
- }
58
- }
59
-
60
- @Override
61
- public CourseArea getDefaultCourseArea() {
62
- return null;
63
- }
64
-}
1
+package com.sap.sailing.domain.leaderboard.meta;
2
+
3
+import java.util.ArrayList;
4
+import java.util.List;
5
+
6
+import com.sap.sailing.domain.base.CourseArea;
7
+import com.sap.sailing.domain.common.LeaderboardType;
8
+import com.sap.sailing.domain.leaderboard.Leaderboard;
9
+import com.sap.sailing.domain.leaderboard.ScoringScheme;
10
+import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule;
11
+import com.sap.sse.concurrent.LockUtil;
12
+import com.sap.sse.concurrent.NamedReentrantReadWriteLock;
13
+
14
+public class FlexibleMetaLeaderboard extends AbstractMetaLeaderboard {
15
+ private static final long serialVersionUID = 789076326144062944L;
16
+
17
+ private final List<Leaderboard> leaderboards;
18
+
19
+ private final NamedReentrantReadWriteLock leaderboardsLock;
20
+
21
+ public FlexibleMetaLeaderboard(String name, ScoringScheme scoringScheme,
22
+ ThresholdBasedResultDiscardingRule resultDiscardingRule) {
23
+ super(name, scoringScheme, resultDiscardingRule);
24
+ leaderboards = new ArrayList<Leaderboard>();
25
+ leaderboardsLock = new NamedReentrantReadWriteLock("leaderboards collection of "+FlexibleMetaLeaderboard.class.getSimpleName()+" "+getName(),
26
+ /* fair */ false);
27
+ }
28
+
29
+ @Override
30
+ public Iterable<Leaderboard> getLeaderboards() {
31
+ LockUtil.lockForRead(leaderboardsLock);
32
+ try {
33
+ return new ArrayList<Leaderboard>(leaderboards);
34
+ } finally {
35
+ LockUtil.unlockAfterRead(leaderboardsLock);
36
+ }
37
+ }
38
+
39
+ public void addLeaderboard(Leaderboard leaderboard) {
40
+ LockUtil.lockForWrite(leaderboardsLock);
41
+ try {
42
+ leaderboards.add(leaderboard);
43
+ registerScoreCorrectionAndRaceColumnChangeForwarder(leaderboard);
44
+ getRaceColumnListeners().notifyListenersAboutRaceColumnAddedToContainer(getColumnForLeaderboard(leaderboard));
45
+ } finally {
46
+ LockUtil.unlockAfterWrite(leaderboardsLock);
47
+ }
48
+ }
49
+
50
+ public void removeLeaderboard(Leaderboard leaderboard) {
51
+ LockUtil.lockForWrite(leaderboardsLock);
52
+ try {
53
+ leaderboards.remove(leaderboard);
54
+ getRaceColumnListeners().notifyListenersAboutRaceColumnRemovedFromContainer(getColumnForLeaderboard(leaderboard));
55
+ unregisterScoreCorrectionChangeForwarder(leaderboard);
56
+ } finally {
57
+ LockUtil.unlockAfterWrite(leaderboardsLock);
58
+ }
59
+ }
60
+
61
+ @Override
62
+ public CourseArea getDefaultCourseArea() {
63
+ return null;
64
+ }
65
+
66
+ @Override
67
+ protected LeaderboardType getLeaderboardType() {
68
+ return LeaderboardType.FlexibleMetaLeaderboard;
69
+ }
70
+}
java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/meta/LeaderboardGroupMetaLeaderboard.java
... ...
@@ -1,92 +1,98 @@
1
-package com.sap.sailing.domain.leaderboard.meta;
2
-
3
-import com.sap.sailing.domain.base.CourseArea;
4
-import com.sap.sailing.domain.base.Fleet;
5
-import com.sap.sailing.domain.base.RaceColumn;
6
-import com.sap.sailing.domain.base.RaceColumnListener;
7
-import com.sap.sailing.domain.base.impl.TrackedRaces;
8
-import com.sap.sailing.domain.common.LeaderboardNameConstants;
9
-import com.sap.sailing.domain.leaderboard.Leaderboard;
10
-import com.sap.sailing.domain.leaderboard.LeaderboardGroup;
11
-import com.sap.sailing.domain.leaderboard.LeaderboardGroupListener;
12
-import com.sap.sailing.domain.leaderboard.ScoringScheme;
13
-import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule;
14
-import com.sap.sailing.domain.tracking.TrackedRace;
15
-
16
-/**
17
- * A meta leaderboard which considers all regular leaderboards of a {@link LeaderboardGroup}. To stay up to date, this
18
- * meta leaderboard registers itself as a {@link LeaderboardGroupListener} on the leaderboard group it represents. This
19
- * way, whenever a leaderboard is added to or removed from the group, this meta leaderboard can in turn inform its
20
- * {@link RaceColumnListener}s about the impact this change has on the set of {@link TrackedRaces} and therefore the
21
- * competitors attached to this meta leaderboard.
22
- * <p>
23
- *
24
- * After an object of this type has been de-serialized, and after all objects referenced by it (leaderboard, leaderboard
25
- * group) have been initialized, {@link #registerAsScoreCorrectionChangeForwarderAndRaceColumnListenerOnAllLeaderboards()} must be called to
26
- * ensure that score corrections are propagated as desired. This is because score correction listeners are "transient"
27
- * and are as such not serialized. Note: calling {@link #registerAsScoreCorrectionChangeForwarderAndRaceColumnListenerOnAllLeaderboards()}
28
- * during <code>readObject</code> or <code>readResolve</code> is not possible because the object graph hasn't been fully
29
- * initialized yet when those methods are called. Hence, the structures for registering the score correction listeners
30
- * are not yet in place at that time.
31
- *
32
- * @author Axel Uhl (d043530)
33
- *
34
- */
35
-public class LeaderboardGroupMetaLeaderboard extends AbstractMetaLeaderboard implements LeaderboardGroupListener, RaceColumnListener {
36
- private static final long serialVersionUID = 8087872002175528002L;
37
-
38
- private final LeaderboardGroup leaderboardGroup;
39
-
40
- public LeaderboardGroupMetaLeaderboard(LeaderboardGroup leaderboardGroup, ScoringScheme scoringScheme,
41
- ThresholdBasedResultDiscardingRule resultDiscardingRule) {
42
- super(leaderboardGroup.getName() + " " + LeaderboardNameConstants.OVERALL, scoringScheme, resultDiscardingRule);
43
- this.leaderboardGroup = leaderboardGroup;
44
- leaderboardGroup.addLeaderboardGroupListener(this);
45
- registerAsScoreCorrectionChangeForwarderAndRaceColumnListenerOnAllLeaderboards();
46
- }
47
-
48
- public void registerAsScoreCorrectionChangeForwarderAndRaceColumnListenerOnAllLeaderboards() {
49
- for (Leaderboard leaderboard : leaderboardGroup.getLeaderboards()) {
50
- registerScoreCorrectionAndRaceColumnChangeForwarder(leaderboard);
51
- }
52
- }
53
-
54
- @Override
55
- public Iterable<Leaderboard> getLeaderboards() {
56
- return leaderboardGroup.getLeaderboards();
57
- }
58
-
59
- @Override
60
- public void leaderboardAdded(LeaderboardGroup group, Leaderboard leaderboard) {
61
- registerScoreCorrectionAndRaceColumnChangeForwarder(leaderboard);
62
- for (RaceColumn raceColumn : leaderboard.getRaceColumns()) {
63
- for (Fleet fleet : raceColumn.getFleets()) {
64
- TrackedRace trackedRace = raceColumn.getTrackedRace(fleet);
65
- if (trackedRace != null) {
66
- getRaceColumnListeners().notifyListenersAboutTrackedRaceLinked(raceColumn, fleet, trackedRace);
67
- }
68
- }
69
- }
70
- getRaceColumnListeners().notifyListenersAboutRaceColumnAddedToContainer(getColumnForLeaderboard(leaderboard));
71
- }
72
-
73
- @Override
74
- public void leaderboardRemoved(LeaderboardGroup group, Leaderboard leaderboard) {
75
- leaderboard.removeRaceColumnListener(this);
76
- unregisterScoreCorrectionChangeForwarder(leaderboard);
77
- for (RaceColumn raceColumn : leaderboard.getRaceColumns()) {
78
- for (Fleet fleet : raceColumn.getFleets()) {
79
- TrackedRace trackedRace = raceColumn.getTrackedRace(fleet);
80
- if (trackedRace != null) {
81
- getRaceColumnListeners().notifyListenersAboutTrackedRaceUnlinked(raceColumn, fleet, trackedRace);
82
- }
83
- }
84
- }
85
- getRaceColumnListeners().notifyListenersAboutRaceColumnRemovedFromContainer(getColumnForLeaderboard(leaderboard));
86
- }
87
-
88
- @Override
89
- public CourseArea getDefaultCourseArea() {
90
- return null;
91
- }
92
-}
1
+package com.sap.sailing.domain.leaderboard.meta;
2
+
3
+import com.sap.sailing.domain.base.CourseArea;
4
+import com.sap.sailing.domain.base.Fleet;
5
+import com.sap.sailing.domain.base.RaceColumn;
6
+import com.sap.sailing.domain.base.RaceColumnListener;
7
+import com.sap.sailing.domain.base.impl.TrackedRaces;
8
+import com.sap.sailing.domain.common.LeaderboardNameConstants;
9
+import com.sap.sailing.domain.common.LeaderboardType;
10
+import com.sap.sailing.domain.leaderboard.Leaderboard;
11
+import com.sap.sailing.domain.leaderboard.LeaderboardGroup;
12
+import com.sap.sailing.domain.leaderboard.LeaderboardGroupListener;
13
+import com.sap.sailing.domain.leaderboard.ScoringScheme;
14
+import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule;
15
+import com.sap.sailing.domain.tracking.TrackedRace;
16
+
17
+/**
18
+ * A meta leaderboard which considers all regular leaderboards of a {@link LeaderboardGroup}. To stay up to date, this
19
+ * meta leaderboard registers itself as a {@link LeaderboardGroupListener} on the leaderboard group it represents. This
20
+ * way, whenever a leaderboard is added to or removed from the group, this meta leaderboard can in turn inform its
21
+ * {@link RaceColumnListener}s about the impact this change has on the set of {@link TrackedRaces} and therefore the
22
+ * competitors attached to this meta leaderboard.
23
+ * <p>
24
+ *
25
+ * After an object of this type has been de-serialized, and after all objects referenced by it (leaderboard, leaderboard
26
+ * group) have been initialized, {@link #registerAsScoreCorrectionChangeForwarderAndRaceColumnListenerOnAllLeaderboards()} must be called to
27
+ * ensure that score corrections are propagated as desired. This is because score correction listeners are "transient"
28
+ * and are as such not serialized. Note: calling {@link #registerAsScoreCorrectionChangeForwarderAndRaceColumnListenerOnAllLeaderboards()}
29
+ * during <code>readObject</code> or <code>readResolve</code> is not possible because the object graph hasn't been fully
30
+ * initialized yet when those methods are called. Hence, the structures for registering the score correction listeners
31
+ * are not yet in place at that time.
32
+ *
33
+ * @author Axel Uhl (d043530)
34
+ *
35
+ */
36
+public class LeaderboardGroupMetaLeaderboard extends AbstractMetaLeaderboard implements LeaderboardGroupListener, RaceColumnListener {
37
+ private static final long serialVersionUID = 8087872002175528002L;
38
+
39
+ private final LeaderboardGroup leaderboardGroup;
40
+
41
+ public LeaderboardGroupMetaLeaderboard(LeaderboardGroup leaderboardGroup, ScoringScheme scoringScheme,
42
+ ThresholdBasedResultDiscardingRule resultDiscardingRule) {
43
+ super(leaderboardGroup.getName() + " " + LeaderboardNameConstants.OVERALL, scoringScheme, resultDiscardingRule);
44
+ this.leaderboardGroup = leaderboardGroup;
45
+ leaderboardGroup.addLeaderboardGroupListener(this);
46
+ registerAsScoreCorrectionChangeForwarderAndRaceColumnListenerOnAllLeaderboards();
47
+ }
48
+
49
+ public void registerAsScoreCorrectionChangeForwarderAndRaceColumnListenerOnAllLeaderboards() {
50
+ for (Leaderboard leaderboard : leaderboardGroup.getLeaderboards()) {
51
+ registerScoreCorrectionAndRaceColumnChangeForwarder(leaderboard);
52
+ }
53
+ }
54
+
55
+ @Override
56
+ public Iterable<Leaderboard> getLeaderboards() {
57
+ return leaderboardGroup.getLeaderboards();
58
+ }
59
+
60
+ @Override
61
+ public void leaderboardAdded(LeaderboardGroup group, Leaderboard leaderboard) {
62
+ registerScoreCorrectionAndRaceColumnChangeForwarder(leaderboard);
63
+ for (RaceColumn raceColumn : leaderboard.getRaceColumns()) {
64
+ for (Fleet fleet : raceColumn.getFleets()) {
65
+ TrackedRace trackedRace = raceColumn.getTrackedRace(fleet);
66
+ if (trackedRace != null) {
67
+ getRaceColumnListeners().notifyListenersAboutTrackedRaceLinked(raceColumn, fleet, trackedRace);
68
+ }
69
+ }
70
+ }
71
+ getRaceColumnListeners().notifyListenersAboutRaceColumnAddedToContainer(getColumnForLeaderboard(leaderboard));
72
+ }
73
+
74
+ @Override
75
+ public void leaderboardRemoved(LeaderboardGroup group, Leaderboard leaderboard) {
76
+ leaderboard.removeRaceColumnListener(this);
77
+ unregisterScoreCorrectionChangeForwarder(leaderboard);
78
+ for (RaceColumn raceColumn : leaderboard.getRaceColumns()) {
79
+ for (Fleet fleet : raceColumn.getFleets()) {
80
+ TrackedRace trackedRace = raceColumn.getTrackedRace(fleet);
81
+ if (trackedRace != null) {
82
+ getRaceColumnListeners().notifyListenersAboutTrackedRaceUnlinked(raceColumn, fleet, trackedRace);
83
+ }
84
+ }
85
+ }
86
+ getRaceColumnListeners().notifyListenersAboutRaceColumnRemovedFromContainer(getColumnForLeaderboard(leaderboard));
87
+ }
88
+
89
+ @Override
90
+ public CourseArea getDefaultCourseArea() {
91
+ return null;
92
+ }
93
+
94
+ @Override
95
+ protected LeaderboardType getLeaderboardType() {
96
+ return LeaderboardType.RegattaMetaLeaderboard;
97
+ }
98
+}
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java
... ...
@@ -13,7 +13,6 @@ import java.util.ConcurrentModificationException;
13 13
import java.util.Date;
14 14
import java.util.HashMap;
15 15
import java.util.HashSet;
16
-import java.util.IdentityHashMap;
17 16
import java.util.Iterator;
18 17
import java.util.LinkedHashMap;
19 18
import java.util.LinkedList;
... ...
@@ -154,6 +153,7 @@ import com.sap.sse.common.Util.Pair;
154 153
import com.sap.sse.common.impl.MillisecondsTimePoint;
155 154
import com.sap.sse.concurrent.LockUtil;
156 155
import com.sap.sse.concurrent.NamedReentrantReadWriteLock;
156
+import com.sap.sse.util.IdentityWrapper;
157 157
import com.sap.sse.util.SmartFutureCache;
158 158
import com.sap.sse.util.SmartFutureCache.AbstractCacheUpdater;
159 159
import com.sap.sse.util.SmartFutureCache.EmptyUpdateInterval;
... ...
@@ -183,7 +183,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
183 183
* By default, all wind sources are used, none are excluded. However, e.g., for performance reasons, particular wind
184 184
* sources such as the track-based estimation wind source, may be excluded by adding them to this set.
185 185
*/
186
- private final Set<WindSource> windSourcesToExclude;
186
+ private final ConcurrentHashMap<WindSource, TrackedRaceImpl> windSourcesToExclude;
187 187
188 188
/**
189 189
* Keeps the oldest timestamp that is fed into this tracked race, either from a boat fix, a mark fix, a race
... ...
@@ -269,7 +269,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
269 269
*/
270 270
private transient SmartFutureCache<Competitor, com.sap.sse.common.Util.Triple<TimePoint, TimePoint, List<Maneuver>>, EmptyUpdateInterval> maneuverCache;
271 271
272
- private transient Map<TimePoint, Future<Wind>> directionFromStartToNextMarkCache;
272
+ private transient ConcurrentHashMap<TimePoint, Future<Wind>> directionFromStartToNextMarkCache;
273 273
274 274
protected final MarkPassingCalculator markPassingCalculator;
275 275
... ...
@@ -354,7 +354,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
354 354
*/
355 355
private final NamedReentrantReadWriteLock loadingFromGPSFixStoreLock;
356 356
357
- private final Map<Iterable<MarkPassing>, NamedReentrantReadWriteLock> locksForMarkPassings;
357
+ private final ConcurrentHashMap<IdentityWrapper<Iterable<MarkPassing>>, NamedReentrantReadWriteLock> locksForMarkPassings;
358 358
359 359
/**
360 360
* Caches wind requests for a few seconds to accelerate access in live mode
... ...
@@ -412,7 +412,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
412 412
rankingMetric = rankingMetricConstructor.apply(this);
413 413
raceStates = new WeakHashMap<>();
414 414
shortTimeWindCache = new ShortTimeWindCache(this, millisecondsOverWhichToAverageWind / 2);
415
- locksForMarkPassings = new IdentityHashMap<>();
415
+ locksForMarkPassings = new ConcurrentHashMap<IdentityWrapper<Iterable<MarkPassing>>, NamedReentrantReadWriteLock>();
416 416
attachedRaceLogs = new ConcurrentHashMap<>();
417 417
attachedRegattaLogs = new ConcurrentHashMap<>();
418 418
attachedRaceExecutionOrderProviders = new ConcurrentHashMap<>();
... ...
@@ -424,8 +424,8 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
424 424
+ race.getName(), /* fair */ false);
425 425
this.cacheInvalidationTimerLock = new Object();
426 426
this.updateCount = 0;
427
- this.windSourcesToExclude = new HashSet<WindSource>();
428
- this.directionFromStartToNextMarkCache = new HashMap<TimePoint, Future<Wind>>();
427
+ this.windSourcesToExclude = new ConcurrentHashMap<>();
428
+ this.directionFromStartToNextMarkCache = new ConcurrentHashMap<TimePoint, Future<Wind>>();
429 429
this.millisecondsOverWhichToAverageSpeed = millisecondsOverWhichToAverageSpeed;
430 430
this.delayToLiveInMillis = delayToLiveInMillis;
431 431
this.startToNextMarkCacheInvalidationListeners = new ConcurrentHashMap<Mark, TrackedRaceImpl.StartToNextMarkCacheInvalidationListener>();
... ...
@@ -624,7 +624,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
624 624
gpsFixStore = EmptyGPSFixStore.INSTANCE;
625 625
competitorRankings = createCompetitorRankingsCache();
626 626
competitorRankingsLocks = createCompetitorRankingsLockMap();
627
- directionFromStartToNextMarkCache = new HashMap<TimePoint, Future<Wind>>();
627
+ directionFromStartToNextMarkCache = new ConcurrentHashMap<>();
628 628
crossTrackErrorCache = new CrossTrackErrorCache(this);
629 629
crossTrackErrorCache.invalidate();
630 630
maneuverCache = createManeuverCache();
... ...
@@ -1146,11 +1146,11 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
1146 1146
int indexOfWaypoint = getRace().getCourse().getIndexOfWaypoint(startOfLeg);
1147 1147
if (indexOfWaypoint == -1) {
1148 1148
throw new IllegalArgumentException("Waypoint " + startOfLeg + " not found in " + getRace().getCourse());
1149
- } else if (indexOfWaypoint == Util.size(getRace().getCourse().getWaypoints()) - 1) {
1149
+ } else if (indexOfWaypoint == getRace().getCourse().getNumberOfWaypoints() - 1) {
1150 1150
throw new IllegalArgumentException("Waypoint " + startOfLeg + " isn't start of any leg in "
1151 1151
+ getRace().getCourse());
1152 1152
}
1153
- return trackedLegs.get(race.getCourse().getLegs().get(indexOfWaypoint));
1153
+ return trackedLegs.get(race.getCourse().getLeg(indexOfWaypoint));
1154 1154
} finally {
1155 1155
getRace().getCourse().unlockAfterRead();
1156 1156
}
... ...
@@ -1679,24 +1679,20 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
1679 1679
1680 1680
@Override
1681 1681
public Set<WindSource> getWindSourcesToExclude() {
1682
- synchronized (windSourcesToExclude) {
1683
- return Collections.unmodifiableSet(windSourcesToExclude);
1684
- }
1682
+ return Collections.unmodifiableSet(windSourcesToExclude.keySet());
1685 1683
}
1686 1684
1687 1685
@Override
1688 1686
public void setWindSourcesToExclude(Iterable<? extends WindSource> windSourcesToExclude) {
1689
- Set<WindSource> old = new HashSet<>(this.windSourcesToExclude);
1690
- synchronized (this.windSourcesToExclude) {
1691
- LockUtil.lockForRead(getSerializationLock());
1692
- try {
1693
- this.windSourcesToExclude.clear();
1694
- for (WindSource windSourceToExclude : windSourcesToExclude) {
1695
- this.windSourcesToExclude.add(windSourceToExclude);
1696
- }
1697
- } finally {
1698
- LockUtil.unlockAfterRead(getSerializationLock());
1687
+ Set<WindSource> old = new HashSet<>(getWindSourcesToExclude());
1688
+ LockUtil.lockForRead(getSerializationLock());
1689
+ try {
1690
+ this.windSourcesToExclude.clear();
1691
+ for (WindSource windSourceToExclude : windSourcesToExclude) {
1692
+ this.windSourcesToExclude.put(windSourceToExclude, this);
1699 1693
}
1694
+ } finally {
1695
+ LockUtil.unlockAfterRead(getSerializationLock());
1700 1696
}
1701 1697
if (!old.equals(this.windSourcesToExclude)) {
1702 1698
clearAllCachesExceptManeuvers();
... ...
@@ -1742,30 +1738,33 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
1742 1738
public Wind getDirectionFromStartToNextMark(final TimePoint at) {
1743 1739
Future<Wind> future;
1744 1740
FutureTask<Wind> newFuture = null;
1745
- synchronized (directionFromStartToNextMarkCache) {
1746
- future = directionFromStartToNextMarkCache.get(at);
1747
- if (future == null) {
1748
- newFuture = new FutureTask<Wind>(new Callable<Wind>() {
1749
- @Override
1750
- public Wind call() {
1751
- Wind result;
1752
- Leg firstLeg = getRace().getCourse().getFirstLeg();
1753
- if (firstLeg != null) {
1754
- Position firstLegEnd = getApproximatePosition(firstLeg.getTo(), at);
1755
- Position firstLegStart = getApproximatePosition(firstLeg.getFrom(), at);
1756
- if (firstLegStart != null && firstLegEnd != null) {
1757
- result = new WindImpl(firstLegStart, at, new KnotSpeedWithBearingImpl(0.0,
1758
- firstLegEnd.getBearingGreatCircle(firstLegStart)));
1741
+ future = directionFromStartToNextMarkCache.get(at);
1742
+ if (future == null) {
1743
+ synchronized (directionFromStartToNextMarkCache) {
1744
+ future = directionFromStartToNextMarkCache.get(at);
1745
+ if (future == null) {
1746
+ newFuture = new FutureTask<Wind>(new Callable<Wind>() {
1747
+ @Override
1748
+ public Wind call() {
1749
+ Wind result;
1750
+ Leg firstLeg = getRace().getCourse().getFirstLeg();
1751
+ if (firstLeg != null) {
1752
+ Position firstLegEnd = getApproximatePosition(firstLeg.getTo(), at);
1753
+ Position firstLegStart = getApproximatePosition(firstLeg.getFrom(), at);
1754
+ if (firstLegStart != null && firstLegEnd != null) {
1755
+ result = new WindImpl(firstLegStart, at, new KnotSpeedWithBearingImpl(0.0,
1756
+ firstLegEnd.getBearingGreatCircle(firstLegStart)));
1757
+ } else {
1758
+ result = null;
1759
+ }
1759 1760
} else {
1760 1761
result = null;
1761 1762
}
1762
- } else {
1763
- result = null;
1763
+ return result;
1764 1764
}
1765
- return result;
1766
- }
1767
- });
1768
- directionFromStartToNextMarkCache.put(at, newFuture);
1765
+ });
1766
+ directionFromStartToNextMarkCache.put(at, newFuture);
1767
+ }
1769 1768
}
1770 1769
}
1771 1770
if (newFuture != null) {
... ...
@@ -2062,15 +2061,19 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
2062 2061
}
2063 2062
2064 2063
protected NamedReentrantReadWriteLock getMarkPassingsLock(Iterable<MarkPassing> markPassings) {
2065
- synchronized (locksForMarkPassings) {
2066
- NamedReentrantReadWriteLock lock = locksForMarkPassings.get(markPassings);
2067
- if (lock == null) {
2068
- lock = new NamedReentrantReadWriteLock("mark passings lock for tracked race " + getRace().getName(), /* fair */
2069
- false);
2070
- locksForMarkPassings.put(markPassings, lock);
2064
+ final IdentityWrapper<Iterable<MarkPassing>> markPassingsIdentity = new IdentityWrapper<>(markPassings);
2065
+ NamedReentrantReadWriteLock lock = locksForMarkPassings.get(markPassingsIdentity);
2066
+ if (lock == null) {
2067
+ synchronized (locksForMarkPassings) {
2068
+ lock = locksForMarkPassings.get(markPassingsIdentity);
2069
+ if (lock == null) {
2070
+ lock = new NamedReentrantReadWriteLock(
2071
+ "mark passings lock for tracked race " + getRace().getName(), /* fair */false);
2072
+ locksForMarkPassings.put(markPassingsIdentity, lock);
2073
+ }
2071 2074
}
2072
- return lock;
2073 2075
}
2076
+ return lock;
2074 2077
}
2075 2078
2076 2079
private void updateStartToNextMarkCacheInvalidationCacheListenersAfterWaypointRemoved(int zeroBasedIndex,
java/com.sap.sailing.feature.p2build/raceanalysis.product
... ...
@@ -50,6 +50,7 @@
50 50
<plugin id="com.sap.sailing.monitoring" autoStart="true" startLevel="6" />
51 51
<plugin id="com.sap.sailing.news" autoStart="true" startLevel="3" />
52 52
<plugin id="com.sap.sailing.polars" autoStart="true" startLevel="4" />
53
+ <plugin id="com.sap.sailing.polars.datamining" autoStart="true" startLevel="4" />
53 54
<plugin id="com.sap.sailing.resultimport" autoStart="true" startLevel="3" />
54 55
<plugin id="com.sap.sailing.sailwave.resultimport" autoStart="true" startLevel="3" />
55 56
<plugin id="com.sap.sailing.server" autoStart="true" startLevel="4" />
java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/GwtTestCaseColumnToggling.java
... ...
@@ -95,7 +95,7 @@ public class GwtTestCaseColumnToggling extends GWTTestCase {
95 95
+ TracTracConnectionConstants.PORT_TUNNEL_STORED : "tcp://" + TracTracConnectionConstants.HOST_NAME
96 96
+ ":" + TracTracConnectionConstants.PORT_STORED,
97 97
COURSE_DESIGN_UPDATE_URI,
98
- false, false, /* simulateWithStartTimeNow */ false, /* ignoreTracTracMarkPassings */ false, TRACTRAC_USERNAME, TRACTRAC_PASSWORD, new AsyncCallback<Void>() {
98
+ false, false, /* offsetToStartTimeOfSimulatedRace */ null, /* ignoreTracTracMarkPassings */ false, TRACTRAC_USERNAME, TRACTRAC_PASSWORD, new AsyncCallback<Void>() {
99 99
100 100
@Override
101 101
public void onFailure(Throwable caught) {
java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/LeaderboardPanelMock.java
... ...
@@ -20,7 +20,7 @@ public class LeaderboardPanelMock extends LeaderboardPanel {
20 20
String leaderboardName, ErrorReporter errorReporter,
21 21
StringMessages stringMessages) {
22 22
super(sailingService, new AsyncActionsExecutor(), LeaderboardSettingsFactory.getInstance().createNewDefaultSettings(
23
- /* racesToShow */null, /* namesOfRacesToShow */ null, null, /* autoExpandFirstRace */ false, /* showRegattaRank */ true), new CompetitorSelectionModel(
23
+ /* racesToShow */null, /* namesOfRacesToShow */ null, null, /* autoExpandFirstRace */ false, /* showRegattaRank */ true, /* showCompetitorSailIdColumn */ true, /* showCompetitorFullNameColumn */ true), new CompetitorSelectionModel(
24 24
/* hasMultiSelection */true), leaderboardName, errorReporter, stringMessages, new UserAgentDetails("gecko1_8"), /* showRaceDetails */ true);
25 25
}
26 26
java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/TestColumnSwapping.java
... ...
@@ -1,162 +1,162 @@
1
-package com.sap.sailing.gwt.ui.test;
2
-
3
-import static org.junit.Assert.assertArrayEquals;
4
-import static org.junit.Assert.assertEquals;
5
-import static org.junit.Assert.assertNotNull;
6
-import static org.junit.Assert.assertTrue;
7
-import static org.junit.Assert.fail;
8
-
9
-import java.util.ArrayList;
10
-import java.util.Collection;
11
-import java.util.Date;
12
-import java.util.List;
13
-import java.util.UUID;
14
-
15
-import org.junit.Before;
16
-import org.junit.Test;
17
-
18
-import com.sap.sailing.domain.common.ScoringSchemeType;
19
-import com.sap.sailing.domain.common.dto.FleetDTO;
20
-import com.sap.sailing.domain.common.dto.LeaderboardDTO;
21
-import com.sap.sailing.domain.common.dto.RaceColumnDTO;
22
-import com.sap.sailing.gwt.ui.server.SailingServiceImpl;
23
-
24
-public class TestColumnSwapping {
25
-
26
- private LeaderboardDTO lb = null;
27
- private static final String LEADERBOARDNAME = "test";
28
- private static final String DEFAULT_FLEET_NAME = "Default";
29
- private static final FleetDTO DEFAULT_FLEET = new FleetDTO(DEFAULT_FLEET_NAME, /* ordering */ 0, /* color */ null);
30
- private SailingServiceImpl service;
31
- private LeaderboardDTO leaderboardOriginalDTO;
32
- private LeaderboardDTO leaderboardDTO;
33
- private Collection<String> leglist;
34
- private Date leaderboardCreationDate;
35
- private SailingServiceImpl sailingService = null;
36
-
37
- // Test-Data
38
- private final String TEST_LEADERBOARD_NAME = "test_board";
39
- private final String[] races = { "1", "2", "3" };
40
- private final boolean[] isMedalRace = { false, false, true };
41
-
42
- @Before
43
- public void prepareColumnSwapping() {
44
- service = new SailingServiceImplMock();
45
- int[] disc = { 5, 8, 9, 0, 7, 5, 43 };
46
- service.removeLeaderboard(LEADERBOARDNAME);
47
- service.createFlexibleLeaderboard(LEADERBOARDNAME, null, disc, ScoringSchemeType.LOW_POINT, null);
48
- service.addColumnToLeaderboard("Race1", LEADERBOARDNAME, true);
49
- service.addColumnToLeaderboard("Race2", LEADERBOARDNAME, true);
50
- service.addColumnToLeaderboard("Race3", LEADERBOARDNAME, true);
51
- leglist = new ArrayList<String>();
52
- leglist.add("Race1");
53
- leglist.add("Race2");
54
- leglist.add("Race3");
55
- leaderboardCreationDate = new Date();
56
- try {
57
- // get Leaderboard with name and current date
58
- leaderboardOriginalDTO = new LeaderboardDTO(null, null, ScoringSchemeType.LOW_POINT, /* higherScoreIsBetter */ false, new LeaderboardDTO.UUIDGenerator() {
59
- @Override
60
- public String generateRandomUUID() {
61
- return UUID.randomUUID().toString();
62
- }
63
- }, /* hasOverallDetails */ false);
64
- leaderboardOriginalDTO.addRace("Race1", /* explicitFactor */ null, 2., /* regattaName */ null, /* seriesName */ null, DEFAULT_FLEET, true, null, null);
65
- leaderboardOriginalDTO.addRace("Race3", /* explicitFactor */ null, 2., /* regattaName */ null, /* seriesName */ null, DEFAULT_FLEET, true, null, null);
66
- leaderboardOriginalDTO.addRace("Race2", /* explicitFactor */ null, 2., /* regattaName */ null, /* seriesName */ null, DEFAULT_FLEET, true, null, null);
67
- } catch (Exception e) {
68
- e.printStackTrace();
69
- }
70
- }
71
-
72
- @Test
73
- public void testColumnSwapping() {
74
- testColumnSwappingFabian();
75
- testLeaderBoardDTOMethods();
76
- testSailingService();
77
- }
78
-
79
- @Test
80
- public void testSailingService() {
81
- sailingService = new SailingServiceImplMock();
82
- assertNotNull("Sailingservice != NULL", sailingService);
83
- int td[] = { 5, 8 };
84
- sailingService.removeLeaderboard(TEST_LEADERBOARD_NAME);
85
- sailingService.createFlexibleLeaderboard(TEST_LEADERBOARD_NAME, null, td, ScoringSchemeType.LOW_POINT, null);
86
- for (int i = 0; i < races.length; i++)
87
- sailingService.addColumnToLeaderboard(races[i], TEST_LEADERBOARD_NAME, isMedalRace[i]);
88
- sailingService.moveLeaderboardColumnDown(TEST_LEADERBOARD_NAME, races[0]);
89
- sailingService.moveLeaderboardColumnDown(TEST_LEADERBOARD_NAME, races[0]);
90
- sailingService.moveLeaderboardColumnUp(TEST_LEADERBOARD_NAME, races[2]);
91
- sailingService.updateIsMedalRace(TEST_LEADERBOARD_NAME, races[0], true);
92
- sailingService.updateIsMedalRace(TEST_LEADERBOARD_NAME, races[2], false);
93
- try {
94
- for (int i = 0; i < races.length; i++)
95
- sailingService.addColumnToLeaderboard(races[i], TEST_LEADERBOARD_NAME, isMedalRace[i]);
96
- sailingService.moveLeaderboardColumnDown(TEST_LEADERBOARD_NAME, races[0]);
97
- sailingService.moveLeaderboardColumnDown(TEST_LEADERBOARD_NAME, races[0]);
98
- sailingService.moveLeaderboardColumnUp(TEST_LEADERBOARD_NAME, races[2]);
99
- sailingService.updateIsMedalRace(TEST_LEADERBOARD_NAME, races[0], true);
100
- sailingService.updateIsMedalRace(TEST_LEADERBOARD_NAME, races[2], false);
101
- lb = sailingService.getLeaderboardByName(TEST_LEADERBOARD_NAME, new Date(), /* races to load */ null, /* addOverallDetails */ true, /* previous leaderboard ID */ null,
102
- /* fillNetPointsUncorrected */ false).getLeaderboardDTO(/* previousVersion */ null);
103
- } catch (Exception e) {
104
- // e.printStackTrace();
105
- fail(e.getLocalizedMessage());
106
- }
107
- assertNotNull("LeaderboardDTO != NULL", lb);
108
-
109
- // asserted data
110
- String[] raceNames = new String[] { "3", "2", "1" };
111
- boolean[] medalRace = { true, false, false };
112
-
113
- for (int i = 0; i < lb.getRaceList().size(); i++) {
114
- assertEquals("Race[" + i + "] == " + raceNames[i], raceNames[i], lb.getRaceList().get(i).getName());
115
- assertTrue("Race[" + i + "] is " + (medalRace[i] ? "" : "no ") + "medalrace.",
116
- lb.raceIsMedalRace(races[i]) == medalRace[i]);
117
- }
118
- }
119
-
120
- @Test
121
- public void testLeaderBoardDTOMethods() {
122
- lb = new LeaderboardDTO(null, null, ScoringSchemeType.LOW_POINT, /* higherScoreIsBetter */ false, new LeaderboardDTO.UUIDGenerator() {
123
- @Override
124
- public String generateRandomUUID() {
125
- return UUID.randomUUID().toString();
126
- }
127
- }, /* hasOverallDetails */ false);
128
- assertNotNull("Leaderboard != NULL", lb);
129
- lb.addRace("1", /* explicitFactor */ null, 1., /* regattaName */ null, /* seriesName */ null, DEFAULT_FLEET, false, null, null);
130
- lb.addRace("2", /* explicitFactor */ null, 1., /* regattaName */ null, /* seriesName */ null, DEFAULT_FLEET, false, null, null);
131
- lb.addRace("3", /* explicitFactor */ null, 1., /* regattaName */ null, /* seriesName */ null, DEFAULT_FLEET, true, null, null);
132
- lb.moveRaceDown("1");
133
- String[] s = new String[] { "2", "1", "3" };
134
- for (int i = 0; i < lb.getRaceList().size(); i++) {
135
- assertEquals("Race[" + i + "] = " + s[i], s[i], lb.getRaceList().get(i).getName());
136
- }
137
- }
138
-
139
- @Test
140
- public void testColumnSwappingFabian() {
141
- service.moveLeaderboardColumnUp(LEADERBOARDNAME, "Race3");
142
- try {
143
- leaderboardDTO = service.getLeaderboardByName(LEADERBOARDNAME, leaderboardCreationDate, leglist, /* addOverallDetails */
144
- true, /* previous leaderboard ID */null, /* fillNetPointsUncorrected */false).getLeaderboardDTO(/* previousVersion */null);
145
- } catch (Exception e) {
146
- // TODO Auto-generated catch block
147
- e.printStackTrace();
148
- }
149
-
150
- // check if leaderboardDTO an dleaderboardOriginalDTO same
151
- List<RaceColumnDTO> leaderboardList = leaderboardDTO.getRaceList();
152
- List<RaceColumnDTO> leaderboardOriginalList = leaderboardOriginalDTO.getRaceList();
153
-
154
- // ??????? assert races in list
155
- assertArrayEquals(leaderboardList.toArray(), leaderboardOriginalList.toArray());
156
-
157
- for (RaceColumnDTO raceDTO : leaderboardOriginalList) {
158
- assert leaderboardDTO.raceIsMedalRace(raceDTO.getRaceColumnName()) == leaderboardOriginalDTO.raceIsMedalRace(raceDTO.getRaceColumnName());
159
- assert leaderboardDTO.raceIsTracked(raceDTO.getRaceColumnName()) == leaderboardOriginalDTO.raceIsTracked(raceDTO.getRaceColumnName());
160
- }
161
- }
162
-}
1
+package com.sap.sailing.gwt.ui.test;
2
+
3
+import static org.junit.Assert.assertArrayEquals;
4
+import static org.junit.Assert.assertEquals;
5
+import static org.junit.Assert.assertNotNull;
6
+import static org.junit.Assert.assertTrue;
7
+import static org.junit.Assert.fail;
8
+
9
+import java.util.ArrayList;
10
+import java.util.Collection;
11
+import java.util.Date;
12
+import java.util.List;
13
+import java.util.UUID;
14
+
15
+import org.junit.Before;
16
+import org.junit.Test;
17
+
18
+import com.sap.sailing.domain.common.ScoringSchemeType;
19
+import com.sap.sailing.domain.common.dto.FleetDTO;
20
+import com.sap.sailing.domain.common.dto.LeaderboardDTO;
21
+import com.sap.sailing.domain.common.dto.RaceColumnDTO;
22
+import com.sap.sailing.gwt.ui.server.SailingServiceImpl;
23
+
24
+public class TestColumnSwapping {
25
+
26
+ private LeaderboardDTO lb = null;
27
+ private static final String LEADERBOARDNAME = "test";
28
+ private static final String DEFAULT_FLEET_NAME = "Default";
29
+ private static final FleetDTO DEFAULT_FLEET = new FleetDTO(DEFAULT_FLEET_NAME, /* ordering */ 0, /* color */ null);
30
+ private SailingServiceImpl service;
31
+ private LeaderboardDTO leaderboardOriginalDTO;
32
+ private LeaderboardDTO leaderboardDTO;
33
+ private Collection<String> leglist;
34
+ private Date leaderboardCreationDate;
35
+ private SailingServiceImpl sailingService = null;
36
+
37
+ // Test-Data
38
+ private final String TEST_LEADERBOARD_NAME = "test_board";
39
+ private final String[] races = { "1", "2", "3" };
40
+ private final boolean[] isMedalRace = { false, false, true };
41
+
42
+ @Before
43
+ public void prepareColumnSwapping() {
44
+ service = new SailingServiceImplMock();
45
+ int[] disc = { 5, 8, 9, 0, 7, 5, 43 };
46
+ service.removeLeaderboard(LEADERBOARDNAME);
47
+ service.createFlexibleLeaderboard(LEADERBOARDNAME, null, disc, ScoringSchemeType.LOW_POINT, null);
48
+ service.addColumnToLeaderboard("Race1", LEADERBOARDNAME, true);
49
+ service.addColumnToLeaderboard("Race2", LEADERBOARDNAME, true);
50
+ service.addColumnToLeaderboard("Race3", LEADERBOARDNAME, true);
51
+ leglist = new ArrayList<String>();
52
+ leglist.add("Race1");
53
+ leglist.add("Race2");
54
+ leglist.add("Race3");
55
+ leaderboardCreationDate = new Date();
56
+ try {
57
+ // get Leaderboard with name and current date
58
+ leaderboardOriginalDTO = new LeaderboardDTO(null, null, ScoringSchemeType.LOW_POINT, /* higherScoreIsBetter */ false, new LeaderboardDTO.UUIDGenerator() {
59
+ @Override
60
+ public String generateRandomUUID() {
61
+ return UUID.randomUUID().toString();
62
+ }
63
+ }, /* hasOverallDetails */ false);
64
+ leaderboardOriginalDTO.addRace("Race1", /* explicitFactor */ null, 2., /* regattaName */ null, /* seriesName */ null, DEFAULT_FLEET, true, null, null, false);
65
+ leaderboardOriginalDTO.addRace("Race3", /* explicitFactor */ null, 2., /* regattaName */ null, /* seriesName */ null, DEFAULT_FLEET, true, null, null, false);
66
+ leaderboardOriginalDTO.addRace("Race2", /* explicitFactor */ null, 2., /* regattaName */ null, /* seriesName */ null, DEFAULT_FLEET, true, null, null, false);
67
+ } catch (Exception e) {
68
+ e.printStackTrace();
69
+ }
70
+ }
71
+
72
+ @Test
73
+ public void testColumnSwapping() {
74
+ testColumnSwappingFabian();
75
+ testLeaderBoardDTOMethods();
76
+ testSailingService();
77
+ }
78
+
79
+ @Test
80
+ public void testSailingService() {
81
+ sailingService = new SailingServiceImplMock();
82
+ assertNotNull("Sailingservice != NULL", sailingService);
83
+ int td[] = { 5, 8 };
84
+ sailingService.removeLeaderboard(TEST_LEADERBOARD_NAME);
85
+ sailingService.createFlexibleLeaderboard(TEST_LEADERBOARD_NAME, null, td, ScoringSchemeType.LOW_POINT, null);
86
+ for (int i = 0; i < races.length; i++)
87
+ sailingService.addColumnToLeaderboard(races[i], TEST_LEADERBOARD_NAME, isMedalRace[i]);
88
+ sailingService.moveLeaderboardColumnDown(TEST_LEADERBOARD_NAME, races[0]);
89
+ sailingService.moveLeaderboardColumnDown(TEST_LEADERBOARD_NAME, races[0]);
90
+ sailingService.moveLeaderboardColumnUp(TEST_LEADERBOARD_NAME, races[2]);
91
+ sailingService.updateIsMedalRace(TEST_LEADERBOARD_NAME, races[0], true);
92
+ sailingService.updateIsMedalRace(TEST_LEADERBOARD_NAME, races[2], false);
93
+ try {
94
+ for (int i = 0; i < races.length; i++)
95
+ sailingService.addColumnToLeaderboard(races[i], TEST_LEADERBOARD_NAME, isMedalRace[i]);
96
+ sailingService.moveLeaderboardColumnDown(TEST_LEADERBOARD_NAME, races[0]);
97
+ sailingService.moveLeaderboardColumnDown(TEST_LEADERBOARD_NAME, races[0]);
98
+ sailingService.moveLeaderboardColumnUp(TEST_LEADERBOARD_NAME, races[2]);
99
+ sailingService.updateIsMedalRace(TEST_LEADERBOARD_NAME, races[0], true);
100
+ sailingService.updateIsMedalRace(TEST_LEADERBOARD_NAME, races[2], false);
101
+ lb = sailingService.getLeaderboardByName(TEST_LEADERBOARD_NAME, new Date(), /* races to load */ null, /* addOverallDetails */ true, /* previous leaderboard ID */ null,
102
+ /* fillNetPointsUncorrected */ false).getLeaderboardDTO(/* previousVersion */ null);
103
+ } catch (Exception e) {
104
+ // e.printStackTrace();
105
+ fail(e.getLocalizedMessage());
106
+ }
107
+ assertNotNull("LeaderboardDTO != NULL", lb);
108
+
109
+ // asserted data
110
+ String[] raceNames = new String[] { "3", "2", "1" };
111
+ boolean[] medalRace = { true, false, false };
112
+
113
+ for (int i = 0; i < lb.getRaceList().size(); i++) {
114
+ assertEquals("Race[" + i + "] == " + raceNames[i], raceNames[i], lb.getRaceList().get(i).getName());
115
+ assertTrue("Race[" + i + "] is " + (medalRace[i] ? "" : "no ") + "medalrace.",
116
+ lb.raceIsMedalRace(races[i]) == medalRace[i]);
117
+ }
118
+ }
119
+
120
+ @Test
121
+ public void testLeaderBoardDTOMethods() {
122
+ lb = new LeaderboardDTO(null, null, ScoringSchemeType.LOW_POINT, /* higherScoreIsBetter */ false, new LeaderboardDTO.UUIDGenerator() {
123
+ @Override
124
+ public String generateRandomUUID() {
125
+ return UUID.randomUUID().toString();
126
+ }
127
+ }, /* hasOverallDetails */ false);
128
+ assertNotNull("Leaderboard != NULL", lb);
129
+ lb.addRace("1", /* explicitFactor */ null, 1., /* regattaName */ null, /* seriesName */ null, DEFAULT_FLEET, false, null, null, false);
130
+ lb.addRace("2", /* explicitFactor */ null, 1., /* regattaName */ null, /* seriesName */ null, DEFAULT_FLEET, false, null, null, false);
131
+ lb.addRace("3", /* explicitFactor */ null, 1., /* regattaName */ null, /* seriesName */ null, DEFAULT_FLEET, true, null, null, false);
132
+ lb.moveRaceDown("1");
133
+ String[] s = new String[] { "2", "1", "3" };
134
+ for (int i = 0; i < lb.getRaceList().size(); i++) {
135
+ assertEquals("Race[" + i + "] = " + s[i], s[i], lb.getRaceList().get(i).getName());
136
+ }
137
+ }
138
+
139
+ @Test
140
+ public void testColumnSwappingFabian() {
141
+ service.moveLeaderboardColumnUp(LEADERBOARDNAME, "Race3");
142
+ try {
143
+ leaderboardDTO = service.getLeaderboardByName(LEADERBOARDNAME, leaderboardCreationDate, leglist, /* addOverallDetails */
144
+ true, /* previous leaderboard ID */null, /* fillNetPointsUncorrected */false).getLeaderboardDTO(/* previousVersion */null);
145
+ } catch (Exception e) {
146
+ // TODO Auto-generated catch block
147
+ e.printStackTrace();
148
+ }
149
+
150
+ // check if leaderboardDTO an dleaderboardOriginalDTO same
151
+ List<RaceColumnDTO> leaderboardList = leaderboardDTO.getRaceList();
152
+ List<RaceColumnDTO> leaderboardOriginalList = leaderboardOriginalDTO.getRaceList();
153
+
154
+ // ??????? assert races in list
155
+ assertArrayEquals(leaderboardList.toArray(), leaderboardOriginalList.toArray());
156
+
157
+ for (RaceColumnDTO raceDTO : leaderboardOriginalList) {
158
+ assert leaderboardDTO.raceIsMedalRace(raceDTO.getRaceColumnName()) == leaderboardOriginalDTO.raceIsMedalRace(raceDTO.getRaceColumnName());
159
+ assert leaderboardDTO.raceIsTracked(raceDTO.getRaceColumnName()) == leaderboardOriginalDTO.raceIsTracked(raceDTO.getRaceColumnName());
160
+ }
161
+ }
162
+}
java/com.sap.sailing.gwt.ui/Leaderboard.css
... ...
@@ -98,13 +98,13 @@ body, table td, select, button {
98 98
padding: 49px 0 0 80px;
99 99
width: 300px;
100 100
color: #7c7c7c;
101
- background: url(images/important-message.png) left center no-repeat;
101
+ background: url(images/important-message.png) left center no-repeat;
102 102
}
103 103
104 104
.LeaderboardHeader {
105 105
position: relative;
106 106
margin: 0 auto;
107
- width: 400px;
107
+ width: 100%;
108 108
text-align: center;
109 109
font-family: 'Open Sans', Arial, Verdana, sans-serif;
110 110
font-size: 18px;
java/com.sap.sailing.gwt.ui/META-INF/MANIFEST.MF
... ...
@@ -49,9 +49,10 @@ Require-Bundle: com.sap.sailing.domain,
49 49
org.apache.shiro.core;bundle-version="1.2.2",
50 50
com.sap.sse.gwt.theme,
51 51
com.sap.sailing.news,
52
- com.sap.sailing.polars.datamining.shared;bundle-version="1.0.0",
52
+ com.sap.sailing.polars.datamining.shared,
53 53
com.sap.sailing.polars.datamining,
54
- com.sap.sse.datamining.annotations;bundle-version="1.0.0"
54
+ com.sap.sse.datamining.annotations,
55
+ com.sap.sse.shared.android
55 56
Bundle-Activator: com.sap.sailing.gwt.ui.server.Activator
56 57
Bundle-ActivationPolicy: lazy
57 58
Import-Package: javax.servlet;version="3.1.0",
java/com.sap.sailing.gwt.ui/RaceBoard.css
... ...
@@ -6,6 +6,13 @@
6 6
height: 0;
7 7
clear: both;
8 8
visibility: hidden;
9
+
10
+ RegattaRaceInformation-Header
11
+ RaceName-Label
12
+ RaceSeriesAndFleet-Label
13
+
14
+ RegattaAndRaceTime-Header???
15
+ RegattaName-Anchor
9 16
*/
10 17
11 18
... ...
@@ -87,6 +94,14 @@ table {
87 94
padding-top: 10px;
88 95
padding-left: 10px;
89 96
}
97
+.CombinedWindPanelParentDiv.indentsmall,
98
+.TrueNorthIndicatorPanelParentDiv.indentsmall {
99
+ padding-left: 30px;
100
+}
101
+.CombinedWindPanelParentDiv.indentbig,
102
+.TrueNorthIndicatorPanelParentDiv.indentbig {
103
+ padding-left: 45px;
104
+}
90 105
91 106
.MapSimulationLegendParentDiv {
92 107
padding-bottom: 10px;
... ...
@@ -573,7 +588,8 @@ img.openColumn {
573 588
url(images/viewicons/@1x/img/SAP_RV_Leaderboard_GHOSTED.png),
574 589
url(images/arrows_top_black_small.png);
575 590
background-repeat: no-repeat, no-repeat;
576
- background-position: 4% 50%, 96% 50%
591
+ background-position: 4% 50%, 96% 50%;
592
+ background-position: 5px center, calc(100% - 6px) center;
577 593
}
578 594
579 595
.gwt-SplitLayoutPanel-EastToggleButton-Closed-leaderboard {
... ...
@@ -582,7 +598,8 @@ img.openColumn {
582 598
url(images/viewicons/@1x/img/SAP_RV_Leaderboard_INACTIVE.png),
583 599
url(images/arrows_bottom_black_small.png);
584 600
background-repeat: no-repeat, no-repeat;
585
- background-position: 4% 50%, 96% 50%
601
+ background-position: 4% 50%, 96% 50%;
602
+ background-position: 5px center, calc(100% - 6px) center;
586 603
}
587 604
588 605
.gwt-SplitLayoutPanel-NorthSouthToggleButton-Closed-media,
... ...
@@ -664,6 +681,32 @@ img.openColumn {
664 681
margin-top: 5px;
665 682
}
666 683
684
+.compactHeader .RegattaRaceInformation-Header {
685
+ left: 110px;
686
+}
687
+.compactHeader .RegattaRaceInformation-Header .RaceName-Label {
688
+ display: block;
689
+ font-size: 16px;
690
+ padding: 2px 0;
691
+}
692
+.compactHeader .RegattaRaceInformation-Header .RaceSeriesAndFleet-Label {
693
+ display: block;
694
+ margin-top: 5px;
695
+ white-space: nowrap;
696
+}
697
+.compactHeader .RegattaAndRaceTime-Header {
698
+ top: 11px;
699
+}
700
+.compactHeader .RegattaAndRaceTime-Header .RegattaName-Anchor {
701
+ width: 115px;
702
+ overflow: hidden;
703
+ text-overflow: ellipsis;
704
+ display: inline-block;
705
+ white-space: nowrap;
706
+ padding: 2px 0 2px 20px;
707
+ background-position: 4px -1px;
708
+}
709
+
667 710
.VideoPopup-Close-Button {
668 711
border: none;
669 712
}
java/com.sap.sailing.gwt.ui/SailingGWT sdm.launch
... ...
@@ -17,7 +17,7 @@
17 17
<listEntry value="com.sap.sailing.gwt.AutoPlay"/>
18 18
<listEntry value="com.sap.sailing.gwt.ui.AdminConsole"/>
19 19
<listEntry value="com.sap.sailing.gwt.home.Home"/>
20
-<listEntry value="com.sap.sailing.gwt.ui.VideoPopup"/>
20
+<listEntry value="com.sap.sailing.gwt.AutoPlay"/>
21 21
</listAttribute>
22 22
<booleanAttribute key="com.google.gwt.eclipse.core.SUPERDEVMODE_ENABLED" value="true"/>
23 23
<stringAttribute key="com.google.gwt.eclipse.core.URL" value="/gwt/Home.html"/>
... ...
@@ -73,7 +73,7 @@
73 73
<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/>
74 74
<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"/>
75 75
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.google.gwt.dev.DevMode"/>
76
-<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-superDevMode -incremental -workDir &quot;${project_loc:com.sap.sailing.gwt.ui}/.tmp/gwt-work&quot; -war &quot;${project_loc:com.sap.sailing.gwt.ui}&quot; -noserver -remoteUI &quot;${gwt_remote_ui_server_port}:${unique_id}&quot; -logLevel INFO -codeServerPort 9876 -startupUrl /gwt/Home.html com.sap.sailing.gwt.home.Home -startupUrl /gwt/AdminConsole.html com.sap.sailing.gwt.ui.AdminConsole -startupUrl /gwt/LeaderboardEditing.html com.sap.sailing.gwt.ui.LeaderboardEditing -startupUrl /gwt/Leaderboard.html com.sap.sailing.gwt.ui.Leaderboard -startupUrl /gwt/Spectator.html com.sap.sailing.gwt.ui.Spectator -startupUrl /gwt/EmbeddedMapAndWindChart com.sap.sailing.gwt.ui.EmbeddedMapAndWindChart -startupUrl /gwt/RaceBoard.html com.sap.sailing.gwt.ui.RaceBoard -startupUrl /gwt/RegattaOverview.html com.sap.sailing.gwt.regattaoverview.RegattaOverview -startupUrl /gwt/DataMining.html com.sap.sailing.gwt.ui.DataMining -startupUrl /gwt/Simulator.html com.sap.sailing.gwt.ui.Simulator -startupUrl /gwt/VideoPopup.html com.sap.sailing.gwt.ui.VideoPopup com.sap.sailing.gwt.home.Home com.sap.sailing.gwt.ui.AdminConsole com.sap.sailing.gwt.AutoPlay com.sap.sailing.gwt.ui.YoutubePopup com.sap.sailing.gwt.ui.Simulator com.sap.sailing.gwt.ui.Leaderboard com.sap.sailing.gwt.regattaoverview.RegattaOverview com.sap.sailing.gwt.ui.Spectator com.sap.sailing.gwt.ui.LeaderboardEditing com.sap.sailing.gwt.ui.DataMining com.sap.sailing.gwt.ui.RaceBoard com.sap.sailing.gwt.ui.EmbeddedMapAndWindChart"/>
76
+<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-superDevMode -incremental -workDir &quot;${project_loc:com.sap.sailing.gwt.ui}/.tmp/gwt-work&quot; -war &quot;${project_loc:com.sap.sailing.gwt.ui}&quot; -noserver -remoteUI &quot;${gwt_remote_ui_server_port}:${unique_id}&quot; -logLevel INFO -codeServerPort 9876 -startupUrl /gwt/Home.html com.sap.sailing.gwt.home.Home -startupUrl /gwt/AdminConsole.html com.sap.sailing.gwt.ui.AdminConsole -startupUrl /gwt/LeaderboardEditing.html com.sap.sailing.gwt.ui.LeaderboardEditing -startupUrl /gwt/Leaderboard.html com.sap.sailing.gwt.ui.Leaderboard -startupUrl /gwt/Spectator.html com.sap.sailing.gwt.ui.Spectator -startupUrl /gwt/EmbeddedMapAndWindChart com.sap.sailing.gwt.ui.EmbeddedMapAndWindChart -startupUrl /gwt/RaceBoard.html com.sap.sailing.gwt.ui.RaceBoard -startupUrl /gwt/RegattaOverview.html com.sap.sailing.gwt.regattaoverview.RegattaOverview -startupUrl /gwt/DataMining.html com.sap.sailing.gwt.ui.DataMining -startupUrl /gwt/Simulator.html com.sap.sailing.gwt.ui.Simulator -startupUrl /gwt/VideoPopup.html com.sap.sailing.gwt.ui.VideoPopup -startupUrl /gwt/AutoPlay.html com.sap.sailing.gwt.AutoPlay com.sap.sailing.gwt.home.Home com.sap.sailing.gwt.ui.AdminConsole com.sap.sailing.gwt.AutoPlay com.sap.sailing.gwt.ui.YoutubePopup com.sap.sailing.gwt.ui.Simulator com.sap.sailing.gwt.ui.Leaderboard com.sap.sailing.gwt.regattaoverview.RegattaOverview com.sap.sailing.gwt.ui.Spectator com.sap.sailing.gwt.ui.LeaderboardEditing com.sap.sailing.gwt.ui.DataMining com.sap.sailing.gwt.ui.RaceBoard com.sap.sailing.gwt.ui.EmbeddedMapAndWindChart"/>
77 77
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="com.sap.sailing.gwt.ui"/>
78 78
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Xms1024m -Xmx3072m"/>
79 79
</launchConfiguration>
java/com.sap.sailing.gwt.ui/SailingGWT.launch
... ...
@@ -16,7 +16,7 @@
16 16
<listEntry value="com.sap.sailing.gwt.AutoPlay"/>
17 17
<listEntry value="com.sap.sailing.gwt.ui.AdminConsole"/>
18 18
<listEntry value="com.sap.sailing.gwt.home.Home"/>
19
-<listEntry value="com.sap.sailing.gwt.ui.VideoPopup"/>
19
+<listEntry value="com.sap.sailing.gwt.AutoPlay"/>
20 20
</listAttribute>
21 21
<stringAttribute key="com.google.gwt.eclipse.core.URL" value="/gwt/Home.html"/>
22 22
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
... ...
@@ -70,7 +70,7 @@
70 70
<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/>
71 71
<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"/>
72 72
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.google.gwt.dev.DevMode"/>
73
-<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-nosuperDevMode -incremental -war &quot;${project_loc:com.sap.sailing.gwt.ui}&quot; -noserver -remoteUI &quot;${gwt_remote_ui_server_port}:${unique_id}&quot; -logLevel INFO -codeServerPort auto -startupUrl /gwt/Home.html com.sap.sailing.gwt.home.Home -startupUrl /gwt/AdminConsole.html com.sap.sailing.gwt.ui.AdminConsole -startupUrl /gwt/LeaderboardEditing.html com.sap.sailing.gwt.ui.LeaderboardEditing -startupUrl /gwt/Leaderboard.html com.sap.sailing.gwt.ui.Leaderboard -startupUrl /gwt/Spectator.html com.sap.sailing.gwt.ui.Spectator -startupUrl /gwt/RaceBoard.html com.sap.sailing.gwt.ui.RaceBoard -startupUrl /gwt/RegattaOverview.html com.sap.sailing.gwt.regattaoverview.RegattaOverview -startupUrl /gwt/DataMining.html com.sap.sailing.gwt.ui.DataMining -startupUrl /gwt/Simulator.html com.sap.sailing.gwt.ui.Simulator -startupUrl /gwt/VideoPopup.html com.sap.sailing.gwt.ui.VideoPopup com.sap.sailing.gwt.home.Home com.sap.sailing.gwt.ui.AdminConsole com.sap.sailing.gwt.AutoPlay com.sap.sailing.gwt.ui.YoutubePopup com.sap.sailing.gwt.ui.Simulator com.sap.sailing.gwt.ui.Leaderboard com.sap.sailing.gwt.regattaoverview.RegattaOverview com.sap.sailing.gwt.ui.Spectator com.sap.sailing.gwt.ui.LeaderboardEditing com.sap.sailing.gwt.ui.DataMining com.sap.sailing.gwt.ui.RaceBoard"/>
73
+<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-nosuperDevMode -incremental -war &quot;${project_loc:com.sap.sailing.gwt.ui}&quot; -noserver -remoteUI &quot;${gwt_remote_ui_server_port}:${unique_id}&quot; -logLevel INFO -codeServerPort auto -startupUrl /gwt/Home.html com.sap.sailing.gwt.home.Home -startupUrl /gwt/AdminConsole.html com.sap.sailing.gwt.ui.AdminConsole -startupUrl /gwt/LeaderboardEditing.html com.sap.sailing.gwt.ui.LeaderboardEditing -startupUrl /gwt/Leaderboard.html com.sap.sailing.gwt.ui.Leaderboard -startupUrl /gwt/Spectator.html com.sap.sailing.gwt.ui.Spectator -startupUrl /gwt/RaceBoard.html com.sap.sailing.gwt.ui.RaceBoard -startupUrl /gwt/RegattaOverview.html com.sap.sailing.gwt.regattaoverview.RegattaOverview -startupUrl /gwt/DataMining.html com.sap.sailing.gwt.ui.DataMining -startupUrl /gwt/Simulator.html com.sap.sailing.gwt.ui.Simulator -startupUrl /gwt/VideoPopup.html com.sap.sailing.gwt.ui.VideoPopup -startupUrl /gwt/AutoPlay.html com.sap.sailing.gwt.AutoPlay com.sap.sailing.gwt.home.Home com.sap.sailing.gwt.ui.AdminConsole com.sap.sailing.gwt.AutoPlay com.sap.sailing.gwt.ui.YoutubePopup com.sap.sailing.gwt.ui.Simulator com.sap.sailing.gwt.ui.Leaderboard com.sap.sailing.gwt.regattaoverview.RegattaOverview com.sap.sailing.gwt.ui.Spectator com.sap.sailing.gwt.ui.LeaderboardEditing com.sap.sailing.gwt.ui.DataMining com.sap.sailing.gwt.ui.RaceBoard"/>
74 74
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="com.sap.sailing.gwt.ui"/>
75 75
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Xmx2048m -Dgwt-usearchives=false -Dgwt.persistentunitcache=false"/>
76 76
</launchConfiguration>
java/com.sap.sailing.gwt.ui/pom.xml
... ...
@@ -11,19 +11,6 @@
11 11
<artifactId>com.sap.sailing.gwt.ui</artifactId>
12 12
<packaging>eclipse-plugin</packaging>
13 13
14
- <repositories>
15
- <repository>
16
- <id>sonatype</id>
17
- <url>http://oss.sonatype.org/content/repositories/snapshots</url>
18
- <snapshots>
19
- <enabled>true</enabled>
20
- </snapshots>
21
- <releases>
22
- <enabled>false</enabled>
23
- </releases>
24
- </repository>
25
- </repositories>
26
-
27 14
<dependencies>
28 15
<!-- The following dependency needs re-declaration with the desired version
29 16
because gwt-maps-api declares it with no version specifier which may be resolved
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/place/player/AutoPlayController.java
... ...
@@ -107,7 +107,7 @@ public class AutoPlayController implements RaceTimesInfoProviderListener {
107 107
asyncActionsExecutor = new AsyncActionsExecutor();
108 108
showWindChart = false;
109 109
leaderboard = null;
110
- leaderboardSettings = LeaderboardSettingsFactory.getInstance().createNewDefaultSettings(null, null, null, /* autoExpandFirstRace */ false, /* showRegattaRank */ true);
110
+ leaderboardSettings = LeaderboardSettingsFactory.getInstance().createNewDefaultSettings(null, null, null, /* autoExpandFirstRace */ false, /* showRegattaRank */ true, /* showCompetitorSailIdColumn */ true, /* showCompetitorFullNameColumn */ true);
111 111
112 112
leaderboardTimer = new Timer(PlayModes.Live, /* delayBetweenAutoAdvancesInMilliseconds */1000l);
113 113
leaderboardTimer.setLivePlayDelayInMillis(delayToLiveInMillis);
... ...
@@ -170,10 +170,10 @@ public class AutoPlayController implements RaceTimesInfoProviderListener {
170 170
RaceSelectionModel raceSelectionModel = new RaceSelectionModel();
171 171
List<RegattaAndRaceIdentifier> singletonList = Collections.singletonList(raceToShow);
172 172
raceSelectionModel.setSelection(singletonList);
173
-
174 173
RaceBoardPanel raceBoardPanel = new RaceBoardPanel(sailingService, mediaService, userService, asyncActionsExecutor,
175
- competitorsAndTheirBoats, raceboardTimer, raceSelectionModel, leaderboardName, null, /* event */null, raceboardViewConfig,
176
- errorReporter, StringMessages.INSTANCE, userAgent, raceTimesInfoProvider, /* showMapControls */false);
174
+ competitorsAndTheirBoats, raceboardTimer, raceSelectionModel, leaderboardName, null, /* event */ null, raceboardViewConfig,
175
+ errorReporter, StringMessages.INSTANCE, userAgent, raceTimesInfoProvider, /* showMapControls */ false,
176
+ /* isScreenLargeEnoughToOfferChartSupport */ true);
177 177
return raceBoardPanel;
178 178
}
179 179
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/event/GetEventViewAction.java
... ...
@@ -22,8 +22,8 @@ import com.sap.sailing.gwt.home.server.EventActionUtil;
22 22
import com.sap.sailing.gwt.home.server.LeaderboardContext;
23 23
import com.sap.sailing.gwt.home.server.EventActionUtil.LeaderboardCallback;
24 24
import com.sap.sailing.gwt.server.HomeServiceUtil;
25
-import com.sap.sse.common.media.ImageDescriptor;
26 25
import com.sap.sse.common.media.MediaTagConstants;
26
+import com.sap.sse.shared.media.ImageDescriptor;
27 27
28 28
public class GetEventViewAction implements SailingAction<EventViewDTO>, IsClientCacheable {
29 29
private static final Logger logger = Logger.getLogger(GetEventViewAction.class.getName());
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/event/eventoverview/GetEventOverviewStageAction.java
... ...
@@ -20,7 +20,7 @@ import com.sap.sse.common.TimePoint;
20 20
import com.sap.sse.common.impl.MillisecondsDurationImpl;
21 21
import com.sap.sse.common.impl.MillisecondsTimePoint;
22 22
import com.sap.sse.common.media.MediaTagConstants;
23
-import com.sap.sse.common.media.VideoDescriptor;
23
+import com.sap.sse.shared.media.VideoDescriptor;
24 24
25 25
public class GetEventOverviewStageAction implements SailingAction<ResultWithTTL<EventOverviewStageDTO>>, IsClientCacheable {
26 26
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/event/news/RaceCompetitorNewsEntryDTO.java
... ...
@@ -2,6 +2,7 @@ package com.sap.sailing.gwt.home.communication.event.news;
2 2
3 3
import java.util.Date;
4 4
5
+import com.google.gwt.core.shared.GwtIncompatible;
5 6
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
6 7
import com.sap.sailing.gwt.ui.client.StringMessages;
7 8
... ...
@@ -18,6 +19,7 @@ public class RaceCompetitorNewsEntryDTO extends AbstractRaceNewsEntryDTO {
18 19
private RaceCompetitorNewsEntryDTO() {
19 20
}
20 21
22
+ @GwtIncompatible
21 23
public RaceCompetitorNewsEntryDTO(String leaderboardName, String leaderboardGroupName,
22 24
RegattaAndRaceIdentifier regattaAndRaceIdentifier, String raceTitle, String boatClass, Date timestamp,
23 25
String competitorName, Type type) {
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/fakeseries/GetEventSeriesViewAction.java
... ...
@@ -16,8 +16,8 @@ import com.sap.sailing.gwt.home.communication.SailingDispatchContext;
16 16
import com.sap.sailing.gwt.home.communication.event.EventMetadataDTO;
17 17
import com.sap.sailing.gwt.home.communication.fakeseries.EventSeriesViewDTO.EventSeriesState;
18 18
import com.sap.sailing.gwt.server.HomeServiceUtil;
19
-import com.sap.sse.common.media.ImageDescriptor;
20 19
import com.sap.sse.common.media.MediaTagConstants;
20
+import com.sap.sse.shared.media.ImageDescriptor;
21 21
22 22
public class GetEventSeriesViewAction implements SailingAction<EventSeriesViewDTO>, IsClientCacheable {
23 23
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/media/GetMediaForEventAction.java
... ...
@@ -11,9 +11,9 @@ import com.sap.sailing.gwt.home.communication.SailingDispatchContext;
11 11
import com.sap.sailing.gwt.home.communication.event.EventReferenceDTO;
12 12
import com.sap.sailing.gwt.server.HomeServiceUtil;
13 13
import com.sap.sailing.gwt.ui.shared.media.MediaConstants;
14
-import com.sap.sse.common.media.ImageDescriptor;
15 14
import com.sap.sse.common.media.MimeType;
16
-import com.sap.sse.common.media.VideoDescriptor;
15
+import com.sap.sse.shared.media.ImageDescriptor;
16
+import com.sap.sse.shared.media.VideoDescriptor;
17 17
18 18
public class GetMediaForEventAction implements SailingAction<MediaDTO>, IsClientCacheable {
19 19
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/start/GetStartViewAction.java
... ...
@@ -22,9 +22,9 @@ import com.sap.sailing.gwt.server.RecentEventsCalculator;
22 22
import com.sap.sailing.gwt.ui.shared.media.MediaConstants;
23 23
import com.sap.sse.common.Util;
24 24
import com.sap.sse.common.Util.Pair;
25
-import com.sap.sse.common.media.ImageDescriptor;
26 25
import com.sap.sse.common.media.MimeType;
27
-import com.sap.sse.common.media.VideoDescriptor;
26
+import com.sap.sse.shared.media.ImageDescriptor;
27
+import com.sap.sse.shared.media.VideoDescriptor;
28 28
29 29
public class GetStartViewAction implements SailingAction<StartViewDTO>, IsClientCacheable {
30 30
private static final int MAX_RECENT_EVENTS = 3;
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/utils/EventParamUtils.java
... ...
@@ -108,7 +108,7 @@ public class EventParamUtils {
108 108
.createNewDefaultSettings(null, null,
109 109
/* overallDetails */overallDetails, null,
110 110
/* autoExpandFirstRace */false, refreshIntervalMillis, numberOfLastRacesToShow,
111
- raceColumnSelectionStrategy);
111
+ raceColumnSelectionStrategy, /* showCompetitorSailIdColumns */ true, /*showCompetitorFullNameColumn*/ true);
112 112
}
113 113
return result;
114 114
}
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/places/event/AbstractEventActivity.java
... ...
@@ -44,7 +44,6 @@ import com.sap.sailing.gwt.home.shared.places.fakeseries.SeriesDefaultPlace;
44 44
import com.sap.sailing.gwt.ui.client.EntryPointLinkFactory;
45 45
import com.sap.sailing.gwt.ui.client.StringMessages;
46 46
import com.sap.sailing.gwt.ui.client.refresh.ErrorAndBusyClientFactory;
47
-import com.sap.sailing.gwt.ui.raceboard.RaceBoardViewConfiguration;
48 47
import com.sap.sailing.gwt.ui.shared.util.NullSafeComparableComparator;
49 48
50 49
public abstract class AbstractEventActivity<PLACE extends AbstractEventPlace> extends AbstractActivity implements Presenter {
... ...
@@ -248,7 +247,6 @@ public abstract class AbstractEventActivity<PLACE extends AbstractEventPlace> ex
248 247
}
249 248
linkParams.put("raceName", trackedRaceName);
250 249
linkParams.put("regattaName", regattaName);
251
- linkParams.put(RaceBoardViewConfiguration.PARAM_VIEW_MODE, "simple");
252 250
// TODO this must only be forwarded if there is a logged-on user
253 251
// linkParams.put(RaceBoardViewConfiguration.PARAM_CAN_REPLAY_DURING_LIVE_RACES, "true");
254 252
return linkParams;
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/ExperimentalFeatures.java
... ...
@@ -54,7 +54,7 @@ public class ExperimentalFeatures {
54 54
/**
55 55
* Enables/disables the link to the race board on mobile races view (competition format), in case of a tracked race
56 56
*/
57
- public static final boolean ENABLE_RACE_VIEWER_LINK_ON_MOBILE = false;
57
+ public static final boolean ENABLE_RACE_VIEWER_LINK_ON_MOBILE = true;
58 58
/**
59 59
* Provide selection to filter regattas by boat category on mobile multiregatta event overview
60 60
*/
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/regattaoverview/client/RegattaOverviewPanel.java
... ...
@@ -237,7 +237,7 @@ public class RegattaOverviewPanel extends SimplePanel {
237 237
if (leaderboardsTabPanel != null) {
238 238
if (showLeaderboard) {
239 239
final CompetitorSelectionModel competitorSelectionProvider = new CompetitorSelectionModel(/* hasMultiSelection */ true);
240
- final LeaderboardSettings leaderboardSettings = LeaderboardSettingsFactory.getInstance().createNewDefaultSettings(null, null, null, /* autoExpandFirstRace */ false, /* showRegattaRank */ true);
240
+ final LeaderboardSettings leaderboardSettings = LeaderboardSettingsFactory.getInstance().createNewDefaultSettings(null, null, null, /* autoExpandFirstRace */ false, /* showRegattaRank */ true, /* showCompetitorSailIdColumn */ true, /* showCompetitorFullNameColumn */ true);
241 241
sailingService.getLeaderboardsByEvent(eventDTO, new MarkedAsyncCallback<List<StrippedLeaderboardDTO>>(
242 242
new AsyncCallback<List<StrippedLeaderboardDTO>>() {
243 243
@Override
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/server/HomeServiceUtil.java
... ...
@@ -44,12 +44,12 @@ import com.sap.sse.common.TimePoint;
44 44
import com.sap.sse.common.Util;
45 45
import com.sap.sse.common.Util.Pair;
46 46
import com.sap.sse.common.impl.MillisecondsTimePoint;
47
-import com.sap.sse.common.media.ImageDescriptor;
48
-import com.sap.sse.common.media.MediaDescriptor;
49 47
import com.sap.sse.common.media.MediaTagConstants;
50
-import com.sap.sse.common.media.VideoDescriptor;
51 48
import com.sap.sse.gwt.client.media.ImageDTO;
52 49
import com.sap.sse.gwt.client.media.VideoDTO;
50
+import com.sap.sse.shared.media.ImageDescriptor;
51
+import com.sap.sse.shared.media.MediaDescriptor;
52
+import com.sap.sse.shared.media.VideoDescriptor;
53 53
54 54
public final class HomeServiceUtil {
55 55
public interface EventVisitor {
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AddWindFixDialog.java
... ...
@@ -40,18 +40,18 @@ public class AddWindFixDialog extends DataEntryDialog<WindDTO> {
40 40
if(windDTO.trueWindSpeedInKnots == null) {
41 41
errorMessage = stringMessages.pleaseEnterAValue();
42 42
} else if(windDTO.trueWindSpeedInKnots != null && (windDTO.trueWindSpeedInKnots < 0.0 || windDTO.trueWindSpeedInKnots > 100.0)) {
43
- errorMessage = stringMessages.valueMustBeBetweenMinMax(stringMessages.speedInKnots(), "0", "100");
43
+ errorMessage = stringMessages.valueMustBeBetweenMinMax(stringMessages.speedInKnots(), 0, 100);
44 44
} else if(windDTO.trueWindFromDeg == null){
45 45
errorMessage = stringMessages.pleaseEnterAValue();
46 46
} else if(windDTO.trueWindFromDeg != null && (windDTO.trueWindFromDeg < 0.0 || windDTO.trueWindFromDeg > 360.0)){
47
- errorMessage = stringMessages.valueMustBeBetweenMinMax(stringMessages.fromDeg(), "0", "360");
47
+ errorMessage = stringMessages.valueMustBeBetweenMinMax(stringMessages.fromDeg(), 0, 360);
48 48
} else if(windDTO.measureTimepoint == null) {
49 49
errorMessage = stringMessages.pleaseEnterAValue();
50 50
} else if(windDTO.position != null) {
51 51
if(windDTO.position.getLatDeg() < -90.0 || windDTO.position.getLatDeg() > 90.0){
52
- errorMessage = stringMessages.valueMustBeBetweenMinMax(stringMessages.latitude(), "-90", "90");
52
+ errorMessage = stringMessages.valueMustBeBetweenMinMax(stringMessages.latitude(), -90, 90);
53 53
} else if(windDTO.position.getLngDeg() < -180.0 || windDTO.position.getLngDeg() > 180.0){
54
- errorMessage = stringMessages.valueMustBeBetweenMinMax(stringMessages.longitude(), "-180", "180");
54
+ errorMessage = stringMessages.valueMustBeBetweenMinMax(stringMessages.longitude(), -180, 180);
55 55
}
56 56
}
57 57
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceLogCourseManagementWidget.java
... ...
@@ -126,6 +126,7 @@ public class RaceLogCourseManagementWidget extends CourseManagementWidget {
126 126
@Override
127 127
public void onSuccess(RaceCourseDTO result) {
128 128
updateWaypointsAndControlPoints(result);
129
+ marks.refresh(result.getMarks());
129 130
}
130 131
131 132
@Override
... ...
@@ -133,18 +134,5 @@ public class RaceLogCourseManagementWidget extends CourseManagementWidget {
133 134
errorReporter.reportError("Could not load course: " + caught.getMessage());
134 135
}
135 136
});
136
-
137
- sailingService.getMarksInRaceLog(leaderboardName, raceColumnName, fleetName,
138
- new AsyncCallback<Iterable<MarkDTO>>() {
139
- @Override
140
- public void onSuccess(Iterable<MarkDTO> result) {
141
- marks.refresh(result);
142
- }
143
-
144
- @Override
145
- public void onFailure(Throwable caught) {
146
- errorReporter.reportError("Could not load marks: " + caught.getMessage());
147
- }
148
- });
149 137
}
150 138
}
... ...
\ No newline at end of file
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SwissTimingEventManagementPanel.java
... ...
@@ -225,7 +225,7 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane
225 225
declinationCheckbox.setValue(true);
226 226
trackableRacesPanel.add(declinationCheckbox);
227 227
228
- final CheckBox simulateWithStartTimeNow = new CheckBox(stringMessages.simulateWithStartTimeNow());
228
+ final CheckBox simulateWithStartTimeNow = new CheckBox(stringMessages.simulateAsLiveRace());
229 229
simulateWithStartTimeNow.setWordWrap(false);
230 230
simulateWithStartTimeNow.setValue(false);
231 231
trackableRacesPanel.add(simulateWithStartTimeNow);
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TracTracEventManagementPanel.java
... ...
@@ -24,6 +24,7 @@ import com.google.gwt.user.client.ui.CaptionPanel;
24 24
import com.google.gwt.user.client.ui.CheckBox;
25 25
import com.google.gwt.user.client.ui.FlexTable;
26 26
import com.google.gwt.user.client.ui.FlexTable.FlexCellFormatter;
27
+import com.google.gwt.user.client.ui.FlowPanel;
27 28
import com.google.gwt.user.client.ui.Grid;
28 29
import com.google.gwt.user.client.ui.HTMLTable.ColumnFormatter;
29 30
import com.google.gwt.user.client.ui.HorizontalPanel;
... ...
@@ -44,6 +45,7 @@ import com.sap.sailing.gwt.ui.client.shared.controls.SelectionCheckboxColumn;
44 45
import com.sap.sailing.gwt.ui.shared.RegattaDTO;
45 46
import com.sap.sailing.gwt.ui.shared.TracTracConfigurationDTO;
46 47
import com.sap.sailing.gwt.ui.shared.TracTracRaceRecordDTO;
48
+import com.sap.sse.common.Duration;
47 49
import com.sap.sse.common.util.NaturalComparator;
48 50
import com.sap.sse.gwt.client.ErrorReporter;
49 51
import com.sap.sse.gwt.client.async.MarkedAsyncCallback;
... ...
@@ -83,7 +85,8 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel {
83 85
84 86
private LabeledAbstractFilterablePanel<TracTracRaceRecordDTO> racesFilterablePanel;
85 87
private CellTable<TracTracRaceRecordDTO> racesTable;
86
-
88
+ private static final String ZERO_AS_STRING = "0";
89
+
87 90
public TracTracEventManagementPanel(final SailingServiceAsync sailingService, ErrorReporter errorReporter,
88 91
RegattaRefresher regattaRefresher, StringMessages stringMessages) {
89 92
super(sailingService, regattaRefresher, errorReporter, new RaceSelectionModel(), true, stringMessages);
... ...
@@ -229,17 +232,12 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel {
229 232
230 233
protected HorizontalPanel createRacesPanel() {
231 234
HorizontalPanel racesPanel = new HorizontalPanel();
232
-
233 235
CaptionPanel trackableRacesPanel = createTrackableRacesPanel();
234
-
235 236
racesPanel.add(trackableRacesPanel);
236 237
racesPanel.setCellWidth(trackableRacesPanel, "50%");
237
-
238 238
CaptionPanel trackedRacesPanel = createTrackedRacesPanel();
239
-
240 239
racesPanel.add(trackedRacesPanel);
241 240
racesPanel.setCellWidth(trackedRacesPanel, "50%");
242
-
243 241
return racesPanel;
244 242
}
245 243
... ...
@@ -276,10 +274,32 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel {
276 274
correctWindCheckBox.setWordWrap(false);
277 275
correctWindCheckBox.setValue(Boolean.TRUE);
278 276
279
- final CheckBox simulateWithStartTimeNowCheckBox = new CheckBox(stringMessages.simulateWithStartTimeNow());
277
+ final TextBox offsetToStartTimeOfSimulatedRaceTextBox = new TextBox();
278
+ offsetToStartTimeOfSimulatedRaceTextBox.setWidth("40px");
279
+ offsetToStartTimeOfSimulatedRaceTextBox.setEnabled(false);
280
+ offsetToStartTimeOfSimulatedRaceTextBox.setValue(ZERO_AS_STRING);
281
+
282
+ final CheckBox simulateWithStartTimeNowCheckBox = new CheckBox(stringMessages.simulateAsLiveRace());
280 283
simulateWithStartTimeNowCheckBox.ensureDebugId("SimulateWithStartTimeNowCheckBox");
281 284
simulateWithStartTimeNowCheckBox.setWordWrap(false);
282 285
simulateWithStartTimeNowCheckBox.setValue(Boolean.FALSE);
286
+ simulateWithStartTimeNowCheckBox.addClickHandler(new ClickHandler() {
287
+ @Override
288
+ public void onClick(ClickEvent event) {
289
+ offsetToStartTimeOfSimulatedRaceTextBox.setEnabled(simulateWithStartTimeNowCheckBox.getValue());
290
+ offsetToStartTimeOfSimulatedRaceTextBox.setFocus(simulateWithStartTimeNowCheckBox.getValue());
291
+ }
292
+ });
293
+
294
+ final FlowPanel simulateAsLiveRacePanel = new FlowPanel();
295
+ simulateAsLiveRacePanel.add(simulateWithStartTimeNowCheckBox);
296
+
297
+ final Label offsetToStartLabel = new Label(stringMessages.simulateWithOffset());
298
+
299
+ final HorizontalPanel simulateWithOffsetPanel = new HorizontalPanel();
300
+ simulateWithOffsetPanel.add(offsetToStartLabel);
301
+ simulateWithOffsetPanel.add(offsetToStartTimeOfSimulatedRaceTextBox);
302
+
283 303
final CheckBox ignoreTracTracMarkPassingsCheckbox = new CheckBox(stringMessages.useInternalAlgorithm());
284 304
ignoreTracTracMarkPassingsCheckbox.setWordWrap(false);
285 305
ignoreTracTracMarkPassingsCheckbox.setValue(Boolean.FALSE);
... ...
@@ -287,8 +307,9 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel {
287 307
layoutTable.setWidget(1, 0, trackSettingsLabel);
288 308
layoutTable.setWidget(1, 1, trackWindCheckBox);
289 309
layoutTable.setWidget(2, 1, correctWindCheckBox);
290
- layoutTable.setWidget(3, 1, simulateWithStartTimeNowCheckBox);
291
- layoutTable.setWidget(4, 1, ignoreTracTracMarkPassingsCheckbox);
310
+ layoutTable.setWidget(3, 1, simulateAsLiveRacePanel);
311
+ layoutTable.setWidget(4, 1, simulateWithOffsetPanel);
312
+ layoutTable.setWidget(5, 1, ignoreTracTracMarkPassingsCheckbox);
292 313
293 314
// Filter
294 315
Label racesFilterLabel = new Label(stringMessages.filterRacesByName() + ":");
... ...
@@ -310,8 +331,8 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel {
310 331
}
311 332
};
312 333
racesFilterablePanel.getTextBox().ensureDebugId("TrackableRacesFilterTextBox");
313
- layoutTable.setWidget(5, 0, racesFilterLabel);
314
- layoutTable.setWidget(5, 1, racesFilterablePanel);
334
+ layoutTable.setWidget(6, 0, racesFilterLabel);
335
+ layoutTable.setWidget(6, 1, racesFilterablePanel);
315 336
316 337
// Races
317 338
TextColumn<TracTracRaceRecordDTO> regattaNameColumn = new TextColumn<TracTracRaceRecordDTO>() {
... ...
@@ -357,7 +378,6 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel {
357 378
}
358 379
};
359 380
raceVisibilityColumn.setSortable(true);
360
-
361 381
SelectionCheckboxColumn<TracTracRaceRecordDTO> selectionCheckboxColumn = new SelectionCheckboxColumn<TracTracRaceRecordDTO>(tableResources.cellTableStyle().cellTableCheckboxSelected(),
362 382
tableResources.cellTableStyle().cellTableCheckboxDeselected(), tableResources.cellTableStyle().cellTableCheckboxColumnCell()) {
363 383
@Override
... ...
@@ -381,26 +401,24 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel {
381 401
raceNameColumn, boatClassColumn, raceStartTrackingColumn, raceStatusColumn));
382 402
racesTable.setSelectionModel(selectionCheckboxColumn.getSelectionModel(), selectionCheckboxColumn.getSelectionManager());
383 403
racesTable.setWidth("100%");
384
-
385 404
raceList.addDataDisplay(racesTable);
386
-
387 405
layoutTable.setWidget(6, 0, racesTable);
388 406
cellFormatter.setColSpan(6, 0, 2);
389
-
390 407
Button startTrackingButton = new Button(stringMessages.startTracking());
391 408
startTrackingButton.ensureDebugId("StartTrackingButton");
392 409
startTrackingButton.addClickHandler(new ClickHandler() {
393 410
@Override
394 411
public void onClick(ClickEvent event) {
412
+ Duration offsetToStartTimeOfSimulatedRace = null;
413
+ if(simulateWithStartTimeNowCheckBox.getValue().booleanValue()) {
414
+ offsetToStartTimeOfSimulatedRace = getMillisecondsDurationFromMinutesAsString(offsetToStartTimeOfSimulatedRaceTextBox.getValue());
415
+ }
395 416
trackSelectedRaces(trackWindCheckBox.getValue(), correctWindCheckBox.getValue(),
396
- simulateWithStartTimeNowCheckBox.getValue(), ignoreTracTracMarkPassingsCheckbox.getValue());
417
+ offsetToStartTimeOfSimulatedRace, ignoreTracTracMarkPassingsCheckbox.getValue());
397 418
}
398 419
});
399
-
400 420
layoutTable.setWidget(7, 1, startTrackingButton);
401
-
402 421
trackableRacesPanel.setContentWidget(layoutTable);
403
-
404 422
return trackableRacesPanel;
405 423
}
406 424
... ...
@@ -462,6 +480,17 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel {
462 480
463 481
return boatClassNames.substring(0, boatClassNames.length() - 2);
464 482
}
483
+
484
+ private Duration getMillisecondsDurationFromMinutesAsString(String minutesAsString) {
485
+ Duration result = null;
486
+ if (minutesAsString != null) {
487
+ Double minutesAsDouble = Double.parseDouble(minutesAsString);
488
+ if (minutesAsDouble != null) {
489
+ result = Duration.ONE_MINUTE.times(minutesAsDouble.longValue());
490
+ }
491
+ }
492
+ return result;
493
+ }
465 494
466 495
private void fillConfigurations() {
467 496
this.sailingService.getPreviousTracTracConfigurations(new MarkedAsyncCallback<List<TracTracConfigurationDTO>>(
... ...
@@ -551,7 +580,7 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel {
551 580
}));
552 581
}
553 582
554
- private void trackSelectedRaces(boolean trackWind, boolean correctWind, final boolean simulateWithStartTimeNow, boolean ignoreTracTracMarkPassings) {
583
+ private void trackSelectedRaces(boolean trackWind, boolean correctWind, final Duration offsetToStartTimeOfSimulatedRace, boolean ignoreTracTracMarkPassings) {
555 584
String liveURI = liveURITextBox.getValue();
556 585
String storedURI = storedURITextBox.getValue();
557 586
String courseDesignUpdateURI = tracTracUpdateURITextBox.getValue();
... ...
@@ -577,7 +606,7 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel {
577 606
}
578 607
if (checkBoatClassOK(selectedRegatta, selectedRaces)) {
579 608
sailingService.trackWithTracTrac(regattaIdentifier, selectedRaces, liveURI, storedURI,
580
- courseDesignUpdateURI, trackWind, correctWind, simulateWithStartTimeNow, ignoreTracTracMarkPassings, tractracUsername,
609
+ courseDesignUpdateURI, trackWind, correctWind, offsetToStartTimeOfSimulatedRace, ignoreTracTracMarkPassings, tractracUsername,
581 610
tractracPassword, new MarkedAsyncCallback<Void>(new AsyncCallback<Void>() {
582 611
@Override
583 612
public void onFailure(Throwable caught) {
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/RaceTimePanel.java
... ...
@@ -30,8 +30,8 @@ public class RaceTimePanel extends TimePanel<RaceTimePanelSettings> implements R
30 30
private boolean redrawAllMarkersPendingForMinMaxBeingInitialized;
31 31
32 32
public RaceTimePanel(Timer timer, TimeRangeWithZoomProvider timeRangeProvider, StringMessages stringMessages,
33
- RaceTimesInfoProvider raceTimesInfoProvider, boolean canReplayWhileLiveIsPossible) {
34
- super(timer, timeRangeProvider, stringMessages, canReplayWhileLiveIsPossible);
33
+ RaceTimesInfoProvider raceTimesInfoProvider, boolean canReplayWhileLiveIsPossible, boolean isScreenLargeEnoughToOfferChartSupport) {
34
+ super(timer, timeRangeProvider, stringMessages, canReplayWhileLiveIsPossible, isScreenLargeEnoughToOfferChartSupport);
35 35
this.raceTimesInfoProvider = raceTimesInfoProvider;
36 36
selectedRace = null;
37 37
autoAdjustPlayMode = true;
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java
... ...
@@ -86,6 +86,7 @@ import com.sap.sailing.gwt.ui.shared.TrackFileImportDeviceIdentifierDTO;
86 86
import com.sap.sailing.gwt.ui.shared.VenueDTO;
87 87
import com.sap.sailing.gwt.ui.shared.WindDTO;
88 88
import com.sap.sailing.gwt.ui.shared.WindInfoForRaceDTO;
89
+import com.sap.sse.common.Duration;
89 90
import com.sap.sse.common.NoCorrespondingServiceRegisteredException;
90 91
import com.sap.sse.common.TimePoint;
91 92
import com.sap.sse.common.Util;
... ...
@@ -116,7 +117,7 @@ public interface SailingService extends RemoteService, FileStorageManagementGwtS
116 117
117 118
void trackWithTracTrac(RegattaIdentifier regattaToAddTo, Iterable<TracTracRaceRecordDTO> rrs, String liveURI,
118 119
String storedURI, String courseDesignUpdateURI, boolean trackWind, boolean correctWindByDeclination,
119
- boolean simulateWithStartTimeNow, boolean useInternalMarkPassingAlgorithm, String tracTracUsername, String tracTracPassword) throws Exception;
120
+ Duration offsetToStartTimeOfSimulatedRace, boolean useInternalMarkPassingAlgorithm, String tracTracUsername, String tracTracPassword) throws Exception;
120 121
121 122
void trackWithSwissTiming(RegattaIdentifier regattaToAddTo, Iterable<SwissTimingRaceRecordDTO> rrs, String hostname, int port,
122 123
boolean trackWind, boolean correctWindByDeclination, boolean useInternalMarkPassingAlgorithm) throws Exception;
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java
... ...
@@ -78,6 +78,7 @@ import com.sap.sailing.gwt.ui.shared.TrackFileImportDeviceIdentifierDTO;
78 78
import com.sap.sailing.gwt.ui.shared.VenueDTO;
79 79
import com.sap.sailing.gwt.ui.shared.WindDTO;
80 80
import com.sap.sailing.gwt.ui.shared.WindInfoForRaceDTO;
81
+import com.sap.sse.common.Duration;
81 82
import com.sap.sse.common.TimePoint;
82 83
import com.sap.sse.common.Util;
83 84
import com.sap.sse.common.Util.Pair;
... ...
@@ -114,10 +115,10 @@ public interface SailingServiceAsync extends ServerInfoRetriever, FileStorageMan
114 115
* @param liveURI
115 116
* may be <code>null</code> or the empty string in which case the server will use the
116 117
* {@link TracTracRaceRecordDTO#liveURI} from the <code>rr</code> race record.
117
- * @param simulateWithStartTimeNow
118
- * if <code>true</code>, the connector will adjust the time stamps of all events received such that the
118
+ * @param offsetToStartTimeOfSimulatedRace
119
+ * if not <code>null</code>, the connector will adjust the time stamps of all events received such that the
119 120
* first mark passing for the first waypoint will be set to "now." It will delay the forwarding of all
120
- * events received such that they seem to be sent in "real-time." So, more or less the time points
121
+ * events received such that they seem to be sent in "real-time" plus the <code>offsetToStartTimeOfSimulatedRace</code>. So, more or less the time points
121 122
* attached to the events sent to the receivers will again approximate the wall time.
122 123
* @param useInternalMarkPassingAlgorithm
123 124
* whether or not to ignore the TracTrac-provided mark passings; if <code>true</code>, a separate mark
... ...
@@ -128,7 +129,7 @@ public interface SailingServiceAsync extends ServerInfoRetriever, FileStorageMan
128 129
*/
129 130
void trackWithTracTrac(RegattaIdentifier regattaToAddTo, Iterable<TracTracRaceRecordDTO> rrs, String liveURI,
130 131
String storedURI, String courseDesignUpdateURI, boolean trackWind, boolean correctWindByDeclination,
131
- boolean simulateWithStartTimeNow, boolean useInternalMarkPassingAlgorithm, String tracTracUsername,
132
+ Duration offsetToStartTimeOfSimulatedRace, boolean useInternalMarkPassingAlgorithm, String tracTracUsername,
132 133
String tracTracPassword, AsyncCallback<Void> callback);
133 134
134 135
void trackWithSwissTiming(RegattaIdentifier regattaToAddTo, Iterable<SwissTimingRaceRecordDTO> rrs,
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java
... ...
@@ -361,7 +361,8 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages, c
361 361
String successfullyUpdatedScores();
362 362
String errorUpdatingScoresForLeaderboard(String leaderboardName, String message);
363 363
String maneuverTypesToShowWhenCompetitorIsClicked();
364
- String simulateWithStartTimeNow();
364
+ String simulateAsLiveRace();
365
+ String simulateWithOffset();
365 366
String boatClassDoesNotMatchSelectedRegatta(String boatClass);
366 367
String regattaExistForSelectedBoatClass();
367 368
String reload();
... ...
@@ -436,7 +437,8 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages, c
436 437
String overallDetailsToShow();
437 438
String hhmmssUnit();
438 439
String actionAddWindData();
439
- String valueMustBeBetweenMinMax(String name, String minValue, String maxValue);
440
+ String valueMustBeBetweenMinMax(String name, int minValue, int maxValue);
441
+ String valueMustBeGreaterThan(String name, String minValue);
440 442
String optional();
441 443
String pleaseEnterAValue();
442 444
String latitude();
... ...
@@ -503,6 +505,8 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages, c
503 505
String sideToWhichMarkAtLegStartWasRounded();
504 506
String raceIsLive(String raceName);
505 507
String racesAreLive(String raceNames);
508
+ String regattaIsLive(String regattaName);
509
+ String regattasAreLive(String regattaNames);
506 510
String scoringSchemeHighPointFirstGetsOne();
507 511
String scoringSchemeHighPointFirstGetsTen();
508 512
String knotsUnit();
... ...
@@ -1444,4 +1448,6 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages, c
1444 1448
String requiresValidRegatta();
1445 1449
String couldNotObtainRace(String regattaLikeName, String raceColumnName, String fleetName, String technicalErrorMessage);
1446 1450
String errorTryingToCreateEmbeddedMap(String message);
1451
+ String transparentBufferLineOnHover();
1452
+ String bufferLineStrokeWeight();
1447 1453
}
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties
... ...
@@ -371,7 +371,8 @@ apply=Apply
371 371
successfullyUpdatedScores=Successfully updated scores
372 372
errorUpdatingScoresForLeaderboard=Error updating scores for leaderboard {0}: {1}
373 373
maneuverTypesToShowWhenCompetitorIsClicked=Maneuver types to show when competitor is clicked
374
-simulateWithStartTimeNow=Simulate with "now" as start time
374
+simulateAsLiveRace=Simulate as live race
375
+simulateWithOffset=Offset before start in minutes:
375 376
boatClassDoesNotMatchSelectedRegatta=The selected races contain boat classes which are not the same as the boat class ''{0}'' of the selected regatta. No races will be loaded.
376 377
regattaExistForSelectedBoatClass=There is at least one regatta for the selected boat classes. Really create default regatta(s) for those race(s)?
377 378
reload=Reload
... ...
@@ -456,6 +457,7 @@ totalAverageSpeedOverGroundTooltip=Average speed over ground across the regatta.
456 457
hhmmssUnit=hh:mm:ss
457 458
actionAddWindData=Add wind data
458 459
valueMustBeBetweenMinMax=The value of ''{0}'' must be between {1} and {2}.
460
+valueMustBeGreaterThan=The value of ''{0}'' must be greater than {1}.
459 461
optional=optional
460 462
pleaseEnterAValue=Please enter a value.
461 463
latitude=Latitude
... ...
@@ -527,6 +529,8 @@ sideToWhichMarkAtLegStartWasRounded=Mark rounded to
527 529
sideToWhichMarkAtLegStartWasRoundedTooltip=Side to which the mark at leg start was rounded.
528 530
raceIsLive=Race {0} is live
529 531
racesAreLive=Races {0} are live
532
+regattaIsLive=Regatta {0} is live
533
+regattasAreLive=Regattas {0} are live
530 534
knotsUnit=kts
531 535
knotsValue={0,number,#0.0} kts
532 536
knotsRange={0,number,#0.0} - {1,number,#0.0} kts
... ...
@@ -1410,4 +1414,6 @@ selectALeaderboardGroup=Select a leaderboard group...
1410 1414
pleaseSelect=Please select
1411 1415
requiresValidRegatta=This page requires a valid regatta, race column and fleet name to identify the race to show.
1412 1416
couldNotObtainRace=Could not obtain a race with name {1} for fleet {2} for a regatta with name {0}: {3}
1413
-errorTryingToCreateEmbeddedMap=Error trying to create the embedded map: {0}
... ...
\ No newline at end of file
0
+errorTryingToCreateEmbeddedMap=Error trying to create the embedded map: {0}
1
+transparentBufferLineOnHover=Transparent buffer lines on hover of helplines
2
+bufferLineStrokeWeight=Buffer line stroke weight
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties
... ...
@@ -372,7 +372,8 @@ apply=Übernehmen
372 372
successfullyUpdatedScores=Ergebnisse erfolgreich aktualisiert
373 373
errorUpdatingScoresForLeaderboard=Fehler beim Aktualisieren der Ergebnisse für {0}: {1}
374 374
maneuverTypesToShowWhenCompetitorIsClicked=Anzuzeigende Manövertypen für angeklickte Teilnehmer
375
-simulateWithStartTimeNow=Simulieren mit "jetzt" als Startzeit
375
+simulateAsLiveRace=Simuliere als Live Rennen
376
+simulateWithOffset=Zeit bis zum Start in Minuten:
376 377
boatClassDoesNotMatchSelectedRegatta=Die ausgewählten Rennen enthalten eine Bootsklasse, die nicht der Bootsklasse der ausgewählten Regatta ''{0}'' entspricht. Es werden keine Rennen geladen.
377 378
regattaExistForSelectedBoatClass=Es gibt mindestens eine Regatta zu den ausgewählten Bootsklassen. Dennoch Default-Regatta für diese(s) Rennen anlegen?
378 379
reload=Neu laden
... ...
@@ -456,7 +457,8 @@ totalAverageSpeedOverGround=\u2205 FüG
456 457
totalAverageSpeedOverGroundTooltip=Durchschnittliche Fahrt über Grund über die gesamte Regatta.
457 458
hhmmssUnit=hh:mm:ss
458 459
actionAddWindData=Winddaten hinzufügen
459
-valueMustBeBetweenMinMax=Der Wert von ''{0}'' muß zwischen {1} und {2} sein.
460
+valueMustBeBetweenMinMax=Der Wert von ''{0}'' muss zwischen {1} und {2} sein.
461
+valueMustBeGreaterThan=Der Wert von ''{0}'' muss größer als {1} sein.
460 462
optional=optional
461 463
pleaseEnterAValue=Bitte geben Sie einen Wert ein.
462 464
latitude=Breite
... ...
@@ -528,6 +530,8 @@ sideToWhichMarkAtLegStartWasRounded=Tonne gerundet an
528 530
sideToWhichMarkAtLegStartWasRoundedTooltip=Seite, an der die Bahnmarke am Schenkelstart gerundet wurde
529 531
raceIsLive=Rennen {0} ist live
530 532
racesAreLive=Rennen {0} sind live
533
+regattaIsLive=Regatta {0} ist live
534
+regattasAreLive=Regatten {0} sind live
531 535
scoringSchemeHighPointFirstGetsOne=High Point System, Gewinner erhält 1 Punkt
532 536
scoringSchemeHighPointFirstGetsTen=High Point System, Gewinner erhält 10 Punkte
533 537
knotsUnit=kn
... ...
@@ -1401,4 +1405,6 @@ selectALeaderboardGroup=Bitte eine Ranglisten-Gruppe auswählen...
1401 1405
pleaseSelect=Bitte auswählen
1402 1406
requiresValidRegatta=Diese Seite benötigt eine gültige Regatta, Rennspalte und einen gültigen Gruppennamen um die anzuzeigende Wettfahrt zu identifizieren.
1403 1407
couldNotObtainRace=Wettfahrt namens {1} für Gruppe {2} in einer Regatta names {0} konnte nicht gefunden werden: {3}
1404
-errorTryingToCreateEmbeddedMap=Fehler beim Erstellen der eingebetteten Karte: {0}
... ...
\ No newline at end of file
0
+errorTryingToCreateEmbeddedMap=Fehler beim Erstellen der eingebetteten Karte: {0}
1
+transparentBufferLineOnHover=Transparente Pufferlinien bei Maus über Hilfslinien
2
+bufferLineStrokeWeight=Linienbreite der Pufferlinien
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties
... ...
@@ -364,7 +364,8 @@ apply=Применить
364 364
successfullyUpdatedScores=Очки обновлены успешно
365 365
errorUpdatingScoresForLeaderboard=Ошибка обновления очков для таблицы лидеров {0}: {1}
366 366
maneuverTypesToShowWhenCompetitorIsClicked=Типы маневров для отображения при нажатии на участника
367
-simulateWithStartTimeNow=Моделирования с "сейчас" в качестве времени начала
367
+simulateAsLiveRace=Simulate as live race
368
+simulateWithOffset=Offset before start in minutes:
368 369
boatClassDoesNotMatchSelectedRegatta=Выбранные гонки содержат классы судов не соответствующие классу судов ''{0}'' выбранной регаты.
369 370
regattaExistForSelectedBoatClass=Есть только одна регата для выбранных классов судов.
370 371
reload=Перезагрузить
... ...
@@ -440,6 +441,7 @@ totalAverageSpeedOverGroundTooltip=Средняя скорость относи
440 441
hhmmssUnit=чч:мм:сс
441 442
actionAddWindData=Добавить данные о ветре
442 443
valueMustBeBetweenMinMax=Значение ''{0}''должно быть в пределах от {1} до {2}.
444
+valueMustBeGreaterThan=The value of ''{0}'' must be greater than {1}.
443 445
optional=Дополнительно
444 446
pleaseEnterAValue=Введите, пожалуйста, значение.
445 447
latitude=Широта
... ...
@@ -511,6 +513,8 @@ sideToWhichMarkAtLegStartWasRounded=Отметка округленная до
511 513
sideToWhichMarkAtLegStartWasRoundedTooltip=Сторона в которую округляется отметка на старте этапа.
512 514
raceIsLive=Гонка {0} в прямом эфире
513 515
racesAreLive=Гонки {0} в прямом эфире
516
+regattaIsLive=Регата {0} в прямом эфире
517
+regattasAreLive=Регаты {0} в прямом эфире
514 518
knotsUnit=Узлы
515 519
knotsValue={0,number,#0.0} Узлы
516 520
knotsRange={0,number,#0.0} - {1,number,#0.0} Узлы
... ...
@@ -1340,4 +1344,6 @@ selectALeaderboardGroup=Select a leaderboard group...
1340 1344
pleaseSelect=Please select
1341 1345
requiresValidRegatta=This page requires a valid regatta, race column and fleet name to identify the race to show.
1342 1346
couldNotObtainRace=Could not obtain a race with name {1} for fleet {2} for a regatta with name {0}: {3}
1343
-errorTryingToCreateEmbeddedMap=Error trying to create the embedded map: {0}
... ...
\ No newline at end of file
0
+errorTryingToCreateEmbeddedMap=Error trying to create the embedded map: {0}
1
+transparentBufferLineOnHover=Transparent buffer lines on hover of helplines
2
+bufferLineStrokeWeight=Buffer line stroke weight
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh.properties
... ...
@@ -538,6 +538,7 @@ connectors=Connectors
538 538
dampeningInterval=Dampening Interval
539 539
trackBasedEstimationWindSourceTypeTooltip=Calculated data from the competitor tracks
540 540
valueMustBeBetweenMinMax=The value of ''{0}'' must be between {1} and {2}.
541
+valueMustBeGreaterThan=The value of ''{0}'' must be greater than {1}.
541 542
orMultipleEmails=or multiple E-Mails separated by comma
542 543
searchEvents=Search for events
543 544
explainNoConnectionsFromReplicas=No connections from a replica found. You can connect replicas by using their AdminConsole.
... ...
@@ -844,7 +845,10 @@ explainReplicationHostname=Hostname where the replication master is running on.
844 845
igtimiAccounts=Igtimi Accounts
845 846
windChart=Wind Chart
846 847
events=活动
848
+raceIsLive=Race {0} is live
847 849
racesAreLive=Races {0} are live
850
+regattaIsLive=Regatta {0} is live
851
+regattasAreLive=Regattas {0} are live
848 852
operatorNotContains=Not Contains
849 853
windwardDistanceToCompetitorFarthestAheadInMetersTooltip=迎/顺风航段时离开上风/下风第一名的平行距离或横风时的实际距离name 名字
850 854
errorAddingResultImportUrl=Error adding result import URL: {0}
... ...
@@ -1066,13 +1070,13 @@ polarSheetMinimumConfidenceMeasure=Minimum confidence measure
1066 1070
operator=operator
1067 1071
tractracUsername=TracTrac username
1068 1072
webWindSourceTypeName=Manual
1069
-raceIsLive=Race {0} is live
1070 1073
sapSailingAnalytics=航行分析
1071 1074
trackWind=Track Wind
1072 1075
rawFixes=All fixes (including outliers)
1073 1076
showCompetitorFullNameColumn=Show Competitor Full Name
1074 1077
disableRaceFilter=Disable filter
1075
-simulateWithStartTimeNow=Simulate with "now" as start time
1078
+simulateAsLiveRace=Simulate as live race
1079
+simulateWithOffset=Offset before start in minutes:
1076 1080
polarSheetOutlierDetectionRadiusTooltip=The radius used in the distance based outlierndetection algorithm.
1077 1081
noWindFixesAvailable=No Wind-Fixes available.
1078 1082
correctScoreFor=Correct score for competitor {0} in race {1}
... ...
@@ -1355,4 +1359,6 @@ selectALeaderboardGroup=Select a leaderboard group...
1355 1359
pleaseSelect=Please select
1356 1360
requiresValidRegatta=This page requires a valid regatta, race column and fleet name to identify the race to show.
1357 1361
couldNotObtainRace=Could not obtain a race with name {1} for fleet {2} for a regatta with name {0}: {3}
1358
-errorTryingToCreateEmbeddedMap=Error trying to create the embedded map: {0}
... ...
\ No newline at end of file
0
+errorTryingToCreateEmbeddedMap=Error trying to create the embedded map: {0}
1
+transparentBufferLineOnHover=Transparent buffer lines on hover of helplines
2
+bufferLineStrokeWeight=Buffer line stroke weight
... ...
\ No newline at end of file
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/TimePanel.java
... ...
@@ -89,7 +89,13 @@ public class TimePanel<T extends TimePanelSettings> extends SimplePanel implemen
89 89
private static ClientResources resources = GWT.create(ClientResources.class);
90 90
protected static TimePanelCss timePanelCss = TimePanelCssResources.INSTANCE.css();
91 91
92
- public TimePanel(Timer timer, TimeRangeWithZoomProvider timeRangeProvider, StringMessages stringMessages, boolean canReplayWhileLiveIsPossible) {
92
+ /**
93
+ * @param isScreenLargeEnoughToOfferChartSupport
94
+ * if <code>true</code>, the right padding will be set such that the time panel lines up with charts such
95
+ * as the competitor chart or the wind chart shown above it
96
+ */
97
+ public TimePanel(Timer timer, TimeRangeWithZoomProvider timeRangeProvider, StringMessages stringMessages,
98
+ boolean canReplayWhileLiveIsPossible, boolean isScreenLargeEnoughToOfferChartSupport) {
93 99
this.timer = timer;
94 100
this.timeRangeProvider = timeRangeProvider;
95 101
this.stringMessages = stringMessages;
... ...
@@ -105,7 +111,9 @@ public class TimePanel<T extends TimePanelSettings> extends SimplePanel implemen
105 111
timePanelSliderFlowWrapper = new FlowPanel();
106 112
timePanelSlider.setStyleName("timePanelSlider");
107 113
timePanelSlider.getElement().getStyle().setPaddingLeft(66, Unit.PX);
108
- timePanelSlider.getElement().getStyle().setPaddingRight(66, Unit.PX);
114
+ if (isScreenLargeEnoughToOfferChartSupport) {
115
+ timePanelSlider.getElement().getStyle().setPaddingRight(66, Unit.PX);
116
+ }
109 117
timePanelSliderFlowWrapper.add(timePanelSlider);
110 118
111 119
playSpeedImg = resources.timesliderPlaySpeedIcon();
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/GalleryImageHolder.java
... ...
@@ -10,7 +10,7 @@ import com.google.gwt.event.shared.HandlerRegistration;
10 10
import com.google.gwt.uibinder.client.UiBinder;
11 11
import com.google.gwt.uibinder.client.UiField;
12 12
import com.google.gwt.user.client.ui.Widget;
13
-import com.sap.sailing.gwt.home.communication.media.SailingImageDTO;
13
+import com.sap.sse.gwt.client.media.ImageDTO;
14 14
15 15
public class GalleryImageHolder extends Widget implements HasClickHandlers {
16 16
... ...
@@ -22,7 +22,7 @@ public class GalleryImageHolder extends Widget implements HasClickHandlers {
22 22
@UiField
23 23
DivElement imageHolderUi;
24 24
25
- public GalleryImageHolder(SailingImageDTO video) {
25
+ public GalleryImageHolder(ImageDTO video) {
26 26
setElement(uiBinder.createAndBindUi(this));
27 27
imageHolderUi.getStyle().setBackgroundImage("url(\"" + video.getSourceRef() + "\")");
28 28
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/VideoThumbnail.java
... ...
@@ -11,9 +11,9 @@ import com.google.gwt.event.shared.HandlerRegistration;
11 11
import com.google.gwt.uibinder.client.UiBinder;
12 12
import com.google.gwt.uibinder.client.UiField;
13 13
import com.google.gwt.user.client.ui.Widget;
14
-import com.sap.sailing.gwt.home.communication.media.SailingVideoDTO;
15 14
import com.sap.sailing.gwt.ui.common.client.YoutubeApi;
16 15
import com.sap.sse.common.media.MediaSubType;
16
+import com.sap.sse.gwt.client.media.VideoDTO;
17 17
18 18
public class VideoThumbnail extends Widget implements HasClickHandlers {
19 19
... ...
@@ -28,7 +28,7 @@ public class VideoThumbnail extends Widget implements HasClickHandlers {
28 28
@UiField
29 29
ImageElement thumbnailUi;
30 30
31
- public VideoThumbnail(SailingVideoDTO video) {
31
+ public VideoThumbnail(VideoDTO video) {
32 32
setElement(uiBinder.createAndBindUi(this));
33 33
getElement().addClassName("videoThumbnail");
34 34
captionUi.setInnerText(video.getTitle());
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/Hoverline.java
... ...
@@ -0,0 +1,57 @@
1
+package com.sap.sailing.gwt.ui.client.shared.racemap;
2
+
3
+import com.google.gwt.event.shared.HandlerRegistration;
4
+import com.google.gwt.maps.client.events.click.ClickMapHandler;
5
+import com.google.gwt.maps.client.events.mouseout.MouseOutMapEvent;
6
+import com.google.gwt.maps.client.events.mouseout.MouseOutMapHandler;
7
+import com.google.gwt.maps.client.events.mouseover.MouseOverMapEvent;
8
+import com.google.gwt.maps.client.events.mouseover.MouseOverMapHandler;
9
+import com.google.gwt.maps.client.overlays.Polyline;
10
+import com.google.gwt.maps.client.overlays.PolylineOptions;
11
+
12
+public class Hoverline {
13
+ private static final double TRANSPARENT = 0;
14
+ private static final double VISIBLE = 0.2d;
15
+
16
+ private final Polyline hoverline;
17
+ private final PolylineOptions options;
18
+
19
+ public Hoverline(final Polyline polyline, PolylineOptions polylineOptions, final RaceMap map) {
20
+ this.options = PolylineOptions.newInstance();
21
+ this.options.setClickable(polylineOptions.getClickable());
22
+ this.options.setGeodesic(polylineOptions.getGeodesic());
23
+ this.options.setMap(polyline.getMap());
24
+ this.options.setPath(polyline.getPath());
25
+ this.options.setStrokeColor(polylineOptions.getStrokeColor());
26
+ this.options.setZindex(polylineOptions.getZindex());
27
+ this.hoverline = Polyline.newInstance(this.options);
28
+ this.hoverline.setVisible(false);
29
+ polyline.addMouseOverHandler(new MouseOverMapHandler() {
30
+ @Override
31
+ public void onEvent(MouseOverMapEvent event) {
32
+ options.setStrokeOpacity(map.getSettings().getTransparentHoverlines() ? TRANSPARENT : VISIBLE);
33
+ options.setStrokeWeight(map.getSettings().getHoverlineStrokeWeight());
34
+ options.setVisible(true);
35
+ hoverline.setOptions(options);
36
+ }
37
+ });
38
+ hoverline.addMouseOutMoveHandler(new MouseOutMapHandler() {
39
+ @Override
40
+ public void onEvent(MouseOutMapEvent event) {
41
+ hoverline.setVisible(false);
42
+ }
43
+ });
44
+ }
45
+
46
+ public HandlerRegistration addClickHandler(ClickMapHandler handler) {
47
+ return this.hoverline.addClickHandler(handler);
48
+ }
49
+
50
+ public HandlerRegistration addMouseOutMoveHandler(MouseOutMapHandler handler) {
51
+ return this.hoverline.addMouseOutMoveHandler(handler);
52
+ }
53
+
54
+ public HandlerRegistration addMouseOverHandler(MouseOverMapHandler handler) {
55
+ return this.hoverline.addMouseOverHandler(handler);
56
+ }
57
+}
... ...
\ No newline at end of file
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java
... ...
@@ -1,2601 +1,2636 @@
1
-package com.sap.sailing.gwt.ui.client.shared.racemap;
2
-
3
-import java.util.ArrayList;
4
-import java.util.Collection;
5
-import java.util.Collections;
6
-import java.util.Comparator;
7
-import java.util.Date;
8
-import java.util.HashMap;
9
-import java.util.HashSet;
10
-import java.util.Iterator;
11
-import java.util.LinkedHashMap;
12
-import java.util.List;
13
-import java.util.Map;
14
-import java.util.Map.Entry;
15
-import java.util.Set;
16
-
17
-import com.google.gwt.canvas.dom.client.CssColor;
18
-import com.google.gwt.core.client.GWT;
19
-import com.google.gwt.core.client.Scheduler;
20
-import com.google.gwt.core.client.Scheduler.ScheduledCommand;
21
-import com.google.gwt.dom.client.Style;
22
-import com.google.gwt.dom.client.Style.Unit;
23
-import com.google.gwt.event.dom.client.ClickEvent;
24
-import com.google.gwt.event.dom.client.ClickHandler;
25
-import com.google.gwt.i18n.client.NumberFormat;
26
-import com.google.gwt.maps.client.LoadApi;
27
-import com.google.gwt.maps.client.LoadApi.LoadLibrary;
28
-import com.google.gwt.maps.client.MapOptions;
29
-import com.google.gwt.maps.client.MapWidget;
30
-import com.google.gwt.maps.client.base.LatLng;
31
-import com.google.gwt.maps.client.base.LatLngBounds;
32
-import com.google.gwt.maps.client.controls.ControlPosition;
33
-import com.google.gwt.maps.client.controls.MapTypeControlOptions;
34
-import com.google.gwt.maps.client.controls.MapTypeStyle;
35
-import com.google.gwt.maps.client.controls.PanControlOptions;
36
-import com.google.gwt.maps.client.controls.ZoomControlOptions;
37
-import com.google.gwt.maps.client.events.bounds.BoundsChangeMapEvent;
38
-import com.google.gwt.maps.client.events.bounds.BoundsChangeMapHandler;
39
-import com.google.gwt.maps.client.events.click.ClickMapEvent;
40
-import com.google.gwt.maps.client.events.click.ClickMapHandler;
41
-import com.google.gwt.maps.client.events.dragend.DragEndMapEvent;
42
-import com.google.gwt.maps.client.events.dragend.DragEndMapHandler;
43
-import com.google.gwt.maps.client.events.idle.IdleMapEvent;
44
-import com.google.gwt.maps.client.events.idle.IdleMapHandler;
45
-import com.google.gwt.maps.client.events.mouseout.MouseOutMapEvent;
46
-import com.google.gwt.maps.client.events.mouseout.MouseOutMapHandler;
47
-import com.google.gwt.maps.client.events.mouseover.MouseOverMapEvent;
48
-import com.google.gwt.maps.client.events.mouseover.MouseOverMapHandler;
49
-import com.google.gwt.maps.client.events.zoom.ZoomChangeMapEvent;
50
-import com.google.gwt.maps.client.events.zoom.ZoomChangeMapHandler;
51
-import com.google.gwt.maps.client.maptypes.MapTypeStyleFeatureType;
52
-import com.google.gwt.maps.client.mvc.MVCArray;
53
-import com.google.gwt.maps.client.overlays.InfoWindow;
54
-import com.google.gwt.maps.client.overlays.InfoWindowOptions;
55
-import com.google.gwt.maps.client.overlays.Marker;
56
-import com.google.gwt.maps.client.overlays.MarkerOptions;
57
-import com.google.gwt.maps.client.overlays.Polygon;
58
-import com.google.gwt.maps.client.overlays.PolygonOptions;
59
-import com.google.gwt.maps.client.overlays.Polyline;
60
-import com.google.gwt.maps.client.overlays.PolylineOptions;
61
-import com.google.gwt.resources.client.ImageResource;
62
-import com.google.gwt.user.client.Window;
63
-import com.google.gwt.user.client.rpc.AsyncCallback;
64
-import com.google.gwt.user.client.ui.AbsolutePanel;
65
-import com.google.gwt.user.client.ui.Button;
66
-import com.google.gwt.user.client.ui.FlowPanel;
67
-import com.google.gwt.user.client.ui.Image;
68
-import com.google.gwt.user.client.ui.Label;
69
-import com.google.gwt.user.client.ui.RequiresResize;
70
-import com.google.gwt.user.client.ui.VerticalPanel;
71
-import com.google.gwt.user.client.ui.Widget;
72
-import com.sap.sailing.domain.common.Bearing;
73
-import com.sap.sailing.domain.common.Bounds;
74
-import com.sap.sailing.domain.common.ManeuverType;
75
-import com.sap.sailing.domain.common.Position;
76
-import com.sap.sailing.domain.common.RaceIdentifier;
77
-import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
78
-import com.sap.sailing.domain.common.Tack;
79
-import com.sap.sailing.domain.common.WindSource;
80
-import com.sap.sailing.domain.common.WindSourceType;
81
-import com.sap.sailing.domain.common.dto.CompetitorDTO;
82
-import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
83
-import com.sap.sailing.domain.common.impl.DegreePosition;
84
-import com.sap.sailing.domain.common.scalablevalue.impl.ScalableBearing;
85
-import com.sap.sailing.domain.common.scalablevalue.impl.ScalablePosition;
86
-import com.sap.sailing.gwt.ui.actions.GetPolarAction;
87
-import com.sap.sailing.gwt.ui.actions.GetRaceMapDataAction;
88
-import com.sap.sailing.gwt.ui.actions.GetWindInfoAction;
89
-import com.sap.sailing.gwt.ui.client.ClientResources;
90
-import com.sap.sailing.gwt.ui.client.CompetitorSelectionChangeListener;
91
-import com.sap.sailing.gwt.ui.client.CompetitorSelectionProvider;
92
-import com.sap.sailing.gwt.ui.client.NumberFormatterFactory;
93
-import com.sap.sailing.gwt.ui.client.RaceSelectionChangeListener;
94
-import com.sap.sailing.gwt.ui.client.RaceTimesInfoProviderListener;
95
-import com.sap.sailing.gwt.ui.client.RequiresDataInitialization;
96
-import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
97
-import com.sap.sailing.gwt.ui.client.StringMessages;
98
-import com.sap.sailing.gwt.ui.client.WindSourceTypeFormatter;
99
-import com.sap.sailing.gwt.ui.client.shared.filter.QuickRankProvider;
100
-import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMapHelpLinesSettings.HelpLineTypes;
101
-import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMapZoomSettings.ZoomTypes;
102
-import com.sap.sailing.gwt.ui.common.client.DateAndTimeFormatterUtil;
103
-import com.sap.sailing.gwt.ui.shared.ControlPointDTO;
104
-import com.sap.sailing.gwt.ui.shared.CoursePositionsDTO;
105
-import com.sap.sailing.gwt.ui.shared.GPSFixDTO;
106
-import com.sap.sailing.gwt.ui.shared.LegInfoDTO;
107
-import com.sap.sailing.gwt.ui.shared.ManeuverDTO;
108
-import com.sap.sailing.gwt.ui.shared.MarkDTO;
109
-import com.sap.sailing.gwt.ui.shared.QuickRankDTO;
110
-import com.sap.sailing.gwt.ui.shared.RaceCourseDTO;
111
-import com.sap.sailing.gwt.ui.shared.RaceMapDataDTO;
112
-import com.sap.sailing.gwt.ui.shared.RaceTimesInfoDTO;
113
-import com.sap.sailing.gwt.ui.shared.SidelineDTO;
114
-import com.sap.sailing.gwt.ui.shared.SpeedWithBearingDTO;
115
-import com.sap.sailing.gwt.ui.shared.WaypointDTO;
116
-import com.sap.sailing.gwt.ui.shared.WindDTO;
117
-import com.sap.sailing.gwt.ui.shared.WindInfoForRaceDTO;
118
-import com.sap.sailing.gwt.ui.shared.WindTrackInfoDTO;
119
-import com.sap.sailing.gwt.ui.shared.racemap.GoogleMapAPIKey;
120
-import com.sap.sailing.gwt.ui.shared.racemap.GoogleMapStyleHelper;
121
-import com.sap.sailing.gwt.ui.shared.racemap.RaceSimulationOverlay;
122
-import com.sap.sailing.gwt.ui.shared.racemap.WindStreamletsRaceboardOverlay;
123
-import com.sap.sse.common.Util;
124
-import com.sap.sse.common.Util.Pair;
125
-import com.sap.sse.common.Util.Triple;
126
-import com.sap.sse.common.filter.Filter;
127
-import com.sap.sse.common.filter.FilterSet;
128
-import com.sap.sse.common.impl.RGBColor;
129
-import com.sap.sse.gwt.client.ErrorReporter;
130
-import com.sap.sse.gwt.client.async.AsyncActionsExecutor;
131
-import com.sap.sse.gwt.client.async.MarkedAsyncCallback;
132
-import com.sap.sse.gwt.client.player.TimeListener;
133
-import com.sap.sse.gwt.client.player.Timer;
134
-import com.sap.sse.gwt.client.player.Timer.PlayModes;
135
-import com.sap.sse.gwt.client.player.Timer.PlayStates;
136
-import com.sap.sse.gwt.client.shared.components.Component;
137
-import com.sap.sse.gwt.client.shared.components.SettingsDialog;
138
-import com.sap.sse.gwt.client.shared.components.SettingsDialogComponent;
139
-
140
-public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSelectionChangeListener, RaceSelectionChangeListener,
141
- RaceTimesInfoProviderListener, TailFactory, Component<RaceMapSettings>, RequiresDataInitialization, RequiresResize, QuickRankProvider {
142
- public static final String GET_RACE_MAP_DATA_CATEGORY = "getRaceMapData";
143
- public static final String GET_WIND_DATA_CATEGORY = "getWindData";
144
-
145
- private MapWidget map;
146
-
147
- /**
148
- * Always valid, non-<code>null</code>. Must be used to map all coordinates, headings, bearings, and directions
149
- * displayed on the map, including the orientations of any canvases such as boat icons, wind displays etc. that are
150
- * embedded in the map. The coordinate systems facilitates the possibility of transformed displays such as
151
- * rotated and translated versions of the map, implementing the "wind-up" view.
152
- */
153
- private DelegateCoordinateSystem coordinateSystem;
154
-
155
- private FlowPanel headerPanel;
156
- private AbsolutePanel panelForLeftHeaderLabels;
157
- private AbsolutePanel panelForRightHeaderLabels;
158
-
159
- private final SailingServiceAsync sailingService;
160
- private final ErrorReporter errorReporter;
161
-
162
- private final static ClientResources resources = GWT.create(ClientResources.class);
163
-
164
- /**
165
- * Polyline for the start line (connecting two marks representing the start gate).
166
- */
167
- private Polyline startLine;
168
-
169
- /**
170
- * Polyline for the finish line (connecting two marks representing the finish gate).
171
- */
172
- private Polyline finishLine;
173
-
174
- /**
175
- * Polyline for the advantage line (the leading line for the boats, orthogonal to the wind direction; touching the leading boat).
176
- */
177
- private Polyline advantageLine;
178
-
179
- /**
180
- * The windward of two Polylines representing a triangle between startline and first mark.
181
- */
182
- private Polyline windwardStartLineMarkToFirstMarkLine;
183
-
184
- /**
185
- * The leeward of two Polylines representing a triangle between startline and first mark.
186
- */
187
- private Polyline leewardStartLineMarkToFirstMarkLine;
188
-
189
- private class AdvantageLineMouseOverMapHandler implements MouseOverMapHandler {
190
- private double trueWindAngle;
191
- private Date date;
192
-
193
- public AdvantageLineMouseOverMapHandler(double trueWindAngle, Date date) {
194
- this.trueWindAngle = trueWindAngle;
195
- this.date = date;
196
- }
197
-
198
- public void setTrueWindBearing(double trueWindAngle) {
199
- this.trueWindAngle = trueWindAngle;
200
- }
201
-
202
- public void setDate(Date date) {
203
- this.date = date;
204
- }
205
-
206
- @Override
207
- public void onEvent(MouseOverMapEvent event) {
208
- map.setTitle(stringMessages.advantageLine()+" (from "+new DegreeBearingImpl(Math.round(trueWindAngle)).reverse().getDegrees()+"deg"+
209
- (date == null ? ")" : ", "+ date) + ")");
210
- }
211
- };
212
-
213
- private AdvantageLineMouseOverMapHandler advantageLineMouseOverHandler;
214
-
215
- /**
216
- * Polylines for the course middle lines; keys are the two control points delimiting the leg for which the
217
- * {@link Polyline} value shows the course middle line. As only one course middle line is shown even if there
218
- * are multiple legs using the same control points in different directions, using a {@link Set} makes this
219
- * independent of the order of the two control points. If no course middle line is currently being shown for
220
- * a pair of control points, the map will not contain a value for this pair.
221
- */
222
- private final Map<Set<ControlPointDTO>, Polyline> courseMiddleLines;
223
-
224
- private final Map<SidelineDTO, Polygon> courseSidelines;
225
-
226
- /**
227
- * When the {@link HelpLineTypes#COURSEGEOMETRY} option is selected, little markers will be displayed on the
228
- * lines that show the tooltip text in a little info box linked to the line. When the line is removed by
229
- * {@link #showOrRemoveOrUpdateLine(Polyline, boolean, Position, Position, LineInfoProvider, String)}, these
230
- * overlays need to be removed as well. Also, when the {@link HelpLineTypes#COURSEGEOMETRY} setting is
231
- * deactivated, all these overlays need to go.
232
- */
233
- private final Map<Polyline, SmallTransparentInfoOverlay> infoOverlaysForLinesForCourseGeometry;
234
-
235
- /**
236
- * Wind data used to display the advantage line. Retrieved by a {@link GetWindInfoAction} execution and used in
237
- * {@link #showAdvantageLine(Iterable, Date)}.
238
- */
239
- private WindInfoForRaceDTO lastCombinedWindTrackInfoDTO;
240
-
241
- /**
242
- * Manages the cached set of {@link GPSFixDTO}s for the boat positions as well as their graphical counterpart in the
243
- * form of {@link Polyline}s.
244
- */
245
- private final FixesAndTails fixesAndTails;
246
-
247
- /**
248
- * html5 canvases used as boat display on the map
249
- */
250
- private final Map<CompetitorDTO, BoatOverlay> boatOverlays;
251
-
252
- /**
253
- * html5 canvases used for competitor info display on the map
254
- */
255
- private final Map<CompetitorDTO, CompetitorInfoOverlay> competitorInfoOverlays;
256
-
257
- private SmallTransparentInfoOverlay countDownOverlay;
258
-
259
- /**
260
- * Map overlays with html5 canvas used to display wind sensors
261
- */
262
- private final Map<WindSource, WindSensorOverlay> windSensorOverlays;
263
-
264
- /**
265
- * Map overlays with html5 canvas used to display course marks including buoy zones
266
- */
267
- private final Map<String, CourseMarkOverlay> courseMarkOverlays;
268
-
269
- private final Map<String, MarkDTO> markDTOs;
270
-
271
- /**
272
- * markers displayed in response to
273
- * {@link SailingServiceAsync#getDouglasPoints(String, String, Map, Map, double, AsyncCallback)}
274
- */
275
- protected Set<Marker> douglasMarkers;
276
-
277
- /**
278
- * markers displayed in response to
279
- * {@link SailingServiceAsync#getDouglasPoints(String, String, Map, Map, double, AsyncCallback)}
280
- */
281
- private Set<Marker> maneuverMarkers;
282
-
283
- private Map<CompetitorDTO, List<ManeuverDTO>> lastManeuverResult;
284
-
285
- private Map<CompetitorDTO, List<GPSFixDTO>> lastDouglasPeuckerResult;
286
-
287
- private final CompetitorSelectionProvider competitorSelection;
288
-
289
- private List<RegattaAndRaceIdentifier> selectedRaces;
290
-
291
- /**
292
- * Used to check if the first initial zoom to the mark markers was already done.
293
- */
294
- private boolean mapFirstZoomDone = false;
295
-
296
- private final Timer timer;
297
-
298
- private RaceTimesInfoDTO lastRaceTimesInfo;
299
-
300
- private InfoWindow lastInfoWindow = null;
301
-
302
- /**
303
- * RPC calls may receive responses out of order if there are multiple calls in-flight at the same time. If the time
304
- * slider is moved quickly it generates many requests for boat positions quickly after each other. Sometimes,
305
- * responses for requests send later may return before the responses to all earlier requests have been received and
306
- * processed. This counter is used to number the requests. When processing of a response for a later request has
307
- * already begun, responses to earlier requests will be ignored.
308
- */
309
- private int boatPositionRequestIDCounter;
310
-
311
- /**
312
- * Corresponds to {@link #boatPositionRequestIDCounter}. As soon as the processing of a response for a request ID
313
- * begins, this attribute is set to the ID. A response won't be processed if a later response is already being
314
- * processed.
315
- */
316
- private int startedProcessingRequestID;
317
-
318
- private RaceMapImageManager raceMapImageManager;
319
-
320
- private final RaceMapSettings settings;
321
-
322
- private final StringMessages stringMessages;
323
-
324
- private boolean isMapInitialized;
325
-
326
- private Date lastTimeChangeBeforeInitialization;
327
-
328
- private int lastLegNumber;
329
-
330
- /**
331
- * The last quick ranks received from a call to {@link SailingServiceAsync#getQuickRanks(RaceIdentifier, Date, AsyncCallback)} upon
332
- * the last {@link #timeChanged(Date, Date)} event. Therefore, the ranks listed here correspond to the {@link #timer}'s time.
333
- */
334
- private LinkedHashMap<CompetitorDTO, QuickRankDTO> quickRanks;
335
-
336
- /**
337
- * Taken from {@link RaceMapDataDTO#competitorsInOrderOfWindwardDistanceTraveledWithOneBasedLegNumber}; tells the
338
- * windward distances traveled and the leg numbers in which the respective competitor is.
339
- */
340
- private LinkedHashMap<CompetitorDTO, Integer> competitorsInOrderOfWindwardDistanceTraveledWithOneBasedLegNumber;
341
-
342
-
343
- private final CombinedWindPanel combinedWindPanel;
344
-
345
- private final TrueNorthIndicatorPanel trueNorthIndicatorPanel;
346
-
347
- private final AsyncActionsExecutor asyncActionsExecutor;
348
-
349
- /**
350
- * The map bounds as last received by map callbacks; used to determine whether to suppress the boat animation during zoom/pan
351
- */
352
- private LatLngBounds currentMapBounds; // bounds to which bounds-changed-handler compares
353
- private int currentZoomLevel; // zoom-level to which bounds-changed-handler compares
354
-
355
- private boolean autoZoomIn = false; // flags auto-zoom-in in progress
356
- private boolean autoZoomOut = false; // flags auto-zoom-out in progress
357
- private int autoZoomLevel; // zoom-level to which auto-zoom-in/-out is zooming
358
- LatLngBounds autoZoomLatLngBounds; // bounds to which auto-zoom-in/-out is panning&zooming
359
-
360
- private RaceSimulationOverlay simulationOverlay;
361
- private WindStreamletsRaceboardOverlay streamletOverlay;
362
- private final boolean showViewStreamlets;
363
- private final boolean showViewStreamletColors;
364
- private final boolean showViewSimulation;
365
-
366
- private static final String GET_POLAR_CATEGORY = "getPolar";
367
-
368
- /**
369
- * Tells about the availability of polar / VPP data for this race. If available, the simulation feature can be
370
- * offered to the user.
371
- */
372
- private boolean hasPolar;
373
-
374
- private final RegattaAndRaceIdentifier raceIdentifier;
375
-
376
- /**
377
- * When the user requests wind-up display this may happen at a point where no mark positions are known or when
378
- * no wind direction is known yet. In this case, this flag will be set, and when wind information or course mark
379
- * positions are received later, this flag is checked, and if set, a {@link #updateCoordinateSystemFromSettings()}
380
- * call is issued to make sure that the user's request for a new coordinate system is honored.
381
- */
382
- private boolean requiresCoordinateSystemUpdateWhenCoursePositionAndWindDirectionIsKnown;
383
-
384
- private final boolean showMapControls;
385
-
386
- /**
387
- * Tells whether currently an auto-zoom is in progress; this is used particularly to keep the smooth CSS boat transitions
388
- * active while auto-zooming whereas stopping them seems the better option for manual zooms.
389
- */
390
- private boolean autoZoomInProgress;
391
-
392
- /**
393
- * Tells whether currently an orientation change is in progress; this is required handle map events during the configuration of the map
394
- * during an orientation change.
395
- */
396
- private boolean orientationChangeInProgress;
397
-
398
- private final NumberFormat numberFormatOneDecimal = NumberFormat.getFormat("0.0");
399
-
400
- public RaceMap(SailingServiceAsync sailingService, AsyncActionsExecutor asyncActionsExecutor,
401
- ErrorReporter errorReporter, Timer timer, CompetitorSelectionProvider competitorSelection, StringMessages stringMessages,
402
- boolean showMapControls, boolean showViewStreamlets, boolean showViewStreamletColors, boolean showViewSimulation,
403
- RegattaAndRaceIdentifier raceIdentifier, CombinedWindPanelStyle combinedWindPanelStyle, boolean showHeaderPanel) {
404
- this.setSize("100%", "100%");
405
- this.showMapControls = showMapControls;
406
- this.stringMessages = stringMessages;
407
- this.sailingService = sailingService;
408
- this.raceIdentifier = raceIdentifier;
409
- this.asyncActionsExecutor = asyncActionsExecutor;
410
- this.errorReporter = errorReporter;
411
- this.timer = timer;
412
- timer.addTimeListener(this);
413
- raceMapImageManager = new RaceMapImageManager();
414
- markDTOs = new HashMap<String, MarkDTO>();
415
- courseSidelines = new HashMap<>();
416
- courseMiddleLines = new HashMap<>();
417
- infoOverlaysForLinesForCourseGeometry = new HashMap<>();
418
- boatOverlays = new HashMap<CompetitorDTO, BoatOverlay>();
419
- competitorInfoOverlays = new HashMap<CompetitorDTO, CompetitorInfoOverlay>();
420
- windSensorOverlays = new HashMap<WindSource, WindSensorOverlay>();
421
- courseMarkOverlays = new HashMap<String, CourseMarkOverlay>();
422
- this.competitorSelection = competitorSelection;
423
- competitorSelection.addCompetitorSelectionChangeListener(this);
424
- settings = new RaceMapSettings();
425
- coordinateSystem = new DelegateCoordinateSystem(new IdentityCoordinateSystem());
426
- fixesAndTails = new FixesAndTails(coordinateSystem);
427
- updateCoordinateSystemFromSettings();
428
- lastTimeChangeBeforeInitialization = null;
429
- isMapInitialized = false;
430
- this.showViewStreamlets = showViewStreamlets;
431
- this.showViewStreamletColors = showViewStreamletColors;
432
- this.showViewSimulation = showViewSimulation;
433
- this.hasPolar = false;
434
- headerPanel = new FlowPanel();
435
- headerPanel.setStyleName("RaceMap-HeaderPanel");
436
- panelForLeftHeaderLabels = new AbsolutePanel();
437
- panelForRightHeaderLabels = new AbsolutePanel();
438
- initializeData(showMapControls, showHeaderPanel);
439
- combinedWindPanel = new CombinedWindPanel(raceMapImageManager, combinedWindPanelStyle, stringMessages, coordinateSystem);
440
- combinedWindPanel.setVisible(false);
441
- trueNorthIndicatorPanel = new TrueNorthIndicatorPanel(this, raceMapImageManager, combinedWindPanelStyle, stringMessages, coordinateSystem);
442
- trueNorthIndicatorPanel.setVisible(true);
443
- orientationChangeInProgress = false;
444
- }
445
-
446
- /**
447
- * The {@link WindDTO#dampenedTrueWindFromDeg} direction if {@link #lastCombinedWindTrackInfoDTO} has a
448
- * {@link WindSourceType#COMBINED} source which has at least one fix recorded; <code>null</code> otherwise.
449
- */
450
- private Bearing getLastCombinedTrueWindFromDirection() {
451
- if (lastCombinedWindTrackInfoDTO != null) {
452
- for (Entry<WindSource, WindTrackInfoDTO> e : lastCombinedWindTrackInfoDTO.windTrackInfoByWindSource.entrySet()) {
453
- if (e.getKey().getType() == WindSourceType.COMBINED) {
454
- final List<WindDTO> windFixes = e.getValue().windFixes;
455
- if (!windFixes.isEmpty()) {
456
- return new DegreeBearingImpl(windFixes.get(0).dampenedTrueWindFromDeg);
457
- }
458
- }
459
- }
460
- }
461
- return null;
462
- }
463
-
464
- private void updateCoordinateSystemFromSettings() {
465
- final MapOptions mapOptions;
466
- orientationChangeInProgress = true;
467
- if (getSettings().isWindUp()) {
468
- final Position centerOfCourse = getCenterOfCourse();
469
- if (centerOfCourse != null) {
470
- final Bearing lastCombinedTrueWindFromDirection = getLastCombinedTrueWindFromDirection();
471
- if (lastCombinedTrueWindFromDirection != null) {
472
- // new equator shall point 90deg right of the "from" wind direction to make wind come from top of map
473
- coordinateSystem.setCoordinateSystem(new RotateAndTranslateCoordinateSystem(centerOfCourse,
474
- lastCombinedTrueWindFromDirection.add(new DegreeBearingImpl(90))));
475
- if (map != null) {
476
- mapOptions = getMapOptions(showMapControls, /* wind-up */ true);
477
- } else {
478
- mapOptions = null;
479
- }
480
- requiresCoordinateSystemUpdateWhenCoursePositionAndWindDirectionIsKnown = false;
481
- } else {
482
- // register callback in case center of course and wind info becomes known
483
- requiresCoordinateSystemUpdateWhenCoursePositionAndWindDirectionIsKnown = true;
484
- mapOptions = null;
485
- }
486
- } else {
487
- // register callback in case center of course and wind info becomes known
488
- requiresCoordinateSystemUpdateWhenCoursePositionAndWindDirectionIsKnown = true;
489
- mapOptions = null;
490
- }
491
- } else {
492
- if (map != null) {
493
- mapOptions = getMapOptions(showMapControls, /* wind-up */ false);
494
- } else {
495
- mapOptions = null;
496
- }
497
- coordinateSystem.setCoordinateSystem(new IdentityCoordinateSystem());
498
- }
499
- if (mapOptions != null) { // if no coordinate system change happened that affects an existing map, don't redraw
500
- fixesAndTails.clearTails();
501
- redraw();
502
- // zooming and setting options while the event loop is still working doesn't work reliably; defer until event loop returns
503
- Scheduler.get().scheduleDeferred(new ScheduledCommand() {
504
- @Override
505
- public void execute() {
506
- if (map != null) {
507
- map.setOptions(mapOptions);
508
- // ensure zooming to what the settings tell, or defaults if what the settings tell isn't possible right now
509
- mapFirstZoomDone = false;
510
- trueNorthIndicatorPanel.redraw();
511
- orientationChangeInProgress = false;
512
- }
513
- }
514
- });
515
- }
516
- }
517
-
518
- private void loadMapsAPIV3(final boolean showMapControls, final boolean showHeaderPanel) {
519
- boolean sensor = true;
520
-
521
- // load all the libs for use in the maps
522
- ArrayList<LoadLibrary> loadLibraries = new ArrayList<LoadApi.LoadLibrary>();
523
- loadLibraries.add(LoadLibrary.DRAWING);
524
- loadLibraries.add(LoadLibrary.GEOMETRY);
525
-
526
- Runnable onLoad = new Runnable() {
527
- @Override
528
- public void run() {
529
- MapOptions mapOptions = getMapOptions(showMapControls, /* wind up */ false);
530
- map = new MapWidget(mapOptions);
531
- RaceMap.this.add(map, 0, 0);
532
- if (showHeaderPanel) {
533
- Image sapLogo = createSAPLogo();
534
- RaceMap.this.add(sapLogo);
535
- }
536
- map.setControls(ControlPosition.LEFT_TOP, combinedWindPanel);
537
- combinedWindPanel.getParent().addStyleName("CombinedWindPanelParentDiv");
538
- map.setControls(ControlPosition.LEFT_TOP, trueNorthIndicatorPanel);
539
- trueNorthIndicatorPanel.getParent().addStyleName("TrueNorthIndicatorPanelParentDiv");
540
-
541
- RaceMap.this.raceMapImageManager.loadMapIcons(map);
542
- map.setSize("100%", "100%");
543
- map.addZoomChangeHandler(new ZoomChangeMapHandler() {
544
- @Override
545
- public void onEvent(ZoomChangeMapEvent event) {
546
- if (!autoZoomIn && !autoZoomOut && !orientationChangeInProgress) {
547
- // stop automatic zoom after a manual zoom event; automatic zoom in zoomMapToNewBounds will restore old settings
548
- final List<RaceMapZoomSettings.ZoomTypes> emptyList = Collections.emptyList();
549
- settings.getZoomSettings().setTypesToConsiderOnZoom(emptyList);
550
- }
551
- // TODO bug489 when in wind-up mode, avoid zooming out too far; perhaps zoom back in if zoomed out too far
552
- }
553
- });
554
- map.addDragEndHandler(new DragEndMapHandler() {
555
- @Override
556
- public void onEvent(DragEndMapEvent event) {
557
- // stop automatic zoom after a manual drag event
558
- autoZoomIn = false;
559
- autoZoomOut = false;
560
- final List<RaceMapZoomSettings.ZoomTypes> emptyList = Collections.emptyList();
561
- settings.getZoomSettings().setTypesToConsiderOnZoom(emptyList);
562
- }
563
- });
564
- map.addIdleHandler(new IdleMapHandler() {
565
- @Override
566
- public void onEvent(IdleMapEvent event) {
567
- // the "idle"-event is raised at the end of map-animations
568
- if (autoZoomIn) {
569
- // finalize zoom-in that was started with panTo() in zoomMapToNewBounds()
570
- map.setZoom(autoZoomLevel);
571
- autoZoomIn = false;
572
- }
573
- if (autoZoomOut) {
574
- // finalize zoom-out that was started with setZoom() in zoomMapToNewBounds()
575
- map.panTo(autoZoomLatLngBounds.getCenter());
576
- autoZoomOut = false;
577
- }
578
- }
579
- });
580
- map.addBoundsChangeHandler(new BoundsChangeMapHandler() {
581
- @Override
582
- public void onEvent(BoundsChangeMapEvent event) {
583
- int newZoomLevel = map.getZoom();
584
- if (!isAutoZoomInProgress() && (newZoomLevel != currentZoomLevel)) {
585
- removeTransitions();
586
- }
587
- if ((streamletOverlay != null) && !map.getBounds().equals(currentMapBounds)) {
588
- streamletOverlay.onBoundsChanged(newZoomLevel != currentZoomLevel);
589
- }
590
- if ((simulationOverlay != null) && !map.getBounds().equals(currentMapBounds)) {
591
- simulationOverlay.onBoundsChanged(newZoomLevel != currentZoomLevel);
592
- }
593
- currentMapBounds = map.getBounds();
594
- currentZoomLevel = newZoomLevel;
595
- headerPanel.getElement().getStyle().setWidth(map.getOffsetWidth(), Unit.PX);
596
- }
597
- });
598
-
599
- // If there was a time change before the API was loaded, reset the time
600
- if (lastTimeChangeBeforeInitialization != null) {
601
- timeChanged(lastTimeChangeBeforeInitialization, null);
602
- lastTimeChangeBeforeInitialization = null;
603
- }
604
- // Initialize streamlet canvas for wind visualization; it shouldn't be doing anything unless it's visible
605
- streamletOverlay = new WindStreamletsRaceboardOverlay(getMap(), /* zIndex */ 0,
606
- timer, raceIdentifier, sailingService, asyncActionsExecutor, stringMessages, coordinateSystem);
607
- streamletOverlay.addToMap();
608
- if (showViewStreamlets) {
609
- streamletOverlay.setColors(showViewStreamletColors);
610
- streamletOverlay.setVisible(true);
611
- }
612
-
613
- if (showViewSimulation) {
614
- // determine availability of polar diagram
615
- setHasPolar();
616
- // initialize simulation canvas
617
- simulationOverlay = new RaceSimulationOverlay(getMap(), /* zIndex */ 0, raceIdentifier, sailingService, stringMessages, asyncActionsExecutor, coordinateSystem);
618
- simulationOverlay.addToMap();
619
- simulationOverlay.setVisible(false);
620
- }
621
- if (showHeaderPanel) {
622
- createHeaderPanel(map);
623
- }
624
- createSettingsButton(map);
625
-
626
- // Data has been initialized
627
- RaceMap.this.isMapInitialized = true;
628
- RaceMap.this.redraw();
629
- trueNorthIndicatorPanel.redraw();
630
- showAdditionalControls(map);
631
- }
632
- };
633
- LoadApi.go(onLoad, loadLibraries, sensor, "key="+GoogleMapAPIKey.V3_APIKey);
634
- }
635
-
636
- /**
637
- * Subclasses may define additional stuff to be shown on the map.
638
- */
639
- protected void showAdditionalControls(MapWidget map) {
640
- }
641
-
642
- private void setHasPolar() {
643
- GetPolarAction getPolar = new GetPolarAction(sailingService, raceIdentifier);
644
- asyncActionsExecutor.execute(getPolar, GET_POLAR_CATEGORY,
645
- new MarkedAsyncCallback<>(new AsyncCallback<Boolean>() {
646
- @Override
647
- public void onFailure(Throwable caught) {
648
- errorReporter.reportError(stringMessages.errorDeterminingPolarAvailability(
649
- raceIdentifier.getRaceName(), caught.getMessage()), /* silent */ true);
650
- }
651
-
652
- @Override
653
- public void onSuccess(Boolean result) {
654
- // store results
655
- hasPolar = result.booleanValue();
656
- }
657
- }));
658
-
659
- }
660
-
661
- /**
662
- * Creates a header panel where additional information can be displayed by using
663
- * {@link #getLeftHeaderPanel()} or {@link #getRightHeaderPanel()}.
664
- *
665
- * This panel is transparent and configured in such a way that it moves other controls
666
- * down by its height. To achieve the goal of not having added widgets transparent
667
- * this widget consists of two parts: First one is the transparent panel and the
668
- * second one is the panel for the controls. The controls then need to moved onto
669
- * the panel by using CSS.
670
- */
671
- private void createHeaderPanel(MapWidget map) {
672
- // we need a panel that does not have any transparency to have the
673
- // labels shown in the right color. This panel also needs to have
674
- // a higher z-index than other elements on the map
675
- map.setControls(ControlPosition.TOP_LEFT, panelForLeftHeaderLabels);
676
- panelForLeftHeaderLabels.getElement().getParentElement().getStyle().setProperty("zIndex", "1");
677
- panelForLeftHeaderLabels.getElement().getStyle().setProperty("overflow", "visible");
678
- add(panelForRightHeaderLabels);
679
- panelForRightHeaderLabels.getElement().getStyle().setProperty("zIndex", "1");
680
- panelForRightHeaderLabels.getElement().getStyle().setProperty("overflow", "visible");
681
- // need to initialize size before css kicks in to make sure
682
- // that controls get positioned right
683
- headerPanel.getElement().getStyle().setHeight(60, Unit.PX);
684
- headerPanel.getElement().getStyle().setWidth(map.getOffsetWidth(), Unit.PX);
685
- // some sort of hack: not positioning TOP_LEFT because then the
686
- // controls at RIGHT would not get the correct top setting
687
- map.setControls(ControlPosition.TOP_RIGHT, headerPanel);
688
- }
689
-
690
- private void createSettingsButton(MapWidget map) {
691
- final Component<RaceMapSettings> component = this;
692
- Button settingsButton = new Button();
693
- settingsButton.setStyleName("gwt-MapSettingsButton");
694
- settingsButton.setTitle(stringMessages.settings());
695
- settingsButton.addClickHandler(new ClickHandler() {
696
- @Override
697
- public void onClick(ClickEvent event) {
698
- new SettingsDialog<RaceMapSettings>(component, stringMessages).show();
699
- }
700
- });
701
- map.setControls(ControlPosition.RIGHT_TOP, settingsButton);
702
- }
703
-
704
- private void removeTransitions() {
705
- // remove the canvas animations for boats
706
- for (BoatOverlay boatOverlay : RaceMap.this.getBoatOverlays().values()) {
707
- boatOverlay.removeCanvasPositionAndRotationTransition();
708
- }
709
- // remove the canvas animations for the info overlays of the selected boats
710
- for (CompetitorInfoOverlay infoOverlay : competitorInfoOverlays.values()) {
711
- infoOverlay.removeCanvasPositionAndRotationTransition();
712
- }
713
- }
714
-
715
- public void redraw() {
716
- timeChanged(timer.getTime(), null);
717
- }
718
-
719
- Map<CompetitorDTO, BoatOverlay> getBoatOverlays() {
720
- return Collections.unmodifiableMap(boatOverlays);
721
- }
722
-
723
- public MapWidget getMap() {
724
- return map;
725
- }
726
-
727
- /**
728
- * @return the Panel where labels or other controls for the header can be positioned
729
- */
730
- public AbsolutePanel getLeftHeaderPanel() {
731
- return panelForLeftHeaderLabels;
732
- }
733
-
734
- public AbsolutePanel getRightHeaderPanel() {
735
- return panelForRightHeaderLabels;
736
- }
737
-
738
- @Override
739
- public void onRaceSelectionChange(List<RegattaAndRaceIdentifier> selectedRaces) {
740
- mapFirstZoomDone = false;
741
- // TODO bug 494: reset zoom settings to user preferences
742
- this.selectedRaces = selectedRaces;
743
- }
744
-
745
- @Override
746
- public void raceTimesInfosReceived(Map<RegattaAndRaceIdentifier, RaceTimesInfoDTO> raceTimesInfos, long clientTimeWhenRequestWasSent, Date serverTimeDuringRequest, long clientTimeWhenResponseWasReceived) {
747
- timer.adjustClientServerOffset(clientTimeWhenRequestWasSent, serverTimeDuringRequest, clientTimeWhenResponseWasReceived);
748
- this.lastRaceTimesInfo = raceTimesInfos.get(selectedRaces.get(0));
749
- }
750
-
751
- /**
752
- * In {@link PlayModes#Live live mode}, when {@link #loadCompleteLeaderboard(Date) loading the leaderboard contents}, <code>null</code>
753
- * is used as time point. The condition for this is encapsulated in this method so others can find out. For example, when a time change
754
- * is signaled due to local offset / delay adjustments, no additional call to {@link #loadCompleteLeaderboard(Date)} would be required
755
- * as <code>null</code> will be passed in any case, not being affected by local time offsets.
756
- */
757
- private boolean useNullAsTimePoint() {
758
- return timer.getPlayMode() == PlayModes.Live;
759
- }
760
-
761
- @Override
762
- public void timeChanged(final Date newTime, final Date oldTime) {
763
- if (newTime != null && isMapInitialized) {
764
- if (selectedRaces != null && !selectedRaces.isEmpty()) {
765
- RegattaAndRaceIdentifier race = selectedRaces.get(selectedRaces.size() - 1);
766
- final Iterable<CompetitorDTO> competitorsToShow = getCompetitorsToShow();
767
-
768
- if (race != null) {
769
- final com.sap.sse.common.Util.Triple<Map<CompetitorDTO, Date>, Map<CompetitorDTO, Date>, Map<CompetitorDTO, Boolean>> fromAndToAndOverlap =
770
- fixesAndTails.computeFromAndTo(newTime, competitorsToShow, settings.getEffectiveTailLengthInMilliseconds());
771
- int requestID = ++boatPositionRequestIDCounter;
772
- // For those competitors for which the tails don't overlap (and therefore will be replaced by the new tail coming from the server)
773
- // we expect some potential delay in computing the full tail. Therefore, in those cases we fire two requests: one fetching only the
774
- // boat positions at newTime with zero tail length; and another one fetching everything else.
775
- GetRaceMapDataAction getRaceMapDataForAllOverlappingAndTipsOfNonOverlapping = getRaceMapDataForAllOverlappingAndTipsOfNonOverlapping(fromAndToAndOverlap, race, newTime);
776
- if (getRaceMapDataForAllOverlappingAndTipsOfNonOverlapping != null) {
777
- asyncActionsExecutor.execute(getRaceMapDataForAllOverlappingAndTipsOfNonOverlapping, GET_RACE_MAP_DATA_CATEGORY,
778
- getRaceMapDataCallback(oldTime, newTime, fromAndToAndOverlap.getC(), competitorsToShow, requestID));
779
- requestID = ++boatPositionRequestIDCounter;
780
- }
781
- // next, do the full thing; being the later call, if request throttling kicks in, the later call
782
- // supersedes the earlier call which may get dropped then
783
- GetRaceMapDataAction getRaceMapDataAction = new GetRaceMapDataAction(sailingService, competitorSelection.getAllCompetitors(), race,
784
- useNullAsTimePoint() ? null : newTime, fromAndToAndOverlap.getA(), fromAndToAndOverlap.getB(), /* extrapolate */ true, (settings.isShowSimulationOverlay() ? simulationOverlay.getLegIdentifier() : null));
785
- asyncActionsExecutor.execute(getRaceMapDataAction, GET_RACE_MAP_DATA_CATEGORY,
786
- getRaceMapDataCallback(oldTime, newTime, fromAndToAndOverlap.getC(), competitorsToShow, requestID));
787
-
788
- // draw the wind into the map, get the combined wind
789
- List<String> windSourceTypeNames = new ArrayList<String>();
790
- windSourceTypeNames.add(WindSourceType.EXPEDITION.name());
791
- windSourceTypeNames.add(WindSourceType.COMBINED.name());
792
- GetWindInfoAction getWindInfoAction = new GetWindInfoAction(sailingService, race, newTime, 1000L, 1, windSourceTypeNames,
793
- /* onlyUpToNewestEvent==false means get us any data we can get by a best effort */ false);
794
- asyncActionsExecutor.execute(getWindInfoAction, GET_WIND_DATA_CATEGORY, new AsyncCallback<WindInfoForRaceDTO>() {
795
- @Override
796
- public void onFailure(Throwable caught) {
797
- errorReporter.reportError("Error obtaining wind information: " + caught.getMessage(), true /*silentMode */);
798
- }
799
-
800
- @Override
801
- public void onSuccess(WindInfoForRaceDTO windInfo) {
802
- List<com.sap.sse.common.Util.Pair<WindSource, WindTrackInfoDTO>> windSourcesToShow = new ArrayList<com.sap.sse.common.Util.Pair<WindSource, WindTrackInfoDTO>>();
803
- if (windInfo != null) {
804
- lastCombinedWindTrackInfoDTO = windInfo;
805
- showAdvantageLine(competitorsToShow, newTime);
806
- for (WindSource windSource : windInfo.windTrackInfoByWindSource.keySet()) {
807
- WindTrackInfoDTO windTrackInfoDTO = windInfo.windTrackInfoByWindSource.get(windSource);
808
- switch (windSource.getType()) {
809
- case EXPEDITION:
810
- // we filter out measured wind sources with vary low confidence
811
- if (windTrackInfoDTO.minWindConfidence > 0.0001) {
812
- windSourcesToShow.add(new com.sap.sse.common.Util.Pair<WindSource, WindTrackInfoDTO>(windSource, windTrackInfoDTO));
813
- }
814
- break;
815
- case COMBINED:
816
- showCombinedWindOnMap(windSource, windTrackInfoDTO);
817
- if (requiresCoordinateSystemUpdateWhenCoursePositionAndWindDirectionIsKnown) {
818
- updateCoordinateSystemFromSettings();
819
- }
820
- break;
821
- default:
822
- // Which wind sources are requested is defined in a list above this
823
- // action. So we throw here an exception to notice a missing source.
824
- throw new UnsupportedOperationException(
825
- "There is currently no support for the enum value '"
826
- + windSource.getType() + "' in this method.");
827
- }
828
- }
829
- }
830
- showWindSensorsOnMap(windSourcesToShow);
831
- }
832
- });
833
- }
834
- }
835
- }
836
- }
837
-
838
- /**
839
- * We assume that overlapping segments usually don't require a lot of loading time as the most typical case will be to update a longer
840
- * tail with a few new fixes that were received since the last time tick. Non-overlapping position requests typically occur for the
841
- * first request when no fix at all is known for the competitor yet, and when the user has radically moved the time slider to some
842
- * other time such that given the current tail length setting the new tail segment does not overlap with the old one, requiring a full
843
- * load of the entire tail data for that competitor.<p>
844
- *
845
- * For the non-overlapping requests, this method creates a separate request which only loads boat positions, quick ranks, sidelines and
846
- * mark positions for the zero-length interval at <code>newTime</code>, assuming that this will work fairly fast and in particular in
847
- * O(1) time regardless of tail length, compared to fetching the entire tail for all competitors.
848
- */
849
- private GetRaceMapDataAction getRaceMapDataForAllOverlappingAndTipsOfNonOverlapping(
850
- Triple<Map<CompetitorDTO, Date>, Map<CompetitorDTO, Date>, Map<CompetitorDTO, Boolean>> fromAndToAndOverlap,
851
- RegattaAndRaceIdentifier race, Date newTime) {
852
- Map<CompetitorDTO, Date> fromTimes = new HashMap<>();
853
- Map<CompetitorDTO, Date> toTimes = new HashMap<>();
854
- for (Map.Entry<CompetitorDTO, Boolean> e : fromAndToAndOverlap.getC().entrySet()) {
855
- if (!e.getValue()) {
856
- // no overlap; add competitor to request
857
- fromTimes.put(e.getKey(), newTime);
858
- toTimes.put(e.getKey(), newTime);
859
- }
860
- }
861
- final GetRaceMapDataAction result;
862
- if (!fromTimes.isEmpty()) {
863
- result = new GetRaceMapDataAction(sailingService, competitorSelection.getAllCompetitors(),
864
- race, useNullAsTimePoint() ? null : newTime, fromTimes, toTimes, /* extrapolate */true, (settings.isShowSimulationOverlay() ? simulationOverlay.getLegIdentifier() : null));
865
- } else {
866
- result = null;
867
- }
868
- return result;
869
- }
870
-
871
- private AsyncCallback<RaceMapDataDTO> getRaceMapDataCallback(
872
- final Date oldTime,
873
- final Date newTime,
874
- final Map<CompetitorDTO, Boolean> hasTailOverlapForCompetitor,
875
- final Iterable<CompetitorDTO> competitorsToShow, final int requestID) {
876
- return new AsyncCallback<RaceMapDataDTO>() {
877
- @Override
878
- public void onFailure(Throwable caught) {
879
- errorReporter.reportError("Error obtaining racemap data: " + caught.getMessage(), true /*silentMode */);
880
- }
881
-
882
- @Override
883
- public void onSuccess(RaceMapDataDTO raceMapDataDTO) {
884
- if (map != null && raceMapDataDTO != null) {
885
- quickRanks = raceMapDataDTO.quickRanks;
886
- competitorsInOrderOfWindwardDistanceTraveledWithOneBasedLegNumber =
887
- raceMapDataDTO.competitorsInOrderOfWindwardDistanceTraveledWithOneBasedLegNumber;
888
- if (showViewSimulation && settings.isShowSimulationOverlay()) {
889
- lastLegNumber = raceMapDataDTO.coursePositions.currentLegNumber;
890
- simulationOverlay.updateLeg(Math.max(lastLegNumber,1), /* clearCanvas */ false, raceMapDataDTO.simulationResultVersion);
891
- }
892
- // process response only if not received out of order
893
- if (startedProcessingRequestID < requestID) {
894
- startedProcessingRequestID = requestID;
895
- // Do boat specific actions
896
- Map<CompetitorDTO, List<GPSFixDTO>> boatData = raceMapDataDTO.boatPositions;
897
- long timeForPositionTransitionMillis = calculateTimeForPositionTransition(newTime, oldTime);
898
- fixesAndTails.updateFixes(boatData, hasTailOverlapForCompetitor, RaceMap.this, timeForPositionTransitionMillis);
899
- showBoatsOnMap(newTime, timeForPositionTransitionMillis, getCompetitorsToShow());
900
- showCompetitorInfoOnMap(newTime, timeForPositionTransitionMillis, competitorSelection.getSelectedFilteredCompetitors());
901
- if (douglasMarkers != null) {
902
- removeAllMarkDouglasPeuckerpoints();
903
- }
904
- if (maneuverMarkers != null) {
905
- removeAllManeuverMarkers();
906
- }
907
-
908
- // Do mark specific actions
909
- showCourseMarksOnMap(raceMapDataDTO.coursePositions);
910
- if (requiresCoordinateSystemUpdateWhenCoursePositionAndWindDirectionIsKnown) {
911
- updateCoordinateSystemFromSettings();
912
- }
913
- showCourseSidelinesOnMap(raceMapDataDTO.courseSidelines);
914
- showStartAndFinishAndCourseMiddleLines(raceMapDataDTO.coursePositions);
915
- showStartLineToFirstMarkTriangle(raceMapDataDTO.coursePositions);
916
- // even though the wind data is retrieved by a separate call, re-draw the advantage line because it needs to
917
- // adjust to new boat positions
918
- showAdvantageLine(competitorsToShow, newTime);
919
-
920
- // Rezoom the map
921
- LatLngBounds zoomToBounds = null;
922
- if (!settings.getZoomSettings().containsZoomType(ZoomTypes.NONE)) { // Auto zoom if setting is not manual
923
- zoomToBounds = settings.getZoomSettings().getNewBounds(RaceMap.this);
924
- if (zoomToBounds == null && !mapFirstZoomDone) {
925
- zoomToBounds = getDefaultZoomBounds(); // the user-specified zoom couldn't find what it was looking for; try defaults once
926
- }
927
- } else if (!mapFirstZoomDone) { // Zoom once to the marks if marks exist
928
- zoomToBounds = new CourseMarksBoundsCalculator().calculateNewBounds(RaceMap.this);
929
- if (zoomToBounds == null) {
930
- zoomToBounds = getDefaultZoomBounds(); // use default zoom, e.g.,
931
- }
932
- /*
933
- * Reset the mapZoomedOrPannedSinceLastRaceSelection: In spite of the fact that
934
- * the map was just zoomed to the bounds of the marks, it was not a zoom or pan
935
- * triggered by the user. As a consequence the
936
- * mapZoomedOrPannedSinceLastRaceSelection option has to reset again.
937
- */
938
- }
939
- zoomMapToNewBounds(zoomToBounds);
940
- mapFirstZoomDone = true;
941
- }
942
- } else {
943
- lastTimeChangeBeforeInitialization = newTime;
944
- }
945
- }
946
- };
947
- }
948
-
949
- private void showCourseSidelinesOnMap(List<SidelineDTO> sidelinesDTOs) {
950
- if (map != null && sidelinesDTOs != null ) {
951
- Map<SidelineDTO, Polygon> toRemoveSidelines = new HashMap<SidelineDTO, Polygon>(courseSidelines);
952
- for (SidelineDTO sidelineDTO : sidelinesDTOs) {
953
- if (sidelineDTO.getMarks().size() == 2) { // right now we only support sidelines with 2 marks
954
- Polygon sideline = courseSidelines.get(sidelineDTO);
955
- LatLng[] sidelinePoints = new LatLng[sidelineDTO.getMarks().size()];
956
- int i=0;
957
- for (MarkDTO sidelineMark : sidelineDTO.getMarks()) {
958
- sidelinePoints[i] = coordinateSystem.toLatLng(sidelineMark.position);
959
- i++;
960
- }
961
- if (sideline == null) {
962
- PolygonOptions options = PolygonOptions.newInstance();
963
- options.setClickable(true);
964
- options.setStrokeColor("#0000FF");
965
- options.setStrokeWeight(1);
966
- options.setStrokeOpacity(1.0);
967
- options.setFillColor(null);
968
- options.setFillOpacity(1.0);
969
-
970
- sideline = Polygon.newInstance(options);
971
- MVCArray<LatLng> pointsAsArray = MVCArray.newInstance(sidelinePoints);
972
- sideline.setPath(pointsAsArray);
973
-
974
- sideline.addMouseOverHandler(new MouseOverMapHandler() {
975
- @Override
976
- public void onEvent(MouseOverMapEvent event) {
977
- map.setTitle(stringMessages.sideline());
978
- }
979
- });
980
- sideline.addMouseOutMoveHandler(new MouseOutMapHandler() {
981
- @Override
982
- public void onEvent(MouseOutMapEvent event) {
983
- map.setTitle("");
984
- }
985
- });
986
- courseSidelines.put(sidelineDTO, sideline);
987
- sideline.setMap(map);
988
- } else {
989
- sideline.getPath().removeAt(1);
990
- sideline.getPath().removeAt(0);
991
- sideline.getPath().insertAt(0, sidelinePoints[0]);
992
- sideline.getPath().insertAt(1, sidelinePoints[1]);
993
- toRemoveSidelines.remove(sidelineDTO);
994
- }
995
- }
996
- }
997
- for (SidelineDTO toRemoveSideline : toRemoveSidelines.keySet()) {
998
- Polygon sideline = courseSidelines.remove(toRemoveSideline);
999
- sideline.setMap(null);
1000
- }
1001
- }
1002
- }
1003
-
1004
- private void showCourseMarksOnMap(CoursePositionsDTO courseDTO) {
1005
- if (map != null && courseDTO != null) {
1006
- WaypointDTO endWaypointForCurrentLegNumber = null;
1007
- if(courseDTO.currentLegNumber > 0 && courseDTO.currentLegNumber <= courseDTO.totalLegsCount) {
1008
- endWaypointForCurrentLegNumber = courseDTO.getEndWaypointForLegNumber(courseDTO.currentLegNumber);
1009
- }
1010
-
1011
- Map<String, CourseMarkOverlay> toRemoveCourseMarks = new HashMap<String, CourseMarkOverlay>(courseMarkOverlays);
1012
- if (courseDTO.marks != null) {
1013
- for (MarkDTO markDTO : courseDTO.marks) {
1014
- boolean isSelected = false;
1015
- if (endWaypointForCurrentLegNumber != null && Util.contains(endWaypointForCurrentLegNumber.controlPoint.getMarks(), markDTO)) {
1016
- isSelected = true;
1017
- }
1018
- CourseMarkOverlay courseMarkOverlay = courseMarkOverlays.get(markDTO.getName());
1019
- if (courseMarkOverlay == null) {
1020
- courseMarkOverlay = createCourseMarkOverlay(RaceMapOverlaysZIndexes.COURSEMARK_ZINDEX, markDTO);
1021
- courseMarkOverlay.setShowBuoyZone(settings.getHelpLinesSettings().isVisible(HelpLineTypes.BUOYZONE));
1022
- courseMarkOverlay.setBuoyZoneRadiusInMeter(settings.getBuoyZoneRadiusInMeters());
1023
- courseMarkOverlay.setSelected(isSelected);
1024
- courseMarkOverlays.put(markDTO.getName(), courseMarkOverlay);
1025
- markDTOs.put(markDTO.getName(), markDTO);
1026
- courseMarkOverlay.addToMap();
1027
- } else {
1028
- courseMarkOverlay.setMarkPosition(markDTO.position);
1029
- courseMarkOverlay.setShowBuoyZone(settings.getHelpLinesSettings().isVisible(HelpLineTypes.BUOYZONE));
1030
- courseMarkOverlay.setBuoyZoneRadiusInMeter(settings.getBuoyZoneRadiusInMeters());
1031
- courseMarkOverlay.setSelected(isSelected);
1032
- courseMarkOverlay.draw();
1033
- toRemoveCourseMarks.remove(markDTO.getName());
1034
- }
1035
- }
1036
- }
1037
- for (String toRemoveMarkName : toRemoveCourseMarks.keySet()) {
1038
- CourseMarkOverlay removedOverlay = courseMarkOverlays.remove(toRemoveMarkName);
1039
- if(removedOverlay != null) {
1040
- removedOverlay.removeFromMap();
1041
- }
1042
- }
1043
- }
1044
- }
1045
-
1046
- /**
1047
- * Based on the mark positions in {@link #courseMarkOverlays}' values this method determines the center of gravity of these marks'
1048
- * {@link CourseMarkOverlay#getPosition() positions}.
1049
- */
1050
- private Position getCenterOfCourse() {
1051
- ScalablePosition center = null;
1052
- int count = 0;
1053
- for (CourseMarkOverlay markOverlay : courseMarkOverlays.values()) {
1054
- ScalablePosition markPosition = new ScalablePosition(markOverlay.getPosition());
1055
- if (center == null) {
1056
- center = markPosition;
1057
- } else {
1058
- center.add(markPosition);
1059
- }
1060
- count++;
1061
- }
1062
- return center == null ? null : center.divide(count);
1063
- }
1064
-
1065
- private void showCombinedWindOnMap(WindSource windSource, WindTrackInfoDTO windTrackInfoDTO) {
1066
- if (map != null) {
1067
- combinedWindPanel.setWindInfo(windTrackInfoDTO, windSource);
1068
- combinedWindPanel.redraw();
1069
- }
1070
- }
1071
-
1072
- private void showWindSensorsOnMap(List<com.sap.sse.common.Util.Pair<WindSource, WindTrackInfoDTO>> windSensorsList) {
1073
- if (map != null) {
1074
- Set<WindSource> toRemoveWindSources = new HashSet<WindSource>(windSensorOverlays.keySet());
1075
- for (com.sap.sse.common.Util.Pair<WindSource, WindTrackInfoDTO> windSourcePair : windSensorsList) {
1076
- WindSource windSource = windSourcePair.getA();
1077
- WindTrackInfoDTO windTrackInfoDTO = windSourcePair.getB();
1078
-
1079
- WindSensorOverlay windSensorOverlay = windSensorOverlays.get(windSource);
1080
- if (windSensorOverlay == null) {
1081
- windSensorOverlay = createWindSensorOverlay(RaceMapOverlaysZIndexes.WINDSENSOR_ZINDEX, windSource, windTrackInfoDTO);
1082
- windSensorOverlays.put(windSource, windSensorOverlay);
1083
- windSensorOverlay.addToMap();
1084
- } else {
1085
- windSensorOverlay.setWindInfo(windTrackInfoDTO, windSource);
1086
- windSensorOverlay.draw();
1087
- toRemoveWindSources.remove(windSource);
1088
- }
1089
- }
1090
- for (WindSource toRemoveWindSource : toRemoveWindSources) {
1091
- WindSensorOverlay removedWindSensorOverlay = windSensorOverlays.remove(toRemoveWindSource);
1092
- if (removedWindSensorOverlay != null) {
1093
- removedWindSensorOverlay.removeFromMap();
1094
- }
1095
- }
1096
- }
1097
- }
1098
-
1099
- private void showCompetitorInfoOnMap(final Date newTime, final long timeForPositionTransitionMillis, final Iterable<CompetitorDTO> competitorsToShow) {
1100
- if (map != null) {
1101
- if (settings.isShowSelectedCompetitorsInfo()) {
1102
- Set<CompetitorDTO> toRemoveCompetorInfoOverlays = new HashSet<CompetitorDTO>(
1103
- competitorInfoOverlays.keySet());
1104
- for (CompetitorDTO competitorDTO : competitorsToShow) {
1105
- if (fixesAndTails.hasFixesFor(competitorDTO)) {
1106
- GPSFixDTO lastBoatFix = getBoatFix(competitorDTO, newTime);
1107
- if (lastBoatFix != null) {
1108
- CompetitorInfoOverlay competitorInfoOverlay = competitorInfoOverlays.get(competitorDTO);
1109
- if (competitorInfoOverlay == null) {
1110
- competitorInfoOverlay = createCompetitorInfoOverlay(RaceMapOverlaysZIndexes.INFO_OVERLAY_ZINDEX, competitorDTO);
1111
- competitorInfoOverlays.put(competitorDTO, competitorInfoOverlay);
1112
- competitorInfoOverlay.setPosition(lastBoatFix.position, timeForPositionTransitionMillis);
1113
- competitorInfoOverlay.addToMap();
1114
- } else {
1115
- competitorInfoOverlay.setPosition(lastBoatFix.position, timeForPositionTransitionMillis);
1116
- competitorInfoOverlay.draw();
1117
- }
1118
- toRemoveCompetorInfoOverlays.remove(competitorDTO);
1119
- }
1120
- }
1121
- }
1122
- for (CompetitorDTO toRemoveCompetorDTO : toRemoveCompetorInfoOverlays) {
1123
- CompetitorInfoOverlay competitorInfoOverlay = competitorInfoOverlays.get(toRemoveCompetorDTO);
1124
- competitorInfoOverlay.removeFromMap();
1125
- competitorInfoOverlays.remove(toRemoveCompetorDTO);
1126
- }
1127
- } else {
1128
- // remove all overlays
1129
- for (CompetitorInfoOverlay competitorInfoOverlay : competitorInfoOverlays.values()) {
1130
- competitorInfoOverlay.removeFromMap();
1131
- }
1132
- competitorInfoOverlays.clear();
1133
- }
1134
- }
1135
- }
1136
-
1137
- private long calculateTimeForPositionTransition(final Date newTime, final Date oldTime) {
1138
- final long timeForPositionTransitionMillis;
1139
- boolean hasTimeJumped = oldTime != null && Math.abs(oldTime.getTime() - newTime.getTime()) > 3*timer.getRefreshInterval();
1140
- if (timer.getPlayState() == PlayStates.Playing && !hasTimeJumped) {
1141
- // choose 130% of the refresh interval as transition period to make it unlikely that the transition
1142
- // stops before the next update has been received
1143
- timeForPositionTransitionMillis = 1300 * timer.getRefreshInterval() / 1000;
1144
- } else {
1145
- timeForPositionTransitionMillis = -1; // -1 means 'no transition
1146
- }
1147
- return timeForPositionTransitionMillis;
1148
- }
1149
-
1150
- private void showBoatsOnMap(final Date newTime, final long timeForPositionTransitionMillis, final Iterable<CompetitorDTO> competitorsToShow) {
1151
- if (map != null) {
1152
- Date tailsFromTime = new Date(newTime.getTime() - settings.getEffectiveTailLengthInMilliseconds());
1153
- Date tailsToTime = newTime;
1154
- Set<CompetitorDTO> competitorDTOsOfUnusedTails = new HashSet<CompetitorDTO>(fixesAndTails.getCompetitorsWithTails());
1155
- Set<CompetitorDTO> competitorDTOsOfUnusedBoatCanvases = new HashSet<CompetitorDTO>(boatOverlays.keySet());
1156
- for (CompetitorDTO competitorDTO : competitorsToShow) {
1157
- if (fixesAndTails.hasFixesFor(competitorDTO)) {
1158
- Polyline tail = fixesAndTails.getTail(competitorDTO);
1159
- if (tail == null) {
1160
- tail = fixesAndTails.createTailAndUpdateIndices(competitorDTO, tailsFromTime, tailsToTime, this);
1161
- tail.setMap(map);
1162
- } else {
1163
- fixesAndTails.updateTail(tail, competitorDTO, tailsFromTime, tailsToTime,
1164
- (int) (timeForPositionTransitionMillis==-1?-1:timeForPositionTransitionMillis/2));
1165
- competitorDTOsOfUnusedTails.remove(competitorDTO);
1166
- PolylineOptions newOptions = createTailStyle(competitorDTO, displayHighlighted(competitorDTO));
1167
- tail.setOptions(newOptions);
1168
- }
1169
- boolean usedExistingBoatCanvas = updateBoatCanvasForCompetitor(competitorDTO, newTime, timeForPositionTransitionMillis);
1170
- if (usedExistingBoatCanvas) {
1171
- competitorDTOsOfUnusedBoatCanvases.remove(competitorDTO);
1172
- }
1173
- }
1174
- }
1175
- for (CompetitorDTO unusedBoatCanvasCompetitorDTO : competitorDTOsOfUnusedBoatCanvases) {
1176
- BoatOverlay boatCanvas = boatOverlays.get(unusedBoatCanvasCompetitorDTO);
1177
- boatCanvas.removeFromMap();
1178
- boatOverlays.remove(unusedBoatCanvasCompetitorDTO);
1179
- }
1180
- for (CompetitorDTO unusedTailCompetitorDTO : competitorDTOsOfUnusedTails) {
1181
- fixesAndTails.removeTail(unusedTailCompetitorDTO);
1182
- }
1183
- }
1184
- }
1185
-
1186
- /**
1187
- * This algorithm is limited to distances such that dlon < pi/2, i.e., those that extend around less than one
1188
- * quarter of the circumference of the earth in longitude. A completely general, but more complicated algorithm is
1189
- * necessary if greater distances are allowed.
1190
- */
1191
- public LatLng calculatePositionAlongRhumbline(LatLng position, double bearingDeg, double distanceInKm) {
1192
- double distianceRad = distanceInKm / 6371.0; // r = 6371 means earth's radius in km
1193
- double lat1 = position.getLatitude() / 180. * Math.PI;
1194
- double lon1 = position.getLongitude() / 180. * Math.PI;
1195
- double bearingRad = bearingDeg / 180. * Math.PI;
1196
- double lat2 = Math.asin(Math.sin(lat1) * Math.cos(distianceRad) +
1197
- Math.cos(lat1) * Math.sin(distianceRad) * Math.cos(bearingRad));
1198
- double lon2 = lon1 + Math.atan2(Math.sin(bearingRad)*Math.sin(distianceRad)*Math.cos(lat1),
1199
- Math.cos(distianceRad)-Math.sin(lat1)*Math.sin(lat2));
1200
- lon2 = (lon2+3*Math.PI) % (2*Math.PI) - Math.PI; // normalize to -180..+180�
1201
- // position is already in LatLng space, so no mapping through coordinateSystem is required here
1202
- return LatLng.newInstance(lat2 / Math.PI * 180., lon2 / Math.PI * 180.);
1203
- }
1204
-
1205
- /**
1206
- * Returns a pair whose first component is the leg number (one-based) of the competitor returned as the second component.
1207
- */
1208
- private com.sap.sse.common.Util.Pair<Integer, CompetitorDTO> getFarthestAheadVisibleCompetitorWithOneBasedLegNumber(
1209
- Iterable<CompetitorDTO> competitorsToShow) {
1210
- CompetitorDTO leadingCompetitorDTO = null;
1211
- int legOfLeaderCompetitor = -1;
1212
- // this only works because the quickRanks are sorted
1213
- for (Entry<CompetitorDTO, Integer> competitorsByWindwardDistanceTraveledAndOneBasedLegNumber :
1214
- competitorsInOrderOfWindwardDistanceTraveledWithOneBasedLegNumber.entrySet()) {
1215
- if (Util.contains(competitorsToShow, competitorsByWindwardDistanceTraveledAndOneBasedLegNumber.getKey()) &&
1216
- competitorsByWindwardDistanceTraveledAndOneBasedLegNumber.getValue() != null) {
1217
- leadingCompetitorDTO = competitorsByWindwardDistanceTraveledAndOneBasedLegNumber.getKey();
1218
- legOfLeaderCompetitor = competitorsByWindwardDistanceTraveledAndOneBasedLegNumber.getValue();
1219
- return new com.sap.sse.common.Util.Pair<Integer, CompetitorDTO>(legOfLeaderCompetitor, leadingCompetitorDTO);
1220
- }
1221
- }
1222
- return null;
1223
- }
1224
-
1225
- private void showAdvantageLine(Iterable<CompetitorDTO> competitorsToShow, Date date) {
1226
- if (map != null && lastRaceTimesInfo != null && quickRanks != null && lastCombinedWindTrackInfoDTO != null) {
1227
- boolean drawAdvantageLine = false;
1228
- if (settings.getHelpLinesSettings().isVisible(HelpLineTypes.ADVANTAGELINE)) {
1229
- // find competitor with highest rank
1230
- com.sap.sse.common.Util.Pair<Integer, CompetitorDTO> visibleLeaderInfo = getFarthestAheadVisibleCompetitorWithOneBasedLegNumber(competitorsToShow);
1231
- // the boat fix may be null; may mean that no positions were loaded yet for the leading visible boat;
1232
- // don't show anything
1233
- GPSFixDTO lastBoatFix = null;
1234
- boolean isVisibleLeaderInfoComplete = false;
1235
- boolean isLegTypeKnown = false;
1236
- WindTrackInfoDTO windDataForLegMiddle = null;
1237
- LegInfoDTO legInfoDTO = null;
1238
- if (visibleLeaderInfo != null
1239
- && visibleLeaderInfo.getA() > 0
1240
- && visibleLeaderInfo.getA() <= lastRaceTimesInfo.getLegInfos().size()
1241
- // get wind at middle of leg for leading visible competitor
1242
- && (windDataForLegMiddle = lastCombinedWindTrackInfoDTO
1243
- .getCombinedWindOnLegMiddle(visibleLeaderInfo.getA() - 1)) != null
1244
- && !windDataForLegMiddle.windFixes.isEmpty()) {
1245
- isVisibleLeaderInfoComplete = true;
1246
- legInfoDTO = lastRaceTimesInfo.getLegInfos().get(visibleLeaderInfo.getA() - 1);
1247
- if (legInfoDTO.legType != null) {
1248
- isLegTypeKnown = true;
1249
- }
1250
- lastBoatFix = getBoatFix(visibleLeaderInfo.getB(), date);
1251
- }
1252
- if (isVisibleLeaderInfoComplete && isLegTypeKnown && lastBoatFix != null && lastBoatFix.speedWithBearing != null) {
1253
- double advantageLineLengthInKm = 1.0; // TODO this should probably rather scale with the visible
1254
- // area of the map; bug 616
1255
- double distanceFromBoatPositionInKm = visibleLeaderInfo.getB().getBoatClass()
1256
- .getHullLengthInMeters() / 1000.; // one hull length
1257
- // implement and use Position.translateRhumb()
1258
- double bearingOfBoatInDeg = lastBoatFix.speedWithBearing.bearingInDegrees;
1259
- LatLng boatPosition = coordinateSystem.toLatLng(lastBoatFix.position);
1260
- LatLng posAheadOfFirstBoat = calculatePositionAlongRhumbline(boatPosition,
1261
- coordinateSystem.mapDegreeBearing(bearingOfBoatInDeg), distanceFromBoatPositionInKm);
1262
- final WindDTO windFix = windDataForLegMiddle.windFixes.get(0);
1263
- double bearingOfCombinedWindInDeg = windFix.trueWindBearingDeg;
1264
- double rotatedBearingDeg1 = 0.0;
1265
- double rotatedBearingDeg2 = 0.0;
1266
- switch (legInfoDTO.legType) {
1267
- case UPWIND:
1268
- case DOWNWIND: {
1269
- rotatedBearingDeg1 = bearingOfCombinedWindInDeg + 90.0;
1270
- if (rotatedBearingDeg1 >= 360.0) {
1271
- rotatedBearingDeg1 -= 360.0;
1272
- }
1273
- rotatedBearingDeg2 = bearingOfCombinedWindInDeg - 90.0;
1274
- if (rotatedBearingDeg2 < 0.0) {
1275
- rotatedBearingDeg2 += 360.0;
1276
- }
1277
- }
1278
- break;
1279
- case REACHING: {
1280
- rotatedBearingDeg1 = legInfoDTO.legBearingInDegrees + 90.0;
1281
- if (rotatedBearingDeg1 >= 360.0) {
1282
- rotatedBearingDeg1 -= 360.0;
1283
- }
1284
- rotatedBearingDeg2 = legInfoDTO.legBearingInDegrees - 90.0;
1285
- if (rotatedBearingDeg2 < 0.0) {
1286
- rotatedBearingDeg2 += 360.0;
1287
- }
1288
- }
1289
- break;
1290
- }
1291
- LatLng advantageLinePos1 = calculatePositionAlongRhumbline(posAheadOfFirstBoat,
1292
- coordinateSystem.mapDegreeBearing(rotatedBearingDeg1), advantageLineLengthInKm / 2.0);
1293
- LatLng advantageLinePos2 = calculatePositionAlongRhumbline(posAheadOfFirstBoat,
1294
- coordinateSystem.mapDegreeBearing(rotatedBearingDeg2), advantageLineLengthInKm / 2.0);
1295
- if (advantageLine == null) {
1296
- PolylineOptions options = PolylineOptions.newInstance();
1297
- options.setClickable(true);
1298
- options.setGeodesic(true);
1299
- options.setStrokeColor("#000000");
1300
- options.setStrokeWeight(1);
1301
- options.setStrokeOpacity(0.5);
1302
-
1303
- advantageLine = Polyline.newInstance(options);
1304
- MVCArray<LatLng> pointsAsArray = MVCArray.newInstance();
1305
- pointsAsArray.insertAt(0, advantageLinePos1);
1306
- pointsAsArray.insertAt(1, advantageLinePos2);
1307
- advantageLine.setPath(pointsAsArray);
1308
-
1309
- advantageLineMouseOverHandler = new AdvantageLineMouseOverMapHandler(
1310
- bearingOfCombinedWindInDeg, new Date(windFix.measureTimepoint));
1311
- advantageLine.addMouseOverHandler(advantageLineMouseOverHandler);
1312
- advantageLine.addMouseOutMoveHandler(new MouseOutMapHandler() {
1313
- @Override
1314
- public void onEvent(MouseOutMapEvent event) {
1315
- map.setTitle("");
1316
- }
1317
- });
1318
- advantageLine.setMap(map);
1319
- } else {
1320
- advantageLine.getPath().removeAt(1);
1321
- advantageLine.getPath().removeAt(0);
1322
- advantageLine.getPath().insertAt(0, advantageLinePos1);
1323
- advantageLine.getPath().insertAt(1, advantageLinePos2);
1324
- advantageLineMouseOverHandler.setTrueWindBearing(bearingOfCombinedWindInDeg);
1325
- advantageLineMouseOverHandler.setDate(new Date(windFix.measureTimepoint));
1326
- }
1327
- drawAdvantageLine = true;
1328
- }
1329
- }
1330
- if (!drawAdvantageLine) {
1331
- if (advantageLine != null) {
1332
- advantageLine.setMap(null);
1333
- advantageLine = null;
1334
- }
1335
- }
1336
- }
1337
- }
1338
-
1339
- private final StringBuilder windwardStartLineMarkToFirstMarkLineText = new StringBuilder();
1340
- private final StringBuilder leewardStartLineMarkToFirstMarkLineText = new StringBuilder();
1341
-
1342
- private void showStartLineToFirstMarkTriangle(final CoursePositionsDTO courseDTO){
1343
- final List<Position> startMarkPositions = courseDTO.getStartMarkPositions();
1344
- final Position windwardStartLinePosition = startMarkPositions.get(0);
1345
- final Position leewardStartLinePosition = startMarkPositions.get(1);
1346
- final Position firstMarkPosition = courseDTO.waypointPositions.get(1);
1347
- windwardStartLineMarkToFirstMarkLineText.replace(0, windwardStartLineMarkToFirstMarkLineText.length(),
1348
- stringMessages.startLineToFirstMarkTriangle(numberFormatOneDecimal
1349
- .format(windwardStartLinePosition.getDistance(firstMarkPosition)
1350
- .getMeters())));
1351
- leewardStartLineMarkToFirstMarkLineText.replace(0, leewardStartLineMarkToFirstMarkLineText.length(),
1352
- stringMessages.startLineToFirstMarkTriangle(numberFormatOneDecimal
1353
- .format(leewardStartLinePosition.getDistance(firstMarkPosition)
1354
- .getMeters())));
1355
- final LineInfoProvider windwardStartLineMarkToFirstMarkLineInfoProvider = new LineInfoProvider() {
1356
- @Override
1357
- public String getLineInfo() {
1358
- return windwardStartLineMarkToFirstMarkLineText.toString();
1359
- }
1360
- };
1361
- final LineInfoProvider leewardStartLineMarkToFirstMarkLineInfoProvider = new LineInfoProvider() {
1362
- @Override
1363
- public String getLineInfo() {
1364
- return leewardStartLineMarkToFirstMarkLineText.toString();
1365
- }
1366
- };
1367
- windwardStartLineMarkToFirstMarkLine = showOrRemoveOrUpdateLine(windwardStartLineMarkToFirstMarkLine, /* showLine */
1368
- (settings.getHelpLinesSettings().isVisible(HelpLineTypes.STARTLINETOFIRSTMARKTRIANGLE))
1369
- && startMarkPositions.size() > 1 && courseDTO.waypointPositions.size() > 1,
1370
- windwardStartLinePosition, firstMarkPosition, windwardStartLineMarkToFirstMarkLineInfoProvider,
1371
- "grey");
1372
- leewardStartLineMarkToFirstMarkLine = showOrRemoveOrUpdateLine(leewardStartLineMarkToFirstMarkLine, /* showLine */
1373
- (settings.getHelpLinesSettings().isVisible(HelpLineTypes.STARTLINETOFIRSTMARKTRIANGLE))
1374
- && startMarkPositions.size() > 1 && courseDTO.waypointPositions.size() > 1,
1375
- leewardStartLinePosition, firstMarkPosition, leewardStartLineMarkToFirstMarkLineInfoProvider,
1376
- "grey");
1377
- }
1378
-
1379
- private final StringBuilder startLineAdvantageText = new StringBuilder();
1380
- private final StringBuilder finishLineAdvantageText = new StringBuilder();
1381
- final LineInfoProvider startLineInfoProvider = new LineInfoProvider() {
1382
- @Override
1383
- public String getLineInfo() {
1384
- return stringMessages.startLine()+startLineAdvantageText;
1385
- }
1386
- };
1387
- final LineInfoProvider finishLineInfoProvider = new LineInfoProvider() {
1388
- @Override
1389
- public String getLineInfo() {
1390
- return stringMessages.finishLine()+finishLineAdvantageText;
1391
- }
1392
- };
1393
-
1394
- private void showStartAndFinishAndCourseMiddleLines(final CoursePositionsDTO courseDTO) {
1395
- if (map != null && courseDTO != null && courseDTO.course != null && courseDTO.course.waypoints != null &&
1396
- !courseDTO.course.waypoints.isEmpty()) {
1397
- // draw the start line
1398
- final WaypointDTO startWaypoint = courseDTO.course.waypoints.get(0);
1399
- updateCountdownCanvas(startWaypoint);
1400
- final int numberOfStartWaypointMarks = courseDTO.getStartMarkPositions() == null ? 0 : courseDTO.getStartMarkPositions().size();
1401
- final int numberOfFinishWaypointMarks = courseDTO.getFinishMarkPositions() == null ? 0 : courseDTO.getFinishMarkPositions().size();
1402
- final Position startLineLeftPosition = numberOfStartWaypointMarks == 0 ? null : courseDTO.getStartMarkPositions().get(0);
1403
- final Position startLineRightPosition = numberOfStartWaypointMarks < 2 ? null : courseDTO.getStartMarkPositions().get(1);
1404
- if (courseDTO.startLineAngleToCombinedWind != null) {
1405
- startLineAdvantageText.replace(0, startLineAdvantageText.length(), " "+stringMessages.lineAngleToWindAndAdvantage(
1406
- NumberFormat.getFormat("0.0").format(courseDTO.startLineLengthInMeters),
1407
- NumberFormat.getFormat("0.0").format(Math.abs(courseDTO.startLineAngleToCombinedWind)),
1408
- courseDTO.startLineAdvantageousSide.name().charAt(0)+courseDTO.startLineAdvantageousSide.name().substring(1).toLowerCase(),
1409
- NumberFormat.getFormat("0.0").format(courseDTO.startLineAdvantageInMeters)));
1410
- } else {
1411
- startLineAdvantageText.delete(0, startLineAdvantageText.length());
1412
- }
1413
- final boolean showStartLineBasedOnCurrentLeg = numberOfStartWaypointMarks == 2 && courseDTO.currentLegNumber <= 1;
1414
- final boolean showFinishLineBasedOnCurrentLeg = numberOfFinishWaypointMarks == 2 && courseDTO.currentLegNumber == courseDTO.totalLegsCount;
1415
- // show the line when STARTLINE is selected and the current leg is around the start leg,
1416
- // or when COURSEGEOMETRY is selected and the finish line isn't equal and wouldn't be shown at the same time based on the current leg.
1417
- // With this, if COURSEGEOMETRY is selected and start and finish line are equal, the start line will not be displayed if
1418
- // based on the race progress the finish line is to be preferred, so only the finish line will be shown.
1419
- final boolean reallyShowStartLine =
1420
- (settings.getHelpLinesSettings().isVisible(HelpLineTypes.STARTLINE) && showStartLineBasedOnCurrentLeg) ||
1421
- (settings.getHelpLinesSettings().isVisible(HelpLineTypes.COURSEGEOMETRY) &&
1422
- (!showFinishLineBasedOnCurrentLeg || !startLineEqualsFinishLine(courseDTO)));
1423
- // show the line when FINISHLINE is selected and the current leg is the last leg,
1424
- // or when COURSEGEOMETRY is selected and the start line isn't equal or the current leg is the last leg.
1425
- // With this, if COURSEGEOMETRY is selected and start and finish line are equal, the start line will be displayed unless
1426
- // the finish line should take precedence based on race progress.
1427
- final boolean reallyShowFinishLine = showFinishLineBasedOnCurrentLeg &&
1428
- (!showStartLineBasedOnCurrentLeg || !startLineEqualsFinishLine(courseDTO)) &&
1429
- (settings.getHelpLinesSettings().isVisible(HelpLineTypes.FINISHLINE) && showFinishLineBasedOnCurrentLeg) ||
1430
- (settings.getHelpLinesSettings().isVisible(HelpLineTypes.COURSEGEOMETRY) &&
1431
- (!startLineEqualsFinishLine(courseDTO) || showFinishLineBasedOnCurrentLeg));
1432
- startLine = showOrRemoveOrUpdateLine(startLine, reallyShowStartLine,
1433
- startLineLeftPosition, startLineRightPosition, startLineInfoProvider, "#ffffff");
1434
- // draw the finish line
1435
- final Position finishLineLeftPosition = numberOfFinishWaypointMarks == 0 ? null : courseDTO.getFinishMarkPositions().get(0);
1436
- final Position finishLineRightPosition = numberOfFinishWaypointMarks < 2 ? null : courseDTO.getFinishMarkPositions().get(1);
1437
- if (courseDTO.finishLineAngleToCombinedWind != null) {
1438
- finishLineAdvantageText.replace(0, finishLineAdvantageText.length(), " "+stringMessages.lineAngleToWindAndAdvantage(
1439
- NumberFormat.getFormat("0.0").format(courseDTO.finishLineLengthInMeters),
1440
- NumberFormat.getFormat("0.0").format(Math.abs(courseDTO.finishLineAngleToCombinedWind)),
1441
- courseDTO.finishLineAdvantageousSide.name().charAt(0)+courseDTO.finishLineAdvantageousSide.name().substring(1).toLowerCase(),
1442
- NumberFormat.getFormat("0.0").format(courseDTO.finishLineAdvantageInMeters)));
1443
- } else {
1444
- finishLineAdvantageText.delete(0, finishLineAdvantageText.length());
1445
- }
1446
- finishLine = showOrRemoveOrUpdateLine(finishLine, reallyShowFinishLine,
1447
- finishLineLeftPosition, finishLineRightPosition, finishLineInfoProvider, "#000000");
1448
- // the control point pairs for which we already decided whether or not
1449
- // to show a course middle line for; values tell whether to show the line and for which zero-based
1450
- // start waypoint index to do so; when for an equal control point pair multiple decisions with different
1451
- // outcome are made, a decision to show the line overrules the decision to not show it (OR-semantics)
1452
- final Map<Set<ControlPointDTO>, Pair<Boolean, Integer>> keysAlreadyHandled = new HashMap<>();
1453
- for (int zeroBasedIndexOfStartWaypoint = 0; zeroBasedIndexOfStartWaypoint<courseDTO.waypointPositions.size()-1; zeroBasedIndexOfStartWaypoint++) {
1454
- final Set<ControlPointDTO> key = getCourseMiddleLinesKey(courseDTO, zeroBasedIndexOfStartWaypoint);
1455
- boolean showCourseMiddleLine = keysAlreadyHandled.containsKey(key) && keysAlreadyHandled.get(key).getA() ||
1456
- settings.getHelpLinesSettings().isVisible(HelpLineTypes.COURSEGEOMETRY) ||
1457
- (settings.getHelpLinesSettings().isVisible(HelpLineTypes.COURSEMIDDLELINE)
1458
- && courseDTO.currentLegNumber > 0
1459
- && courseDTO.currentLegNumber-1 == zeroBasedIndexOfStartWaypoint);
1460
- keysAlreadyHandled.put(key, new Pair<>(showCourseMiddleLine, zeroBasedIndexOfStartWaypoint));
1461
- }
1462
- Set<Set<ControlPointDTO>> keysToConsider = new HashSet<>(keysAlreadyHandled.keySet());
1463
- keysToConsider.addAll(courseMiddleLines.keySet());
1464
- for (final Set<ControlPointDTO> key : keysToConsider) {
1465
- final int zeroBasedIndexOfStartWaypoint = keysAlreadyHandled.containsKey(key) ?
1466
- keysAlreadyHandled.get(key).getB() : 0; // if not handled, the line will be removed, so the waypoint index doesn't matter
1467
- final Pair<Boolean, Integer> showLineAndZeroBasedIndexOfStartWaypoint = keysAlreadyHandled.get(key);
1468
- final boolean showCourseMiddleLine = showLineAndZeroBasedIndexOfStartWaypoint != null && showLineAndZeroBasedIndexOfStartWaypoint.getA();
1469
- courseMiddleLines.put(key, showOrRemoveCourseMiddleLine(courseDTO, courseMiddleLines.get(key), zeroBasedIndexOfStartWaypoint, showCourseMiddleLine));
1470
- }
1471
- }
1472
- }
1473
-
1474
- private boolean startLineEqualsFinishLine(CoursePositionsDTO courseDTO) {
1475
- final List<WaypointDTO> waypoints;
1476
- return courseDTO != null && courseDTO.course != null &&
1477
- (waypoints = courseDTO.course.waypoints) != null &&
1478
- waypoints.get(0).controlPoint.equals(waypoints.get(waypoints.size()-1).controlPoint);
1479
- }
1480
-
1481
- /**
1482
- * Given a zero-based index into <code>courseDTO</code>'s {@link RaceCourseDTO#waypoints waypoints list} that denotes the start
1483
- * waypoint of the leg in question, returns a key that can be used for the {@link #courseMiddleLines} map, consisting of a set
1484
- * that holds the two {@link ControlPointDTO}s representing the start and finish control point of that leg.
1485
- */
1486
- private Set<ControlPointDTO> getCourseMiddleLinesKey(final CoursePositionsDTO courseDTO,
1487
- final int zeroBasedIndexOfStartWaypoint) {
1488
- ControlPointDTO startControlPoint = courseDTO.course.waypoints.get(zeroBasedIndexOfStartWaypoint).controlPoint;
1489
- ControlPointDTO endControlPoint = courseDTO.course.waypoints.get(zeroBasedIndexOfStartWaypoint+1).controlPoint;
1490
- final Set<ControlPointDTO> key = new HashSet<>();
1491
- key.add(startControlPoint);
1492
- key.add(endControlPoint);
1493
- return key;
1494
- }
1495
-
1496
- /**
1497
- * @param showLine
1498
- * tells whether or not to show the line; if the <code>lineToShowOrRemoveOrUpdate</code> references a
1499
- * line but the line shall not be shown, the line is removed from the map; conversely, if the line is not
1500
- * yet shown but shall be, a new line is created, added to the map and returned. If the line is shown and
1501
- * shall continue to be shown, the line is returned after updating its vertex coordinates.
1502
- * @return <code>null</code> if the line is not shown; the polyline object representing the line being displayed
1503
- * otherwise
1504
- */
1505
- private Polyline showOrRemoveCourseMiddleLine(final CoursePositionsDTO courseDTO, Polyline lineToShowOrRemoveOrUpdate,
1506
- final int zeroBasedIndexOfStartWaypoint, final boolean showLine) {
1507
- final Position position1DTO = courseDTO.waypointPositions.get(zeroBasedIndexOfStartWaypoint);
1508
- final Position position2DTO = courseDTO.waypointPositions.get(zeroBasedIndexOfStartWaypoint+1);
1509
- final LineInfoProvider lineInfoProvider = new LineInfoProvider() {
1510
- @Override
1511
- public String getLineInfo() {
1512
- final StringBuilder sb = new StringBuilder();
1513
- sb.append(stringMessages.courseMiddleLine());
1514
- sb.append('\n');
1515
- sb.append(NumberFormat.getFormat("0").format(
1516
- Math.abs(position1DTO.getDistance(position2DTO).getMeters()))+stringMessages.metersUnit());
1517
- if (lastCombinedWindTrackInfoDTO != null) {
1518
- final WindTrackInfoDTO windTrackAtLegMiddle = lastCombinedWindTrackInfoDTO.getCombinedWindOnLegMiddle(zeroBasedIndexOfStartWaypoint);
1519
- if (windTrackAtLegMiddle != null && windTrackAtLegMiddle.windFixes != null && !windTrackAtLegMiddle.windFixes.isEmpty()) {
1520
- WindDTO windAtLegMiddle = windTrackAtLegMiddle.windFixes.get(0);
1521
- final double legBearingDeg = position1DTO.getBearingGreatCircle(position2DTO).getDegrees();
1522
- final String diff = NumberFormat.getFormat("0.0").format(
1523
- Math.min(Math.abs(windAtLegMiddle.dampenedTrueWindBearingDeg-legBearingDeg),
1524
- Math.abs(windAtLegMiddle.dampenedTrueWindFromDeg-legBearingDeg)));
1525
- sb.append(", ");
1526
- sb.append(stringMessages.degreesToWind(diff));
1527
- }
1528
- }
1529
- return sb.toString();
1530
- }
1531
- };
1532
- return showOrRemoveOrUpdateLine(lineToShowOrRemoveOrUpdate, showLine, position1DTO, position2DTO, lineInfoProvider, "#2268a0");
1533
- }
1534
-
1535
- private interface LineInfoProvider {
1536
- String getLineInfo();
1537
- }
1538
-
1539
- /**
1540
- * @param showLine
1541
- * tells whether or not to show the line; if the <code>lineToShowOrRemoveOrUpdate</code> references a
1542
- * line but the line shall not be shown, the line is removed from the map; conversely, if the line is not
1543
- * yet shown but shall be, a new line is created, added to the map and returned. If the line is shown and
1544
- * shall continue to be shown, the line is returned after updating its vertex coordinates.
1545
- * @return <code>null</code> if the line is not shown; the polyline object representing the line being displayed
1546
- * otherwise
1547
- */
1548
- private Polyline showOrRemoveOrUpdateLine(Polyline lineToShowOrRemoveOrUpdate, final boolean showLine,
1549
- final Position position1DTO, final Position position2DTO, final LineInfoProvider lineInfoProvider, String lineColorRGB) {
1550
- if (showLine) {
1551
- LatLng courseMiddleLinePoint1 = coordinateSystem.toLatLng(position1DTO);
1552
- LatLng courseMiddleLinePoint2 = coordinateSystem.toLatLng(position2DTO);
1553
- final MVCArray<LatLng> pointsAsArray;
1554
- if (lineToShowOrRemoveOrUpdate == null) {
1555
- PolylineOptions options = PolylineOptions.newInstance();
1556
- options.setClickable(true);
1557
- options.setGeodesic(true);
1558
- options.setStrokeColor(lineColorRGB);
1559
- options.setStrokeWeight(1);
1560
- options.setStrokeOpacity(1.0);
1561
- pointsAsArray = MVCArray.newInstance();
1562
- lineToShowOrRemoveOrUpdate = Polyline.newInstance(options);
1563
- lineToShowOrRemoveOrUpdate.setPath(pointsAsArray);
1564
- lineToShowOrRemoveOrUpdate.addMouseOverHandler(new MouseOverMapHandler() {
1565
- @Override
1566
- public void onEvent(MouseOverMapEvent event) {
1567
- map.setTitle(lineInfoProvider.getLineInfo());
1568
- }
1569
- });
1570
- lineToShowOrRemoveOrUpdate.addMouseOutMoveHandler(new MouseOutMapHandler() {
1571
- @Override
1572
- public void onEvent(MouseOutMapEvent event) {
1573
- map.setTitle("");
1574
- }
1575
- });
1576
- lineToShowOrRemoveOrUpdate.setMap(map);
1577
-
1578
- } else {
1579
- pointsAsArray = lineToShowOrRemoveOrUpdate.getPath();
1580
- pointsAsArray.removeAt(1);
1581
- pointsAsArray.removeAt(0);
1582
- }
1583
- adjustInfoOverlayForVisibleLine(lineToShowOrRemoveOrUpdate, position1DTO, position2DTO, lineInfoProvider);
1584
- pointsAsArray.insertAt(0, courseMiddleLinePoint1);
1585
- pointsAsArray.insertAt(1, courseMiddleLinePoint2);
1586
- } else {
1587
- if (lineToShowOrRemoveOrUpdate != null) {
1588
- lineToShowOrRemoveOrUpdate.setMap(null);
1589
- adjustInfoOverlayForRemovedLine(lineToShowOrRemoveOrUpdate);
1590
- lineToShowOrRemoveOrUpdate = null;
1591
- }
1592
- }
1593
- return lineToShowOrRemoveOrUpdate;
1594
- }
1595
-
1596
- private void adjustInfoOverlayForRemovedLine(Polyline lineToShowOrRemoveOrUpdate) {
1597
- SmallTransparentInfoOverlay infoOverlay = infoOverlaysForLinesForCourseGeometry.remove(lineToShowOrRemoveOrUpdate);
1598
- if (infoOverlay != null) {
1599
- infoOverlay.removeFromMap();
1600
- }
1601
- }
1602
-
1603
- private void adjustInfoOverlayForVisibleLine(Polyline lineToShowOrRemoveOrUpdate, final Position position1DTO,
1604
- final Position position2DTO, final LineInfoProvider lineInfoProvider) {
1605
- SmallTransparentInfoOverlay infoOverlay = infoOverlaysForLinesForCourseGeometry.get(lineToShowOrRemoveOrUpdate);
1606
- if (getSettings().getHelpLinesSettings().isVisible(HelpLineTypes.COURSEGEOMETRY)) {
1607
- if (infoOverlay == null) {
1608
- infoOverlay = new SmallTransparentInfoOverlay(map, RaceMapOverlaysZIndexes.INFO_OVERLAY_ZINDEX, lineInfoProvider.getLineInfo(), coordinateSystem);
1609
- infoOverlaysForLinesForCourseGeometry.put(lineToShowOrRemoveOrUpdate, infoOverlay);
1610
- infoOverlay.addToMap();
1611
- } else {
1612
- infoOverlay.setInfoText(lineInfoProvider.getLineInfo());
1613
- }
1614
- infoOverlay.setPosition(position1DTO.translateGreatCircle(position1DTO.getBearingGreatCircle(position2DTO),
1615
- position1DTO.getDistance(position2DTO).scale(0.5)), /* transition time */ -1);
1616
- infoOverlay.draw();
1617
- } else {
1618
- if (infoOverlay != null) {
1619
- infoOverlay.removeFromMap();
1620
- infoOverlaysForLinesForCourseGeometry.remove(lineToShowOrRemoveOrUpdate);
1621
- }
1622
- }
1623
- }
1624
-
1625
- /**
1626
- * If, according to {@link #lastRaceTimesInfo} and {@link #timer} the race is
1627
- * still in the pre-start phase, show a {@link SmallTransparentInfoOverlay} at the
1628
- * start line that shows the count down.
1629
- */
1630
- private void updateCountdownCanvas(WaypointDTO startWaypoint) {
1631
- if (!settings.isShowSelectedCompetitorsInfo() || startWaypoint == null || Util.isEmpty(startWaypoint.controlPoint.getMarks())
1632
- || lastRaceTimesInfo == null || lastRaceTimesInfo.startOfRace == null || timer.getTime().after(lastRaceTimesInfo.startOfRace)) {
1633
- if (countDownOverlay != null) {
1634
- countDownOverlay.removeFromMap();
1635
- countDownOverlay = null;
1636
- }
1637
- } else {
1638
- long timeToStartInMs = lastRaceTimesInfo.startOfRace.getTime() - timer.getTime().getTime();
1639
- String countDownText = timeToStartInMs > 1000 ? stringMessages.timeToStart(DateAndTimeFormatterUtil
1640
- .formatElapsedTime(timeToStartInMs)) : stringMessages.start();
1641
- if (countDownOverlay == null) {
1642
- countDownOverlay = new SmallTransparentInfoOverlay(map, RaceMapOverlaysZIndexes.INFO_OVERLAY_ZINDEX,
1643
- countDownText, coordinateSystem);
1644
- countDownOverlay.addToMap();
1645
- } else {
1646
- countDownOverlay.setInfoText(countDownText);
1647
- }
1648
- countDownOverlay.setPosition(startWaypoint.controlPoint.getMarks().iterator().next().position, /* transition time */ -1);
1649
- countDownOverlay.draw();
1650
- }
1651
- }
1652
-
1653
- // Google scales coordinates so that the globe-tile has mercator-latitude [-pi, +pi], i.e. tile height of 2*pi
1654
- // mercator-latitude pi corresponds to geo-latitude of approx. 85.0998 (where Google cuts off the map visualization)
1655
- // official documentation: http://developers.google.com/maps/documentation/javascript/maptypes#TileCoordinates
1656
- private double getMercatorLatitude(double lat) {
1657
- // cutting-off for latitudes close to +-90 degrees is recommended (to avoid division by zero)
1658
- double sine = Math.max(-0.9999, Math.min(0.9999, Math.sin(Math.PI * lat / 180)));
1659
- return Math.log((1 + sine) / (1 - sine)) / 2;
1660
- }
1661
-
1662
- private int getZoomLevel(LatLngBounds bounds) {
1663
- int GLOBE_PXSIZE = 256; // a constant in Google's map projection
1664
- int MAX_ZOOM = 20; // maximum zoom-level that should be automatically selected
1665
- double LOG2 = Math.log(2.0);
1666
- double deltaLng = bounds.getNorthEast().getLongitude() - bounds.getSouthWest().getLongitude();
1667
- double deltaLat = getMercatorLatitude(bounds.getNorthEast().getLatitude()) - getMercatorLatitude(bounds.getSouthWest().getLatitude());
1668
- if ((deltaLng == 0) && (deltaLat == 0)) {
1669
- return MAX_ZOOM;
1670
- }
1671
- if (deltaLng < 0) {
1672
- deltaLng += 360;
1673
- }
1674
- int zoomLng = (int) Math.floor(Math.log(map.getDiv().getClientWidth() * 360 / deltaLng / GLOBE_PXSIZE) / LOG2);
1675
- int zoomLat = (int) Math.floor(Math.log(map.getDiv().getClientHeight() * 2 * Math.PI / deltaLat / GLOBE_PXSIZE) / LOG2);
1676
- return Math.min(Math.min(zoomLat, zoomLng), MAX_ZOOM);
1677
- }
1678
-
1679
- private void zoomMapToNewBounds(LatLngBounds newBounds) {
1680
- if (newBounds != null) {
1681
- LatLngBounds currentMapBounds;
1682
- if (map.getBounds() == null
1683
- || !BoundsUtil.contains((currentMapBounds = map.getBounds()), newBounds)
1684
- || graticuleAreaRatio(currentMapBounds, newBounds) > 10) {
1685
- // only change bounds if the new bounds don't fit into the current map zoom
1686
- Iterable<ZoomTypes> oldZoomSettings = settings.getZoomSettings().getTypesToConsiderOnZoom();
1687
- setAutoZoomInProgress(true);
1688
- autoZoomLatLngBounds = newBounds;
1689
- int newZoomLevel = getZoomLevel(autoZoomLatLngBounds);
1690
- if (newZoomLevel != map.getZoom()) {
1691
- // distinguish between zoom-in and zoom-out, because the sequence of panTo() and setZoom()
1692
- // appears different on the screen due to map-animations
1693
- // following sequences keep the selected boats allways visible:
1694
- // zoom-in : 1. panTo(), 2. setZoom()
1695
- // zoom-out: 1. setZoom(), 2. panTo()
1696
- autoZoomIn = newZoomLevel > map.getZoom();
1697
- autoZoomOut = !autoZoomIn;
1698
- autoZoomLevel = newZoomLevel;
1699
- removeTransitions();
1700
- if (autoZoomIn) {
1701
- map.panTo(autoZoomLatLngBounds.getCenter());
1702
- } else {
1703
- map.setZoom(autoZoomLevel);
1704
- }
1705
- } else {
1706
- map.panTo(autoZoomLatLngBounds.getCenter());
1707
- }
1708
- settings.getZoomSettings().setTypesToConsiderOnZoom(oldZoomSettings);
1709
- setAutoZoomInProgress(false);
1710
- }
1711
- }
1712
- }
1713
-
1714
- private double graticuleAreaRatio(LatLngBounds containing, LatLngBounds contained) {
1715
- assert BoundsUtil.contains(containing, contained);
1716
- double containingAreaRatio = getGraticuleArea(containing) / getGraticuleArea(contained);
1717
- return containingAreaRatio;
1718
- }
1719
-
1720
- /**
1721
- * A much simplified "area" calculation for a {@link Bounds} object, multiplying the differences in latitude and longitude degrees.
1722
- * The result therefore is in the order of magnitude of 60*60 square nautical miles.
1723
- */
1724
- private double getGraticuleArea(LatLngBounds bounds) {
1725
- return ((BoundsUtil.isCrossesDateLine(bounds) ? bounds.getNorthEast().getLongitude()+360 : bounds.getNorthEast().getLongitude())-bounds.getSouthWest().getLongitude()) *
1726
- (bounds.getNorthEast().getLatitude() - bounds.getSouthWest().getLatitude());
1727
- }
1728
-
1729
- private void setAutoZoomInProgress(boolean autoZoomInProgress) {
1730
- this.autoZoomInProgress = autoZoomInProgress;
1731
- }
1732
-
1733
- boolean isAutoZoomInProgress() {
1734
- return autoZoomInProgress;
1735
- }
1736
-
1737
- /**
1738
- * @param timeForPositionTransitionMillis use -1 to not animate the position transition, e.g., during map zoom or non-play
1739
- */
1740
- private boolean updateBoatCanvasForCompetitor(CompetitorDTO competitorDTO, Date date, long timeForPositionTransitionMillis) {
1741
- boolean usedExistingCanvas = false;
1742
- GPSFixDTO lastBoatFix = getBoatFix(competitorDTO, date);
1743
- if (lastBoatFix != null) {
1744
- BoatOverlay boatOverlay = boatOverlays.get(competitorDTO);
1745
- if (boatOverlay == null) {
1746
- boatOverlay = createBoatOverlay(RaceMapOverlaysZIndexes.BOATS_ZINDEX, competitorDTO, displayHighlighted(competitorDTO));
1747
- boatOverlays.put(competitorDTO, boatOverlay);
1748
- boatOverlay.setSelected(displayHighlighted(competitorDTO));
1749
- boatOverlay.setBoatFix(lastBoatFix, timeForPositionTransitionMillis);
1750
- boatOverlay.addToMap();
1751
- } else {
1752
- usedExistingCanvas = true;
1753
- boatOverlay.setSelected(displayHighlighted(competitorDTO));
1754
- boatOverlay.setBoatFix(lastBoatFix, timeForPositionTransitionMillis);
1755
- boatOverlay.draw();
1756
- }
1757
- }
1758
-
1759
- return usedExistingCanvas;
1760
- }
1761
-
1762
- private boolean displayHighlighted(CompetitorDTO competitorDTO) {
1763
- return !settings.isShowOnlySelectedCompetitors() && competitorSelection.isSelected(competitorDTO);
1764
- }
1765
-
1766
- protected CourseMarkOverlay createCourseMarkOverlay(int zIndex, final MarkDTO markDTO) {
1767
- final CourseMarkOverlay courseMarkOverlay = new CourseMarkOverlay(map, zIndex, markDTO, coordinateSystem);
1768
- courseMarkOverlay.addClickHandler(new ClickMapHandler() {
1769
- @Override
1770
- public void onEvent(ClickMapEvent event) {
1771
- LatLng latlng = courseMarkOverlay.getMarkLatLngPosition();
1772
- showMarkInfoWindow(markDTO, latlng);
1773
- }
1774
- });
1775
- return courseMarkOverlay;
1776
- }
1777
-
1778
- private CompetitorInfoOverlay createCompetitorInfoOverlay(int zIndex, final CompetitorDTO competitorDTO) {
1779
- String infoText = competitorDTO.getSailID() == null || competitorDTO.getSailID().isEmpty() ? competitorDTO.getName() : competitorDTO.getSailID();
1780
- return new CompetitorInfoOverlay(map, zIndex, competitorSelection.getColor(competitorDTO, raceIdentifier), infoText, coordinateSystem);
1781
- }
1782
-
1783
- private BoatOverlay createBoatOverlay(int zIndex, final CompetitorDTO competitorDTO, boolean highlighted) {
1784
- final BoatOverlay boatCanvas = new BoatOverlay(map, zIndex, competitorDTO, competitorSelection.getColor(competitorDTO, raceIdentifier), coordinateSystem);
1785
- boatCanvas.setSelected(highlighted);
1786
- boatCanvas.addClickHandler(new ClickMapHandler() {
1787
- @Override
1788
- public void onEvent(ClickMapEvent event) {
1789
- if (lastInfoWindow != null) {
1790
- lastInfoWindow.close();
1791
- }
1792
- GPSFixDTO latestFixForCompetitor = getBoatFix(competitorDTO, timer.getTime());
1793
- LatLng where = coordinateSystem.toLatLng(latestFixForCompetitor.position);
1794
- InfoWindowOptions options = InfoWindowOptions.newInstance();
1795
- InfoWindow infoWindow = InfoWindow.newInstance(options);
1796
- infoWindow.setContent(getInfoWindowContent(competitorDTO, latestFixForCompetitor));
1797
- infoWindow.setPosition(where);
1798
- lastInfoWindow = infoWindow;
1799
- infoWindow.open(map);
1800
- }
1801
- });
1802
-
1803
- boatCanvas.addMouseOverHandler(new MouseOverMapHandler() {
1804
- @Override
1805
- public void onEvent(MouseOverMapEvent event) {
1806
- map.setTitle(competitorDTO.getSailID());
1807
- }
1808
- });
1809
- boatCanvas.addMouseOutMoveHandler(new MouseOutMapHandler() {
1810
- @Override
1811
- public void onEvent(MouseOutMapEvent event) {
1812
- map.setTitle("");
1813
- }
1814
- });
1815
-
1816
- return boatCanvas;
1817
- }
1818
-
1819
- protected WindSensorOverlay createWindSensorOverlay(int zIndex, final WindSource windSource, final WindTrackInfoDTO windTrackInfoDTO) {
1820
- final WindSensorOverlay windSensorOverlay = new WindSensorOverlay(map, zIndex, raceMapImageManager, stringMessages, coordinateSystem);
1821
- windSensorOverlay.setWindInfo(windTrackInfoDTO, windSource);
1822
- windSensorOverlay.addClickHandler(new ClickMapHandler() {
1823
- @Override
1824
- public void onEvent(ClickMapEvent event) {
1825
- showWindSensorInfoWindow(windSensorOverlay);
1826
- }
1827
- });
1828
- return windSensorOverlay;
1829
- }
1830
-
1831
- private void showMarkInfoWindow(MarkDTO markDTO, LatLng position) {
1832
- if(lastInfoWindow != null) {
1833
- lastInfoWindow.close();
1834
- }
1835
- InfoWindowOptions options = InfoWindowOptions.newInstance();
1836
- InfoWindow infoWindow = InfoWindow.newInstance(options);
1837
- infoWindow.setContent(getInfoWindowContent(markDTO));
1838
- infoWindow.setPosition(position);
1839
- lastInfoWindow = infoWindow;
1840
- infoWindow.open(map);
1841
- }
1842
-
1843
- private void showCompetitorInfoWindow(final CompetitorDTO competitorDTO, LatLng where) {
1844
- if(lastInfoWindow != null) {
1845
- lastInfoWindow.close();
1846
- }
1847
- GPSFixDTO latestFixForCompetitor = getBoatFix(competitorDTO, timer.getTime());
1848
- // TODO find close fix where the mouse was; see BUG 470
1849
- InfoWindowOptions options = InfoWindowOptions.newInstance();
1850
- InfoWindow infoWindow = InfoWindow.newInstance(options);
1851
- infoWindow.setContent(getInfoWindowContent(competitorDTO, latestFixForCompetitor));
1852
- infoWindow.setPosition(where);
1853
- lastInfoWindow = infoWindow;
1854
- infoWindow.open(map);
1855
- }
1856
-
1857
- private String formatPosition(double lat, double lng) {
1858
- NumberFormat numberFormat = NumberFormat.getFormat("0.00000");
1859
- String result = numberFormat.format(lat) + " lat, " + numberFormat.format(lng) + " lng";
1860
- return result;
1861
- }
1862
-
1863
- private void showWindSensorInfoWindow(final WindSensorOverlay windSensorOverlay) {
1864
- WindSource windSource = windSensorOverlay.getWindSource();
1865
- WindTrackInfoDTO windTrackInfoDTO = windSensorOverlay.getWindTrackInfoDTO();
1866
- WindDTO windDTO = windTrackInfoDTO.windFixes.get(0);
1867
- if(windDTO != null && windDTO.position != null) {
1868
- if(lastInfoWindow != null) {
1869
- lastInfoWindow.close();
1870
- }
1871
- LatLng where = coordinateSystem.toLatLng(windDTO.position);
1872
- InfoWindowOptions options = InfoWindowOptions.newInstance();
1873
- InfoWindow infoWindow = InfoWindow.newInstance(options);
1874
- infoWindow.setContent(getInfoWindowContent(windSource, windTrackInfoDTO));
1875
- infoWindow.setPosition(where);
1876
- lastInfoWindow = infoWindow;
1877
- infoWindow.open(map);
1878
- }
1879
- }
1880
-
1881
- private Widget createInfoWindowLabelAndValue(String labelName, String value) {
1882
- FlowPanel flowPanel = new FlowPanel();
1883
- Label label = new Label(labelName + ":");
1884
- label.setWordWrap(false);
1885
- label.getElement().getStyle().setFloat(Style.Float.LEFT);
1886
- label.getElement().getStyle().setPadding(3, Style.Unit.PX);
1887
- label.getElement().getStyle().setFontWeight(Style.FontWeight.BOLD);
1888
- flowPanel.add(label);
1889
-
1890
- Label valueLabel = new Label(value);
1891
- valueLabel.setWordWrap(false);
1892
- valueLabel.getElement().getStyle().setFloat(Style.Float.LEFT);
1893
- valueLabel.getElement().getStyle().setPadding(3, Style.Unit.PX);
1894
- flowPanel.add(valueLabel);
1895
-
1896
- return flowPanel;
1897
- }
1898
-
1899
- private Widget getInfoWindowContent(MarkDTO markDTO) {
1900
- VerticalPanel vPanel = new VerticalPanel();
1901
- vPanel.add(createInfoWindowLabelAndValue(stringMessages.mark(), markDTO.getName()));
1902
- vPanel.add(createInfoWindowLabelAndValue(stringMessages.position(), formatPosition(markDTO.position.getLatDeg(), markDTO.position.getLngDeg())));
1903
- return vPanel;
1904
- }
1905
-
1906
- private Widget getInfoWindowContent(WindSource windSource, WindTrackInfoDTO windTrackInfoDTO) {
1907
- WindDTO windDTO = windTrackInfoDTO.windFixes.get(0);
1908
- NumberFormat numberFormat = NumberFormat.getFormat("0.0");
1909
- VerticalPanel vPanel = new VerticalPanel();
1910
- vPanel.add(createInfoWindowLabelAndValue(stringMessages.windSource(), WindSourceTypeFormatter.format(windSource, stringMessages)));
1911
- vPanel.add(createInfoWindowLabelAndValue(stringMessages.wind(), Math.round(windDTO.dampenedTrueWindFromDeg) + " " + stringMessages.degreesShort()));
1912
- vPanel.add(createInfoWindowLabelAndValue(stringMessages.windSpeed(), numberFormat.format(windDTO.dampenedTrueWindSpeedInKnots)));
1913
- vPanel.add(createInfoWindowLabelAndValue(stringMessages.position(), formatPosition(windDTO.position.getLatDeg(), windDTO.position.getLngDeg())));
1914
- return vPanel;
1915
- }
1916
-
1917
- private Widget getInfoWindowContent(CompetitorDTO competitorDTO, GPSFixDTO lastFix) {
1918
- final VerticalPanel vPanel = new VerticalPanel();
1919
- vPanel.setWidth("350px");
1920
- vPanel.add(createInfoWindowLabelAndValue(stringMessages.competitor(), competitorDTO.getName()));
1921
- vPanel.add(createInfoWindowLabelAndValue(stringMessages.sailNumber(), competitorDTO.getSailID()));
1922
- Integer rank = null;
1923
- if (quickRanks != null) {
1924
- QuickRankDTO quickRank = quickRanks.get(competitorDTO);
1925
- if (quickRank != null) {
1926
- rank = quickRank.rank;
1927
- }
1928
- }
1929
- if (rank != null) {
1930
- vPanel.add(createInfoWindowLabelAndValue(stringMessages.rank(), String.valueOf(rank)));
1931
- }
1932
- SpeedWithBearingDTO speedWithBearing = lastFix.speedWithBearing;
1933
- if (speedWithBearing == null) {
1934
- // TODO should we show the boat at all?
1935
- speedWithBearing = new SpeedWithBearingDTO(0, 0);
1936
- }
1937
- vPanel.add(createInfoWindowLabelAndValue(stringMessages.speed(),
1938
- NumberFormatterFactory.getDecimalFormat(1).format(speedWithBearing.speedInKnots) + " "+stringMessages.knotsUnit()));
1939
- vPanel.add(createInfoWindowLabelAndValue(stringMessages.bearing(), (int) speedWithBearing.bearingInDegrees + " "+stringMessages.degreesShort()));
1940
- if (lastFix.degreesBoatToTheWind != null) {
1941
- vPanel.add(createInfoWindowLabelAndValue(stringMessages.degreesBoatToTheWind(),
1942
- (int) Math.abs(lastFix.degreesBoatToTheWind) + " " + stringMessages.degreesShort()));
1943
- }
1944
- if (!selectedRaces.isEmpty()) {
1945
- RegattaAndRaceIdentifier race = selectedRaces.get(selectedRaces.size() - 1);
1946
- if (race != null) {
1947
- Map<CompetitorDTO, Date> from = new HashMap<CompetitorDTO, Date>();
1948
- from.put(competitorDTO, fixesAndTails.getFixes(competitorDTO).get(fixesAndTails.getFirstShownFix(competitorDTO)).timepoint);
1949
- Map<CompetitorDTO, Date> to = new HashMap<CompetitorDTO, Date>();
1950
- to.put(competitorDTO, getBoatFix(competitorDTO, timer.getTime()).timepoint);
1951
- sailingService.getDouglasPoints(race, from, to, 3,
1952
- new AsyncCallback<Map<CompetitorDTO, List<GPSFixDTO>>>() {
1953
- @Override
1954
- public void onFailure(Throwable caught) {
1955
- errorReporter.reportError("Error obtaining douglas positions: " + caught.getMessage(), true /*silentMode */);
1956
- }
1957
-
1958
- @Override
1959
- public void onSuccess(Map<CompetitorDTO, List<GPSFixDTO>> result) {
1960
- lastDouglasPeuckerResult = result;
1961
- if (douglasMarkers != null) {
1962
- removeAllMarkDouglasPeuckerpoints();
1963
- }
1964
- if (!(timer.getPlayState() == PlayStates.Playing)) {
1965
- if (settings.isShowDouglasPeuckerPoints()) {
1966
- showMarkDouglasPeuckerPoints(result);
1967
- }
1968
- }
1969
- }
1970
- });
1971
- sailingService.getManeuvers(race, from, to,
1972
- new AsyncCallback<Map<CompetitorDTO, List<ManeuverDTO>>>() {
1973
- @Override
1974
- public void onFailure(Throwable caught) {
1975
- errorReporter.reportError("Error obtaining maneuvers: " + caught.getMessage(), true /*silentMode */);
1976
- }
1977
-
1978
- @Override
1979
- public void onSuccess(Map<CompetitorDTO, List<ManeuverDTO>> result) {
1980
- lastManeuverResult = result;
1981
- if (maneuverMarkers != null) {
1982
- removeAllManeuverMarkers();
1983
- }
1984
- if (!(timer.getPlayState() == PlayStates.Playing)) {
1985
- showManeuvers(result);
1986
- }
1987
- }
1988
- });
1989
-
1990
- }
1991
- }
1992
- return vPanel;
1993
- }
1994
-
1995
- private Iterable<CompetitorDTO> getCompetitorsToShow() {
1996
- Iterable<CompetitorDTO> result;
1997
- Iterable<CompetitorDTO> selection = competitorSelection.getSelectedCompetitors();
1998
- if (!settings.isShowOnlySelectedCompetitors() || Util.isEmpty(selection)) {
1999
- result = competitorSelection.getFilteredCompetitors();
2000
- } else {
2001
- result = selection;
2002
- }
2003
- return result;
2004
- }
2005
-
2006
- protected void removeAllMarkDouglasPeuckerpoints() {
2007
- if (douglasMarkers != null) {
2008
- for (Marker marker : douglasMarkers) {
2009
- marker.setMap((MapWidget) null);
2010
- }
2011
- }
2012
- douglasMarkers = null;
2013
- }
2014
-
2015
- protected void removeAllManeuverMarkers() {
2016
- if (maneuverMarkers != null) {
2017
- for (Marker marker : maneuverMarkers) {
2018
- marker.setMap((MapWidget) null);
2019
- }
2020
- maneuverMarkers = null;
2021
- }
2022
- }
2023
-
2024
- private void showMarkDouglasPeuckerPoints(Map<CompetitorDTO, List<GPSFixDTO>> gpsFixPointMapForCompetitors) {
2025
- douglasMarkers = new HashSet<Marker>();
2026
- if (map != null && gpsFixPointMapForCompetitors != null) {
2027
- Set<CompetitorDTO> keySet = gpsFixPointMapForCompetitors.keySet();
2028
- Iterator<CompetitorDTO> iter = keySet.iterator();
2029
- while (iter.hasNext()) {
2030
- CompetitorDTO competitorDTO = iter.next();
2031
- List<GPSFixDTO> gpsFix = gpsFixPointMapForCompetitors.get(competitorDTO);
2032
- for (GPSFixDTO fix : gpsFix) {
2033
- LatLng latLng = coordinateSystem.toLatLng(fix.position);
2034
- MarkerOptions options = MarkerOptions.newInstance();
2035
- options.setTitle(fix.timepoint+": "+fix.position+", "+fix.speedWithBearing.toString());
2036
- Marker marker = Marker.newInstance(options);
2037
- marker.setPosition(latLng);
2038
- douglasMarkers.add(marker);
2039
- marker.setMap(map);
2040
- }
2041
- }
2042
- }
2043
- }
2044
-
2045
- private void showManeuvers(Map<CompetitorDTO, List<ManeuverDTO>> maneuvers) {
2046
- maneuverMarkers = new HashSet<Marker>();
2047
- if (map != null && maneuvers != null) {
2048
- Set<CompetitorDTO> keySet = maneuvers.keySet();
2049
- Iterator<CompetitorDTO> iter = keySet.iterator();
2050
- while (iter.hasNext()) {
2051
- CompetitorDTO competitorDTO = iter.next();
2052
- List<ManeuverDTO> maneuversForCompetitor = maneuvers.get(competitorDTO);
2053
- for (ManeuverDTO maneuver : maneuversForCompetitor) {
2054
- if (settings.isShowManeuverType(maneuver.type)) {
2055
- LatLng latLng = coordinateSystem.toLatLng(maneuver.position);
2056
- Marker maneuverMarker = raceMapImageManager.maneuverIconsForTypeAndTargetTack.get(new com.sap.sse.common.Util.Pair<ManeuverType, Tack>(maneuver.type, maneuver.newTack));
2057
- MarkerOptions options = MarkerOptions.newInstance();
2058
- options.setTitle(maneuver.toString(stringMessages));
2059
- options.setIcon(maneuverMarker.getIcon_MarkerImage());
2060
- Marker marker = Marker.newInstance(options);
2061
- marker.setPosition(latLng);
2062
- maneuverMarkers.add(marker);
2063
- marker.setMap(map);
2064
- }
2065
- }
2066
- }
2067
- }
2068
- }
2069
-
2070
- /**
2071
- * @param date
2072
- * the point in time for which to determine the competitor's boat position; approximated by using the fix
2073
- * from {@link #fixes} whose time point comes closest to this date
2074
- *
2075
- * @return The GPS fix for the given competitor from {@link #fixes} that is closest to <code>date</code>, or
2076
- * <code>null</code> if no fix is available
2077
- */
2078
- private GPSFixDTO getBoatFix(CompetitorDTO competitorDTO, Date date) {
2079
- GPSFixDTO result = null;
2080
- List<GPSFixDTO> competitorFixes = fixesAndTails.getFixes(competitorDTO);
2081
- if (competitorFixes != null && !competitorFixes.isEmpty()) {
2082
- int i = Collections.binarySearch(competitorFixes, new GPSFixDTO(date, null, null, (WindDTO) null, null, null, false),
2083
- new Comparator<GPSFixDTO>() {
2084
- @Override
2085
- public int compare(GPSFixDTO o1, GPSFixDTO o2) {
2086
- return o1.timepoint.compareTo(o2.timepoint);
2087
- }
2088
- });
2089
- if (i<0) {
2090
- i = -i-1; // no perfect match; i is now the insertion point
2091
- // if the insertion point is at the end, use last fix
2092
- if (i >= competitorFixes.size()) {
2093
- result = competitorFixes.get(competitorFixes.size()-1);
2094
- } else if (i == 0) {
2095
- // if the insertion point is at the beginning, use first fix
2096
- result = competitorFixes.get(0);
2097
- } else {
2098
- // competitorFixes must have at least two elements, and i points neither to the end nor the beginning;
2099
- // get the fix from i and i+1 whose timepoint is closer to date
2100
- final GPSFixDTO fixBefore = competitorFixes.get(i-1);
2101
- final GPSFixDTO fixAfter = competitorFixes.get(i);
2102
- final GPSFixDTO closer;
2103
- if (date.getTime() - fixBefore.timepoint.getTime() < fixAfter.timepoint.getTime() - date.getTime()) {
2104
- closer = fixBefore;
2105
- } else {
2106
- closer = fixAfter;
2107
- }
2108
- // now compute a weighted average depending on the time difference to "date" (see also bug 1924)
2109
- double factorForAfter = (double) (date.getTime()-fixBefore.timepoint.getTime()) / (double) (fixAfter.timepoint.getTime() - fixBefore.timepoint.getTime());
2110
- double factorForBefore = 1-factorForAfter;
2111
- DegreePosition betweenPosition = new DegreePosition(factorForBefore*fixBefore.position.getLatDeg() + factorForAfter*fixAfter.position.getLatDeg(),
2112
- factorForBefore*fixBefore.position.getLngDeg() + factorForAfter*fixAfter.position.getLngDeg());
2113
- final double betweenBearing;
2114
- if (fixBefore.speedWithBearing == null) {
2115
- if (fixAfter.speedWithBearing == null) {
2116
- betweenBearing = 0;
2117
- } else {
2118
- betweenBearing = fixAfter.speedWithBearing.bearingInDegrees;
2119
- }
2120
- } else if (fixAfter.speedWithBearing == null) {
2121
- betweenBearing = fixBefore.speedWithBearing.bearingInDegrees;
2122
- } else {
2123
- betweenBearing = new ScalableBearing(new DegreeBearingImpl(fixBefore.speedWithBearing.bearingInDegrees)).
2124
- multiply(factorForBefore).add(new ScalableBearing(new DegreeBearingImpl(fixAfter.speedWithBearing.bearingInDegrees)).
2125
- multiply(factorForAfter)).divide(1).getDegrees();
2126
- }
2127
- SpeedWithBearingDTO betweenSpeed = new SpeedWithBearingDTO(
2128
- factorForBefore*(fixBefore.speedWithBearing==null?0:fixBefore.speedWithBearing.speedInKnots) +
2129
- factorForAfter*(fixAfter.speedWithBearing==null?0:fixAfter.speedWithBearing.speedInKnots),
2130
- betweenBearing);
2131
- result = new GPSFixDTO(date, betweenPosition, betweenSpeed, closer.degreesBoatToTheWind,
2132
- closer.tack, closer.legType, fixBefore.extrapolated || fixAfter.extrapolated);
2133
- }
2134
- } else {
2135
- // perfect match
2136
- final GPSFixDTO fixAfter = competitorFixes.get(i);
2137
- result = fixAfter;
2138
- }
2139
- }
2140
- return result;
2141
- }
2142
-
2143
- public RaceMapSettings getSettings() {
2144
- return settings;
2145
- }
2146
-
2147
- @Override
2148
- public void addedToSelection(CompetitorDTO competitor) {
2149
- if (settings.isShowOnlySelectedCompetitors()) {
2150
- if (Util.size(competitorSelection.getSelectedCompetitors()) == 1) {
2151
- // first competitors selected; remove all others from map
2152
- Iterator<Map.Entry<CompetitorDTO, BoatOverlay>> i = boatOverlays.entrySet().iterator();
2153
- while (i.hasNext()) {
2154
- Entry<CompetitorDTO, BoatOverlay> next = i.next();
2155
- if (!next.getKey().equals(competitor)) {
2156
- BoatOverlay boatOverlay = next.getValue();
2157
- boatOverlay.removeFromMap();
2158
- fixesAndTails.removeTail(next.getKey());
2159
- i.remove(); // only this way a ConcurrentModificationException while looping can be avoided
2160
- }
2161
- }
2162
- showCompetitorInfoOnMap(timer.getTime(), -1, competitorSelection.getSelectedFilteredCompetitors());
2163
- } else {
2164
- // adding a single competitor; may need to re-load data, so refresh:
2165
- timeChanged(timer.getTime(), null);
2166
- }
2167
- } else {
2168
- // only change highlighting
2169
- BoatOverlay boatCanvas = boatOverlays.get(competitor);
2170
- if (boatCanvas != null) {
2171
- boatCanvas.setSelected(displayHighlighted(competitor));
2172
- boatCanvas.draw();
2173
- showCompetitorInfoOnMap(timer.getTime(), -1, competitorSelection.getSelectedFilteredCompetitors());
2174
- } else {
2175
- // seems like an internal error not to find the lowlighted marker; but maybe the
2176
- // competitor was added late to the race;
2177
- // data for newly selected competitor supposedly missing; refresh
2178
- timeChanged(timer.getTime(), null);
2179
- }
2180
- }
2181
- // Now update tails for all competitors because selection change may also affect all unselected competitors
2182
- for (CompetitorDTO oneOfAllCompetitors : competitorSelection.getAllCompetitors()) {
2183
- Polyline tail = fixesAndTails.getTail(oneOfAllCompetitors);
2184
- if (tail != null) {
2185
- PolylineOptions newOptions = createTailStyle(oneOfAllCompetitors, displayHighlighted(oneOfAllCompetitors));
2186
- tail.setOptions(newOptions);
2187
- }
2188
- }
2189
- // Trigger auto-zoom if needed
2190
- RaceMapZoomSettings zoomSettings = settings.getZoomSettings();
2191
- if (!zoomSettings.containsZoomType(ZoomTypes.NONE) && zoomSettings.isZoomToSelectedCompetitors()) {
2192
- zoomMapToNewBounds(zoomSettings.getNewBounds(this));
2193
- }
2194
- }
2195
-
2196
- @Override
2197
- public void removedFromSelection(CompetitorDTO competitor) {
2198
- if (isShowAnyHelperLines()) {
2199
- // helper lines depend on which competitor is visible, because the *visible* leader is used for
2200
- // deciding which helper lines to show:
2201
- timeChanged(timer.getTime(), null);
2202
- } else {
2203
- // try a more incremental update otherwise
2204
- if (settings.isShowOnlySelectedCompetitors()) {
2205
- // if selection is now empty, show all competitors
2206
- if (Util.isEmpty(competitorSelection.getSelectedCompetitors())) {
2207
- timeChanged(timer.getTime(), null);
2208
- } else {
2209
- // otherwise remove only deselected competitor's boat images and tail
2210
- BoatOverlay removedBoatOverlay = boatOverlays.remove(competitor);
2211
- if (removedBoatOverlay != null) {
2212
- removedBoatOverlay.removeFromMap();
2213
- }
2214
- fixesAndTails.removeTail(competitor);
2215
- showCompetitorInfoOnMap(timer.getTime(), -1, competitorSelection.getSelectedFilteredCompetitors());
2216
- }
2217
- } else {
2218
- // "lowlight" currently selected competitor
2219
- BoatOverlay boatCanvas = boatOverlays.get(competitor);
2220
- if (boatCanvas != null) {
2221
- boatCanvas.setSelected(displayHighlighted(competitor));
2222
- boatCanvas.draw();
2223
- }
2224
- showCompetitorInfoOnMap(timer.getTime(), -1, competitorSelection.getSelectedFilteredCompetitors());
2225
- }
2226
- }
2227
- //Trigger auto-zoom if needed
2228
- RaceMapZoomSettings zoomSettings = settings.getZoomSettings();
2229
- if (!zoomSettings.containsZoomType(ZoomTypes.NONE) && zoomSettings.isZoomToSelectedCompetitors()) {
2230
- zoomMapToNewBounds(zoomSettings.getNewBounds(this));
2231
- }
2232
- }
2233
-
2234
- private boolean isShowAnyHelperLines() {
2235
- return settings.getHelpLinesSettings().isShowAnyHelperLines();
2236
- }
2237
-
2238
- @Override
2239
- public String getLocalizedShortName() {
2240
- return stringMessages.map();
2241
- }
2242
-
2243
- @Override
2244
- public Widget getEntryWidget() {
2245
- return this;
2246
- }
2247
-
2248
- @Override
2249
- public boolean hasSettings() {
2250
- return true;
2251
- }
2252
-
2253
- @Override
2254
- public SettingsDialogComponent<RaceMapSettings> getSettingsDialogComponent() {
2255
- return new RaceMapSettingsDialogComponent(settings, stringMessages, this.showViewSimulation && this.hasPolar);
2256
- }
2257
-
2258
- @Override
2259
- public void updateSettings(RaceMapSettings newSettings) {
2260
- boolean maneuverTypeSelectionChanged = false;
2261
- boolean requiredRedraw = false;
2262
- for (ManeuverType maneuverType : ManeuverType.values()) {
2263
- if (newSettings.isShowManeuverType(maneuverType) != settings.isShowManeuverType(maneuverType)) {
2264
- maneuverTypeSelectionChanged = true;
2265
- settings.showManeuverType(maneuverType, newSettings.isShowManeuverType(maneuverType));
2266
- }
2267
- }
2268
- if (maneuverTypeSelectionChanged) {
2269
- if (!(timer.getPlayState() == PlayStates.Playing) && lastManeuverResult != null) {
2270
- removeAllManeuverMarkers();
2271
- showManeuvers(lastManeuverResult);
2272
- }
2273
- }
2274
- if (newSettings.isShowDouglasPeuckerPoints() != settings.isShowDouglasPeuckerPoints()) {
2275
- if (!(timer.getPlayState() == PlayStates.Playing) && lastDouglasPeuckerResult != null && newSettings.isShowDouglasPeuckerPoints()) {
2276
- settings.setShowDouglasPeuckerPoints(true);
2277
- removeAllMarkDouglasPeuckerpoints();
2278
- showMarkDouglasPeuckerPoints(lastDouglasPeuckerResult);
2279
- } else if (!newSettings.isShowDouglasPeuckerPoints()) {
2280
- settings.setShowDouglasPeuckerPoints(false);
2281
- removeAllMarkDouglasPeuckerpoints();
2282
- }
2283
- }
2284
- if (newSettings.getTailLengthInMilliseconds() != settings.getTailLengthInMilliseconds()) {
2285
- settings.setTailLengthInMilliseconds(newSettings.getTailLengthInMilliseconds());
2286
- requiredRedraw = true;
2287
- }
2288
- if (newSettings.getBuoyZoneRadiusInMeters() != settings.getBuoyZoneRadiusInMeters()) {
2289
- settings.setBuoyZoneRadiusInMeters(newSettings.getBuoyZoneRadiusInMeters());
2290
- requiredRedraw = true;
2291
- }
2292
- if (newSettings.isShowOnlySelectedCompetitors() != settings.isShowOnlySelectedCompetitors()) {
2293
- settings.setShowOnlySelectedCompetitors(newSettings.isShowOnlySelectedCompetitors());
2294
- requiredRedraw = true;
2295
- }
2296
- if (newSettings.isShowSelectedCompetitorsInfo() != settings.isShowSelectedCompetitorsInfo()) {
2297
- settings.setShowSelectedCompetitorsInfo(newSettings.isShowSelectedCompetitorsInfo());
2298
- requiredRedraw = true;
2299
- }
2300
- if (!newSettings.getZoomSettings().equals(settings.getZoomSettings())) {
2301
- settings.setZoomSettings(newSettings.getZoomSettings());
2302
- if (!settings.getZoomSettings().containsZoomType(ZoomTypes.NONE)) {
2303
- removeTransitions();
2304
- zoomMapToNewBounds(settings.getZoomSettings().getNewBounds(this));
2305
- }
2306
- }
2307
- if (!newSettings.getHelpLinesSettings().equals(settings.getHelpLinesSettings())) {
2308
- settings.setHelpLinesSettings(newSettings.getHelpLinesSettings());
2309
- requiredRedraw = true;
2310
- }
2311
- if (newSettings.isShowWindStreamletOverlay() != settings.isShowWindStreamletOverlay()) {
2312
- settings.setShowWindStreamletOverlay(newSettings.isShowWindStreamletOverlay());
2313
- streamletOverlay.setVisible(newSettings.isShowWindStreamletOverlay());
2314
- }
2315
- if (newSettings.isShowWindStreamletColors() != settings.isShowWindStreamletColors()) {
2316
- settings.setShowWindStreamletColors(newSettings.isShowWindStreamletColors());
2317
- streamletOverlay.setColors(newSettings.isShowWindStreamletColors());
2318
- }
2319
- if (newSettings.isShowSimulationOverlay() != settings.isShowSimulationOverlay()) {
2320
- settings.setShowSimulationOverlay(newSettings.isShowSimulationOverlay());
2321
- simulationOverlay.setVisible(newSettings.isShowSimulationOverlay());
2322
- if (newSettings.isShowSimulationOverlay()) {
2323
- simulationOverlay.updateLeg(Math.max(lastLegNumber,1), true, -1 /* ensure ui-update */);
2324
- }
2325
- }
2326
- if (newSettings.isWindUp() != settings.isWindUp()) {
2327
- settings.setWindUp(newSettings.isWindUp());
2328
- updateCoordinateSystemFromSettings();
2329
- requiredRedraw = true;
2330
- }
2331
- if (requiredRedraw) {
2332
- redraw();
2333
- }
2334
- }
2335
-
2336
- public static class BoatsBoundsCalculator extends LatLngBoundsCalculatorForSelected {
2337
-
2338
- @Override
2339
- public LatLngBounds calculateNewBounds(RaceMap forMap) {
2340
- LatLngBounds newBounds = null;
2341
- Iterable<CompetitorDTO> selectedCompetitors = forMap.competitorSelection.getSelectedCompetitors();
2342
- Iterable<CompetitorDTO> competitors = new ArrayList<CompetitorDTO>();
2343
- if (selectedCompetitors == null || !selectedCompetitors.iterator().hasNext()) {
2344
- competitors = forMap.getCompetitorsToShow();
2345
- } else {
2346
- competitors = isZoomOnlyToSelectedCompetitors(forMap) ? selectedCompetitors : forMap.getCompetitorsToShow();
2347
- }
2348
- for (CompetitorDTO competitor : competitors) {
2349
- try {
2350
- GPSFixDTO competitorFix = forMap.getBoatFix(competitor, forMap.timer.getTime());
2351
- Position competitorPosition = competitorFix != null ? competitorFix.position : null;
2352
- if (competitorPosition != null) {
2353
- if (newBounds == null) {
2354
- newBounds = BoundsUtil.getAsBounds(forMap.coordinateSystem.toLatLng(competitorPosition));
2355
- } else {
2356
- newBounds = newBounds.extend(forMap.coordinateSystem.toLatLng(competitorPosition));
2357
- }
2358
- }
2359
- } catch (IndexOutOfBoundsException e) {
2360
- // TODO can't this be predicted and the exception be avoided in the first place?
2361
- // Catch this in case the competitor has no GPS fixes at the current time (e.g. in race 'Finale 2' of STG)
2362
- }
2363
- }
2364
- return newBounds;
2365
- }
2366
-
2367
- }
2368
-
2369
- public static class TailsBoundsCalculator extends LatLngBoundsCalculatorForSelected {
2370
- @Override
2371
- public LatLngBounds calculateNewBounds(RaceMap racemap) {
2372
- LatLngBounds newBounds = null;
2373
- Iterable<CompetitorDTO> competitors = isZoomOnlyToSelectedCompetitors(racemap) ? racemap.competitorSelection.getSelectedCompetitors() : racemap.getCompetitorsToShow();
2374
- for (CompetitorDTO competitor : competitors) {
2375
- Polyline tail = racemap.fixesAndTails.getTail(competitor);
2376
- LatLngBounds bounds = null;
2377
- // TODO: Find a replacement for missing Polyline function getBounds() from v2
2378
- // see also http://stackoverflow.com/questions/3284808/getting-the-bounds-of-a-polyine-in-google-maps-api-v3;
2379
- // optionally, consider providing a bounds cache with two sorted sets that organize the LatLng objects for O(1) bounds calculation and logarithmic add, ideally O(1) remove
2380
- if (tail != null && tail.getPath().getLength() >= 1) {
2381
- bounds = BoundsUtil.getAsBounds(tail.getPath().get(0));
2382
- for (int i = 1; i < tail.getPath().getLength(); i++) {
2383
- bounds = bounds.extend(tail.getPath().get(i));
2384
- }
2385
- }
2386
- if (bounds != null) {
2387
- if (newBounds == null) {
2388
- newBounds = bounds;
2389
- } else {
2390
- newBounds = newBounds.union(bounds);
2391
- }
2392
- }
2393
- }
2394
- return newBounds;
2395
- }
2396
- }
2397
-
2398
- public static class CourseMarksBoundsCalculator implements LatLngBoundsCalculator {
2399
- @Override
2400
- public LatLngBounds calculateNewBounds(RaceMap forMap) {
2401
- LatLngBounds newBounds = null;
2402
- Iterable<MarkDTO> marksToZoom = forMap.markDTOs.values();
2403
- if (marksToZoom != null) {
2404
- for (MarkDTO markDTO : marksToZoom) {
2405
- if (newBounds == null) {
2406
- newBounds = BoundsUtil.getAsBounds(forMap.coordinateSystem.toLatLng(markDTO.position));
2407
- } else {
2408
- newBounds = newBounds.extend(forMap.coordinateSystem.toLatLng(markDTO.position));
2409
- }
2410
- }
2411
- }
2412
- return newBounds;
2413
- }
2414
- }
2415
-
2416
- public static class WindSensorsBoundsCalculator implements LatLngBoundsCalculator {
2417
- @Override
2418
- public LatLngBounds calculateNewBounds(RaceMap forMap) {
2419
- LatLngBounds newBounds = null;
2420
- Collection<WindSensorOverlay> marksToZoom = forMap.windSensorOverlays.values();
2421
- if (marksToZoom != null) {
2422
- for (WindSensorOverlay windSensorOverlay : marksToZoom) {
2423
- if (BoundsUtil.getAsPosition(windSensorOverlay.getLatLngPosition()) != null) {
2424
- LatLngBounds bounds = BoundsUtil.getAsBounds(windSensorOverlay.getLatLngPosition());
2425
- if (newBounds == null) {
2426
- newBounds = bounds;
2427
- } else {
2428
- newBounds = newBounds.extend(windSensorOverlay.getLatLngPosition());
2429
- }
2430
- }
2431
- }
2432
- }
2433
- return newBounds;
2434
- }
2435
- }
2436
-
2437
- @Override
2438
- public void initializeData(boolean showMapControls, boolean showHeaderPanel) {
2439
- loadMapsAPIV3(showMapControls, showHeaderPanel);
2440
- }
2441
-
2442
- @Override
2443
- public boolean isDataInitialized() {
2444
- return isMapInitialized;
2445
- }
2446
-
2447
- @Override
2448
- public void onResize() {
2449
- if (map != null) {
2450
- map.triggerResize();
2451
- zoomMapToNewBounds(settings.getZoomSettings().getNewBounds(RaceMap.this));
2452
- }
2453
- }
2454
-
2455
- @Override
2456
- public void competitorsListChanged(Iterable<CompetitorDTO> competitors) {
2457
- timeChanged(timer.getTime(), null);
2458
- }
2459
-
2460
- @Override
2461
- public void filteredCompetitorsListChanged(Iterable<CompetitorDTO> filteredCompetitors) {
2462
- timeChanged(timer.getTime(), null);
2463
- }
2464
-
2465
- @Override
2466
- public void filterChanged(FilterSet<CompetitorDTO, ? extends Filter<CompetitorDTO>> oldFilterSet,
2467
- FilterSet<CompetitorDTO, ? extends Filter<CompetitorDTO>> newFilterSet) {
2468
- // nothing to do; if the list of filtered competitors has changed, a separate call to filteredCompetitorsListChanged will occur
2469
- }
2470
-
2471
- @Override
2472
- public PolylineOptions createTailStyle(CompetitorDTO competitor, boolean isHighlighted) {
2473
- PolylineOptions options = PolylineOptions.newInstance();
2474
- options.setClickable(true);
2475
- options.setGeodesic(true);
2476
- options.setStrokeOpacity(1.0);
2477
- boolean noCompetitorSelected = Util.isEmpty(competitorSelection.getSelectedCompetitors());
2478
- if (isHighlighted || noCompetitorSelected) {
2479
- options.setStrokeColor(competitorSelection.getColor(competitor, raceIdentifier).getAsHtml());
2480
- } else {
2481
- options.setStrokeColor(CssColor.make(200, 200, 200).toString());
2482
- }
2483
- if (isHighlighted) {
2484
- options.setStrokeWeight(2);
2485
- } else {
2486
- options.setStrokeWeight(1);
2487
- }
2488
- options.setZindex(RaceMapOverlaysZIndexes.BOATTAILS_ZINDEX);
2489
- return options;
2490
- }
2491
-
2492
- @Override
2493
- public Polyline createTail(final CompetitorDTO competitor, List<LatLng> points) {
2494
- PolylineOptions options = createTailStyle(competitor, displayHighlighted(competitor));
2495
- Polyline result = Polyline.newInstance(options);
2496
-
2497
- MVCArray<LatLng> pointsAsArray = MVCArray.newInstance(points.toArray(new LatLng[0]));
2498
- result.setPath(pointsAsArray);
2499
-
2500
- result.addClickHandler(new ClickMapHandler() {
2501
- @Override
2502
- public void onEvent(ClickMapEvent event) {
2503
- showCompetitorInfoWindow(competitor, event.getMouseEvent().getLatLng());
2504
- }
2505
- });
2506
- result.addMouseOverHandler(new MouseOverMapHandler() {
2507
- @Override
2508
- public void onEvent(MouseOverMapEvent event) {
2509
- map.setTitle(competitor.getSailID() + ", " + competitor.getName());
2510
- }
2511
- });
2512
- result.addMouseOutMoveHandler(new MouseOutMapHandler() {
2513
- @Override
2514
- public void onEvent(MouseOutMapEvent event) {
2515
- map.setTitle("");
2516
- }
2517
- });
2518
- return result;
2519
- }
2520
-
2521
- @Override
2522
- public Integer getRank(CompetitorDTO competitor) {
2523
- final Integer result;
2524
- QuickRankDTO quickRank = quickRanks.get(competitor);
2525
- if (quickRank != null) {
2526
- result = quickRank.rank;
2527
- } else {
2528
- result = null;
2529
- }
2530
- return result;
2531
- }
2532
-
2533
- private Image createSAPLogo() {
2534
- ImageResource sapLogoResource = resources.sapLogoOverlay();
2535
- Image sapLogo = new Image(sapLogoResource);
2536
- sapLogo.addClickHandler(new ClickHandler() {
2537
- @Override
2538
- public void onClick(ClickEvent event) {
2539
- Window.open("http://www.sap.com", "_blank", null);
2540
- }
2541
- });
2542
- sapLogo.setStyleName("raceBoard-Logo");
2543
- return sapLogo;
2544
- }
2545
-
2546
- @Override
2547
- public String getDependentCssClassName() {
2548
- return "raceMap";
2549
- }
2550
-
2551
- /**
2552
- * The default zoom bounds are defined by the boats
2553
- */
2554
- private LatLngBounds getDefaultZoomBounds() {
2555
- return new BoatsBoundsCalculator().calculateNewBounds(RaceMap.this);
2556
- }
2557
-
2558
- private MapOptions getMapOptions(final boolean showMapControls, boolean windUp) {
2559
- MapOptions mapOptions = MapOptions.newInstance();
2560
- mapOptions.setScrollWheel(true);
2561
- mapOptions.setMapTypeControl(showMapControls && !windUp);
2562
- mapOptions.setPanControl(showMapControls);
2563
- mapOptions.setZoomControl(showMapControls);
2564
- mapOptions.setScaleControl(true);
2565
- if (windUp) {
2566
- mapOptions.setMinZoom(8);
2567
- } else {
2568
- mapOptions.setMinZoom(0);
2569
- }
2570
- MapTypeStyle[] mapTypeStyles = new MapTypeStyle[4];
2571
-
2572
- // hide all transit lines including ferry lines
2573
- mapTypeStyles[0] = GoogleMapStyleHelper.createHiddenStyle(MapTypeStyleFeatureType.TRANSIT);
2574
- // hide points of interest
2575
- mapTypeStyles[1] = GoogleMapStyleHelper.createHiddenStyle(MapTypeStyleFeatureType.POI);
2576
- // simplify road display
2577
- mapTypeStyles[2] = GoogleMapStyleHelper.createSimplifiedStyle(MapTypeStyleFeatureType.ROAD);
2578
- // set water color
2579
- // To play with the styles, check out http://gmaps-samples-v3.googlecode.com/svn/trunk/styledmaps/wizard/index.html.
2580
- // To convert an RGB color into the strange hue/saturation/lightness model used by the Google Map use
2581
- // http://software.stadtwerk.org/google_maps_colorizr/#water/all/123456/.
2582
- mapTypeStyles[3] = GoogleMapStyleHelper.createColorStyle(MapTypeStyleFeatureType.WATER, new RGBColor(0, 136, 255), 0, -70);
2583
-
2584
- MapTypeControlOptions mapTypeControlOptions = MapTypeControlOptions.newInstance();
2585
- mapTypeControlOptions.setPosition(ControlPosition.BOTTOM_RIGHT);
2586
- mapOptions.setMapTypeControlOptions(mapTypeControlOptions);
2587
-
2588
- mapOptions.setMapTypeStyles(mapTypeStyles);
2589
- // no need to try to position the scale control; it always ends up at the right bottom corner
2590
- mapOptions.setStreetViewControl(false);
2591
- if (showMapControls) {
2592
- ZoomControlOptions zoomControlOptions = ZoomControlOptions.newInstance();
2593
- zoomControlOptions.setPosition(ControlPosition.RIGHT_TOP);
2594
- mapOptions.setZoomControlOptions(zoomControlOptions);
2595
- PanControlOptions panControlOptions = PanControlOptions.newInstance();
2596
- panControlOptions.setPosition(ControlPosition.RIGHT_TOP);
2597
- mapOptions.setPanControlOptions(panControlOptions);
2598
- }
2599
- return mapOptions;
2600
- }
2601
-}
1
+package com.sap.sailing.gwt.ui.client.shared.racemap;
2
+
3
+import java.util.ArrayList;
4
+import java.util.Collection;
5
+import java.util.Collections;
6
+import java.util.Comparator;
7
+import java.util.Date;
8
+import java.util.HashMap;
9
+import java.util.HashSet;
10
+import java.util.Iterator;
11
+import java.util.LinkedHashMap;
12
+import java.util.List;
13
+import java.util.Map;
14
+import java.util.Map.Entry;
15
+import java.util.Set;
16
+
17
+import com.google.gwt.canvas.dom.client.CssColor;
18
+import com.google.gwt.core.client.GWT;
19
+import com.google.gwt.core.client.Scheduler;
20
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
21
+import com.google.gwt.dom.client.Style;
22
+import com.google.gwt.dom.client.Style.Unit;
23
+import com.google.gwt.event.dom.client.ClickEvent;
24
+import com.google.gwt.event.dom.client.ClickHandler;
25
+import com.google.gwt.i18n.client.NumberFormat;
26
+import com.google.gwt.maps.client.LoadApi;
27
+import com.google.gwt.maps.client.LoadApi.LoadLibrary;
28
+import com.google.gwt.maps.client.MapOptions;
29
+import com.google.gwt.maps.client.MapWidget;
30
+import com.google.gwt.maps.client.base.LatLng;
31
+import com.google.gwt.maps.client.base.LatLngBounds;
32
+import com.google.gwt.maps.client.controls.ControlPosition;
33
+import com.google.gwt.maps.client.controls.MapTypeControlOptions;
34
+import com.google.gwt.maps.client.controls.MapTypeStyle;
35
+import com.google.gwt.maps.client.controls.PanControlOptions;
36
+import com.google.gwt.maps.client.controls.ZoomControlOptions;
37
+import com.google.gwt.maps.client.events.bounds.BoundsChangeMapEvent;
38
+import com.google.gwt.maps.client.events.bounds.BoundsChangeMapHandler;
39
+import com.google.gwt.maps.client.events.click.ClickMapEvent;
40
+import com.google.gwt.maps.client.events.click.ClickMapHandler;
41
+import com.google.gwt.maps.client.events.dragend.DragEndMapEvent;
42
+import com.google.gwt.maps.client.events.dragend.DragEndMapHandler;
43
+import com.google.gwt.maps.client.events.idle.IdleMapEvent;
44
+import com.google.gwt.maps.client.events.idle.IdleMapHandler;
45
+import com.google.gwt.maps.client.events.mouseout.MouseOutMapEvent;
46
+import com.google.gwt.maps.client.events.mouseout.MouseOutMapHandler;
47
+import com.google.gwt.maps.client.events.mouseover.MouseOverMapEvent;
48
+import com.google.gwt.maps.client.events.mouseover.MouseOverMapHandler;
49
+import com.google.gwt.maps.client.events.zoom.ZoomChangeMapEvent;
50
+import com.google.gwt.maps.client.events.zoom.ZoomChangeMapHandler;
51
+import com.google.gwt.maps.client.maptypes.MapTypeStyleFeatureType;
52
+import com.google.gwt.maps.client.mvc.MVCArray;
53
+import com.google.gwt.maps.client.overlays.InfoWindow;
54
+import com.google.gwt.maps.client.overlays.InfoWindowOptions;
55
+import com.google.gwt.maps.client.overlays.Marker;
56
+import com.google.gwt.maps.client.overlays.MarkerOptions;
57
+import com.google.gwt.maps.client.overlays.Polygon;
58
+import com.google.gwt.maps.client.overlays.PolygonOptions;
59
+import com.google.gwt.maps.client.overlays.Polyline;
60
+import com.google.gwt.maps.client.overlays.PolylineOptions;
61
+import com.google.gwt.resources.client.ImageResource;
62
+import com.google.gwt.user.client.Window;
63
+import com.google.gwt.user.client.rpc.AsyncCallback;
64
+import com.google.gwt.user.client.ui.AbsolutePanel;
65
+import com.google.gwt.user.client.ui.Button;
66
+import com.google.gwt.user.client.ui.FlowPanel;
67
+import com.google.gwt.user.client.ui.Image;
68
+import com.google.gwt.user.client.ui.Label;
69
+import com.google.gwt.user.client.ui.RequiresResize;
70
+import com.google.gwt.user.client.ui.VerticalPanel;
71
+import com.google.gwt.user.client.ui.Widget;
72
+import com.sap.sailing.domain.common.Bearing;
73
+import com.sap.sailing.domain.common.Bounds;
74
+import com.sap.sailing.domain.common.ManeuverType;
75
+import com.sap.sailing.domain.common.Position;
76
+import com.sap.sailing.domain.common.RaceIdentifier;
77
+import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
78
+import com.sap.sailing.domain.common.Tack;
79
+import com.sap.sailing.domain.common.WindSource;
80
+import com.sap.sailing.domain.common.WindSourceType;
81
+import com.sap.sailing.domain.common.dto.CompetitorDTO;
82
+import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
83
+import com.sap.sailing.domain.common.impl.DegreePosition;
84
+import com.sap.sailing.domain.common.scalablevalue.impl.ScalableBearing;
85
+import com.sap.sailing.domain.common.scalablevalue.impl.ScalablePosition;
86
+import com.sap.sailing.gwt.ui.actions.GetPolarAction;
87
+import com.sap.sailing.gwt.ui.actions.GetRaceMapDataAction;
88
+import com.sap.sailing.gwt.ui.actions.GetWindInfoAction;
89
+import com.sap.sailing.gwt.ui.client.ClientResources;
90
+import com.sap.sailing.gwt.ui.client.CompetitorSelectionChangeListener;
91
+import com.sap.sailing.gwt.ui.client.CompetitorSelectionProvider;
92
+import com.sap.sailing.gwt.ui.client.NumberFormatterFactory;
93
+import com.sap.sailing.gwt.ui.client.RaceSelectionChangeListener;
94
+import com.sap.sailing.gwt.ui.client.RaceTimesInfoProviderListener;
95
+import com.sap.sailing.gwt.ui.client.RequiresDataInitialization;
96
+import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
97
+import com.sap.sailing.gwt.ui.client.StringMessages;
98
+import com.sap.sailing.gwt.ui.client.WindSourceTypeFormatter;
99
+import com.sap.sailing.gwt.ui.client.shared.filter.QuickRankProvider;
100
+import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMapHelpLinesSettings.HelpLineTypes;
101
+import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMapZoomSettings.ZoomTypes;
102
+import com.sap.sailing.gwt.ui.common.client.DateAndTimeFormatterUtil;
103
+import com.sap.sailing.gwt.ui.shared.ControlPointDTO;
104
+import com.sap.sailing.gwt.ui.shared.CoursePositionsDTO;
105
+import com.sap.sailing.gwt.ui.shared.GPSFixDTO;
106
+import com.sap.sailing.gwt.ui.shared.LegInfoDTO;
107
+import com.sap.sailing.gwt.ui.shared.ManeuverDTO;
108
+import com.sap.sailing.gwt.ui.shared.MarkDTO;
109
+import com.sap.sailing.gwt.ui.shared.QuickRankDTO;
110
+import com.sap.sailing.gwt.ui.shared.RaceCourseDTO;
111
+import com.sap.sailing.gwt.ui.shared.RaceMapDataDTO;
112
+import com.sap.sailing.gwt.ui.shared.RaceTimesInfoDTO;
113
+import com.sap.sailing.gwt.ui.shared.SidelineDTO;
114
+import com.sap.sailing.gwt.ui.shared.SpeedWithBearingDTO;
115
+import com.sap.sailing.gwt.ui.shared.WaypointDTO;
116
+import com.sap.sailing.gwt.ui.shared.WindDTO;
117
+import com.sap.sailing.gwt.ui.shared.WindInfoForRaceDTO;
118
+import com.sap.sailing.gwt.ui.shared.WindTrackInfoDTO;
119
+import com.sap.sailing.gwt.ui.shared.racemap.GoogleMapAPIKey;
120
+import com.sap.sailing.gwt.ui.shared.racemap.GoogleMapStyleHelper;
121
+import com.sap.sailing.gwt.ui.shared.racemap.RaceSimulationOverlay;
122
+import com.sap.sailing.gwt.ui.shared.racemap.WindStreamletsRaceboardOverlay;
123
+import com.sap.sse.common.Util;
124
+import com.sap.sse.common.Util.Pair;
125
+import com.sap.sse.common.Util.Triple;
126
+import com.sap.sse.common.filter.Filter;
127
+import com.sap.sse.common.filter.FilterSet;
128
+import com.sap.sse.common.impl.RGBColor;
129
+import com.sap.sse.gwt.client.ErrorReporter;
130
+import com.sap.sse.gwt.client.async.AsyncActionsExecutor;
131
+import com.sap.sse.gwt.client.async.MarkedAsyncCallback;
132
+import com.sap.sse.gwt.client.player.TimeListener;
133
+import com.sap.sse.gwt.client.player.Timer;
134
+import com.sap.sse.gwt.client.player.Timer.PlayModes;
135
+import com.sap.sse.gwt.client.player.Timer.PlayStates;
136
+import com.sap.sse.gwt.client.shared.components.Component;
137
+import com.sap.sse.gwt.client.shared.components.SettingsDialog;
138
+import com.sap.sse.gwt.client.shared.components.SettingsDialogComponent;
139
+
140
+public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSelectionChangeListener, RaceSelectionChangeListener,
141
+ RaceTimesInfoProviderListener, TailFactory, Component<RaceMapSettings>, RequiresDataInitialization, RequiresResize, QuickRankProvider {
142
+ public static final String GET_RACE_MAP_DATA_CATEGORY = "getRaceMapData";
143
+ public static final String GET_WIND_DATA_CATEGORY = "getWindData";
144
+
145
+ private static final String COMPACT_HEADER_STYLE = "compactHeader";
146
+
147
+ private MapWidget map;
148
+
149
+ /**
150
+ * Always valid, non-<code>null</code>. Must be used to map all coordinates, headings, bearings, and directions
151
+ * displayed on the map, including the orientations of any canvases such as boat icons, wind displays etc. that are
152
+ * embedded in the map. The coordinate systems facilitates the possibility of transformed displays such as
153
+ * rotated and translated versions of the map, implementing the "wind-up" view.
154
+ */
155
+ private DelegateCoordinateSystem coordinateSystem;
156
+
157
+ private FlowPanel headerPanel;
158
+ private AbsolutePanel panelForLeftHeaderLabels;
159
+ private AbsolutePanel panelForRightHeaderLabels;
160
+
161
+ private final SailingServiceAsync sailingService;
162
+ private final ErrorReporter errorReporter;
163
+
164
+ private final static ClientResources resources = GWT.create(ClientResources.class);
165
+
166
+ /**
167
+ * Polyline for the start line (connecting two marks representing the start gate).
168
+ */
169
+ private Polyline startLine;
170
+
171
+ /**
172
+ * Polyline for the finish line (connecting two marks representing the finish gate).
173
+ */
174
+ private Polyline finishLine;
175
+
176
+ /**
177
+ * Polyline for the advantage line (the leading line for the boats, orthogonal to the wind direction; touching the leading boat).
178
+ */
179
+ private Polyline advantageLine;
180
+
181
+ /**
182
+ * The windward of two Polylines representing a triangle between startline and first mark.
183
+ */
184
+ private Polyline windwardStartLineMarkToFirstMarkLine;
185
+
186
+ /**
187
+ * The leeward of two Polylines representing a triangle between startline and first mark.
188
+ */
189
+ private Polyline leewardStartLineMarkToFirstMarkLine;
190
+
191
+ private class AdvantageLineMouseOverMapHandler implements MouseOverMapHandler {
192
+ private double trueWindAngle;
193
+ private Date date;
194
+
195
+ public AdvantageLineMouseOverMapHandler(double trueWindAngle, Date date) {
196
+ this.trueWindAngle = trueWindAngle;
197
+ this.date = date;
198
+ }
199
+
200
+ public void setTrueWindBearing(double trueWindAngle) {
201
+ this.trueWindAngle = trueWindAngle;
202
+ }
203
+
204
+ public void setDate(Date date) {
205
+ this.date = date;
206
+ }
207
+
208
+ @Override
209
+ public void onEvent(MouseOverMapEvent event) {
210
+ map.setTitle(stringMessages.advantageLine()+" (from "+new DegreeBearingImpl(Math.round(trueWindAngle)).reverse().getDegrees()+"deg"+
211
+ (date == null ? ")" : ", "+ date) + ")");
212
+ }
213
+ };
214
+
215
+ private AdvantageLineMouseOverMapHandler advantageLineMouseOverHandler;
216
+
217
+ /**
218
+ * Polylines for the course middle lines; keys are the two control points delimiting the leg for which the
219
+ * {@link Polyline} value shows the course middle line. As only one course middle line is shown even if there
220
+ * are multiple legs using the same control points in different directions, using a {@link Set} makes this
221
+ * independent of the order of the two control points. If no course middle line is currently being shown for
222
+ * a pair of control points, the map will not contain a value for this pair.
223
+ */
224
+ private final Map<Set<ControlPointDTO>, Polyline> courseMiddleLines;
225
+
226
+ private final Map<SidelineDTO, Polygon> courseSidelines;
227
+
228
+ /**
229
+ * When the {@link HelpLineTypes#COURSEGEOMETRY} option is selected, little markers will be displayed on the
230
+ * lines that show the tooltip text in a little info box linked to the line. When the line is removed by
231
+ * {@link #showOrRemoveOrUpdateLine(Polyline, boolean, Position, Position, LineInfoProvider, String)}, these
232
+ * overlays need to be removed as well. Also, when the {@link HelpLineTypes#COURSEGEOMETRY} setting is
233
+ * deactivated, all these overlays need to go.
234
+ */
235
+ private final Map<Polyline, SmallTransparentInfoOverlay> infoOverlaysForLinesForCourseGeometry;
236
+
237
+ /**
238
+ * Wind data used to display the advantage line. Retrieved by a {@link GetWindInfoAction} execution and used in
239
+ * {@link #showAdvantageLine(Iterable, Date)}.
240
+ */
241
+ private WindInfoForRaceDTO lastCombinedWindTrackInfoDTO;
242
+
243
+ /**
244
+ * Manages the cached set of {@link GPSFixDTO}s for the boat positions as well as their graphical counterpart in the
245
+ * form of {@link Polyline}s.
246
+ */
247
+ private final FixesAndTails fixesAndTails;
248
+
249
+ /**
250
+ * html5 canvases used as boat display on the map
251
+ */
252
+ private final Map<CompetitorDTO, BoatOverlay> boatOverlays;
253
+
254
+ /**
255
+ * html5 canvases used for competitor info display on the map
256
+ */
257
+ private final Map<CompetitorDTO, CompetitorInfoOverlay> competitorInfoOverlays;
258
+
259
+ private SmallTransparentInfoOverlay countDownOverlay;
260
+
261
+ /**
262
+ * Map overlays with html5 canvas used to display wind sensors
263
+ */
264
+ private final Map<WindSource, WindSensorOverlay> windSensorOverlays;
265
+
266
+ /**
267
+ * Map overlays with html5 canvas used to display course marks including buoy zones
268
+ */
269
+ private final Map<String, CourseMarkOverlay> courseMarkOverlays;
270
+
271
+ private final Map<String, MarkDTO> markDTOs;
272
+
273
+ /**
274
+ * markers displayed in response to
275
+ * {@link SailingServiceAsync#getDouglasPoints(String, String, Map, Map, double, AsyncCallback)}
276
+ */
277
+ protected Set<Marker> douglasMarkers;
278
+
279
+ /**
280
+ * markers displayed in response to
281
+ * {@link SailingServiceAsync#getDouglasPoints(String, String, Map, Map, double, AsyncCallback)}
282
+ */
283
+ private Set<Marker> maneuverMarkers;
284
+
285
+ private Map<CompetitorDTO, List<ManeuverDTO>> lastManeuverResult;
286
+
287
+ private Map<CompetitorDTO, List<GPSFixDTO>> lastDouglasPeuckerResult;
288
+
289
+ private final CompetitorSelectionProvider competitorSelection;
290
+
291
+ private List<RegattaAndRaceIdentifier> selectedRaces;
292
+
293
+ /**
294
+ * Used to check if the first initial zoom to the mark markers was already done.
295
+ */
296
+ private boolean mapFirstZoomDone = false;
297
+
298
+ private final Timer timer;
299
+
300
+ private RaceTimesInfoDTO lastRaceTimesInfo;
301
+
302
+ private InfoWindow lastInfoWindow = null;
303
+
304
+ /**
305
+ * RPC calls may receive responses out of order if there are multiple calls in-flight at the same time. If the time
306
+ * slider is moved quickly it generates many requests for boat positions quickly after each other. Sometimes,
307
+ * responses for requests send later may return before the responses to all earlier requests have been received and
308
+ * processed. This counter is used to number the requests. When processing of a response for a later request has
309
+ * already begun, responses to earlier requests will be ignored.
310
+ */
311
+ private int boatPositionRequestIDCounter;
312
+
313
+ /**
314
+ * Corresponds to {@link #boatPositionRequestIDCounter}. As soon as the processing of a response for a request ID
315
+ * begins, this attribute is set to the ID. A response won't be processed if a later response is already being
316
+ * processed.
317
+ */
318
+ private int startedProcessingRequestID;
319
+
320
+ private RaceMapImageManager raceMapImageManager;
321
+
322
+ private final RaceMapSettings settings;
323
+
324
+ private final StringMessages stringMessages;
325
+
326
+ private boolean isMapInitialized;
327
+
328
+ private Date lastTimeChangeBeforeInitialization;
329
+
330
+ private int lastLegNumber;
331
+
332
+ /**
333
+ * The last quick ranks received from a call to {@link SailingServiceAsync#getQuickRanks(RaceIdentifier, Date, AsyncCallback)} upon
334
+ * the last {@link #timeChanged(Date, Date)} event. Therefore, the ranks listed here correspond to the {@link #timer}'s time.
335
+ */
336
+ private LinkedHashMap<CompetitorDTO, QuickRankDTO> quickRanks;
337
+
338
+ /**
339
+ * Taken from {@link RaceMapDataDTO#competitorsInOrderOfWindwardDistanceTraveledWithOneBasedLegNumber}; tells the
340
+ * windward distances traveled and the leg numbers in which the respective competitor is.
341
+ */
342
+ private LinkedHashMap<CompetitorDTO, Integer> competitorsInOrderOfWindwardDistanceTraveledWithOneBasedLegNumber;
343
+
344
+
345
+ private final CombinedWindPanel combinedWindPanel;
346
+
347
+ private final TrueNorthIndicatorPanel trueNorthIndicatorPanel;
348
+
349
+ private final AsyncActionsExecutor asyncActionsExecutor;
350
+
351
+ /**
352
+ * The map bounds as last received by map callbacks; used to determine whether to suppress the boat animation during zoom/pan
353
+ */
354
+ private LatLngBounds currentMapBounds; // bounds to which bounds-changed-handler compares
355
+ private int currentZoomLevel; // zoom-level to which bounds-changed-handler compares
356
+
357
+ private boolean autoZoomIn = false; // flags auto-zoom-in in progress
358
+ private boolean autoZoomOut = false; // flags auto-zoom-out in progress
359
+ private int autoZoomLevel; // zoom-level to which auto-zoom-in/-out is zooming
360
+ LatLngBounds autoZoomLatLngBounds; // bounds to which auto-zoom-in/-out is panning&zooming
361
+
362
+ private RaceSimulationOverlay simulationOverlay;
363
+ private WindStreamletsRaceboardOverlay streamletOverlay;
364
+ private final boolean showViewStreamlets;
365
+ private final boolean showViewStreamletColors;
366
+ private final boolean showViewSimulation;
367
+
368
+ private static final String GET_POLAR_CATEGORY = "getPolar";
369
+
370
+ /**
371
+ * Tells about the availability of polar / VPP data for this race. If available, the simulation feature can be
372
+ * offered to the user.
373
+ */
374
+ private boolean hasPolar;
375
+
376
+ private final RegattaAndRaceIdentifier raceIdentifier;
377
+
378
+ /**
379
+ * When the user requests wind-up display this may happen at a point where no mark positions are known or when
380
+ * no wind direction is known yet. In this case, this flag will be set, and when wind information or course mark
381
+ * positions are received later, this flag is checked, and if set, a {@link #updateCoordinateSystemFromSettings()}
382
+ * call is issued to make sure that the user's request for a new coordinate system is honored.
383
+ */
384
+ private boolean requiresCoordinateSystemUpdateWhenCoursePositionAndWindDirectionIsKnown;
385
+
386
+ private final boolean showMapControls;
387
+
388
+ /**
389
+ * Tells whether currently an auto-zoom is in progress; this is used particularly to keep the smooth CSS boat transitions
390
+ * active while auto-zooming whereas stopping them seems the better option for manual zooms.
391
+ */
392
+ private boolean autoZoomInProgress;
393
+
394
+ /**
395
+ * Tells whether currently an orientation change is in progress; this is required handle map events during the configuration of the map
396
+ * during an orientation change.
397
+ */
398
+ private boolean orientationChangeInProgress;
399
+
400
+ private final NumberFormat numberFormatOneDecimal = NumberFormat.getFormat("0.0");
401
+
402
+ public RaceMap(SailingServiceAsync sailingService, AsyncActionsExecutor asyncActionsExecutor,
403
+ ErrorReporter errorReporter, Timer timer, CompetitorSelectionProvider competitorSelection, StringMessages stringMessages,
404
+ boolean showMapControls, boolean showViewStreamlets, boolean showViewStreamletColors, boolean showViewSimulation,
405
+ RegattaAndRaceIdentifier raceIdentifier, CombinedWindPanelStyle combinedWindPanelStyle, boolean showHeaderPanel) {
406
+ this.setSize("100%", "100%");
407
+ this.showMapControls = showMapControls;
408
+ this.stringMessages = stringMessages;
409
+ this.sailingService = sailingService;
410
+ this.raceIdentifier = raceIdentifier;
411
+ this.asyncActionsExecutor = asyncActionsExecutor;
412
+ this.errorReporter = errorReporter;
413
+ this.timer = timer;
414
+ timer.addTimeListener(this);
415
+ raceMapImageManager = new RaceMapImageManager();
416
+ markDTOs = new HashMap<String, MarkDTO>();
417
+ courseSidelines = new HashMap<>();
418
+ courseMiddleLines = new HashMap<>();
419
+ infoOverlaysForLinesForCourseGeometry = new HashMap<>();
420
+ boatOverlays = new HashMap<CompetitorDTO, BoatOverlay>();
421
+ competitorInfoOverlays = new HashMap<CompetitorDTO, CompetitorInfoOverlay>();
422
+ windSensorOverlays = new HashMap<WindSource, WindSensorOverlay>();
423
+ courseMarkOverlays = new HashMap<String, CourseMarkOverlay>();
424
+ this.competitorSelection = competitorSelection;
425
+ competitorSelection.addCompetitorSelectionChangeListener(this);
426
+ settings = new RaceMapSettings();
427
+ coordinateSystem = new DelegateCoordinateSystem(new IdentityCoordinateSystem());
428
+ fixesAndTails = new FixesAndTails(coordinateSystem);
429
+ updateCoordinateSystemFromSettings();
430
+ lastTimeChangeBeforeInitialization = null;
431
+ isMapInitialized = false;
432
+ this.showViewStreamlets = showViewStreamlets;
433
+ this.showViewStreamletColors = showViewStreamletColors;
434
+ this.showViewSimulation = showViewSimulation;
435
+ this.hasPolar = false;
436
+ headerPanel = new FlowPanel();
437
+ headerPanel.setStyleName("RaceMap-HeaderPanel");
438
+ panelForLeftHeaderLabels = new AbsolutePanel();
439
+ panelForRightHeaderLabels = new AbsolutePanel();
440
+ initializeData(showMapControls, showHeaderPanel);
441
+ combinedWindPanel = new CombinedWindPanel(raceMapImageManager, combinedWindPanelStyle, stringMessages, coordinateSystem);
442
+ combinedWindPanel.setVisible(false);
443
+ trueNorthIndicatorPanel = new TrueNorthIndicatorPanel(this, raceMapImageManager, combinedWindPanelStyle, stringMessages, coordinateSystem);
444
+ trueNorthIndicatorPanel.setVisible(true);
445
+ orientationChangeInProgress = false;
446
+ }
447
+
448
+ /**
449
+ * The {@link WindDTO#dampenedTrueWindFromDeg} direction if {@link #lastCombinedWindTrackInfoDTO} has a
450
+ * {@link WindSourceType#COMBINED} source which has at least one fix recorded; <code>null</code> otherwise.
451
+ */
452
+ private Bearing getLastCombinedTrueWindFromDirection() {
453
+ if (lastCombinedWindTrackInfoDTO != null) {
454
+ for (Entry<WindSource, WindTrackInfoDTO> e : lastCombinedWindTrackInfoDTO.windTrackInfoByWindSource.entrySet()) {
455
+ if (e.getKey().getType() == WindSourceType.COMBINED) {
456
+ final List<WindDTO> windFixes = e.getValue().windFixes;
457
+ if (!windFixes.isEmpty()) {
458
+ return new DegreeBearingImpl(windFixes.get(0).dampenedTrueWindFromDeg);
459
+ }
460
+ }
461
+ }
462
+ }
463
+ return null;
464
+ }
465
+
466
+ private void updateCoordinateSystemFromSettings() {
467
+ final MapOptions mapOptions;
468
+ orientationChangeInProgress = true;
469
+ if (getSettings().isWindUp()) {
470
+ final Position centerOfCourse = getCenterOfCourse();
471
+ if (centerOfCourse != null) {
472
+ final Bearing lastCombinedTrueWindFromDirection = getLastCombinedTrueWindFromDirection();
473
+ if (lastCombinedTrueWindFromDirection != null) {
474
+ // new equator shall point 90deg right of the "from" wind direction to make wind come from top of map
475
+ coordinateSystem.setCoordinateSystem(new RotateAndTranslateCoordinateSystem(centerOfCourse,
476
+ lastCombinedTrueWindFromDirection.add(new DegreeBearingImpl(90))));
477
+ if (map != null) {
478
+ mapOptions = getMapOptions(showMapControls, /* wind-up */ true);
479
+ } else {
480
+ mapOptions = null;
481
+ }
482
+ requiresCoordinateSystemUpdateWhenCoursePositionAndWindDirectionIsKnown = false;
483
+ } else {
484
+ // register callback in case center of course and wind info becomes known
485
+ requiresCoordinateSystemUpdateWhenCoursePositionAndWindDirectionIsKnown = true;
486
+ mapOptions = null;
487
+ }
488
+ } else {
489
+ // register callback in case center of course and wind info becomes known
490
+ requiresCoordinateSystemUpdateWhenCoursePositionAndWindDirectionIsKnown = true;
491
+ mapOptions = null;
492
+ }
493
+ } else {
494
+ if (map != null) {
495
+ mapOptions = getMapOptions(showMapControls, /* wind-up */ false);
496
+ } else {
497
+ mapOptions = null;
498
+ }
499
+ coordinateSystem.setCoordinateSystem(new IdentityCoordinateSystem());
500
+ }
501
+ if (mapOptions != null) { // if no coordinate system change happened that affects an existing map, don't redraw
502
+ fixesAndTails.clearTails();
503
+ redraw();
504
+ // zooming and setting options while the event loop is still working doesn't work reliably; defer until event loop returns
505
+ Scheduler.get().scheduleDeferred(new ScheduledCommand() {
506
+ @Override
507
+ public void execute() {
508
+ if (map != null) {
509
+ map.setOptions(mapOptions);
510
+ // ensure zooming to what the settings tell, or defaults if what the settings tell isn't possible right now
511
+ mapFirstZoomDone = false;
512
+ trueNorthIndicatorPanel.redraw();
513
+ orientationChangeInProgress = false;
514
+ }
515
+ }
516
+ });
517
+ }
518
+ }
519
+
520
+ private void loadMapsAPIV3(final boolean showMapControls, final boolean showHeaderPanel) {
521
+ boolean sensor = true;
522
+
523
+ // load all the libs for use in the maps
524
+ ArrayList<LoadLibrary> loadLibraries = new ArrayList<LoadApi.LoadLibrary>();
525
+ loadLibraries.add(LoadLibrary.DRAWING);
526
+ loadLibraries.add(LoadLibrary.GEOMETRY);
527
+
528
+ Runnable onLoad = new Runnable() {
529
+ @Override
530
+ public void run() {
531
+ MapOptions mapOptions = getMapOptions(showMapControls, /* wind up */ false);
532
+ map = new MapWidget(mapOptions);
533
+ RaceMap.this.add(map, 0, 0);
534
+ if (showHeaderPanel) {
535
+ Image sapLogo = createSAPLogo();
536
+ RaceMap.this.add(sapLogo);
537
+ }
538
+ map.setControls(ControlPosition.LEFT_TOP, combinedWindPanel);
539
+ map.setControls(ControlPosition.LEFT_TOP, trueNorthIndicatorPanel);
540
+ adjustLeftControlsIndent();
541
+
542
+ RaceMap.this.raceMapImageManager.loadMapIcons(map);
543
+ map.setSize("100%", "100%");
544
+ map.addZoomChangeHandler(new ZoomChangeMapHandler() {
545
+ @Override
546
+ public void onEvent(ZoomChangeMapEvent event) {
547
+ if (!autoZoomIn && !autoZoomOut && !orientationChangeInProgress) {
548
+ // stop automatic zoom after a manual zoom event; automatic zoom in zoomMapToNewBounds will restore old settings
549
+ final List<RaceMapZoomSettings.ZoomTypes> emptyList = Collections.emptyList();
550
+ settings.getZoomSettings().setTypesToConsiderOnZoom(emptyList);
551
+ }
552
+ // TODO bug489 when in wind-up mode, avoid zooming out too far; perhaps zoom back in if zoomed out too far
553
+ }
554
+ });
555
+ map.addDragEndHandler(new DragEndMapHandler() {
556
+ @Override
557
+ public void onEvent(DragEndMapEvent event) {
558
+ // stop automatic zoom after a manual drag event
559
+ autoZoomIn = false;
560
+ autoZoomOut = false;
561
+ final List<RaceMapZoomSettings.ZoomTypes> emptyList = Collections.emptyList();
562
+ settings.getZoomSettings().setTypesToConsiderOnZoom(emptyList);
563
+ }
564
+ });
565
+ map.addIdleHandler(new IdleMapHandler() {
566
+ @Override
567
+ public void onEvent(IdleMapEvent event) {
568
+ // the "idle"-event is raised at the end of map-animations
569
+ if (autoZoomIn) {
570
+ // finalize zoom-in that was started with panTo() in zoomMapToNewBounds()
571
+ map.setZoom(autoZoomLevel);
572
+ autoZoomIn = false;
573
+ }
574
+ if (autoZoomOut) {
575
+ // finalize zoom-out that was started with setZoom() in zoomMapToNewBounds()
576
+ map.panTo(autoZoomLatLngBounds.getCenter());
577
+ autoZoomOut = false;
578
+ }
579
+ }
580
+ });
581
+ map.addBoundsChangeHandler(new BoundsChangeMapHandler() {
582
+ @Override
583
+ public void onEvent(BoundsChangeMapEvent event) {
584
+ int newZoomLevel = map.getZoom();
585
+ if (!isAutoZoomInProgress() && (newZoomLevel != currentZoomLevel)) {
586
+ removeTransitions();
587
+ }
588
+ if ((streamletOverlay != null) && !map.getBounds().equals(currentMapBounds)) {
589
+ streamletOverlay.onBoundsChanged(newZoomLevel != currentZoomLevel);
590
+ }
591
+ if ((simulationOverlay != null) && !map.getBounds().equals(currentMapBounds)) {
592
+ simulationOverlay.onBoundsChanged(newZoomLevel != currentZoomLevel);
593
+ }
594
+ currentMapBounds = map.getBounds();
595
+ currentZoomLevel = newZoomLevel;
596
+ headerPanel.getElement().getStyle().setWidth(map.getOffsetWidth(), Unit.PX);
597
+ }
598
+ });
599
+
600
+ // If there was a time change before the API was loaded, reset the time
601
+ if (lastTimeChangeBeforeInitialization != null) {
602
+ timeChanged(lastTimeChangeBeforeInitialization, null);
603
+ lastTimeChangeBeforeInitialization = null;
604
+ }
605
+ // Initialize streamlet canvas for wind visualization; it shouldn't be doing anything unless it's visible
606
+ streamletOverlay = new WindStreamletsRaceboardOverlay(getMap(), /* zIndex */ 0,
607
+ timer, raceIdentifier, sailingService, asyncActionsExecutor, stringMessages, coordinateSystem);
608
+ streamletOverlay.addToMap();
609
+ if (showViewStreamlets) {
610
+ streamletOverlay.setColors(showViewStreamletColors);
611
+ streamletOverlay.setVisible(true);
612
+ }
613
+
614
+ if (showViewSimulation) {
615
+ // determine availability of polar diagram
616
+ setHasPolar();
617
+ // initialize simulation canvas
618
+ simulationOverlay = new RaceSimulationOverlay(getMap(), /* zIndex */ 0, raceIdentifier, sailingService, stringMessages, asyncActionsExecutor, coordinateSystem);
619
+ simulationOverlay.addToMap();
620
+ simulationOverlay.setVisible(false);
621
+ }
622
+ if (showHeaderPanel) {
623
+ createHeaderPanel(map);
624
+ }
625
+ createSettingsButton(map);
626
+
627
+ // Data has been initialized
628
+ RaceMap.this.isMapInitialized = true;
629
+ RaceMap.this.redraw();
630
+ trueNorthIndicatorPanel.redraw();
631
+ showAdditionalControls(map);
632
+ }
633
+ };
634
+ LoadApi.go(onLoad, loadLibraries, sensor, "key="+GoogleMapAPIKey.V3_APIKey);
635
+ }
636
+
637
+ /**
638
+ * Subclasses may define additional stuff to be shown on the map.
639
+ */
640
+ protected void showAdditionalControls(MapWidget map) {
641
+ }
642
+
643
+ private void setHasPolar() {
644
+ GetPolarAction getPolar = new GetPolarAction(sailingService, raceIdentifier);
645
+ asyncActionsExecutor.execute(getPolar, GET_POLAR_CATEGORY,
646
+ new MarkedAsyncCallback<>(new AsyncCallback<Boolean>() {
647
+ @Override
648
+ public void onFailure(Throwable caught) {
649
+ errorReporter.reportError(stringMessages.errorDeterminingPolarAvailability(
650
+ raceIdentifier.getRaceName(), caught.getMessage()), /* silent */ true);
651
+ }
652
+
653
+ @Override
654
+ public void onSuccess(Boolean result) {
655
+ // store results
656
+ hasPolar = result.booleanValue();
657
+ }
658
+ }));
659
+
660
+ }
661
+
662
+ /**
663
+ * Creates a header panel where additional information can be displayed by using
664
+ * {@link #getLeftHeaderPanel()} or {@link #getRightHeaderPanel()}.
665
+ *
666
+ * This panel is transparent and configured in such a way that it moves other controls
667
+ * down by its height. To achieve the goal of not having added widgets transparent
668
+ * this widget consists of two parts: First one is the transparent panel and the
669
+ * second one is the panel for the controls. The controls then need to moved onto
670
+ * the panel by using CSS.
671
+ */
672
+ private void createHeaderPanel(MapWidget map) {
673
+ // we need a panel that does not have any transparency to have the
674
+ // labels shown in the right color. This panel also needs to have
675
+ // a higher z-index than other elements on the map
676
+ map.setControls(ControlPosition.TOP_LEFT, panelForLeftHeaderLabels);
677
+ panelForLeftHeaderLabels.getElement().getParentElement().getStyle().setProperty("zIndex", "1");
678
+ panelForLeftHeaderLabels.getElement().getStyle().setProperty("overflow", "visible");
679
+ add(panelForRightHeaderLabels);
680
+ panelForRightHeaderLabels.getElement().getStyle().setProperty("zIndex", "1");
681
+ panelForRightHeaderLabels.getElement().getStyle().setProperty("overflow", "visible");
682
+ // need to initialize size before css kicks in to make sure
683
+ // that controls get positioned right
684
+ headerPanel.getElement().getStyle().setHeight(60, Unit.PX);
685
+ headerPanel.getElement().getStyle().setWidth(map.getOffsetWidth(), Unit.PX);
686
+ // some sort of hack: not positioning TOP_LEFT because then the
687
+ // controls at RIGHT would not get the correct top setting
688
+ map.setControls(ControlPosition.TOP_RIGHT, headerPanel);
689
+ }
690
+
691
+ private void createSettingsButton(MapWidget map) {
692
+ final Component<RaceMapSettings> component = this;
693
+ Button settingsButton = new Button();
694
+ settingsButton.setStyleName("gwt-MapSettingsButton");
695
+ settingsButton.setTitle(stringMessages.settings());
696
+ settingsButton.addClickHandler(new ClickHandler() {
697
+ @Override
698
+ public void onClick(ClickEvent event) {
699
+ new SettingsDialog<RaceMapSettings>(component, stringMessages).show();
700
+ }
701
+ });
702
+ map.setControls(ControlPosition.RIGHT_TOP, settingsButton);
703
+ }
704
+
705
+ private void removeTransitions() {
706
+ // remove the canvas animations for boats
707
+ for (BoatOverlay boatOverlay : RaceMap.this.getBoatOverlays().values()) {
708
+ boatOverlay.removeCanvasPositionAndRotationTransition();
709
+ }
710
+ // remove the canvas animations for the info overlays of the selected boats
711
+ for (CompetitorInfoOverlay infoOverlay : competitorInfoOverlays.values()) {
712
+ infoOverlay.removeCanvasPositionAndRotationTransition();
713
+ }
714
+ }
715
+
716
+ public void redraw() {
717
+ timeChanged(timer.getTime(), null);
718
+ }
719
+
720
+ Map<CompetitorDTO, BoatOverlay> getBoatOverlays() {
721
+ return Collections.unmodifiableMap(boatOverlays);
722
+ }
723
+
724
+ public MapWidget getMap() {
725
+ return map;
726
+ }
727
+
728
+ /**
729
+ * @return the Panel where labels or other controls for the header can be positioned
730
+ */
731
+ public AbsolutePanel getLeftHeaderPanel() {
732
+ return panelForLeftHeaderLabels;
733
+ }
734
+
735
+ public AbsolutePanel getRightHeaderPanel() {
736
+ return panelForRightHeaderLabels;
737
+ }
738
+
739
+ @Override
740
+ public void onRaceSelectionChange(List<RegattaAndRaceIdentifier> selectedRaces) {
741
+ mapFirstZoomDone = false;
742
+ // TODO bug 494: reset zoom settings to user preferences
743
+ this.selectedRaces = selectedRaces;
744
+ }
745
+
746
+ @Override
747
+ public void raceTimesInfosReceived(Map<RegattaAndRaceIdentifier, RaceTimesInfoDTO> raceTimesInfos, long clientTimeWhenRequestWasSent, Date serverTimeDuringRequest, long clientTimeWhenResponseWasReceived) {
748
+ timer.adjustClientServerOffset(clientTimeWhenRequestWasSent, serverTimeDuringRequest, clientTimeWhenResponseWasReceived);
749
+ this.lastRaceTimesInfo = raceTimesInfos.get(selectedRaces.get(0));
750
+ }
751
+
752
+ /**
753
+ * In {@link PlayModes#Live live mode}, when {@link #loadCompleteLeaderboard(Date) loading the leaderboard contents}, <code>null</code>
754
+ * is used as time point. The condition for this is encapsulated in this method so others can find out. For example, when a time change
755
+ * is signaled due to local offset / delay adjustments, no additional call to {@link #loadCompleteLeaderboard(Date)} would be required
756
+ * as <code>null</code> will be passed in any case, not being affected by local time offsets.
757
+ */
758
+ private boolean useNullAsTimePoint() {
759
+ return timer.getPlayMode() == PlayModes.Live;
760
+ }
761
+
762
+ @Override
763
+ public void timeChanged(final Date newTime, final Date oldTime) {
764
+ if (newTime != null && isMapInitialized) {
765
+ if (selectedRaces != null && !selectedRaces.isEmpty()) {
766
+ RegattaAndRaceIdentifier race = selectedRaces.get(selectedRaces.size() - 1);
767
+ final Iterable<CompetitorDTO> competitorsToShow = getCompetitorsToShow();
768
+
769
+ if (race != null) {
770
+ final com.sap.sse.common.Util.Triple<Map<CompetitorDTO, Date>, Map<CompetitorDTO, Date>, Map<CompetitorDTO, Boolean>> fromAndToAndOverlap =
771
+ fixesAndTails.computeFromAndTo(newTime, competitorsToShow, settings.getEffectiveTailLengthInMilliseconds());
772
+ int requestID = ++boatPositionRequestIDCounter;
773
+ // For those competitors for which the tails don't overlap (and therefore will be replaced by the new tail coming from the server)
774
+ // we expect some potential delay in computing the full tail. Therefore, in those cases we fire two requests: one fetching only the
775
+ // boat positions at newTime with zero tail length; and another one fetching everything else.
776
+ GetRaceMapDataAction getRaceMapDataForAllOverlappingAndTipsOfNonOverlapping = getRaceMapDataForAllOverlappingAndTipsOfNonOverlapping(fromAndToAndOverlap, race, newTime);
777
+ if (getRaceMapDataForAllOverlappingAndTipsOfNonOverlapping != null) {
778
+ asyncActionsExecutor.execute(getRaceMapDataForAllOverlappingAndTipsOfNonOverlapping, GET_RACE_MAP_DATA_CATEGORY,
779
+ getRaceMapDataCallback(oldTime, newTime, fromAndToAndOverlap.getC(), competitorsToShow, requestID));
780
+ requestID = ++boatPositionRequestIDCounter;
781
+ }
782
+ // next, do the full thing; being the later call, if request throttling kicks in, the later call
783
+ // supersedes the earlier call which may get dropped then
784
+ GetRaceMapDataAction getRaceMapDataAction = new GetRaceMapDataAction(sailingService, competitorSelection.getAllCompetitors(), race,
785
+ useNullAsTimePoint() ? null : newTime, fromAndToAndOverlap.getA(), fromAndToAndOverlap.getB(), /* extrapolate */ true, (settings.isShowSimulationOverlay() ? simulationOverlay.getLegIdentifier() : null));
786
+ asyncActionsExecutor.execute(getRaceMapDataAction, GET_RACE_MAP_DATA_CATEGORY,
787
+ getRaceMapDataCallback(oldTime, newTime, fromAndToAndOverlap.getC(), competitorsToShow, requestID));
788
+
789
+ // draw the wind into the map, get the combined wind
790
+ List<String> windSourceTypeNames = new ArrayList<String>();
791
+ windSourceTypeNames.add(WindSourceType.EXPEDITION.name());
792
+ windSourceTypeNames.add(WindSourceType.COMBINED.name());
793
+ GetWindInfoAction getWindInfoAction = new GetWindInfoAction(sailingService, race, newTime, 1000L, 1, windSourceTypeNames,
794
+ /* onlyUpToNewestEvent==false means get us any data we can get by a best effort */ false);
795
+ asyncActionsExecutor.execute(getWindInfoAction, GET_WIND_DATA_CATEGORY, new AsyncCallback<WindInfoForRaceDTO>() {
796
+ @Override
797
+ public void onFailure(Throwable caught) {
798
+ errorReporter.reportError("Error obtaining wind information: " + caught.getMessage(), true /*silentMode */);
799
+ }
800
+
801
+ @Override
802
+ public void onSuccess(WindInfoForRaceDTO windInfo) {
803
+ List<com.sap.sse.common.Util.Pair<WindSource, WindTrackInfoDTO>> windSourcesToShow = new ArrayList<com.sap.sse.common.Util.Pair<WindSource, WindTrackInfoDTO>>();
804
+ if (windInfo != null) {
805
+ lastCombinedWindTrackInfoDTO = windInfo;
806
+ showAdvantageLine(competitorsToShow, newTime);
807
+ for (WindSource windSource : windInfo.windTrackInfoByWindSource.keySet()) {
808
+ WindTrackInfoDTO windTrackInfoDTO = windInfo.windTrackInfoByWindSource.get(windSource);
809
+ switch (windSource.getType()) {
810
+ case EXPEDITION:
811
+ // we filter out measured wind sources with vary low confidence
812
+ if (windTrackInfoDTO.minWindConfidence > 0.0001) {
813
+ windSourcesToShow.add(new com.sap.sse.common.Util.Pair<WindSource, WindTrackInfoDTO>(windSource, windTrackInfoDTO));
814
+ }
815
+ break;
816
+ case COMBINED:
817
+ showCombinedWindOnMap(windSource, windTrackInfoDTO);
818
+ if (requiresCoordinateSystemUpdateWhenCoursePositionAndWindDirectionIsKnown) {
819
+ updateCoordinateSystemFromSettings();
820
+ }
821
+ break;
822
+ default:
823
+ // Which wind sources are requested is defined in a list above this
824
+ // action. So we throw here an exception to notice a missing source.
825
+ throw new UnsupportedOperationException(
826
+ "There is currently no support for the enum value '"
827
+ + windSource.getType() + "' in this method.");
828
+ }
829
+ }
830
+ }
831
+ showWindSensorsOnMap(windSourcesToShow);
832
+ }
833
+ });
834
+ }
835
+ }
836
+ }
837
+ }
838
+
839
+ /**
840
+ * We assume that overlapping segments usually don't require a lot of loading time as the most typical case will be to update a longer
841
+ * tail with a few new fixes that were received since the last time tick. Non-overlapping position requests typically occur for the
842
+ * first request when no fix at all is known for the competitor yet, and when the user has radically moved the time slider to some
843
+ * other time such that given the current tail length setting the new tail segment does not overlap with the old one, requiring a full
844
+ * load of the entire tail data for that competitor.<p>
845
+ *
846
+ * For the non-overlapping requests, this method creates a separate request which only loads boat positions, quick ranks, sidelines and
847
+ * mark positions for the zero-length interval at <code>newTime</code>, assuming that this will work fairly fast and in particular in
848
+ * O(1) time regardless of tail length, compared to fetching the entire tail for all competitors.
849
+ */
850
+ private GetRaceMapDataAction getRaceMapDataForAllOverlappingAndTipsOfNonOverlapping(
851
+ Triple<Map<CompetitorDTO, Date>, Map<CompetitorDTO, Date>, Map<CompetitorDTO, Boolean>> fromAndToAndOverlap,
852
+ RegattaAndRaceIdentifier race, Date newTime) {
853
+ Map<CompetitorDTO, Date> fromTimes = new HashMap<>();
854
+ Map<CompetitorDTO, Date> toTimes = new HashMap<>();
855
+ for (Map.Entry<CompetitorDTO, Boolean> e : fromAndToAndOverlap.getC().entrySet()) {
856
+ if (!e.getValue()) {
857
+ // no overlap; add competitor to request
858
+ fromTimes.put(e.getKey(), newTime);
859
+ toTimes.put(e.getKey(), newTime);
860
+ }
861
+ }
862
+ final GetRaceMapDataAction result;
863
+ if (!fromTimes.isEmpty()) {
864
+ result = new GetRaceMapDataAction(sailingService, competitorSelection.getAllCompetitors(),
865
+ race, useNullAsTimePoint() ? null : newTime, fromTimes, toTimes, /* extrapolate */true, (settings.isShowSimulationOverlay() ? simulationOverlay.getLegIdentifier() : null));
866
+ } else {
867
+ result = null;
868
+ }
869
+ return result;
870
+ }
871
+
872
+ private AsyncCallback<RaceMapDataDTO> getRaceMapDataCallback(
873
+ final Date oldTime,
874
+ final Date newTime,
875
+ final Map<CompetitorDTO, Boolean> hasTailOverlapForCompetitor,
876
+ final Iterable<CompetitorDTO> competitorsToShow, final int requestID) {
877
+ return new AsyncCallback<RaceMapDataDTO>() {
878
+ @Override
879
+ public void onFailure(Throwable caught) {
880
+ errorReporter.reportError("Error obtaining racemap data: " + caught.getMessage(), true /*silentMode */);
881
+ }
882
+
883
+ @Override
884
+ public void onSuccess(RaceMapDataDTO raceMapDataDTO) {
885
+ if (map != null && raceMapDataDTO != null) {
886
+ quickRanks = raceMapDataDTO.quickRanks;
887
+ competitorsInOrderOfWindwardDistanceTraveledWithOneBasedLegNumber =
888
+ raceMapDataDTO.competitorsInOrderOfWindwardDistanceTraveledWithOneBasedLegNumber;
889
+ if (showViewSimulation && settings.isShowSimulationOverlay()) {
890
+ lastLegNumber = raceMapDataDTO.coursePositions.currentLegNumber;
891
+ simulationOverlay.updateLeg(Math.max(lastLegNumber,1), /* clearCanvas */ false, raceMapDataDTO.simulationResultVersion);
892
+ }
893
+ // process response only if not received out of order
894
+ if (startedProcessingRequestID < requestID) {
895
+ startedProcessingRequestID = requestID;
896
+ // Do boat specific actions
897
+ Map<CompetitorDTO, List<GPSFixDTO>> boatData = raceMapDataDTO.boatPositions;
898
+ long timeForPositionTransitionMillis = calculateTimeForPositionTransition(newTime, oldTime);
899
+ fixesAndTails.updateFixes(boatData, hasTailOverlapForCompetitor, RaceMap.this, timeForPositionTransitionMillis);
900
+ showBoatsOnMap(newTime, timeForPositionTransitionMillis, getCompetitorsToShow());
901
+ showCompetitorInfoOnMap(newTime, timeForPositionTransitionMillis, competitorSelection.getSelectedFilteredCompetitors());
902
+ if (douglasMarkers != null) {
903
+ removeAllMarkDouglasPeuckerpoints();
904
+ }
905
+ if (maneuverMarkers != null) {
906
+ removeAllManeuverMarkers();
907
+ }
908
+
909
+ // Do mark specific actions
910
+ showCourseMarksOnMap(raceMapDataDTO.coursePositions);
911
+ if (requiresCoordinateSystemUpdateWhenCoursePositionAndWindDirectionIsKnown) {
912
+ updateCoordinateSystemFromSettings();
913
+ }
914
+ showCourseSidelinesOnMap(raceMapDataDTO.courseSidelines);
915
+ showStartAndFinishAndCourseMiddleLines(raceMapDataDTO.coursePositions);
916
+ showStartLineToFirstMarkTriangle(raceMapDataDTO.coursePositions);
917
+ // even though the wind data is retrieved by a separate call, re-draw the advantage line because it needs to
918
+ // adjust to new boat positions
919
+ showAdvantageLine(competitorsToShow, newTime);
920
+
921
+ // Rezoom the map
922
+ LatLngBounds zoomToBounds = null;
923
+ if (!settings.getZoomSettings().containsZoomType(ZoomTypes.NONE)) { // Auto zoom if setting is not manual
924
+ zoomToBounds = settings.getZoomSettings().getNewBounds(RaceMap.this);
925
+ if (zoomToBounds == null && !mapFirstZoomDone) {
926
+ zoomToBounds = getDefaultZoomBounds(); // the user-specified zoom couldn't find what it was looking for; try defaults once
927
+ }
928
+ } else if (!mapFirstZoomDone) { // Zoom once to the marks if marks exist
929
+ zoomToBounds = new CourseMarksBoundsCalculator().calculateNewBounds(RaceMap.this);
930
+ if (zoomToBounds == null) {
931
+ zoomToBounds = getDefaultZoomBounds(); // use default zoom, e.g.,
932
+ }
933
+ /*
934
+ * Reset the mapZoomedOrPannedSinceLastRaceSelection: In spite of the fact that
935
+ * the map was just zoomed to the bounds of the marks, it was not a zoom or pan
936
+ * triggered by the user. As a consequence the
937
+ * mapZoomedOrPannedSinceLastRaceSelection option has to reset again.
938
+ */
939
+ }
940
+ zoomMapToNewBounds(zoomToBounds);
941
+ mapFirstZoomDone = true;
942
+ }
943
+ } else {
944
+ lastTimeChangeBeforeInitialization = newTime;
945
+ }
946
+ }
947
+ };
948
+ }
949
+
950
+ private void showCourseSidelinesOnMap(List<SidelineDTO> sidelinesDTOs) {
951
+ if (map != null && sidelinesDTOs != null ) {
952
+ Map<SidelineDTO, Polygon> toRemoveSidelines = new HashMap<SidelineDTO, Polygon>(courseSidelines);
953
+ for (SidelineDTO sidelineDTO : sidelinesDTOs) {
954
+ if (sidelineDTO.getMarks().size() == 2) { // right now we only support sidelines with 2 marks
955
+ Polygon sideline = courseSidelines.get(sidelineDTO);
956
+ LatLng[] sidelinePoints = new LatLng[sidelineDTO.getMarks().size()];
957
+ int i=0;
958
+ for (MarkDTO sidelineMark : sidelineDTO.getMarks()) {
959
+ sidelinePoints[i] = coordinateSystem.toLatLng(sidelineMark.position);
960
+ i++;
961
+ }
962
+ if (sideline == null) {
963
+ PolygonOptions options = PolygonOptions.newInstance();
964
+ options.setClickable(true);
965
+ options.setStrokeColor("#0000FF");
966
+ options.setStrokeWeight(1);
967
+ options.setStrokeOpacity(1.0);
968
+ options.setFillColor(null);
969
+ options.setFillOpacity(1.0);
970
+
971
+ sideline = Polygon.newInstance(options);
972
+ MVCArray<LatLng> pointsAsArray = MVCArray.newInstance(sidelinePoints);
973
+ sideline.setPath(pointsAsArray);
974
+
975
+ sideline.addMouseOverHandler(new MouseOverMapHandler() {
976
+ @Override
977
+ public void onEvent(MouseOverMapEvent event) {
978
+ map.setTitle(stringMessages.sideline());
979
+ }
980
+ });
981
+ sideline.addMouseOutMoveHandler(new MouseOutMapHandler() {
982
+ @Override
983
+ public void onEvent(MouseOutMapEvent event) {
984
+ map.setTitle("");
985
+ }
986
+ });
987
+ courseSidelines.put(sidelineDTO, sideline);
988
+ sideline.setMap(map);
989
+ } else {
990
+ sideline.getPath().removeAt(1);
991
+ sideline.getPath().removeAt(0);
992
+ sideline.getPath().insertAt(0, sidelinePoints[0]);
993
+ sideline.getPath().insertAt(1, sidelinePoints[1]);
994
+ toRemoveSidelines.remove(sidelineDTO);
995
+ }
996
+ }
997
+ }
998
+ for (SidelineDTO toRemoveSideline : toRemoveSidelines.keySet()) {
999
+ Polygon sideline = courseSidelines.remove(toRemoveSideline);
1000
+ sideline.setMap(null);
1001
+ }
1002
+ }
1003
+ }
1004
+
1005
+ private void showCourseMarksOnMap(CoursePositionsDTO courseDTO) {
1006
+ if (map != null && courseDTO != null) {
1007
+ WaypointDTO endWaypointForCurrentLegNumber = null;
1008
+ if(courseDTO.currentLegNumber > 0 && courseDTO.currentLegNumber <= courseDTO.totalLegsCount) {
1009
+ endWaypointForCurrentLegNumber = courseDTO.getEndWaypointForLegNumber(courseDTO.currentLegNumber);
1010
+ }
1011
+
1012
+ Map<String, CourseMarkOverlay> toRemoveCourseMarks = new HashMap<String, CourseMarkOverlay>(courseMarkOverlays);
1013
+ if (courseDTO.marks != null) {
1014
+ for (MarkDTO markDTO : courseDTO.marks) {
1015
+ boolean isSelected = false;
1016
+ if (endWaypointForCurrentLegNumber != null && Util.contains(endWaypointForCurrentLegNumber.controlPoint.getMarks(), markDTO)) {
1017
+ isSelected = true;
1018
+ }
1019
+ CourseMarkOverlay courseMarkOverlay = courseMarkOverlays.get(markDTO.getName());
1020
+ if (courseMarkOverlay == null) {
1021
+ courseMarkOverlay = createCourseMarkOverlay(RaceMapOverlaysZIndexes.COURSEMARK_ZINDEX, markDTO);
1022
+ courseMarkOverlay.setShowBuoyZone(settings.getHelpLinesSettings().isVisible(HelpLineTypes.BUOYZONE));
1023
+ courseMarkOverlay.setBuoyZoneRadiusInMeter(settings.getBuoyZoneRadiusInMeters());
1024
+ courseMarkOverlay.setSelected(isSelected);
1025
+ courseMarkOverlays.put(markDTO.getName(), courseMarkOverlay);
1026
+ markDTOs.put(markDTO.getName(), markDTO);
1027
+ courseMarkOverlay.addToMap();
1028
+ } else {
1029
+ courseMarkOverlay.setMarkPosition(markDTO.position);
1030
+ courseMarkOverlay.setShowBuoyZone(settings.getHelpLinesSettings().isVisible(HelpLineTypes.BUOYZONE));
1031
+ courseMarkOverlay.setBuoyZoneRadiusInMeter(settings.getBuoyZoneRadiusInMeters());
1032
+ courseMarkOverlay.setSelected(isSelected);
1033
+ courseMarkOverlay.draw();
1034
+ toRemoveCourseMarks.remove(markDTO.getName());
1035
+ }
1036
+ }
1037
+ }
1038
+ for (String toRemoveMarkName : toRemoveCourseMarks.keySet()) {
1039
+ CourseMarkOverlay removedOverlay = courseMarkOverlays.remove(toRemoveMarkName);
1040
+ if(removedOverlay != null) {
1041
+ removedOverlay.removeFromMap();
1042
+ }
1043
+ }
1044
+ }
1045
+ }
1046
+
1047
+ /**
1048
+ * Based on the mark positions in {@link #courseMarkOverlays}' values this method determines the center of gravity of these marks'
1049
+ * {@link CourseMarkOverlay#getPosition() positions}.
1050
+ */
1051
+ private Position getCenterOfCourse() {
1052
+ ScalablePosition center = null;
1053
+ int count = 0;
1054
+ for (CourseMarkOverlay markOverlay : courseMarkOverlays.values()) {
1055
+ ScalablePosition markPosition = new ScalablePosition(markOverlay.getPosition());
1056
+ if (center == null) {
1057
+ center = markPosition;
1058
+ } else {
1059
+ center.add(markPosition);
1060
+ }
1061
+ count++;
1062
+ }
1063
+ return center == null ? null : center.divide(count);
1064
+ }
1065
+
1066
+ private void showCombinedWindOnMap(WindSource windSource, WindTrackInfoDTO windTrackInfoDTO) {
1067
+ if (map != null) {
1068
+ combinedWindPanel.setWindInfo(windTrackInfoDTO, windSource);
1069
+ combinedWindPanel.redraw();
1070
+ }
1071
+ }
1072
+
1073
+ private void showWindSensorsOnMap(List<com.sap.sse.common.Util.Pair<WindSource, WindTrackInfoDTO>> windSensorsList) {
1074
+ if (map != null) {
1075
+ Set<WindSource> toRemoveWindSources = new HashSet<WindSource>(windSensorOverlays.keySet());
1076
+ for (com.sap.sse.common.Util.Pair<WindSource, WindTrackInfoDTO> windSourcePair : windSensorsList) {
1077
+ WindSource windSource = windSourcePair.getA();
1078
+ WindTrackInfoDTO windTrackInfoDTO = windSourcePair.getB();
1079
+
1080
+ WindSensorOverlay windSensorOverlay = windSensorOverlays.get(windSource);
1081
+ if (windSensorOverlay == null) {
1082
+ windSensorOverlay = createWindSensorOverlay(RaceMapOverlaysZIndexes.WINDSENSOR_ZINDEX, windSource, windTrackInfoDTO);
1083
+ windSensorOverlays.put(windSource, windSensorOverlay);
1084
+ windSensorOverlay.addToMap();
1085
+ } else {
1086
+ windSensorOverlay.setWindInfo(windTrackInfoDTO, windSource);
1087
+ windSensorOverlay.draw();
1088
+ toRemoveWindSources.remove(windSource);
1089
+ }
1090
+ }
1091
+ for (WindSource toRemoveWindSource : toRemoveWindSources) {
1092
+ WindSensorOverlay removedWindSensorOverlay = windSensorOverlays.remove(toRemoveWindSource);
1093
+ if (removedWindSensorOverlay != null) {
1094
+ removedWindSensorOverlay.removeFromMap();
1095
+ }
1096
+ }
1097
+ }
1098
+ }
1099
+
1100
+ private void showCompetitorInfoOnMap(final Date newTime, final long timeForPositionTransitionMillis, final Iterable<CompetitorDTO> competitorsToShow) {
1101
+ if (map != null) {
1102
+ if (settings.isShowSelectedCompetitorsInfo()) {
1103
+ Set<CompetitorDTO> toRemoveCompetorInfoOverlays = new HashSet<CompetitorDTO>(
1104
+ competitorInfoOverlays.keySet());
1105
+ for (CompetitorDTO competitorDTO : competitorsToShow) {
1106
+ if (fixesAndTails.hasFixesFor(competitorDTO)) {
1107
+ GPSFixDTO lastBoatFix = getBoatFix(competitorDTO, newTime);
1108
+ if (lastBoatFix != null) {
1109
+ CompetitorInfoOverlay competitorInfoOverlay = competitorInfoOverlays.get(competitorDTO);
1110
+ if (competitorInfoOverlay == null) {
1111
+ competitorInfoOverlay = createCompetitorInfoOverlay(RaceMapOverlaysZIndexes.INFO_OVERLAY_ZINDEX, competitorDTO);
1112
+ competitorInfoOverlays.put(competitorDTO, competitorInfoOverlay);
1113
+ competitorInfoOverlay.setPosition(lastBoatFix.position, timeForPositionTransitionMillis);
1114
+ competitorInfoOverlay.addToMap();
1115
+ } else {
1116
+ competitorInfoOverlay.setPosition(lastBoatFix.position, timeForPositionTransitionMillis);
1117
+ competitorInfoOverlay.draw();
1118
+ }
1119
+ toRemoveCompetorInfoOverlays.remove(competitorDTO);
1120
+ }
1121
+ }
1122
+ }
1123
+ for (CompetitorDTO toRemoveCompetorDTO : toRemoveCompetorInfoOverlays) {
1124
+ CompetitorInfoOverlay competitorInfoOverlay = competitorInfoOverlays.get(toRemoveCompetorDTO);
1125
+ competitorInfoOverlay.removeFromMap();
1126
+ competitorInfoOverlays.remove(toRemoveCompetorDTO);
1127
+ }
1128
+ } else {
1129
+ // remove all overlays
1130
+ for (CompetitorInfoOverlay competitorInfoOverlay : competitorInfoOverlays.values()) {
1131
+ competitorInfoOverlay.removeFromMap();
1132
+ }
1133
+ competitorInfoOverlays.clear();
1134
+ }
1135
+ }
1136
+ }
1137
+
1138
+ private long calculateTimeForPositionTransition(final Date newTime, final Date oldTime) {
1139
+ final long timeForPositionTransitionMillis;
1140
+ boolean hasTimeJumped = oldTime != null && Math.abs(oldTime.getTime() - newTime.getTime()) > 3*timer.getRefreshInterval();
1141
+ if (timer.getPlayState() == PlayStates.Playing && !hasTimeJumped) {
1142
+ // choose 130% of the refresh interval as transition period to make it unlikely that the transition
1143
+ // stops before the next update has been received
1144
+ timeForPositionTransitionMillis = 1300 * timer.getRefreshInterval() / 1000;
1145
+ } else {
1146
+ timeForPositionTransitionMillis = -1; // -1 means 'no transition
1147
+ }
1148
+ return timeForPositionTransitionMillis;
1149
+ }
1150
+
1151
+ private void showBoatsOnMap(final Date newTime, final long timeForPositionTransitionMillis, final Iterable<CompetitorDTO> competitorsToShow) {
1152
+ if (map != null) {
1153
+ Date tailsFromTime = new Date(newTime.getTime() - settings.getEffectiveTailLengthInMilliseconds());
1154
+ Date tailsToTime = newTime;
1155
+ Set<CompetitorDTO> competitorDTOsOfUnusedTails = new HashSet<CompetitorDTO>(fixesAndTails.getCompetitorsWithTails());
1156
+ Set<CompetitorDTO> competitorDTOsOfUnusedBoatCanvases = new HashSet<CompetitorDTO>(boatOverlays.keySet());
1157
+ for (CompetitorDTO competitorDTO : competitorsToShow) {
1158
+ if (fixesAndTails.hasFixesFor(competitorDTO)) {
1159
+ Polyline tail = fixesAndTails.getTail(competitorDTO);
1160
+ if (tail == null) {
1161
+ tail = fixesAndTails.createTailAndUpdateIndices(competitorDTO, tailsFromTime, tailsToTime, this);
1162
+ } else {
1163
+ fixesAndTails.updateTail(tail, competitorDTO, tailsFromTime, tailsToTime,
1164
+ (int) (timeForPositionTransitionMillis==-1?-1:timeForPositionTransitionMillis/2));
1165
+ competitorDTOsOfUnusedTails.remove(competitorDTO);
1166
+ PolylineOptions newOptions = createTailStyle(competitorDTO, displayHighlighted(competitorDTO));
1167
+ tail.setOptions(newOptions);
1168
+ }
1169
+ boolean usedExistingBoatCanvas = updateBoatCanvasForCompetitor(competitorDTO, newTime, timeForPositionTransitionMillis);
1170
+ if (usedExistingBoatCanvas) {
1171
+ competitorDTOsOfUnusedBoatCanvases.remove(competitorDTO);
1172
+ }
1173
+ }
1174
+ }
1175
+ for (CompetitorDTO unusedBoatCanvasCompetitorDTO : competitorDTOsOfUnusedBoatCanvases) {
1176
+ BoatOverlay boatCanvas = boatOverlays.get(unusedBoatCanvasCompetitorDTO);
1177
+ boatCanvas.removeFromMap();
1178
+ boatOverlays.remove(unusedBoatCanvasCompetitorDTO);
1179
+ }
1180
+ for (CompetitorDTO unusedTailCompetitorDTO : competitorDTOsOfUnusedTails) {
1181
+ fixesAndTails.removeTail(unusedTailCompetitorDTO);
1182
+ }
1183
+ }
1184
+ }
1185
+
1186
+ /**
1187
+ * This algorithm is limited to distances such that dlon < pi/2, i.e., those that extend around less than one
1188
+ * quarter of the circumference of the earth in longitude. A completely general, but more complicated algorithm is
1189
+ * necessary if greater distances are allowed.
1190
+ */
1191
+ public LatLng calculatePositionAlongRhumbline(LatLng position, double bearingDeg, double distanceInKm) {
1192
+ double distianceRad = distanceInKm / 6371.0; // r = 6371 means earth's radius in km
1193
+ double lat1 = position.getLatitude() / 180. * Math.PI;
1194
+ double lon1 = position.getLongitude() / 180. * Math.PI;
1195
+ double bearingRad = bearingDeg / 180. * Math.PI;
1196
+ double lat2 = Math.asin(Math.sin(lat1) * Math.cos(distianceRad) +
1197
+ Math.cos(lat1) * Math.sin(distianceRad) * Math.cos(bearingRad));
1198
+ double lon2 = lon1 + Math.atan2(Math.sin(bearingRad)*Math.sin(distianceRad)*Math.cos(lat1),
1199
+ Math.cos(distianceRad)-Math.sin(lat1)*Math.sin(lat2));
1200
+ lon2 = (lon2+3*Math.PI) % (2*Math.PI) - Math.PI; // normalize to -180..+180�
1201
+ // position is already in LatLng space, so no mapping through coordinateSystem is required here
1202
+ return LatLng.newInstance(lat2 / Math.PI * 180., lon2 / Math.PI * 180.);
1203
+ }
1204
+
1205
+ /**
1206
+ * Returns a pair whose first component is the leg number (one-based) of the competitor returned as the second component.
1207
+ */
1208
+ private com.sap.sse.common.Util.Pair<Integer, CompetitorDTO> getFarthestAheadVisibleCompetitorWithOneBasedLegNumber(
1209
+ Iterable<CompetitorDTO> competitorsToShow) {
1210
+ CompetitorDTO leadingCompetitorDTO = null;
1211
+ int legOfLeaderCompetitor = -1;
1212
+ // this only works because the quickRanks are sorted
1213
+ for (Entry<CompetitorDTO, Integer> competitorsByWindwardDistanceTraveledAndOneBasedLegNumber :
1214
+ competitorsInOrderOfWindwardDistanceTraveledWithOneBasedLegNumber.entrySet()) {
1215
+ if (Util.contains(competitorsToShow, competitorsByWindwardDistanceTraveledAndOneBasedLegNumber.getKey()) &&
1216
+ competitorsByWindwardDistanceTraveledAndOneBasedLegNumber.getValue() != null) {
1217
+ leadingCompetitorDTO = competitorsByWindwardDistanceTraveledAndOneBasedLegNumber.getKey();
1218
+ legOfLeaderCompetitor = competitorsByWindwardDistanceTraveledAndOneBasedLegNumber.getValue();
1219
+ return new com.sap.sse.common.Util.Pair<Integer, CompetitorDTO>(legOfLeaderCompetitor, leadingCompetitorDTO);
1220
+ }
1221
+ }
1222
+ return null;
1223
+ }
1224
+
1225
+ private void showAdvantageLine(Iterable<CompetitorDTO> competitorsToShow, Date date) {
1226
+ if (map != null && lastRaceTimesInfo != null && quickRanks != null && lastCombinedWindTrackInfoDTO != null) {
1227
+ boolean drawAdvantageLine = false;
1228
+ if (settings.getHelpLinesSettings().isVisible(HelpLineTypes.ADVANTAGELINE)) {
1229
+ // find competitor with highest rank
1230
+ com.sap.sse.common.Util.Pair<Integer, CompetitorDTO> visibleLeaderInfo = getFarthestAheadVisibleCompetitorWithOneBasedLegNumber(competitorsToShow);
1231
+ // the boat fix may be null; may mean that no positions were loaded yet for the leading visible boat;
1232
+ // don't show anything
1233
+ GPSFixDTO lastBoatFix = null;
1234
+ boolean isVisibleLeaderInfoComplete = false;
1235
+ boolean isLegTypeKnown = false;
1236
+ WindTrackInfoDTO windDataForLegMiddle = null;
1237
+ LegInfoDTO legInfoDTO = null;
1238
+ if (visibleLeaderInfo != null
1239
+ && visibleLeaderInfo.getA() > 0
1240
+ && visibleLeaderInfo.getA() <= lastRaceTimesInfo.getLegInfos().size()
1241
+ // get wind at middle of leg for leading visible competitor
1242
+ && (windDataForLegMiddle = lastCombinedWindTrackInfoDTO
1243
+ .getCombinedWindOnLegMiddle(visibleLeaderInfo.getA() - 1)) != null
1244
+ && !windDataForLegMiddle.windFixes.isEmpty()) {
1245
+ isVisibleLeaderInfoComplete = true;
1246
+ legInfoDTO = lastRaceTimesInfo.getLegInfos().get(visibleLeaderInfo.getA() - 1);
1247
+ if (legInfoDTO.legType != null) {
1248
+ isLegTypeKnown = true;
1249
+ }
1250
+ lastBoatFix = getBoatFix(visibleLeaderInfo.getB(), date);
1251
+ }
1252
+ if (isVisibleLeaderInfoComplete && isLegTypeKnown && lastBoatFix != null && lastBoatFix.speedWithBearing != null) {
1253
+ double advantageLineLengthInKm = 1.0; // TODO this should probably rather scale with the visible
1254
+ // area of the map; bug 616
1255
+ double distanceFromBoatPositionInKm = visibleLeaderInfo.getB().getBoatClass()
1256
+ .getHullLengthInMeters() / 1000.; // one hull length
1257
+ // implement and use Position.translateRhumb()
1258
+ double bearingOfBoatInDeg = lastBoatFix.speedWithBearing.bearingInDegrees;
1259
+ LatLng boatPosition = coordinateSystem.toLatLng(lastBoatFix.position);
1260
+ LatLng posAheadOfFirstBoat = calculatePositionAlongRhumbline(boatPosition,
1261
+ coordinateSystem.mapDegreeBearing(bearingOfBoatInDeg), distanceFromBoatPositionInKm);
1262
+ final WindDTO windFix = windDataForLegMiddle.windFixes.get(0);
1263
+ double bearingOfCombinedWindInDeg = windFix.trueWindBearingDeg;
1264
+ double rotatedBearingDeg1 = 0.0;
1265
+ double rotatedBearingDeg2 = 0.0;
1266
+ switch (legInfoDTO.legType) {
1267
+ case UPWIND:
1268
+ case DOWNWIND: {
1269
+ rotatedBearingDeg1 = bearingOfCombinedWindInDeg + 90.0;
1270
+ if (rotatedBearingDeg1 >= 360.0) {
1271
+ rotatedBearingDeg1 -= 360.0;
1272
+ }
1273
+ rotatedBearingDeg2 = bearingOfCombinedWindInDeg - 90.0;
1274
+ if (rotatedBearingDeg2 < 0.0) {
1275
+ rotatedBearingDeg2 += 360.0;
1276
+ }
1277
+ }
1278
+ break;
1279
+ case REACHING: {
1280
+ rotatedBearingDeg1 = legInfoDTO.legBearingInDegrees + 90.0;
1281
+ if (rotatedBearingDeg1 >= 360.0) {
1282
+ rotatedBearingDeg1 -= 360.0;
1283
+ }
1284
+ rotatedBearingDeg2 = legInfoDTO.legBearingInDegrees - 90.0;
1285
+ if (rotatedBearingDeg2 < 0.0) {
1286
+ rotatedBearingDeg2 += 360.0;
1287
+ }
1288
+ }
1289
+ break;
1290
+ }
1291
+ LatLng advantageLinePos1 = calculatePositionAlongRhumbline(posAheadOfFirstBoat,
1292
+ coordinateSystem.mapDegreeBearing(rotatedBearingDeg1), advantageLineLengthInKm / 2.0);
1293
+ LatLng advantageLinePos2 = calculatePositionAlongRhumbline(posAheadOfFirstBoat,
1294
+ coordinateSystem.mapDegreeBearing(rotatedBearingDeg2), advantageLineLengthInKm / 2.0);
1295
+ if (advantageLine == null) {
1296
+ PolylineOptions options = PolylineOptions.newInstance();
1297
+ options.setClickable(true);
1298
+ options.setGeodesic(true);
1299
+ options.setStrokeColor("#000000");
1300
+ options.setStrokeWeight(1);
1301
+ options.setStrokeOpacity(0.5);
1302
+
1303
+ advantageLine = Polyline.newInstance(options);
1304
+ MVCArray<LatLng> pointsAsArray = MVCArray.newInstance();
1305
+ pointsAsArray.insertAt(0, advantageLinePos1);
1306
+ pointsAsArray.insertAt(1, advantageLinePos2);
1307
+ advantageLine.setPath(pointsAsArray);
1308
+ advantageLine.setMap(map);
1309
+ Hoverline advantageHoverline = new Hoverline(advantageLine, options, this);
1310
+
1311
+ advantageLineMouseOverHandler = new AdvantageLineMouseOverMapHandler(
1312
+ bearingOfCombinedWindInDeg, new Date(windFix.measureTimepoint));
1313
+ advantageLine.addMouseOverHandler(advantageLineMouseOverHandler);
1314
+ advantageHoverline.addMouseOutMoveHandler(new MouseOutMapHandler() {
1315
+ @Override
1316
+ public void onEvent(MouseOutMapEvent event) {
1317
+ map.setTitle("");
1318
+ }
1319
+ });
1320
+ } else {
1321
+ advantageLine.getPath().removeAt(1);
1322
+ advantageLine.getPath().removeAt(0);
1323
+ advantageLine.getPath().insertAt(0, advantageLinePos1);
1324
+ advantageLine.getPath().insertAt(1, advantageLinePos2);
1325
+ advantageLineMouseOverHandler.setTrueWindBearing(bearingOfCombinedWindInDeg);
1326
+ advantageLineMouseOverHandler.setDate(new Date(windFix.measureTimepoint));
1327
+ }
1328
+ drawAdvantageLine = true;
1329
+ }
1330
+ }
1331
+ if (!drawAdvantageLine) {
1332
+ if (advantageLine != null) {
1333
+ advantageLine.setMap(null);
1334
+ advantageLine = null;
1335
+ }
1336
+ }
1337
+ }
1338
+ }
1339
+
1340
+ private final StringBuilder windwardStartLineMarkToFirstMarkLineText = new StringBuilder();
1341
+ private final StringBuilder leewardStartLineMarkToFirstMarkLineText = new StringBuilder();
1342
+
1343
+ private void showStartLineToFirstMarkTriangle(final CoursePositionsDTO courseDTO){
1344
+ final List<Position> startMarkPositions = courseDTO.getStartMarkPositions();
1345
+ final Position windwardStartLinePosition = startMarkPositions.get(0);
1346
+ final Position leewardStartLinePosition = startMarkPositions.get(1);
1347
+ final Position firstMarkPosition = courseDTO.waypointPositions.get(1);
1348
+ windwardStartLineMarkToFirstMarkLineText.replace(0, windwardStartLineMarkToFirstMarkLineText.length(),
1349
+ stringMessages.startLineToFirstMarkTriangle(numberFormatOneDecimal
1350
+ .format(windwardStartLinePosition.getDistance(firstMarkPosition)
1351
+ .getMeters())));
1352
+ leewardStartLineMarkToFirstMarkLineText.replace(0, leewardStartLineMarkToFirstMarkLineText.length(),
1353
+ stringMessages.startLineToFirstMarkTriangle(numberFormatOneDecimal
1354
+ .format(leewardStartLinePosition.getDistance(firstMarkPosition)
1355
+ .getMeters())));
1356
+ final LineInfoProvider windwardStartLineMarkToFirstMarkLineInfoProvider = new LineInfoProvider() {
1357
+ @Override
1358
+ public String getLineInfo() {
1359
+ return windwardStartLineMarkToFirstMarkLineText.toString();
1360
+ }
1361
+ };
1362
+ final LineInfoProvider leewardStartLineMarkToFirstMarkLineInfoProvider = new LineInfoProvider() {
1363
+ @Override
1364
+ public String getLineInfo() {
1365
+ return leewardStartLineMarkToFirstMarkLineText.toString();
1366
+ }
1367
+ };
1368
+ windwardStartLineMarkToFirstMarkLine = showOrRemoveOrUpdateLine(windwardStartLineMarkToFirstMarkLine, /* showLine */
1369
+ (settings.getHelpLinesSettings().isVisible(HelpLineTypes.STARTLINETOFIRSTMARKTRIANGLE))
1370
+ && startMarkPositions.size() > 1 && courseDTO.waypointPositions.size() > 1,
1371
+ windwardStartLinePosition, firstMarkPosition, windwardStartLineMarkToFirstMarkLineInfoProvider,
1372
+ "grey");
1373
+ leewardStartLineMarkToFirstMarkLine = showOrRemoveOrUpdateLine(leewardStartLineMarkToFirstMarkLine, /* showLine */
1374
+ (settings.getHelpLinesSettings().isVisible(HelpLineTypes.STARTLINETOFIRSTMARKTRIANGLE))
1375
+ && startMarkPositions.size() > 1 && courseDTO.waypointPositions.size() > 1,
1376
+ leewardStartLinePosition, firstMarkPosition, leewardStartLineMarkToFirstMarkLineInfoProvider,
1377
+ "grey");
1378
+ }
1379
+
1380
+ private final StringBuilder startLineAdvantageText = new StringBuilder();
1381
+ private final StringBuilder finishLineAdvantageText = new StringBuilder();
1382
+ final LineInfoProvider startLineInfoProvider = new LineInfoProvider() {
1383
+ @Override
1384
+ public String getLineInfo() {
1385
+ return stringMessages.startLine()+startLineAdvantageText;
1386
+ }
1387
+ };
1388
+ final LineInfoProvider finishLineInfoProvider = new LineInfoProvider() {
1389
+ @Override
1390
+ public String getLineInfo() {
1391
+ return stringMessages.finishLine()+finishLineAdvantageText;
1392
+ }
1393
+ };
1394
+
1395
+ private void showStartAndFinishAndCourseMiddleLines(final CoursePositionsDTO courseDTO) {
1396
+ if (map != null && courseDTO != null && courseDTO.course != null && courseDTO.course.waypoints != null &&
1397
+ !courseDTO.course.waypoints.isEmpty()) {
1398
+ // draw the start line
1399
+ final WaypointDTO startWaypoint = courseDTO.course.waypoints.get(0);
1400
+ updateCountdownCanvas(startWaypoint);
1401
+ final int numberOfStartWaypointMarks = courseDTO.getStartMarkPositions() == null ? 0 : courseDTO.getStartMarkPositions().size();
1402
+ final int numberOfFinishWaypointMarks = courseDTO.getFinishMarkPositions() == null ? 0 : courseDTO.getFinishMarkPositions().size();
1403
+ final Position startLineLeftPosition = numberOfStartWaypointMarks == 0 ? null : courseDTO.getStartMarkPositions().get(0);
1404
+ final Position startLineRightPosition = numberOfStartWaypointMarks < 2 ? null : courseDTO.getStartMarkPositions().get(1);
1405
+ if (courseDTO.startLineAngleToCombinedWind != null) {
1406
+ startLineAdvantageText.replace(0, startLineAdvantageText.length(), " "+stringMessages.lineAngleToWindAndAdvantage(
1407
+ NumberFormat.getFormat("0.0").format(courseDTO.startLineLengthInMeters),
1408
+ NumberFormat.getFormat("0.0").format(Math.abs(courseDTO.startLineAngleToCombinedWind)),
1409
+ courseDTO.startLineAdvantageousSide.name().charAt(0)+courseDTO.startLineAdvantageousSide.name().substring(1).toLowerCase(),
1410
+ NumberFormat.getFormat("0.0").format(courseDTO.startLineAdvantageInMeters)));
1411
+ } else {
1412
+ startLineAdvantageText.delete(0, startLineAdvantageText.length());
1413
+ }
1414
+ final boolean showStartLineBasedOnCurrentLeg = numberOfStartWaypointMarks == 2 && courseDTO.currentLegNumber <= 1;
1415
+ final boolean showFinishLineBasedOnCurrentLeg = numberOfFinishWaypointMarks == 2 && courseDTO.currentLegNumber == courseDTO.totalLegsCount;
1416
+ // show the line when STARTLINE is selected and the current leg is around the start leg,
1417
+ // or when COURSEGEOMETRY is selected and the finish line isn't equal and wouldn't be shown at the same time based on the current leg.
1418
+ // With this, if COURSEGEOMETRY is selected and start and finish line are equal, the start line will not be displayed if
1419
+ // based on the race progress the finish line is to be preferred, so only the finish line will be shown.
1420
+ final boolean reallyShowStartLine =
1421
+ (settings.getHelpLinesSettings().isVisible(HelpLineTypes.STARTLINE) && showStartLineBasedOnCurrentLeg) ||
1422
+ (settings.getHelpLinesSettings().isVisible(HelpLineTypes.COURSEGEOMETRY) &&
1423
+ (!showFinishLineBasedOnCurrentLeg || !startLineEqualsFinishLine(courseDTO)));
1424
+ // show the line when FINISHLINE is selected and the current leg is the last leg,
1425
+ // or when COURSEGEOMETRY is selected and the start line isn't equal or the current leg is the last leg.
1426
+ // With this, if COURSEGEOMETRY is selected and start and finish line are equal, the start line will be displayed unless
1427
+ // the finish line should take precedence based on race progress.
1428
+ final boolean reallyShowFinishLine = showFinishLineBasedOnCurrentLeg &&
1429
+ (!showStartLineBasedOnCurrentLeg || !startLineEqualsFinishLine(courseDTO)) &&
1430
+ (settings.getHelpLinesSettings().isVisible(HelpLineTypes.FINISHLINE) && showFinishLineBasedOnCurrentLeg) ||
1431
+ (settings.getHelpLinesSettings().isVisible(HelpLineTypes.COURSEGEOMETRY) &&
1432
+ (!startLineEqualsFinishLine(courseDTO) || showFinishLineBasedOnCurrentLeg));
1433
+ startLine = showOrRemoveOrUpdateLine(startLine, reallyShowStartLine,
1434
+ startLineLeftPosition, startLineRightPosition, startLineInfoProvider, "#ffffff");
1435
+ // draw the finish line
1436
+ final Position finishLineLeftPosition = numberOfFinishWaypointMarks == 0 ? null : courseDTO.getFinishMarkPositions().get(0);
1437
+ final Position finishLineRightPosition = numberOfFinishWaypointMarks < 2 ? null : courseDTO.getFinishMarkPositions().get(1);
1438
+ if (courseDTO.finishLineAngleToCombinedWind != null) {
1439
+ finishLineAdvantageText.replace(0, finishLineAdvantageText.length(), " "+stringMessages.lineAngleToWindAndAdvantage(
1440
+ NumberFormat.getFormat("0.0").format(courseDTO.finishLineLengthInMeters),
1441
+ NumberFormat.getFormat("0.0").format(Math.abs(courseDTO.finishLineAngleToCombinedWind)),
1442
+ courseDTO.finishLineAdvantageousSide.name().charAt(0)+courseDTO.finishLineAdvantageousSide.name().substring(1).toLowerCase(),
1443
+ NumberFormat.getFormat("0.0").format(courseDTO.finishLineAdvantageInMeters)));
1444
+ } else {
1445
+ finishLineAdvantageText.delete(0, finishLineAdvantageText.length());
1446
+ }
1447
+ finishLine = showOrRemoveOrUpdateLine(finishLine, reallyShowFinishLine,
1448
+ finishLineLeftPosition, finishLineRightPosition, finishLineInfoProvider, "#000000");
1449
+ // the control point pairs for which we already decided whether or not
1450
+ // to show a course middle line for; values tell whether to show the line and for which zero-based
1451
+ // start waypoint index to do so; when for an equal control point pair multiple decisions with different
1452
+ // outcome are made, a decision to show the line overrules the decision to not show it (OR-semantics)
1453
+ final Map<Set<ControlPointDTO>, Pair<Boolean, Integer>> keysAlreadyHandled = new HashMap<>();
1454
+ for (int zeroBasedIndexOfStartWaypoint = 0; zeroBasedIndexOfStartWaypoint<courseDTO.waypointPositions.size()-1; zeroBasedIndexOfStartWaypoint++) {
1455
+ final Set<ControlPointDTO> key = getCourseMiddleLinesKey(courseDTO, zeroBasedIndexOfStartWaypoint);
1456
+ boolean showCourseMiddleLine = keysAlreadyHandled.containsKey(key) && keysAlreadyHandled.get(key).getA() ||
1457
+ settings.getHelpLinesSettings().isVisible(HelpLineTypes.COURSEGEOMETRY) ||
1458
+ (settings.getHelpLinesSettings().isVisible(HelpLineTypes.COURSEMIDDLELINE)
1459
+ && courseDTO.currentLegNumber > 0
1460
+ && courseDTO.currentLegNumber-1 == zeroBasedIndexOfStartWaypoint);
1461
+ keysAlreadyHandled.put(key, new Pair<>(showCourseMiddleLine, zeroBasedIndexOfStartWaypoint));
1462
+ }
1463
+ Set<Set<ControlPointDTO>> keysToConsider = new HashSet<>(keysAlreadyHandled.keySet());
1464
+ keysToConsider.addAll(courseMiddleLines.keySet());
1465
+ for (final Set<ControlPointDTO> key : keysToConsider) {
1466
+ final int zeroBasedIndexOfStartWaypoint = keysAlreadyHandled.containsKey(key) ?
1467
+ keysAlreadyHandled.get(key).getB() : 0; // if not handled, the line will be removed, so the waypoint index doesn't matter
1468
+ final Pair<Boolean, Integer> showLineAndZeroBasedIndexOfStartWaypoint = keysAlreadyHandled.get(key);
1469
+ final boolean showCourseMiddleLine = showLineAndZeroBasedIndexOfStartWaypoint != null && showLineAndZeroBasedIndexOfStartWaypoint.getA();
1470
+ courseMiddleLines.put(key, showOrRemoveCourseMiddleLine(courseDTO, courseMiddleLines.get(key), zeroBasedIndexOfStartWaypoint, showCourseMiddleLine));
1471
+ }
1472
+ }
1473
+ }
1474
+
1475
+ private boolean startLineEqualsFinishLine(CoursePositionsDTO courseDTO) {
1476
+ final List<WaypointDTO> waypoints;
1477
+ return courseDTO != null && courseDTO.course != null &&
1478
+ (waypoints = courseDTO.course.waypoints) != null &&
1479
+ waypoints.get(0).controlPoint.equals(waypoints.get(waypoints.size()-1).controlPoint);
1480
+ }
1481
+
1482
+ /**
1483
+ * Given a zero-based index into <code>courseDTO</code>'s {@link RaceCourseDTO#waypoints waypoints list} that denotes the start
1484
+ * waypoint of the leg in question, returns a key that can be used for the {@link #courseMiddleLines} map, consisting of a set
1485
+ * that holds the two {@link ControlPointDTO}s representing the start and finish control point of that leg.
1486
+ */
1487
+ private Set<ControlPointDTO> getCourseMiddleLinesKey(final CoursePositionsDTO courseDTO,
1488
+ final int zeroBasedIndexOfStartWaypoint) {
1489
+ ControlPointDTO startControlPoint = courseDTO.course.waypoints.get(zeroBasedIndexOfStartWaypoint).controlPoint;
1490
+ ControlPointDTO endControlPoint = courseDTO.course.waypoints.get(zeroBasedIndexOfStartWaypoint+1).controlPoint;
1491
+ final Set<ControlPointDTO> key = new HashSet<>();
1492
+ key.add(startControlPoint);
1493
+ key.add(endControlPoint);
1494
+ return key;
1495
+ }
1496
+
1497
+ /**
1498
+ * @param showLine
1499
+ * tells whether or not to show the line; if the <code>lineToShowOrRemoveOrUpdate</code> references a
1500
+ * line but the line shall not be shown, the line is removed from the map; conversely, if the line is not
1501
+ * yet shown but shall be, a new line is created, added to the map and returned. If the line is shown and
1502
+ * shall continue to be shown, the line is returned after updating its vertex coordinates.
1503
+ * @return <code>null</code> if the line is not shown; the polyline object representing the line being displayed
1504
+ * otherwise
1505
+ */
1506
+ private Polyline showOrRemoveCourseMiddleLine(final CoursePositionsDTO courseDTO, Polyline lineToShowOrRemoveOrUpdate,
1507
+ final int zeroBasedIndexOfStartWaypoint, final boolean showLine) {
1508
+ final Position position1DTO = courseDTO.waypointPositions.get(zeroBasedIndexOfStartWaypoint);
1509
+ final Position position2DTO = courseDTO.waypointPositions.get(zeroBasedIndexOfStartWaypoint+1);
1510
+ final LineInfoProvider lineInfoProvider = new LineInfoProvider() {
1511
+ @Override
1512
+ public String getLineInfo() {
1513
+ final StringBuilder sb = new StringBuilder();
1514
+ sb.append(stringMessages.courseMiddleLine());
1515
+ sb.append('\n');
1516
+ sb.append(NumberFormat.getFormat("0").format(
1517
+ Math.abs(position1DTO.getDistance(position2DTO).getMeters()))+stringMessages.metersUnit());
1518
+ if (lastCombinedWindTrackInfoDTO != null) {
1519
+ final WindTrackInfoDTO windTrackAtLegMiddle = lastCombinedWindTrackInfoDTO.getCombinedWindOnLegMiddle(zeroBasedIndexOfStartWaypoint);
1520
+ if (windTrackAtLegMiddle != null && windTrackAtLegMiddle.windFixes != null && !windTrackAtLegMiddle.windFixes.isEmpty()) {
1521
+ WindDTO windAtLegMiddle = windTrackAtLegMiddle.windFixes.get(0);
1522
+ final double legBearingDeg = position1DTO.getBearingGreatCircle(position2DTO).getDegrees();
1523
+ final String diff = NumberFormat.getFormat("0.0").format(
1524
+ Math.min(Math.abs(windAtLegMiddle.dampenedTrueWindBearingDeg-legBearingDeg),
1525
+ Math.abs(windAtLegMiddle.dampenedTrueWindFromDeg-legBearingDeg)));
1526
+ sb.append(", ");
1527
+ sb.append(stringMessages.degreesToWind(diff));
1528
+ }
1529
+ }
1530
+ return sb.toString();
1531
+ }
1532
+ };
1533
+ return showOrRemoveOrUpdateLine(lineToShowOrRemoveOrUpdate, showLine, position1DTO, position2DTO, lineInfoProvider, "#2268a0");
1534
+ }
1535
+
1536
+ private interface LineInfoProvider {
1537
+ String getLineInfo();
1538
+ }
1539
+
1540
+ /**
1541
+ * @param showLine
1542
+ * tells whether or not to show the line; if the <code>lineToShowOrRemoveOrUpdate</code> references a
1543
+ * line but the line shall not be shown, the line is removed from the map; conversely, if the line is not
1544
+ * yet shown but shall be, a new line is created, added to the map and returned. If the line is shown and
1545
+ * shall continue to be shown, the line is returned after updating its vertex coordinates.
1546
+ * @return <code>null</code> if the line is not shown; the polyline object representing the line being displayed
1547
+ * otherwise
1548
+ */
1549
+ private Polyline showOrRemoveOrUpdateLine(Polyline lineToShowOrRemoveOrUpdate, final boolean showLine,
1550
+ final Position position1DTO, final Position position2DTO, final LineInfoProvider lineInfoProvider, String lineColorRGB) {
1551
+ if (showLine) {
1552
+ LatLng courseMiddleLinePoint1 = coordinateSystem.toLatLng(position1DTO);
1553
+ LatLng courseMiddleLinePoint2 = coordinateSystem.toLatLng(position2DTO);
1554
+ final MVCArray<LatLng> pointsAsArray;
1555
+ if (lineToShowOrRemoveOrUpdate == null) {
1556
+ PolylineOptions options = PolylineOptions.newInstance();
1557
+ options.setClickable(true);
1558
+ options.setGeodesic(true);
1559
+ options.setStrokeColor(lineColorRGB);
1560
+ options.setStrokeWeight(1);
1561
+ options.setStrokeOpacity(1.0);
1562
+ pointsAsArray = MVCArray.newInstance();
1563
+ lineToShowOrRemoveOrUpdate = Polyline.newInstance(options);
1564
+ lineToShowOrRemoveOrUpdate.setPath(pointsAsArray);
1565
+ lineToShowOrRemoveOrUpdate.setMap(map);
1566
+ Hoverline lineToShowOrRemoveOrUpdateHoverline = new Hoverline(lineToShowOrRemoveOrUpdate, options, this);
1567
+ lineToShowOrRemoveOrUpdate.addMouseOverHandler(new MouseOverMapHandler() {
1568
+ @Override
1569
+ public void onEvent(MouseOverMapEvent event) {
1570
+ map.setTitle(lineInfoProvider.getLineInfo());
1571
+ }
1572
+ });
1573
+ lineToShowOrRemoveOrUpdateHoverline.addMouseOutMoveHandler(new MouseOutMapHandler() {
1574
+ @Override
1575
+ public void onEvent(MouseOutMapEvent event) {
1576
+ map.setTitle("");
1577
+ }
1578
+ });
1579
+ } else {
1580
+ pointsAsArray = lineToShowOrRemoveOrUpdate.getPath();
1581
+ pointsAsArray.removeAt(1);
1582
+ pointsAsArray.removeAt(0);
1583
+ }
1584
+ adjustInfoOverlayForVisibleLine(lineToShowOrRemoveOrUpdate, position1DTO, position2DTO, lineInfoProvider);
1585
+ pointsAsArray.insertAt(0, courseMiddleLinePoint1);
1586
+ pointsAsArray.insertAt(1, courseMiddleLinePoint2);
1587
+ } else {
1588
+ if (lineToShowOrRemoveOrUpdate != null) {
1589
+ lineToShowOrRemoveOrUpdate.setMap(null);
1590
+ adjustInfoOverlayForRemovedLine(lineToShowOrRemoveOrUpdate);
1591
+ lineToShowOrRemoveOrUpdate = null;
1592
+ }
1593
+ }
1594
+ return lineToShowOrRemoveOrUpdate;
1595
+ }
1596
+
1597
+ private void adjustInfoOverlayForRemovedLine(Polyline lineToShowOrRemoveOrUpdate) {
1598
+ SmallTransparentInfoOverlay infoOverlay = infoOverlaysForLinesForCourseGeometry.remove(lineToShowOrRemoveOrUpdate);
1599
+ if (infoOverlay != null) {
1600
+ infoOverlay.removeFromMap();
1601
+ }
1602
+ }
1603
+
1604
+ private void adjustInfoOverlayForVisibleLine(Polyline lineToShowOrRemoveOrUpdate, final Position position1DTO,
1605
+ final Position position2DTO, final LineInfoProvider lineInfoProvider) {
1606
+ SmallTransparentInfoOverlay infoOverlay = infoOverlaysForLinesForCourseGeometry.get(lineToShowOrRemoveOrUpdate);
1607
+ if (getSettings().getHelpLinesSettings().isVisible(HelpLineTypes.COURSEGEOMETRY)) {
1608
+ if (infoOverlay == null) {
1609
+ infoOverlay = new SmallTransparentInfoOverlay(map, RaceMapOverlaysZIndexes.INFO_OVERLAY_ZINDEX, lineInfoProvider.getLineInfo(), coordinateSystem);
1610
+ infoOverlaysForLinesForCourseGeometry.put(lineToShowOrRemoveOrUpdate, infoOverlay);
1611
+ infoOverlay.addToMap();
1612
+ } else {
1613
+ infoOverlay.setInfoText(lineInfoProvider.getLineInfo());
1614
+ }
1615
+ infoOverlay.setPosition(position1DTO.translateGreatCircle(position1DTO.getBearingGreatCircle(position2DTO),
1616
+ position1DTO.getDistance(position2DTO).scale(0.5)), /* transition time */ -1);
1617
+ infoOverlay.draw();
1618
+ } else {
1619
+ if (infoOverlay != null) {
1620
+ infoOverlay.removeFromMap();
1621
+ infoOverlaysForLinesForCourseGeometry.remove(lineToShowOrRemoveOrUpdate);
1622
+ }
1623
+ }
1624
+ }
1625
+
1626
+ /**
1627
+ * If, according to {@link #lastRaceTimesInfo} and {@link #timer} the race is
1628
+ * still in the pre-start phase, show a {@link SmallTransparentInfoOverlay} at the
1629
+ * start line that shows the count down.
1630
+ */
1631
+ private void updateCountdownCanvas(WaypointDTO startWaypoint) {
1632
+ if (!settings.isShowSelectedCompetitorsInfo() || startWaypoint == null || Util.isEmpty(startWaypoint.controlPoint.getMarks())
1633
+ || lastRaceTimesInfo == null || lastRaceTimesInfo.startOfRace == null || timer.getTime().after(lastRaceTimesInfo.startOfRace)) {
1634
+ if (countDownOverlay != null) {
1635
+ countDownOverlay.removeFromMap();
1636
+ countDownOverlay = null;
1637
+ }
1638
+ } else {
1639
+ long timeToStartInMs = lastRaceTimesInfo.startOfRace.getTime() - timer.getTime().getTime();
1640
+ String countDownText = timeToStartInMs > 1000 ? stringMessages.timeToStart(DateAndTimeFormatterUtil
1641
+ .formatElapsedTime(timeToStartInMs)) : stringMessages.start();
1642
+ if (countDownOverlay == null) {
1643
+ countDownOverlay = new SmallTransparentInfoOverlay(map, RaceMapOverlaysZIndexes.INFO_OVERLAY_ZINDEX,
1644
+ countDownText, coordinateSystem);
1645
+ countDownOverlay.addToMap();
1646
+ } else {
1647
+ countDownOverlay.setInfoText(countDownText);
1648
+ }
1649
+ countDownOverlay.setPosition(startWaypoint.controlPoint.getMarks().iterator().next().position, /* transition time */ -1);
1650
+ countDownOverlay.draw();
1651
+ }
1652
+ }
1653
+
1654
+ // Google scales coordinates so that the globe-tile has mercator-latitude [-pi, +pi], i.e. tile height of 2*pi
1655
+ // mercator-latitude pi corresponds to geo-latitude of approx. 85.0998 (where Google cuts off the map visualization)
1656
+ // official documentation: http://developers.google.com/maps/documentation/javascript/maptypes#TileCoordinates
1657
+ private double getMercatorLatitude(double lat) {
1658
+ // cutting-off for latitudes close to +-90 degrees is recommended (to avoid division by zero)
1659
+ double sine = Math.max(-0.9999, Math.min(0.9999, Math.sin(Math.PI * lat / 180)));
1660
+ return Math.log((1 + sine) / (1 - sine)) / 2;
1661
+ }
1662
+
1663
+ private int getZoomLevel(LatLngBounds bounds) {
1664
+ int GLOBE_PXSIZE = 256; // a constant in Google's map projection
1665
+ int MAX_ZOOM = 20; // maximum zoom-level that should be automatically selected
1666
+ double LOG2 = Math.log(2.0);
1667
+ double deltaLng = bounds.getNorthEast().getLongitude() - bounds.getSouthWest().getLongitude();
1668
+ double deltaLat = getMercatorLatitude(bounds.getNorthEast().getLatitude()) - getMercatorLatitude(bounds.getSouthWest().getLatitude());
1669
+ if ((deltaLng == 0) && (deltaLat == 0)) {
1670
+ return MAX_ZOOM;
1671
+ }
1672
+ if (deltaLng < 0) {
1673
+ deltaLng += 360;
1674
+ }
1675
+ int zoomLng = (int) Math.floor(Math.log(map.getDiv().getClientWidth() * 360 / deltaLng / GLOBE_PXSIZE) / LOG2);
1676
+ int zoomLat = (int) Math.floor(Math.log(map.getDiv().getClientHeight() * 2 * Math.PI / deltaLat / GLOBE_PXSIZE) / LOG2);
1677
+ return Math.min(Math.min(zoomLat, zoomLng), MAX_ZOOM);
1678
+ }
1679
+
1680
+ private void zoomMapToNewBounds(LatLngBounds newBounds) {
1681
+ if (newBounds != null) {
1682
+ LatLngBounds currentMapBounds;
1683
+ if (map.getBounds() == null
1684
+ || !BoundsUtil.contains((currentMapBounds = map.getBounds()), newBounds)
1685
+ || graticuleAreaRatio(currentMapBounds, newBounds) > 10) {
1686
+ // only change bounds if the new bounds don't fit into the current map zoom
1687
+ Iterable<ZoomTypes> oldZoomSettings = settings.getZoomSettings().getTypesToConsiderOnZoom();
1688
+ setAutoZoomInProgress(true);
1689
+ autoZoomLatLngBounds = newBounds;
1690
+ int newZoomLevel = getZoomLevel(autoZoomLatLngBounds);
1691
+ if (newZoomLevel != map.getZoom()) {
1692
+ // distinguish between zoom-in and zoom-out, because the sequence of panTo() and setZoom()
1693
+ // appears different on the screen due to map-animations
1694
+ // following sequences keep the selected boats allways visible:
1695
+ // zoom-in : 1. panTo(), 2. setZoom()
1696
+ // zoom-out: 1. setZoom(), 2. panTo()
1697
+ autoZoomIn = newZoomLevel > map.getZoom();
1698
+ autoZoomOut = !autoZoomIn;
1699
+ autoZoomLevel = newZoomLevel;
1700
+ removeTransitions();
1701
+ if (autoZoomIn) {
1702
+ map.panTo(autoZoomLatLngBounds.getCenter());
1703
+ } else {
1704
+ map.setZoom(autoZoomLevel);
1705
+ }
1706
+ } else {
1707
+ map.panTo(autoZoomLatLngBounds.getCenter());
1708
+ }
1709
+ settings.getZoomSettings().setTypesToConsiderOnZoom(oldZoomSettings);
1710
+ setAutoZoomInProgress(false);
1711
+ }
1712
+ }
1713
+ }
1714
+
1715
+ private double graticuleAreaRatio(LatLngBounds containing, LatLngBounds contained) {
1716
+ assert BoundsUtil.contains(containing, contained);
1717
+ double containingAreaRatio = getGraticuleArea(containing) / getGraticuleArea(contained);
1718
+ return containingAreaRatio;
1719
+ }
1720
+
1721
+ /**
1722
+ * A much simplified "area" calculation for a {@link Bounds} object, multiplying the differences in latitude and longitude degrees.
1723
+ * The result therefore is in the order of magnitude of 60*60 square nautical miles.
1724
+ */
1725
+ private double getGraticuleArea(LatLngBounds bounds) {
1726
+ return ((BoundsUtil.isCrossesDateLine(bounds) ? bounds.getNorthEast().getLongitude()+360 : bounds.getNorthEast().getLongitude())-bounds.getSouthWest().getLongitude()) *
1727
+ (bounds.getNorthEast().getLatitude() - bounds.getSouthWest().getLatitude());
1728
+ }
1729
+
1730
+ private void setAutoZoomInProgress(boolean autoZoomInProgress) {
1731
+ this.autoZoomInProgress = autoZoomInProgress;
1732
+ }
1733
+
1734
+ boolean isAutoZoomInProgress() {
1735
+ return autoZoomInProgress;
1736
+ }
1737
+
1738
+ /**
1739
+ * @param timeForPositionTransitionMillis use -1 to not animate the position transition, e.g., during map zoom or non-play
1740
+ */
1741
+ private boolean updateBoatCanvasForCompetitor(CompetitorDTO competitorDTO, Date date, long timeForPositionTransitionMillis) {
1742
+ boolean usedExistingCanvas = false;
1743
+ GPSFixDTO lastBoatFix = getBoatFix(competitorDTO, date);
1744
+ if (lastBoatFix != null) {
1745
+ BoatOverlay boatOverlay = boatOverlays.get(competitorDTO);
1746
+ if (boatOverlay == null) {
1747
+ boatOverlay = createBoatOverlay(RaceMapOverlaysZIndexes.BOATS_ZINDEX, competitorDTO, displayHighlighted(competitorDTO));
1748
+ boatOverlays.put(competitorDTO, boatOverlay);
1749
+ boatOverlay.setSelected(displayHighlighted(competitorDTO));
1750
+ boatOverlay.setBoatFix(lastBoatFix, timeForPositionTransitionMillis);
1751
+ boatOverlay.addToMap();
1752
+ } else {
1753
+ usedExistingCanvas = true;
1754
+ boatOverlay.setSelected(displayHighlighted(competitorDTO));
1755
+ boatOverlay.setBoatFix(lastBoatFix, timeForPositionTransitionMillis);
1756
+ boatOverlay.draw();
1757
+ }
1758
+ }
1759
+
1760
+ return usedExistingCanvas;
1761
+ }
1762
+
1763
+ private boolean displayHighlighted(CompetitorDTO competitorDTO) {
1764
+ return !settings.isShowOnlySelectedCompetitors() && competitorSelection.isSelected(competitorDTO);
1765
+ }
1766
+
1767
+ protected CourseMarkOverlay createCourseMarkOverlay(int zIndex, final MarkDTO markDTO) {
1768
+ final CourseMarkOverlay courseMarkOverlay = new CourseMarkOverlay(map, zIndex, markDTO, coordinateSystem);
1769
+ courseMarkOverlay.addClickHandler(new ClickMapHandler() {
1770
+ @Override
1771
+ public void onEvent(ClickMapEvent event) {
1772
+ LatLng latlng = courseMarkOverlay.getMarkLatLngPosition();
1773
+ showMarkInfoWindow(markDTO, latlng);
1774
+ }
1775
+ });
1776
+ return courseMarkOverlay;
1777
+ }
1778
+
1779
+ private CompetitorInfoOverlay createCompetitorInfoOverlay(int zIndex, final CompetitorDTO competitorDTO) {
1780
+ String infoText = competitorDTO.getSailID() == null || competitorDTO.getSailID().isEmpty() ? competitorDTO.getName() : competitorDTO.getSailID();
1781
+ return new CompetitorInfoOverlay(map, zIndex, competitorSelection.getColor(competitorDTO, raceIdentifier), infoText, coordinateSystem);
1782
+ }
1783
+
1784
+ private BoatOverlay createBoatOverlay(int zIndex, final CompetitorDTO competitorDTO, boolean highlighted) {
1785
+ final BoatOverlay boatCanvas = new BoatOverlay(map, zIndex, competitorDTO, competitorSelection.getColor(competitorDTO, raceIdentifier), coordinateSystem);
1786
+ boatCanvas.setSelected(highlighted);
1787
+ boatCanvas.addClickHandler(new ClickMapHandler() {
1788
+ @Override
1789
+ public void onEvent(ClickMapEvent event) {
1790
+ if (lastInfoWindow != null) {
1791
+ lastInfoWindow.close();
1792
+ }
1793
+ GPSFixDTO latestFixForCompetitor = getBoatFix(competitorDTO, timer.getTime());
1794
+ LatLng where = coordinateSystem.toLatLng(latestFixForCompetitor.position);
1795
+ InfoWindowOptions options = InfoWindowOptions.newInstance();
1796
+ InfoWindow infoWindow = InfoWindow.newInstance(options);
1797
+ infoWindow.setContent(getInfoWindowContent(competitorDTO, latestFixForCompetitor));
1798
+ infoWindow.setPosition(where);
1799
+ lastInfoWindow = infoWindow;
1800
+ infoWindow.open(map);
1801
+ }
1802
+ });
1803
+
1804
+ boatCanvas.addMouseOverHandler(new MouseOverMapHandler() {
1805
+ @Override
1806
+ public void onEvent(MouseOverMapEvent event) {
1807
+ map.setTitle(competitorDTO.getSailID());
1808
+ }
1809
+ });
1810
+ boatCanvas.addMouseOutMoveHandler(new MouseOutMapHandler() {
1811
+ @Override
1812
+ public void onEvent(MouseOutMapEvent event) {
1813
+ map.setTitle("");
1814
+ }
1815
+ });
1816
+
1817
+ return boatCanvas;
1818
+ }
1819
+
1820
+ protected WindSensorOverlay createWindSensorOverlay(int zIndex, final WindSource windSource, final WindTrackInfoDTO windTrackInfoDTO) {
1821
+ final WindSensorOverlay windSensorOverlay = new WindSensorOverlay(map, zIndex, raceMapImageManager, stringMessages, coordinateSystem);
1822
+ windSensorOverlay.setWindInfo(windTrackInfoDTO, windSource);
1823
+ windSensorOverlay.addClickHandler(new ClickMapHandler() {
1824
+ @Override
1825
+ public void onEvent(ClickMapEvent event) {
1826
+ showWindSensorInfoWindow(windSensorOverlay);
1827
+ }
1828
+ });
1829
+ return windSensorOverlay;
1830
+ }
1831
+
1832
+ private void showMarkInfoWindow(MarkDTO markDTO, LatLng position) {
1833
+ if(lastInfoWindow != null) {
1834
+ lastInfoWindow.close();
1835
+ }
1836
+ InfoWindowOptions options = InfoWindowOptions.newInstance();
1837
+ InfoWindow infoWindow = InfoWindow.newInstance(options);
1838
+ infoWindow.setContent(getInfoWindowContent(markDTO));
1839
+ infoWindow.setPosition(position);
1840
+ lastInfoWindow = infoWindow;
1841
+ infoWindow.open(map);
1842
+ }
1843
+
1844
+ private void showCompetitorInfoWindow(final CompetitorDTO competitorDTO, LatLng where) {
1845
+ if(lastInfoWindow != null) {
1846
+ lastInfoWindow.close();
1847
+ }
1848
+ GPSFixDTO latestFixForCompetitor = getBoatFix(competitorDTO, timer.getTime());
1849
+ // TODO find close fix where the mouse was; see BUG 470
1850
+ InfoWindowOptions options = InfoWindowOptions.newInstance();
1851
+ InfoWindow infoWindow = InfoWindow.newInstance(options);
1852
+ infoWindow.setContent(getInfoWindowContent(competitorDTO, latestFixForCompetitor));
1853
+ infoWindow.setPosition(where);
1854
+ lastInfoWindow = infoWindow;
1855
+ infoWindow.open(map);
1856
+ }
1857
+
1858
+ private String formatPosition(double lat, double lng) {
1859
+ NumberFormat numberFormat = NumberFormat.getFormat("0.00000");
1860
+ String result = numberFormat.format(lat) + " lat, " + numberFormat.format(lng) + " lng";
1861
+ return result;
1862
+ }
1863
+
1864
+ private void showWindSensorInfoWindow(final WindSensorOverlay windSensorOverlay) {
1865
+ WindSource windSource = windSensorOverlay.getWindSource();
1866
+ WindTrackInfoDTO windTrackInfoDTO = windSensorOverlay.getWindTrackInfoDTO();
1867
+ WindDTO windDTO = windTrackInfoDTO.windFixes.get(0);
1868
+ if(windDTO != null && windDTO.position != null) {
1869
+ if(lastInfoWindow != null) {
1870
+ lastInfoWindow.close();
1871
+ }
1872
+ LatLng where = coordinateSystem.toLatLng(windDTO.position);
1873
+ InfoWindowOptions options = InfoWindowOptions.newInstance();
1874
+ InfoWindow infoWindow = InfoWindow.newInstance(options);
1875
+ infoWindow.setContent(getInfoWindowContent(windSource, windTrackInfoDTO));
1876
+ infoWindow.setPosition(where);
1877
+ lastInfoWindow = infoWindow;
1878
+ infoWindow.open(map);
1879
+ }
1880
+ }
1881
+
1882
+ private Widget createInfoWindowLabelAndValue(String labelName, String value) {
1883
+ FlowPanel flowPanel = new FlowPanel();
1884
+ Label label = new Label(labelName + ":");
1885
+ label.setWordWrap(false);
1886
+ label.getElement().getStyle().setFloat(Style.Float.LEFT);
1887
+ label.getElement().getStyle().setPadding(3, Style.Unit.PX);
1888
+ label.getElement().getStyle().setFontWeight(Style.FontWeight.BOLD);
1889
+ flowPanel.add(label);
1890
+
1891
+ Label valueLabel = new Label(value);
1892
+ valueLabel.setWordWrap(false);
1893
+ valueLabel.getElement().getStyle().setFloat(Style.Float.LEFT);
1894
+ valueLabel.getElement().getStyle().setPadding(3, Style.Unit.PX);
1895
+ flowPanel.add(valueLabel);
1896
+
1897
+ return flowPanel;
1898
+ }
1899
+
1900
+ private Widget getInfoWindowContent(MarkDTO markDTO) {
1901
+ VerticalPanel vPanel = new VerticalPanel();
1902
+ vPanel.add(createInfoWindowLabelAndValue(stringMessages.mark(), markDTO.getName()));
1903
+ vPanel.add(createInfoWindowLabelAndValue(stringMessages.position(), formatPosition(markDTO.position.getLatDeg(), markDTO.position.getLngDeg())));
1904
+ return vPanel;
1905
+ }
1906
+
1907
+ private Widget getInfoWindowContent(WindSource windSource, WindTrackInfoDTO windTrackInfoDTO) {
1908
+ WindDTO windDTO = windTrackInfoDTO.windFixes.get(0);
1909
+ NumberFormat numberFormat = NumberFormat.getFormat("0.0");
1910
+ VerticalPanel vPanel = new VerticalPanel();
1911
+ vPanel.add(createInfoWindowLabelAndValue(stringMessages.windSource(), WindSourceTypeFormatter.format(windSource, stringMessages)));
1912
+ vPanel.add(createInfoWindowLabelAndValue(stringMessages.wind(), Math.round(windDTO.dampenedTrueWindFromDeg) + " " + stringMessages.degreesShort()));
1913
+ vPanel.add(createInfoWindowLabelAndValue(stringMessages.windSpeed(), numberFormat.format(windDTO.dampenedTrueWindSpeedInKnots)));
1914
+ vPanel.add(createInfoWindowLabelAndValue(stringMessages.position(), formatPosition(windDTO.position.getLatDeg(), windDTO.position.getLngDeg())));
1915
+ return vPanel;
1916
+ }
1917
+
1918
+ private Widget getInfoWindowContent(CompetitorDTO competitorDTO, GPSFixDTO lastFix) {
1919
+ final VerticalPanel vPanel = new VerticalPanel();
1920
+ vPanel.setWidth("350px");
1921
+ vPanel.add(createInfoWindowLabelAndValue(stringMessages.competitor(), competitorDTO.getName()));
1922
+ vPanel.add(createInfoWindowLabelAndValue(stringMessages.sailNumber(), competitorDTO.getSailID()));
1923
+ Integer rank = null;
1924
+ if (quickRanks != null) {
1925
+ QuickRankDTO quickRank = quickRanks.get(competitorDTO);
1926
+ if (quickRank != null) {
1927
+ rank = quickRank.rank;
1928
+ }
1929
+ }
1930
+ if (rank != null) {
1931
+ vPanel.add(createInfoWindowLabelAndValue(stringMessages.rank(), String.valueOf(rank)));
1932
+ }
1933
+ SpeedWithBearingDTO speedWithBearing = lastFix.speedWithBearing;
1934
+ if (speedWithBearing == null) {
1935
+ // TODO should we show the boat at all?
1936
+ speedWithBearing = new SpeedWithBearingDTO(0, 0);
1937
+ }
1938
+ vPanel.add(createInfoWindowLabelAndValue(stringMessages.speed(),
1939
+ NumberFormatterFactory.getDecimalFormat(1).format(speedWithBearing.speedInKnots) + " "+stringMessages.knotsUnit()));
1940
+ vPanel.add(createInfoWindowLabelAndValue(stringMessages.bearing(), (int) speedWithBearing.bearingInDegrees + " "+stringMessages.degreesShort()));
1941
+ if (lastFix.degreesBoatToTheWind != null) {
1942
+ vPanel.add(createInfoWindowLabelAndValue(stringMessages.degreesBoatToTheWind(),
1943
+ (int) Math.abs(lastFix.degreesBoatToTheWind) + " " + stringMessages.degreesShort()));
1944
+ }
1945
+ if (!selectedRaces.isEmpty()) {
1946
+ RegattaAndRaceIdentifier race = selectedRaces.get(selectedRaces.size() - 1);
1947
+ if (race != null) {
1948
+ Map<CompetitorDTO, Date> from = new HashMap<CompetitorDTO, Date>();
1949
+ from.put(competitorDTO, fixesAndTails.getFixes(competitorDTO).get(fixesAndTails.getFirstShownFix(competitorDTO)).timepoint);
1950
+ Map<CompetitorDTO, Date> to = new HashMap<CompetitorDTO, Date>();
1951
+ to.put(competitorDTO, getBoatFix(competitorDTO, timer.getTime()).timepoint);
1952
+ sailingService.getDouglasPoints(race, from, to, 3,
1953
+ new AsyncCallback<Map<CompetitorDTO, List<GPSFixDTO>>>() {
1954
+ @Override
1955
+ public void onFailure(Throwable caught) {
1956
+ errorReporter.reportError("Error obtaining douglas positions: " + caught.getMessage(), true /*silentMode */);
1957
+ }
1958
+
1959
+ @Override
1960
+ public void onSuccess(Map<CompetitorDTO, List<GPSFixDTO>> result) {
1961
+ lastDouglasPeuckerResult = result;
1962
+ if (douglasMarkers != null) {
1963
+ removeAllMarkDouglasPeuckerpoints();
1964
+ }
1965
+ if (!(timer.getPlayState() == PlayStates.Playing)) {
1966
+ if (settings.isShowDouglasPeuckerPoints()) {
1967
+ showMarkDouglasPeuckerPoints(result);
1968
+ }
1969
+ }
1970
+ }
1971
+ });
1972
+ sailingService.getManeuvers(race, from, to,
1973
+ new AsyncCallback<Map<CompetitorDTO, List<ManeuverDTO>>>() {
1974
+ @Override
1975
+ public void onFailure(Throwable caught) {
1976
+ errorReporter.reportError("Error obtaining maneuvers: " + caught.getMessage(), true /*silentMode */);
1977
+ }
1978
+
1979
+ @Override
1980
+ public void onSuccess(Map<CompetitorDTO, List<ManeuverDTO>> result) {
1981
+ lastManeuverResult = result;
1982
+ if (maneuverMarkers != null) {
1983
+ removeAllManeuverMarkers();
1984
+ }
1985
+ if (!(timer.getPlayState() == PlayStates.Playing)) {
1986
+ showManeuvers(result);
1987
+ }
1988
+ }
1989
+ });
1990
+
1991
+ }
1992
+ }
1993
+ return vPanel;
1994
+ }
1995
+
1996
+ private Iterable<CompetitorDTO> getCompetitorsToShow() {
1997
+ Iterable<CompetitorDTO> result;
1998
+ Iterable<CompetitorDTO> selection = competitorSelection.getSelectedCompetitors();
1999
+ if (!settings.isShowOnlySelectedCompetitors() || Util.isEmpty(selection)) {
2000
+ result = competitorSelection.getFilteredCompetitors();
2001
+ } else {
2002
+ result = selection;
2003
+ }
2004
+ return result;
2005
+ }
2006
+
2007
+ protected void removeAllMarkDouglasPeuckerpoints() {
2008
+ if (douglasMarkers != null) {
2009
+ for (Marker marker : douglasMarkers) {
2010
+ marker.setMap((MapWidget) null);
2011
+ }
2012
+ }
2013
+ douglasMarkers = null;
2014
+ }
2015
+
2016
+ protected void removeAllManeuverMarkers() {
2017
+ if (maneuverMarkers != null) {
2018
+ for (Marker marker : maneuverMarkers) {
2019
+ marker.setMap((MapWidget) null);
2020
+ }
2021
+ maneuverMarkers = null;
2022
+ }
2023
+ }
2024
+
2025
+ private void showMarkDouglasPeuckerPoints(Map<CompetitorDTO, List<GPSFixDTO>> gpsFixPointMapForCompetitors) {
2026
+ douglasMarkers = new HashSet<Marker>();
2027
+ if (map != null && gpsFixPointMapForCompetitors != null) {
2028
+ Set<CompetitorDTO> keySet = gpsFixPointMapForCompetitors.keySet();
2029
+ Iterator<CompetitorDTO> iter = keySet.iterator();
2030
+ while (iter.hasNext()) {
2031
+ CompetitorDTO competitorDTO = iter.next();
2032
+ List<GPSFixDTO> gpsFix = gpsFixPointMapForCompetitors.get(competitorDTO);
2033
+ for (GPSFixDTO fix : gpsFix) {
2034
+ LatLng latLng = coordinateSystem.toLatLng(fix.position);
2035
+ MarkerOptions options = MarkerOptions.newInstance();
2036
+ options.setTitle(fix.timepoint+": "+fix.position+", "+fix.speedWithBearing.toString());
2037
+ Marker marker = Marker.newInstance(options);
2038
+ marker.setPosition(latLng);
2039
+ douglasMarkers.add(marker);
2040
+ marker.setMap(map);
2041
+ }
2042
+ }
2043
+ }
2044
+ }
2045
+
2046
+ private void showManeuvers(Map<CompetitorDTO, List<ManeuverDTO>> maneuvers) {
2047
+ maneuverMarkers = new HashSet<Marker>();
2048
+ if (map != null && maneuvers != null) {
2049
+ Set<CompetitorDTO> keySet = maneuvers.keySet();
2050
+ Iterator<CompetitorDTO> iter = keySet.iterator();
2051
+ while (iter.hasNext()) {
2052
+ CompetitorDTO competitorDTO = iter.next();
2053
+ List<ManeuverDTO> maneuversForCompetitor = maneuvers.get(competitorDTO);
2054
+ for (ManeuverDTO maneuver : maneuversForCompetitor) {
2055
+ if (settings.isShowManeuverType(maneuver.type)) {
2056
+ LatLng latLng = coordinateSystem.toLatLng(maneuver.position);
2057
+ Marker maneuverMarker = raceMapImageManager.maneuverIconsForTypeAndTargetTack.get(new com.sap.sse.common.Util.Pair<ManeuverType, Tack>(maneuver.type, maneuver.newTack));
2058
+ MarkerOptions options = MarkerOptions.newInstance();
2059
+ options.setTitle(maneuver.toString(stringMessages));
2060
+ options.setIcon(maneuverMarker.getIcon_MarkerImage());
2061
+ Marker marker = Marker.newInstance(options);
2062
+ marker.setPosition(latLng);
2063
+ maneuverMarkers.add(marker);
2064
+ marker.setMap(map);
2065
+ }
2066
+ }
2067
+ }
2068
+ }
2069
+ }
2070
+
2071
+ /**
2072
+ * @param date
2073
+ * the point in time for which to determine the competitor's boat position; approximated by using the fix
2074
+ * from {@link #fixes} whose time point comes closest to this date
2075
+ *
2076
+ * @return The GPS fix for the given competitor from {@link #fixes} that is closest to <code>date</code>, or
2077
+ * <code>null</code> if no fix is available
2078
+ */
2079
+ private GPSFixDTO getBoatFix(CompetitorDTO competitorDTO, Date date) {
2080
+ GPSFixDTO result = null;
2081
+ List<GPSFixDTO> competitorFixes = fixesAndTails.getFixes(competitorDTO);
2082
+ if (competitorFixes != null && !competitorFixes.isEmpty()) {
2083
+ int i = Collections.binarySearch(competitorFixes, new GPSFixDTO(date, null, null, (WindDTO) null, null, null, false),
2084
+ new Comparator<GPSFixDTO>() {
2085
+ @Override
2086
+ public int compare(GPSFixDTO o1, GPSFixDTO o2) {
2087
+ return o1.timepoint.compareTo(o2.timepoint);
2088
+ }
2089
+ });
2090
+ if (i<0) {
2091
+ i = -i-1; // no perfect match; i is now the insertion point
2092
+ // if the insertion point is at the end, use last fix
2093
+ if (i >= competitorFixes.size()) {
2094
+ result = competitorFixes.get(competitorFixes.size()-1);
2095
+ } else if (i == 0) {
2096
+ // if the insertion point is at the beginning, use first fix
2097
+ result = competitorFixes.get(0);
2098
+ } else {
2099
+ // competitorFixes must have at least two elements, and i points neither to the end nor the beginning;
2100
+ // get the fix from i and i+1 whose timepoint is closer to date
2101
+ final GPSFixDTO fixBefore = competitorFixes.get(i-1);
2102
+ final GPSFixDTO fixAfter = competitorFixes.get(i);
2103
+ final GPSFixDTO closer;
2104
+ if (date.getTime() - fixBefore.timepoint.getTime() < fixAfter.timepoint.getTime() - date.getTime()) {
2105
+ closer = fixBefore;
2106
+ } else {
2107
+ closer = fixAfter;
2108
+ }
2109
+ // now compute a weighted average depending on the time difference to "date" (see also bug 1924)
2110
+ double factorForAfter = (double) (date.getTime()-fixBefore.timepoint.getTime()) / (double) (fixAfter.timepoint.getTime() - fixBefore.timepoint.getTime());
2111
+ double factorForBefore = 1-factorForAfter;
2112
+ DegreePosition betweenPosition = new DegreePosition(factorForBefore*fixBefore.position.getLatDeg() + factorForAfter*fixAfter.position.getLatDeg(),
2113
+ factorForBefore*fixBefore.position.getLngDeg() + factorForAfter*fixAfter.position.getLngDeg());
2114
+ final double betweenBearing;
2115
+ if (fixBefore.speedWithBearing == null) {
2116
+ if (fixAfter.speedWithBearing == null) {
2117
+ betweenBearing = 0;
2118
+ } else {
2119
+ betweenBearing = fixAfter.speedWithBearing.bearingInDegrees;
2120
+ }
2121
+ } else if (fixAfter.speedWithBearing == null) {
2122
+ betweenBearing = fixBefore.speedWithBearing.bearingInDegrees;
2123
+ } else {
2124
+ betweenBearing = new ScalableBearing(new DegreeBearingImpl(fixBefore.speedWithBearing.bearingInDegrees)).
2125
+ multiply(factorForBefore).add(new ScalableBearing(new DegreeBearingImpl(fixAfter.speedWithBearing.bearingInDegrees)).
2126
+ multiply(factorForAfter)).divide(1).getDegrees();
2127
+ }
2128
+ SpeedWithBearingDTO betweenSpeed = new SpeedWithBearingDTO(
2129
+ factorForBefore*(fixBefore.speedWithBearing==null?0:fixBefore.speedWithBearing.speedInKnots) +
2130
+ factorForAfter*(fixAfter.speedWithBearing==null?0:fixAfter.speedWithBearing.speedInKnots),
2131
+ betweenBearing);
2132
+ result = new GPSFixDTO(date, betweenPosition, betweenSpeed, closer.degreesBoatToTheWind,
2133
+ closer.tack, closer.legType, fixBefore.extrapolated || fixAfter.extrapolated);
2134
+ }
2135
+ } else {
2136
+ // perfect match
2137
+ final GPSFixDTO fixAfter = competitorFixes.get(i);
2138
+ result = fixAfter;
2139
+ }
2140
+ }
2141
+ return result;
2142
+ }
2143
+
2144
+ public RaceMapSettings getSettings() {
2145
+ return settings;
2146
+ }
2147
+
2148
+ @Override
2149
+ public void addedToSelection(CompetitorDTO competitor) {
2150
+ if (settings.isShowOnlySelectedCompetitors()) {
2151
+ if (Util.size(competitorSelection.getSelectedCompetitors()) == 1) {
2152
+ // first competitors selected; remove all others from map
2153
+ Iterator<Map.Entry<CompetitorDTO, BoatOverlay>> i = boatOverlays.entrySet().iterator();
2154
+ while (i.hasNext()) {
2155
+ Entry<CompetitorDTO, BoatOverlay> next = i.next();
2156
+ if (!next.getKey().equals(competitor)) {
2157
+ BoatOverlay boatOverlay = next.getValue();
2158
+ boatOverlay.removeFromMap();
2159
+ fixesAndTails.removeTail(next.getKey());
2160
+ i.remove(); // only this way a ConcurrentModificationException while looping can be avoided
2161
+ }
2162
+ }
2163
+ showCompetitorInfoOnMap(timer.getTime(), -1, competitorSelection.getSelectedFilteredCompetitors());
2164
+ } else {
2165
+ // adding a single competitor; may need to re-load data, so refresh:
2166
+ timeChanged(timer.getTime(), null);
2167
+ }
2168
+ } else {
2169
+ // only change highlighting
2170
+ BoatOverlay boatCanvas = boatOverlays.get(competitor);
2171
+ if (boatCanvas != null) {
2172
+ boatCanvas.setSelected(displayHighlighted(competitor));
2173
+ boatCanvas.draw();
2174
+ showCompetitorInfoOnMap(timer.getTime(), -1, competitorSelection.getSelectedFilteredCompetitors());
2175
+ } else {
2176
+ // seems like an internal error not to find the lowlighted marker; but maybe the
2177
+ // competitor was added late to the race;
2178
+ // data for newly selected competitor supposedly missing; refresh
2179
+ timeChanged(timer.getTime(), null);
2180
+ }
2181
+ }
2182
+ // Now update tails for all competitors because selection change may also affect all unselected competitors
2183
+ for (CompetitorDTO oneOfAllCompetitors : competitorSelection.getAllCompetitors()) {
2184
+ Polyline tail = fixesAndTails.getTail(oneOfAllCompetitors);
2185
+ if (tail != null) {
2186
+ PolylineOptions newOptions = createTailStyle(oneOfAllCompetitors, displayHighlighted(oneOfAllCompetitors));
2187
+ tail.setOptions(newOptions);
2188
+ }
2189
+ }
2190
+ // Trigger auto-zoom if needed
2191
+ RaceMapZoomSettings zoomSettings = settings.getZoomSettings();
2192
+ if (!zoomSettings.containsZoomType(ZoomTypes.NONE) && zoomSettings.isZoomToSelectedCompetitors()) {
2193
+ zoomMapToNewBounds(zoomSettings.getNewBounds(this));
2194
+ }
2195
+ }
2196
+
2197
+ @Override
2198
+ public void removedFromSelection(CompetitorDTO competitor) {
2199
+ if (isShowAnyHelperLines()) {
2200
+ // helper lines depend on which competitor is visible, because the *visible* leader is used for
2201
+ // deciding which helper lines to show:
2202
+ timeChanged(timer.getTime(), null);
2203
+ } else {
2204
+ // try a more incremental update otherwise
2205
+ if (settings.isShowOnlySelectedCompetitors()) {
2206
+ // if selection is now empty, show all competitors
2207
+ if (Util.isEmpty(competitorSelection.getSelectedCompetitors())) {
2208
+ timeChanged(timer.getTime(), null);
2209
+ } else {
2210
+ // otherwise remove only deselected competitor's boat images and tail
2211
+ BoatOverlay removedBoatOverlay = boatOverlays.remove(competitor);
2212
+ if (removedBoatOverlay != null) {
2213
+ removedBoatOverlay.removeFromMap();
2214
+ }
2215
+ fixesAndTails.removeTail(competitor);
2216
+ showCompetitorInfoOnMap(timer.getTime(), -1, competitorSelection.getSelectedFilteredCompetitors());
2217
+ }
2218
+ } else {
2219
+ // "lowlight" currently selected competitor
2220
+ BoatOverlay boatCanvas = boatOverlays.get(competitor);
2221
+ if (boatCanvas != null) {
2222
+ boatCanvas.setSelected(displayHighlighted(competitor));
2223
+ boatCanvas.draw();
2224
+ }
2225
+ showCompetitorInfoOnMap(timer.getTime(), -1, competitorSelection.getSelectedFilteredCompetitors());
2226
+ }
2227
+ }
2228
+ //Trigger auto-zoom if needed
2229
+ RaceMapZoomSettings zoomSettings = settings.getZoomSettings();
2230
+ if (!zoomSettings.containsZoomType(ZoomTypes.NONE) && zoomSettings.isZoomToSelectedCompetitors()) {
2231
+ zoomMapToNewBounds(zoomSettings.getNewBounds(this));
2232
+ }
2233
+ }
2234
+
2235
+ private boolean isShowAnyHelperLines() {
2236
+ return settings.getHelpLinesSettings().isShowAnyHelperLines();
2237
+ }
2238
+
2239
+ @Override
2240
+ public String getLocalizedShortName() {
2241
+ return stringMessages.map();
2242
+ }
2243
+
2244
+ @Override
2245
+ public Widget getEntryWidget() {
2246
+ return this;
2247
+ }
2248
+
2249
+ @Override
2250
+ public boolean hasSettings() {
2251
+ return true;
2252
+ }
2253
+
2254
+ @Override
2255
+ public SettingsDialogComponent<RaceMapSettings> getSettingsDialogComponent() {
2256
+ return new RaceMapSettingsDialogComponent(settings, stringMessages, this.showViewSimulation && this.hasPolar);
2257
+ }
2258
+
2259
+ @Override
2260
+ public void updateSettings(RaceMapSettings newSettings) {
2261
+ boolean maneuverTypeSelectionChanged = false;
2262
+ boolean requiredRedraw = false;
2263
+ for (ManeuverType maneuverType : ManeuverType.values()) {
2264
+ if (newSettings.isShowManeuverType(maneuverType) != settings.isShowManeuverType(maneuverType)) {
2265
+ maneuverTypeSelectionChanged = true;
2266
+ settings.showManeuverType(maneuverType, newSettings.isShowManeuverType(maneuverType));
2267
+ }
2268
+ }
2269
+ if (maneuverTypeSelectionChanged) {
2270
+ if (!(timer.getPlayState() == PlayStates.Playing) && lastManeuverResult != null) {
2271
+ removeAllManeuverMarkers();
2272
+ showManeuvers(lastManeuverResult);
2273
+ }
2274
+ }
2275
+ if (newSettings.isShowDouglasPeuckerPoints() != settings.isShowDouglasPeuckerPoints()) {
2276
+ if (!(timer.getPlayState() == PlayStates.Playing) && lastDouglasPeuckerResult != null && newSettings.isShowDouglasPeuckerPoints()) {
2277
+ settings.setShowDouglasPeuckerPoints(true);
2278
+ removeAllMarkDouglasPeuckerpoints();
2279
+ showMarkDouglasPeuckerPoints(lastDouglasPeuckerResult);
2280
+ } else if (!newSettings.isShowDouglasPeuckerPoints()) {
2281
+ settings.setShowDouglasPeuckerPoints(false);
2282
+ removeAllMarkDouglasPeuckerpoints();
2283
+ }
2284
+ }
2285
+ if (newSettings.getTailLengthInMilliseconds() != settings.getTailLengthInMilliseconds()) {
2286
+ settings.setTailLengthInMilliseconds(newSettings.getTailLengthInMilliseconds());
2287
+ requiredRedraw = true;
2288
+ }
2289
+ if (newSettings.getBuoyZoneRadiusInMeters() != settings.getBuoyZoneRadiusInMeters()) {
2290
+ settings.setBuoyZoneRadiusInMeters(newSettings.getBuoyZoneRadiusInMeters());
2291
+ requiredRedraw = true;
2292
+ }
2293
+ if (newSettings.isShowOnlySelectedCompetitors() != settings.isShowOnlySelectedCompetitors()) {
2294
+ settings.setShowOnlySelectedCompetitors(newSettings.isShowOnlySelectedCompetitors());
2295
+ requiredRedraw = true;
2296
+ }
2297
+ if (newSettings.isShowSelectedCompetitorsInfo() != settings.isShowSelectedCompetitorsInfo()) {
2298
+ settings.setShowSelectedCompetitorsInfo(newSettings.isShowSelectedCompetitorsInfo());
2299
+ requiredRedraw = true;
2300
+ }
2301
+ if (!newSettings.getZoomSettings().equals(settings.getZoomSettings())) {
2302
+ settings.setZoomSettings(newSettings.getZoomSettings());
2303
+ if (!settings.getZoomSettings().containsZoomType(ZoomTypes.NONE)) {
2304
+ removeTransitions();
2305
+ zoomMapToNewBounds(settings.getZoomSettings().getNewBounds(this));
2306
+ }
2307
+ }
2308
+ if (!newSettings.getHelpLinesSettings().equals(settings.getHelpLinesSettings())) {
2309
+ settings.setHelpLinesSettings(newSettings.getHelpLinesSettings());
2310
+ requiredRedraw = true;
2311
+ }
2312
+ if (newSettings.isShowWindStreamletOverlay() != settings.isShowWindStreamletOverlay()) {
2313
+ settings.setShowWindStreamletOverlay(newSettings.isShowWindStreamletOverlay());
2314
+ streamletOverlay.setVisible(newSettings.isShowWindStreamletOverlay());
2315
+ }
2316
+ if (newSettings.isShowWindStreamletColors() != settings.isShowWindStreamletColors()) {
2317
+ settings.setShowWindStreamletColors(newSettings.isShowWindStreamletColors());
2318
+ streamletOverlay.setColors(newSettings.isShowWindStreamletColors());
2319
+ }
2320
+ if (newSettings.isShowSimulationOverlay() != settings.isShowSimulationOverlay()) {
2321
+ settings.setShowSimulationOverlay(newSettings.isShowSimulationOverlay());
2322
+ simulationOverlay.setVisible(newSettings.isShowSimulationOverlay());
2323
+ if (newSettings.isShowSimulationOverlay()) {
2324
+ simulationOverlay.updateLeg(Math.max(lastLegNumber,1), true, -1 /* ensure ui-update */);
2325
+ }
2326
+ }
2327
+ if (newSettings.isWindUp() != settings.isWindUp()) {
2328
+ settings.setWindUp(newSettings.isWindUp());
2329
+ updateCoordinateSystemFromSettings();
2330
+ requiredRedraw = true;
2331
+ }
2332
+ if (newSettings.getTransparentHoverlines() != settings.getTransparentHoverlines()) {
2333
+ settings.setTransparentHoverlines(newSettings.getTransparentHoverlines());
2334
+ }
2335
+ if (newSettings.getHoverlineStrokeWeight() != settings.getHoverlineStrokeWeight()) {
2336
+ settings.setHoverlineStrokeWeight(newSettings.getHoverlineStrokeWeight());
2337
+ }
2338
+ if (requiredRedraw) {
2339
+ redraw();
2340
+ }
2341
+ }
2342
+
2343
+ public static class BoatsBoundsCalculator extends LatLngBoundsCalculatorForSelected {
2344
+
2345
+ @Override
2346
+ public LatLngBounds calculateNewBounds(RaceMap forMap) {
2347
+ LatLngBounds newBounds = null;
2348
+ Iterable<CompetitorDTO> selectedCompetitors = forMap.competitorSelection.getSelectedCompetitors();
2349
+ Iterable<CompetitorDTO> competitors = new ArrayList<CompetitorDTO>();
2350
+ if (selectedCompetitors == null || !selectedCompetitors.iterator().hasNext()) {
2351
+ competitors = forMap.getCompetitorsToShow();
2352
+ } else {
2353
+ competitors = isZoomOnlyToSelectedCompetitors(forMap) ? selectedCompetitors : forMap.getCompetitorsToShow();
2354
+ }
2355
+ for (CompetitorDTO competitor : competitors) {
2356
+ try {
2357
+ GPSFixDTO competitorFix = forMap.getBoatFix(competitor, forMap.timer.getTime());
2358
+ Position competitorPosition = competitorFix != null ? competitorFix.position : null;
2359
+ if (competitorPosition != null) {
2360
+ if (newBounds == null) {
2361
+ newBounds = BoundsUtil.getAsBounds(forMap.coordinateSystem.toLatLng(competitorPosition));
2362
+ } else {
2363
+ newBounds = newBounds.extend(forMap.coordinateSystem.toLatLng(competitorPosition));
2364
+ }
2365
+ }
2366
+ } catch (IndexOutOfBoundsException e) {
2367
+ // TODO can't this be predicted and the exception be avoided in the first place?
2368
+ // Catch this in case the competitor has no GPS fixes at the current time (e.g. in race 'Finale 2' of STG)
2369
+ }
2370
+ }
2371
+ return newBounds;
2372
+ }
2373
+
2374
+ }
2375
+
2376
+ public static class TailsBoundsCalculator extends LatLngBoundsCalculatorForSelected {
2377
+ @Override
2378
+ public LatLngBounds calculateNewBounds(RaceMap racemap) {
2379
+ LatLngBounds newBounds = null;
2380
+ Iterable<CompetitorDTO> competitors = isZoomOnlyToSelectedCompetitors(racemap) ? racemap.competitorSelection.getSelectedCompetitors() : racemap.getCompetitorsToShow();
2381
+ for (CompetitorDTO competitor : competitors) {
2382
+ Polyline tail = racemap.fixesAndTails.getTail(competitor);
2383
+ LatLngBounds bounds = null;
2384
+ // TODO: Find a replacement for missing Polyline function getBounds() from v2
2385
+ // see also http://stackoverflow.com/questions/3284808/getting-the-bounds-of-a-polyine-in-google-maps-api-v3;
2386
+ // optionally, consider providing a bounds cache with two sorted sets that organize the LatLng objects for O(1) bounds calculation and logarithmic add, ideally O(1) remove
2387
+ if (tail != null && tail.getPath().getLength() >= 1) {
2388
+ bounds = BoundsUtil.getAsBounds(tail.getPath().get(0));
2389
+ for (int i = 1; i < tail.getPath().getLength(); i++) {
2390
+ bounds = bounds.extend(tail.getPath().get(i));
2391
+ }
2392
+ }
2393
+ if (bounds != null) {
2394
+ if (newBounds == null) {
2395
+ newBounds = bounds;
2396
+ } else {
2397
+ newBounds = newBounds.union(bounds);
2398
+ }
2399
+ }
2400
+ }
2401
+ return newBounds;
2402
+ }
2403
+ }
2404
+
2405
+ public static class CourseMarksBoundsCalculator implements LatLngBoundsCalculator {
2406
+ @Override
2407
+ public LatLngBounds calculateNewBounds(RaceMap forMap) {
2408
+ LatLngBounds newBounds = null;
2409
+ Iterable<MarkDTO> marksToZoom = forMap.markDTOs.values();
2410
+ if (marksToZoom != null) {
2411
+ for (MarkDTO markDTO : marksToZoom) {
2412
+ if (newBounds == null) {
2413
+ newBounds = BoundsUtil.getAsBounds(forMap.coordinateSystem.toLatLng(markDTO.position));
2414
+ } else {
2415
+ newBounds = newBounds.extend(forMap.coordinateSystem.toLatLng(markDTO.position));
2416
+ }
2417
+ }
2418
+ }
2419
+ return newBounds;
2420
+ }
2421
+ }
2422
+
2423
+ public static class WindSensorsBoundsCalculator implements LatLngBoundsCalculator {
2424
+ @Override
2425
+ public LatLngBounds calculateNewBounds(RaceMap forMap) {
2426
+ LatLngBounds newBounds = null;
2427
+ Collection<WindSensorOverlay> marksToZoom = forMap.windSensorOverlays.values();
2428
+ if (marksToZoom != null) {
2429
+ for (WindSensorOverlay windSensorOverlay : marksToZoom) {
2430
+ if (BoundsUtil.getAsPosition(windSensorOverlay.getLatLngPosition()) != null) {
2431
+ LatLngBounds bounds = BoundsUtil.getAsBounds(windSensorOverlay.getLatLngPosition());
2432
+ if (newBounds == null) {
2433
+ newBounds = bounds;
2434
+ } else {
2435
+ newBounds = newBounds.extend(windSensorOverlay.getLatLngPosition());
2436
+ }
2437
+ }
2438
+ }
2439
+ }
2440
+ return newBounds;
2441
+ }
2442
+ }
2443
+
2444
+ @Override
2445
+ public void initializeData(boolean showMapControls, boolean showHeaderPanel) {
2446
+ loadMapsAPIV3(showMapControls, showHeaderPanel);
2447
+ }
2448
+
2449
+ @Override
2450
+ public boolean isDataInitialized() {
2451
+ return isMapInitialized;
2452
+ }
2453
+
2454
+ @Override
2455
+ public void onResize() {
2456
+ if (map != null) {
2457
+ map.triggerResize();
2458
+ zoomMapToNewBounds(settings.getZoomSettings().getNewBounds(RaceMap.this));
2459
+ }
2460
+ // Adjust RaceMap headers to avoid overlapping based on the RaceMap width
2461
+ boolean isCompactHeader = this.getOffsetWidth() <= 600;
2462
+ getLeftHeaderPanel().setStyleName(COMPACT_HEADER_STYLE, isCompactHeader);
2463
+ getRightHeaderPanel().setStyleName(COMPACT_HEADER_STYLE, isCompactHeader);
2464
+
2465
+ // Adjust combined wind and true north indicator panel indent, based on the RaceMap height
2466
+ if (combinedWindPanel.getParent() != null && trueNorthIndicatorPanel.getParent() != null) {
2467
+ this.adjustLeftControlsIndent();
2468
+ }
2469
+ }
2470
+
2471
+ private void adjustLeftControlsIndent() {
2472
+ combinedWindPanel.getParent().setStyleName("CombinedWindPanelParentDiv");
2473
+ trueNorthIndicatorPanel.getParent().setStyleName("TrueNorthIndicatorPanelParentDiv");
2474
+ String leftControlsIndentStyle = getLeftControlsIndentStyle();
2475
+ if (leftControlsIndentStyle != null) {
2476
+ combinedWindPanel.getParent().addStyleName(leftControlsIndentStyle);
2477
+ trueNorthIndicatorPanel.getParent().addStyleName(leftControlsIndentStyle);
2478
+ }
2479
+ }
2480
+
2481
+ @Override
2482
+ public void competitorsListChanged(Iterable<CompetitorDTO> competitors) {
2483
+ timeChanged(timer.getTime(), null);
2484
+ }
2485
+
2486
+ @Override
2487
+ public void filteredCompetitorsListChanged(Iterable<CompetitorDTO> filteredCompetitors) {
2488
+ timeChanged(timer.getTime(), null);
2489
+ }
2490
+
2491
+ @Override
2492
+ public void filterChanged(FilterSet<CompetitorDTO, ? extends Filter<CompetitorDTO>> oldFilterSet,
2493
+ FilterSet<CompetitorDTO, ? extends Filter<CompetitorDTO>> newFilterSet) {
2494
+ // nothing to do; if the list of filtered competitors has changed, a separate call to filteredCompetitorsListChanged will occur
2495
+ }
2496
+
2497
+ @Override
2498
+ public PolylineOptions createTailStyle(CompetitorDTO competitor, boolean isHighlighted) {
2499
+ PolylineOptions options = PolylineOptions.newInstance();
2500
+ options.setClickable(true);
2501
+ options.setGeodesic(true);
2502
+ options.setStrokeOpacity(1.0);
2503
+ boolean noCompetitorSelected = Util.isEmpty(competitorSelection.getSelectedCompetitors());
2504
+ if (isHighlighted || noCompetitorSelected) {
2505
+ options.setStrokeColor(competitorSelection.getColor(competitor, raceIdentifier).getAsHtml());
2506
+ } else {
2507
+ options.setStrokeColor(CssColor.make(200, 200, 200).toString());
2508
+ }
2509
+ if (isHighlighted) {
2510
+ options.setStrokeWeight(2);
2511
+ } else {
2512
+ options.setStrokeWeight(1);
2513
+ }
2514
+ options.setZindex(RaceMapOverlaysZIndexes.BOATTAILS_ZINDEX);
2515
+ return options;
2516
+ }
2517
+
2518
+ @Override
2519
+ public Polyline createTail(final CompetitorDTO competitor, List<LatLng> points) {
2520
+ PolylineOptions options = createTailStyle(competitor, displayHighlighted(competitor));
2521
+ Polyline result = Polyline.newInstance(options);
2522
+ MVCArray<LatLng> pointsAsArray = MVCArray.newInstance(points.toArray(new LatLng[0]));
2523
+ result.setPath(pointsAsArray);
2524
+ result.setMap(map);
2525
+ Hoverline resultHoverline = new Hoverline(result, options, this);
2526
+ final ClickMapHandler clickHandler = new ClickMapHandler() {
2527
+ @Override
2528
+ public void onEvent(ClickMapEvent event) {
2529
+ showCompetitorInfoWindow(competitor, event.getMouseEvent().getLatLng());
2530
+ }
2531
+ };
2532
+ result.addClickHandler(clickHandler);
2533
+ resultHoverline.addClickHandler(clickHandler);
2534
+ result.addMouseOverHandler(new MouseOverMapHandler() {
2535
+ @Override
2536
+ public void onEvent(MouseOverMapEvent event) {
2537
+ map.setTitle(competitor.getSailID() + ", " + competitor.getName());
2538
+ }
2539
+ });
2540
+ resultHoverline.addMouseOutMoveHandler(new MouseOutMapHandler() {
2541
+ @Override
2542
+ public void onEvent(MouseOutMapEvent event) {
2543
+ map.setTitle("");
2544
+ }
2545
+ });
2546
+ return result;
2547
+ }
2548
+
2549
+ @Override
2550
+ public Integer getRank(CompetitorDTO competitor) {
2551
+ final Integer result;
2552
+ QuickRankDTO quickRank = quickRanks.get(competitor);
2553
+ if (quickRank != null) {
2554
+ result = quickRank.rank;
2555
+ } else {
2556
+ result = null;
2557
+ }
2558
+ return result;
2559
+ }
2560
+
2561
+ private Image createSAPLogo() {
2562
+ ImageResource sapLogoResource = resources.sapLogoOverlay();
2563
+ Image sapLogo = new Image(sapLogoResource);
2564
+ sapLogo.addClickHandler(new ClickHandler() {
2565
+ @Override
2566
+ public void onClick(ClickEvent event) {
2567
+ Window.open("http://www.sap.com", "_blank", null);
2568
+ }
2569
+ });
2570
+ sapLogo.setStyleName("raceBoard-Logo");
2571
+ return sapLogo;
2572
+ }
2573
+
2574
+ @Override
2575
+ public String getDependentCssClassName() {
2576
+ return "raceMap";
2577
+ }
2578
+
2579
+ /**
2580
+ * The default zoom bounds are defined by the boats
2581
+ */
2582
+ private LatLngBounds getDefaultZoomBounds() {
2583
+ return new BoatsBoundsCalculator().calculateNewBounds(RaceMap.this);
2584
+ }
2585
+
2586
+ private MapOptions getMapOptions(final boolean showMapControls, boolean windUp) {
2587
+ MapOptions mapOptions = MapOptions.newInstance();
2588
+ mapOptions.setScrollWheel(true);
2589
+ mapOptions.setMapTypeControl(showMapControls && !windUp);
2590
+ mapOptions.setPanControl(showMapControls);
2591
+ mapOptions.setZoomControl(showMapControls);
2592
+ mapOptions.setScaleControl(true);
2593
+ if (windUp) {
2594
+ mapOptions.setMinZoom(8);
2595
+ } else {
2596
+ mapOptions.setMinZoom(0);
2597
+ }
2598
+ MapTypeStyle[] mapTypeStyles = new MapTypeStyle[4];
2599
+
2600
+ // hide all transit lines including ferry lines
2601
+ mapTypeStyles[0] = GoogleMapStyleHelper.createHiddenStyle(MapTypeStyleFeatureType.TRANSIT);
2602
+ // hide points of interest
2603
+ mapTypeStyles[1] = GoogleMapStyleHelper.createHiddenStyle(MapTypeStyleFeatureType.POI);
2604
+ // simplify road display
2605
+ mapTypeStyles[2] = GoogleMapStyleHelper.createSimplifiedStyle(MapTypeStyleFeatureType.ROAD);
2606
+ // set water color
2607
+ // To play with the styles, check out http://gmaps-samples-v3.googlecode.com/svn/trunk/styledmaps/wizard/index.html.
2608
+ // To convert an RGB color into the strange hue/saturation/lightness model used by the Google Map use
2609
+ // http://software.stadtwerk.org/google_maps_colorizr/#water/all/123456/.
2610
+ mapTypeStyles[3] = GoogleMapStyleHelper.createColorStyle(MapTypeStyleFeatureType.WATER, new RGBColor(0, 136, 255), 0, -70);
2611
+
2612
+ MapTypeControlOptions mapTypeControlOptions = MapTypeControlOptions.newInstance();
2613
+ mapTypeControlOptions.setPosition(ControlPosition.BOTTOM_RIGHT);
2614
+ mapOptions.setMapTypeControlOptions(mapTypeControlOptions);
2615
+
2616
+ mapOptions.setMapTypeStyles(mapTypeStyles);
2617
+ // no need to try to position the scale control; it always ends up at the right bottom corner
2618
+ mapOptions.setStreetViewControl(false);
2619
+ if (showMapControls) {
2620
+ ZoomControlOptions zoomControlOptions = ZoomControlOptions.newInstance();
2621
+ zoomControlOptions.setPosition(ControlPosition.RIGHT_TOP);
2622
+ mapOptions.setZoomControlOptions(zoomControlOptions);
2623
+ PanControlOptions panControlOptions = PanControlOptions.newInstance();
2624
+ panControlOptions.setPosition(ControlPosition.RIGHT_TOP);
2625
+ mapOptions.setPanControlOptions(panControlOptions);
2626
+ }
2627
+ return mapOptions;
2628
+ }
2629
+
2630
+ /**
2631
+ * @return CSS style name to adjust the indent of left controls (combined wind and true north indicator).
2632
+ */
2633
+ protected String getLeftControlsIndentStyle() {
2634
+ return null;
2635
+ }
2636
+}
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMapSettings.java
... ...
@@ -19,6 +19,10 @@ public class RaceMapSettings extends AbstractSettings {
19 19
private RaceMapZoomSettings zoomSettings;
20 20
21 21
private RaceMapHelpLinesSettings helpLinesSettings;
22
+
23
+ private boolean transparentHoverlines = true;
24
+
25
+ private int hoverlineStrokeWeight = 10;
22 26
23 27
private long tailLengthInMilliseconds = 100000l;
24 28
... ...
@@ -55,6 +59,8 @@ public class RaceMapSettings extends AbstractSettings {
55 59
public RaceMapSettings(RaceMapSettings settings) {
56 60
this.buoyZoneRadiusInMeters = settings.buoyZoneRadiusInMeters;
57 61
this.helpLinesSettings = new RaceMapHelpLinesSettings(settings.getHelpLinesSettings().getVisibleHelpLineTypes());
62
+ this.transparentHoverlines = settings.transparentHoverlines;
63
+ this.hoverlineStrokeWeight = settings.hoverlineStrokeWeight;
58 64
this.maneuverTypesToShow = settings.maneuverTypesToShow;
59 65
this.showDouglasPeuckerPoints = settings.showDouglasPeuckerPoints;
60 66
this.showOnlySelectedCompetitors = settings.showOnlySelectedCompetitors;
... ...
@@ -153,6 +159,22 @@ public class RaceMapSettings extends AbstractSettings {
153 159
public void setHelpLinesSettings(RaceMapHelpLinesSettings helpLinesSettings) {
154 160
this.helpLinesSettings = helpLinesSettings;
155 161
}
162
+
163
+ public boolean getTransparentHoverlines() {
164
+ return this.transparentHoverlines;
165
+ }
166
+
167
+ public void setTransparentHoverlines(boolean transparentHoverlines) {
168
+ this.transparentHoverlines = transparentHoverlines;
169
+ }
170
+
171
+ public int getHoverlineStrokeWeight() {
172
+ return this.hoverlineStrokeWeight;
173
+ }
174
+
175
+ public void setHoverlineStrokeWeight(int hoverlineStrokeWeight) {
176
+ this.hoverlineStrokeWeight = hoverlineStrokeWeight;
177
+ }
156 178
157 179
public boolean isShowSelectedCompetitorsInfo() {
158 180
return showSelectedCompetitorsInfo;
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMapSettingsDialogComponent.java
... ...
@@ -24,11 +24,14 @@ import com.sap.sailing.gwt.ui.client.StringMessages;
24 24
import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMapHelpLinesSettings.HelpLineTypes;
25 25
import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMapZoomSettings.ZoomTypes;
26 26
import com.sap.sse.common.Util;
27
+import com.sap.sse.gwt.client.controls.IntegerBox;
27 28
import com.sap.sse.gwt.client.dialog.DataEntryDialog;
28 29
import com.sap.sse.gwt.client.dialog.DataEntryDialog.Validator;
29 30
import com.sap.sse.gwt.client.shared.components.SettingsDialogComponent;
30 31
31 32
public class RaceMapSettingsDialogComponent implements SettingsDialogComponent<RaceMapSettings> {
33
+ private static final int MAX_BUOY_ZONE_RADIUS_IN_METERS = 100;
34
+ private static final int MAX_STROKE_WEIGHT = 33;
32 35
//Initializing the lists to prevent a null pointer exception in the first validation call
33 36
private List<Util.Pair<CheckBox, ManeuverType>> checkboxAndManeuverType = new ArrayList<Util.Pair<CheckBox, ManeuverType>>();
34 37
private List<Util.Pair<CheckBox, ZoomTypes>> checkboxAndZoomType = new ArrayList<Util.Pair<CheckBox,ZoomTypes>>();
... ...
@@ -43,6 +46,8 @@ public class RaceMapSettingsDialogComponent implements SettingsDialogComponent<R
43 46
private CheckBox showSelectedCompetitorsInfoCheckBox;
44 47
private LongBox tailLengthBox;
45 48
private DoubleBox buoyZoneRadiusBox;
49
+ private CheckBox transparentHoverlines;
50
+ private IntegerBox hoverlineStrokeWeight;
46 51
private boolean showViewSimulation;
47 52
48 53
private final StringMessages stringMessages;
... ...
@@ -211,6 +216,17 @@ public class RaceMapSettingsDialogComponent implements SettingsDialogComponent<R
211 216
vp.add(createHelpLineCheckBox(dialog, HelpLineTypes.STARTLINETOFIRSTMARKTRIANGLE));
212 217
vp.add(createHelpLineCheckBox(dialog, HelpLineTypes.COURSEGEOMETRY));
213 218
219
+ transparentHoverlines = dialog.createCheckbox(stringMessages.transparentBufferLineOnHover());
220
+ transparentHoverlines.setValue(initialSettings.getTransparentHoverlines());
221
+ vp.add(transparentHoverlines);
222
+
223
+ HorizontalPanel hoverlineStrokeWeightPanel = new HorizontalPanel();
224
+ Label hoverlineStrokeWeightLabel = new Label(stringMessages.bufferLineStrokeWeight() + ":");
225
+ hoverlineStrokeWeightPanel.add(hoverlineStrokeWeightLabel);
226
+ hoverlineStrokeWeight = dialog.createIntegerBox(initialSettings.getHoverlineStrokeWeight(), 3);
227
+ hoverlineStrokeWeightPanel.add(hoverlineStrokeWeight);
228
+ vp.add(hoverlineStrokeWeightPanel);
229
+
214 230
return vp;
215 231
}
216 232
... ...
@@ -267,6 +283,8 @@ public class RaceMapSettingsDialogComponent implements SettingsDialogComponent<R
267 283
result.setBuoyZoneRadiusInMeters(value);
268 284
}
269 285
}
286
+ result.setTransparentHoverlines(transparentHoverlines.getValue());
287
+ result.setHoverlineStrokeWeight(hoverlineStrokeWeight.getValue());
270 288
return result;
271 289
}
272 290
... ...
@@ -304,8 +322,10 @@ public class RaceMapSettingsDialogComponent implements SettingsDialogComponent<R
304 322
if (valueToValidate.getHelpLinesSettings().isVisible(HelpLineTypes.BOATTAILS) && valueToValidate.getTailLengthInMilliseconds() <= 0) {
305 323
errorMessage = stringMessages.tailLengthMustBePositive();
306 324
} else if (valueToValidate.getHelpLinesSettings().isVisible(HelpLineTypes.BUOYZONE)
307
- && (valueToValidate.getBuoyZoneRadiusInMeters() < 0.0 || valueToValidate.getBuoyZoneRadiusInMeters() > 100.0)) {
308
- errorMessage = stringMessages.valueMustBeBetweenMinMax(stringMessages.buoyZone(), "0", "100");
325
+ && (valueToValidate.getBuoyZoneRadiusInMeters() < 0 || valueToValidate.getBuoyZoneRadiusInMeters() > MAX_BUOY_ZONE_RADIUS_IN_METERS)) {
326
+ errorMessage = stringMessages.valueMustBeBetweenMinMax(stringMessages.buoyZone(), 0, 100);
327
+ } else if (valueToValidate.getHoverlineStrokeWeight() < 0 || valueToValidate.getHoverlineStrokeWeight() > MAX_STROKE_WEIGHT) {
328
+ errorMessage = stringMessages.valueMustBeBetweenMinMax(stringMessages.bufferLineStrokeWeight(), 0, MAX_STROKE_WEIGHT);
309 329
}
310 330
return errorMessage;
311 331
}
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/CompetitorFilter.css
... ...
@@ -1,5 +1,6 @@
1 1
.competitorFilterContainer {
2
- width: 400px;
2
+ max-width: 400px;
3
+ width: 100%;
3 4
height: 2.14em;
4 5
background-color: #FFFFFF;
5 6
margin-left: 0px;
... ...
@@ -22,7 +23,7 @@
22 23
.searchInput {
23 24
font-size: .933333333333333em;
24 25
height: 2.142857142857143em;
25
- width: 280px;
26
+ width: 70%;
26 27
border: none;
27 28
background-color: #eaeaea;
28 29
padding: 0;
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardEntryPoint.java
... ...
@@ -258,7 +258,7 @@ public class LeaderboardEntryPoint extends AbstractSailingEntryPoint {
258 258
result = LeaderboardSettingsFactory.getInstance().createNewDefaultSettings(null, null,
259 259
/* overallDetails */ overallDetails, null,
260 260
/* autoExpandFirstRace */false, refreshIntervalMillis, numberOfLastRacesToShow,
261
- raceColumnSelectionStrategy);
261
+ raceColumnSelectionStrategy, /* showCompetitorSailIdColumns */ true, /*showCompetitorFullNameColumn*/ true);
262 262
}
263 263
return result;
264 264
}
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java
... ...
@@ -348,7 +348,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS
348 348
private Element elementToBlur;
349 349
private boolean showSelectionCheckbox;
350 350
351
- private List<LeaderboardUpdateListener> leaderboardUpdateListener;
351
+ private final List<LeaderboardUpdateListener> leaderboardUpdateListener;
352 352
353 353
private boolean initialCompetitorFilterHasBeenApplied = false;
354 354
private final boolean showCompetitorFilterStatus;
... ...
@@ -673,6 +673,13 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS
673 673
final String twoLetterIsoCountryCode = competitor.getTwoLetterIsoCountryCode();
674 674
final String flagImageURL = competitor.getFlagImageURL();
675 675
676
+ boolean showBoatColor = !isShowCompetitorFullName() && LeaderboardPanel.this.isEmbedded && preSelectedRace != null;
677
+ if (showBoatColor) {
678
+ String competitorColor = LeaderboardPanel.this.competitorSelectionProvider.getColor(
679
+ competitorFetcher.getCompetitor(object), LeaderboardPanel.this.preSelectedRace).getAsHtml();
680
+ sb.appendHtmlConstant("<div style=\"border-bottom: 2px solid " + competitorColor + ";\">");
681
+ }
682
+
676 683
if (flagImageURL != null && !flagImageURL.isEmpty()) {
677 684
sb.appendHtmlConstant("<img src=\"" + flagImageURL + "\" width=\"18px\" height=\"12px\" title=\"" + competitor.getName() + "\"/>");
678 685
sb.appendHtmlConstant("&nbsp;");
... ...
@@ -689,6 +696,9 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS
689 696
}
690 697
}
691 698
sb.appendEscaped(competitor.getSailID());
699
+ if (showBoatColor) {
700
+ sb.appendHtmlConstant("</div>");
701
+ }
692 702
}
693 703
694 704
@Override
... ...
@@ -2403,27 +2413,8 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS
2403 2413
scoreCorrectionLastUpdateTimeLabel.setText("");
2404 2414
}
2405 2415
2406
- List<com.sap.sse.common.Util.Pair<RaceColumnDTO, FleetDTO>> liveRaces = leaderboard.getLiveRaces(timer.getLiveTimePointInMillis());
2407
- boolean hasLiveRace = !liveRaces.isEmpty();
2416
+ boolean hasLiveRace = !leaderboard.getLiveRaces(timer.getLiveTimePointInMillis()).isEmpty();
2408 2417
liveRaceLabel.setText(hasLiveRace ? getLiveRacesText() : "");
2409
- if (hasLiveRace) {
2410
- String liveRaceText = "";
2411
- if(liveRaces.size() == 1) {
2412
- com.sap.sse.common.Util.Pair<RaceColumnDTO, FleetDTO> liveRace = liveRaces.get(0);
2413
- liveRaceText = stringMessages.raceIsLive("'" + liveRace.getA().getRaceColumnName() + "'");
2414
- } else {
2415
- String raceNames = "";
2416
- for (com.sap.sse.common.Util.Pair<RaceColumnDTO, FleetDTO> liveRace : liveRaces) {
2417
- raceNames += "'" + liveRace.getA().getRaceColumnName() + "', ";
2418
- }
2419
- // remove last ", "
2420
- raceNames = raceNames.substring(0, raceNames.length() - 2);
2421
- liveRaceText = stringMessages.racesAreLive(raceNames);
2422
- }
2423
- liveRaceLabel.setText(liveRaceText);
2424
- } else {
2425
- liveRaceLabel.setText("");
2426
- }
2427 2418
scoreCorrectionLastUpdateTimeLabel.setVisible(!hasLiveRace);
2428 2419
liveRaceLabel.setVisible(hasLiveRace);
2429 2420
}
... ...
@@ -3191,7 +3182,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS
3191 3182
if (timer != null) {
3192 3183
timer.removeTimeListener(this);
3193 3184
}
3194
- if(leaderboardUpdateListener != null) {
3185
+ if (leaderboardUpdateListener != null) {
3195 3186
leaderboardUpdateListener.clear();
3196 3187
}
3197 3188
}
... ...
@@ -3250,18 +3241,20 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS
3250 3241
public String getLiveRacesText() {
3251 3242
String result = "";
3252 3243
List<com.sap.sse.common.Util.Pair<RaceColumnDTO, FleetDTO>> liveRaces = leaderboard.getLiveRaces(timer.getLiveTimePointInMillis());
3244
+ boolean isMeta = leaderboard.type.isMetaLeaderboard();
3253 3245
if (!liveRaces.isEmpty()) {
3254
- if(liveRaces.size() == 1) {
3246
+ if (liveRaces.size() == 1) {
3255 3247
com.sap.sse.common.Util.Pair<RaceColumnDTO, FleetDTO> liveRace = liveRaces.get(0);
3256
- result = stringMessages.raceIsLive("'" + liveRace.getA().getRaceColumnName() + "'");
3248
+ String text = "'" + liveRace.getA().getRaceColumnName() + "'";
3249
+ result = isMeta ? stringMessages.regattaIsLive(text) : stringMessages.raceIsLive(text);
3257 3250
} else {
3258
- String raceNames = "";
3251
+ String names = "";
3259 3252
for (com.sap.sse.common.Util.Pair<RaceColumnDTO, FleetDTO> liveRace : liveRaces) {
3260
- raceNames += "'" + liveRace.getA().getRaceColumnName() + "', ";
3253
+ names += "'" + liveRace.getA().getRaceColumnName() + "', ";
3261 3254
}
3262 3255
// remove last ", "
3263
- raceNames = raceNames.substring(0, raceNames.length() - 2);
3264
- result = stringMessages.racesAreLive(raceNames);
3256
+ names = names.substring(0, names.length() - 2);
3257
+ result = isMeta ? stringMessages.regattasAreLive(names) : stringMessages.racesAreLive(names);
3265 3258
}
3266 3259
}
3267 3260
return result;
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardSettingsFactory.java
... ...
@@ -78,11 +78,11 @@ public class LeaderboardSettingsFactory {
78 78
/* refresh interval */ null, /* name of race to sort */ nameOfRaceToSort,
79 79
/* ascending */ true, /* updateUponPlayStateChange */ true, raceColumnSelection.getType(),
80 80
/*showAddedScores*/ false, /*showOverallRacesCompleted*/ false,
81
- /*showCompetitorSailIdColumns*/showCompetitorSailIdColumn, /*showCompetitorFullNameColumn*/showCompetitorNameColumn);
81
+ showCompetitorSailIdColumn, showCompetitorNameColumn);
82 82
break;
83 83
case Replay:
84 84
settings = createNewDefaultSettings(namesOfRaceColumnsToShow, namesOfRacesToShow, nameOfRaceToSort, /* autoExpandFirstRace */
85
- nameOfRaceColumnToShow != null, showRegattaRank);
85
+ nameOfRaceColumnToShow != null, showRegattaRank, showCompetitorSailIdColumn, showCompetitorNameColumn);
86 86
break;
87 87
}
88 88
return settings;
... ...
@@ -127,28 +127,32 @@ public class LeaderboardSettingsFactory {
127 127
* such a meta leaderboard exists; if multiple such meta leaderboards exist, they are all shown
128 128
*/
129 129
public LeaderboardSettings createNewDefaultSettings(List<String> namesOfRaceColumnsToShow,
130
- List<String> namesOfRacesToShow, String nameOfRaceToSort, boolean autoExpandPreSelectedRace, Boolean showRegattaRank) {
130
+ List<String> namesOfRacesToShow, String nameOfRaceToSort, boolean autoExpandPreSelectedRace, Boolean showRegattaRank,
131
+ boolean showCompetitorSailIdColumn, boolean showCompetitorFullNameColumn) {
131 132
final List<DetailType> overallDetails = createOverallDetailsForShowRegattaRank(showRegattaRank);
132 133
return createNewDefaultSettings(namesOfRaceColumnsToShow, namesOfRacesToShow, overallDetails,
133
- nameOfRaceToSort, autoExpandPreSelectedRace, /* refreshIntervalMillis */ null);
134
+ nameOfRaceToSort, autoExpandPreSelectedRace, /* refreshIntervalMillis */ null,
135
+ showCompetitorSailIdColumn, showCompetitorFullNameColumn);
134 136
}
135 137
136 138
/**
137
- * Like {@link #createNewDefaultSettings(List, List, String, boolean, boolean)}, only that an additional refresh interval for auto-refresh
139
+ * Like {@link #createNewDefaultSettings(List, List, String, boolean, boolean, boolean, boolean)}, only that an additional refresh interval for auto-refresh
138 140
* may be specified; if <code>null</code>, no auto-refresh shall be performed
139 141
*/
140
- public LeaderboardSettings createNewDefaultSettings(List<String> namesOfRaceColumnsToShow,
142
+ private LeaderboardSettings createNewDefaultSettings(List<String> namesOfRaceColumnsToShow,
141 143
List<String> namesOfRacesToShow, List<DetailType> overallDetailsToShow, String nameOfRaceToSort,
142
- boolean autoExpandPreSelectedRace, Long refreshIntervalMillis) {
144
+ boolean autoExpandPreSelectedRace, Long refreshIntervalMillis, boolean showCompetitorSailIdColumns, boolean showCompetitorFullNameColumn) {
143 145
return createNewDefaultSettings(namesOfRaceColumnsToShow, namesOfRacesToShow, overallDetailsToShow,
144 146
nameOfRaceToSort, autoExpandPreSelectedRace, refreshIntervalMillis,
145
- /* numberOfLastRacesToShow */null, /* raceColumnSelectionStrategy */ RaceColumnSelectionStrategies.EXPLICIT);
147
+ /* numberOfLastRacesToShow */null, /* raceColumnSelectionStrategy */ RaceColumnSelectionStrategies.EXPLICIT,
148
+ showCompetitorSailIdColumns, showCompetitorFullNameColumn);
146 149
}
147 150
148 151
public LeaderboardSettings createNewDefaultSettings(List<String> namesOfRaceColumnsToShow,
149 152
List<String> namesOfRacesToShow, List<DetailType> overallDetailsToShow, String nameOfRaceToSort,
150 153
boolean autoExpandPreSelectedRace, Long refreshIntervalMillis, Integer numberOfLastRacesToShow,
151
- RaceColumnSelectionStrategies raceColumnSelectionStrategy) {
154
+ RaceColumnSelectionStrategies raceColumnSelectionStrategy, boolean showCompetitorSailIdColumns,
155
+ boolean showCompetitorFullNameColumn) {
152 156
if (namesOfRaceColumnsToShow != null && namesOfRacesToShow != null) {
153 157
throw new IllegalArgumentException("Can specify race columns either by column or by race name, not both");
154 158
}
... ...
@@ -168,7 +172,7 @@ public class LeaderboardSettingsFactory {
168 172
autoExpandPreSelectedRace, refreshIntervalMillis, /* sort by column */ nameOfRaceToSort,
169 173
/* ascending */ true, /* updateUponPlayStateChange */ true, raceColumnSelectionStrategy,
170 174
/*showAddedScores*/ false, /*showOverallRacesCompleted*/ false,
171
- /*showCompetitorSailIdColumns*/true, /*showCompetitorFullNameColumn*/true);
175
+ showCompetitorSailIdColumns, showCompetitorFullNameColumn);
172 176
}
173 177
174 178
public LeaderboardSettings mergeLeaderboardSettings(LeaderboardSettings settingsWithRaceSelection, LeaderboardSettings settingsWithDetails) {
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardUrlConfigurationDialog.java
... ...
@@ -106,7 +106,7 @@ public class LeaderboardUrlConfigurationDialog extends SettingsDialog<Leaderboar
106 106
}
107 107
LeaderboardSettings settings = LeaderboardSettingsFactory.getInstance().createNewDefaultSettings(
108 108
namesOfRaceColumnsToShow, /* namesOfRacesToShow */null, /* nameOfRaceToSort */null, /* autoExpandPreSelectedRace */
109
- false, /* showRegattaRank */ true);
109
+ false, /* showRegattaRank */ true, /* showCompetitorSailIdColumn */ true, /* showCompetitorFullNameColumn */ true);
110 110
leaderboardSettingsDialogComponent = new LeaderboardSettingsDialogComponent(settings.getManeuverDetailsToShow(),
111 111
settings.getLegDetailsToShow(), settings.getRaceDetailsToShow(), settings.getOverallDetailsToShow(), raceList,
112 112
/* select all races by default */ raceList, new ExplicitRaceColumnSelection(),
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/MultiLeaderboardPanel.java
... ...
@@ -91,7 +91,7 @@ public class MultiLeaderboardPanel extends AbstractLazyComponent<LeaderboardSett
91 91
private LeaderboardSettings getOrCreateLeaderboardSettings(String leaderboardName, LeaderboardSettings currentLeaderboardSettings) {
92 92
LeaderboardSettings newLeaderboardSettings = leaderboardNamesAndSettings.get(leaderboardName);
93 93
if(newLeaderboardSettings == null) {
94
- newLeaderboardSettings = LeaderboardSettingsFactory.getInstance().createNewDefaultSettings(null, null, null, false, /* showRegattaRank */ true);
94
+ newLeaderboardSettings = LeaderboardSettingsFactory.getInstance().createNewDefaultSettings(null, null, null, false, /* showRegattaRank */ true, /* showCompetitorSailIdColumn */ true, /* showCompetitorFullNameColumn */ true);
95 95
}
96 96
if(currentLeaderboardSettings != null) {
97 97
newLeaderboardSettings = LeaderboardSettingsFactory.getInstance().mergeLeaderboardSettings(newLeaderboardSettings, currentLeaderboardSettings);
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboardedit/EditableLeaderboardPanel.java
... ...
@@ -584,7 +584,7 @@ public class EditableLeaderboardPanel extends LeaderboardPanel {
584 584
String leaderboardName, String leaderboardGroupName, final ErrorReporter errorReporter,
585 585
final StringMessages stringMessages, UserAgentDetails userAgent) {
586 586
super(sailingService, asyncActionsExecutor, LeaderboardSettingsFactory.getInstance().createNewDefaultSettings(
587
- /* racesToShow */ null, /* namesOfRacesToShow */ null, null, /* autoExpandFirstRace */ false, /* showRegattaRank */ true),
587
+ /* racesToShow */ null, /* namesOfRacesToShow */ null, null, /* autoExpandFirstRace */ false, /* showRegattaRank */ true, /* showCompetitorSailIdColumn */ true, /* showCompetitorFullNameColumn */ true),
588 588
new CompetitorSelectionModel(/* hasMultiSelection */true),
589 589
leaderboardName, errorReporter, stringMessages, userAgent, /* showRaceDetails */ true);
590 590
suppressedCompetitorsShown = new ListDataProvider<CompetitorDTO>(new ArrayList<CompetitorDTO>());
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/EmbeddedMapAndWindChartEntryPoint.java
... ...
@@ -151,7 +151,8 @@ public class EmbeddedMapAndWindChartEntryPoint extends AbstractSailingEntryPoint
151 151
// Use a TimePanel to manage wind chart zoom, although the TimePanel itself is not being displayed;
152 152
// let the time panel always return to "live" mode.
153 153
final TimePanel<TimePanelSettings> timePanel = new TimePanel<TimePanelSettings>(
154
- timer, timeRangeWithZoomProvider, getStringMessages(), /* canReplayWhileLive */ false) {
154
+ timer, timeRangeWithZoomProvider, getStringMessages(), /* canReplayWhileLive */ false,
155
+ /* isScreenLargeEnoughToOfferChartSupport set to true iff wind chart will be displayed */ showWindChart) {
155 156
protected boolean isLiveModeToBeMadePossible() {
156 157
return true;
157 158
}
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardEntryPoint.java
... ...
@@ -1,197 +1,205 @@
1
-
2
-package com.sap.sailing.gwt.ui.raceboard;
3
-
4
-import java.util.Collections;
5
-import java.util.List;
6
-import java.util.UUID;
7
-
8
-import com.google.gwt.core.client.GWT;
9
-import com.google.gwt.dom.client.Style.Unit;
10
-import com.google.gwt.event.dom.client.ClickEvent;
11
-import com.google.gwt.event.dom.client.ClickHandler;
12
-import com.google.gwt.user.client.Window;
13
-import com.google.gwt.user.client.rpc.AsyncCallback;
14
-import com.google.gwt.user.client.rpc.ServiceDefTarget;
15
-import com.google.gwt.user.client.ui.Button;
16
-import com.google.gwt.user.client.ui.DockLayoutPanel;
17
-import com.google.gwt.user.client.ui.FlowPanel;
18
-import com.google.gwt.user.client.ui.Label;
19
-import com.google.gwt.user.client.ui.RootLayoutPanel;
20
-import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
21
-import com.sap.sailing.gwt.ui.client.AbstractSailingEntryPoint;
22
-import com.sap.sailing.gwt.ui.client.LogoAndTitlePanel;
23
-import com.sap.sailing.gwt.ui.client.MediaService;
24
-import com.sap.sailing.gwt.ui.client.MediaServiceAsync;
25
-import com.sap.sailing.gwt.ui.client.RaceSelectionModel;
26
-import com.sap.sailing.gwt.ui.client.RaceTimesInfoProvider;
27
-import com.sap.sailing.gwt.ui.client.RemoteServiceMappingConstants;
28
-import com.sap.sailing.gwt.ui.shared.RaceWithCompetitorsDTO;
29
-import com.sap.sailing.gwt.ui.shared.RaceboardDataDTO;
30
-import com.sap.sse.gwt.client.EntryPointHelper;
31
-import com.sap.sse.gwt.client.async.AsyncActionsExecutor;
32
-import com.sap.sse.gwt.client.player.Timer;
33
-import com.sap.sse.gwt.client.player.Timer.PlayModes;
34
-import com.sap.sse.gwt.shared.GwtHttpRequestUtils;
35
-
36
-public class RaceBoardEntryPoint extends AbstractSailingEntryPoint {
37
- private RaceWithCompetitorsDTO selectedRace;
38
-
39
- private static final String PARAM_REGATTA_NAME = "regattaName";
40
- private static final String PARAM_RACE_NAME = "raceName";
41
- private static final String PARAM_LEADERBOARD_NAME = "leaderboardName";
42
- private static final String PARAM_LEADERBOARD_GROUP_NAME = "leaderboardGroupName";
43
- private static final String PARAM_EVENT_ID = "eventId";
44
-
45
- private String regattaName;
46
- private String raceName;
47
- private String leaderboardName;
48
- private String leaderboardGroupName;
49
- private UUID eventId;
50
- private RaceBoardViewConfiguration raceboardViewConfig;
51
-
52
- private final MediaServiceAsync mediaService = GWT.create(MediaService.class);
53
-
54
- @Override
55
- protected void doOnModuleLoad() {
56
- super.doOnModuleLoad();
57
- EntryPointHelper.registerASyncService((ServiceDefTarget) mediaService, RemoteServiceMappingConstants.mediaServiceRemotePath);
58
- // read mandatory parameters
59
- regattaName = Window.Location.getParameter(PARAM_REGATTA_NAME);
60
- raceName = Window.Location.getParameter(PARAM_RACE_NAME);
61
- String leaderboardNameParamValue = Window.Location.getParameter(PARAM_LEADERBOARD_NAME);
62
- String leaderboardGroupNameParamValue = Window.Location.getParameter(PARAM_LEADERBOARD_GROUP_NAME);
63
- if (leaderboardNameParamValue != null && !leaderboardNameParamValue.isEmpty()) {
64
- leaderboardName = leaderboardNameParamValue;
65
- }
66
- if (leaderboardGroupNameParamValue != null && !leaderboardGroupNameParamValue.isEmpty()) {
67
- leaderboardGroupName = leaderboardGroupNameParamValue;
68
- }
69
- String eventIdParamValue = Window.Location.getParameter(PARAM_EVENT_ID);
70
- if (eventIdParamValue != null && !eventIdParamValue.isEmpty()) {
71
- eventId = UUID.fromString(eventIdParamValue);
72
- }
73
- if (leaderboardGroupNameParamValue != null && !leaderboardGroupNameParamValue.isEmpty()) {
74
- leaderboardGroupName = leaderboardGroupNameParamValue;
75
- }
76
- if (regattaName == null || regattaName.isEmpty() || raceName == null || raceName.isEmpty() ||
77
- leaderboardName == null || leaderboardName.isEmpty()) {
78
- createErrorPage("This page requires a valid regatta name, race name and leaderboard name.");
79
- return;
80
- }
81
-
82
- // read optional parameters
83
- boolean showLeaderboard = GwtHttpRequestUtils.getBooleanParameter(RaceBoardViewConfiguration.PARAM_VIEW_SHOW_LEADERBOARD, true /* default*/);
84
- boolean showWindChart = GwtHttpRequestUtils.getBooleanParameter(RaceBoardViewConfiguration.PARAM_VIEW_SHOW_WINDCHART, false /* default*/);
85
- boolean showCompetitorsChart = GwtHttpRequestUtils.getBooleanParameter(RaceBoardViewConfiguration.PARAM_VIEW_SHOW_COMPETITORSCHART, false /* default*/);
86
- boolean showViewStreamlets = GwtHttpRequestUtils.getBooleanParameter(RaceBoardViewConfiguration.PARAM_VIEW_SHOW_STREAMLETS, false /* default*/);
87
- boolean showViewStreamletColors = GwtHttpRequestUtils.getBooleanParameter(RaceBoardViewConfiguration.PARAM_VIEW_SHOW_STREAMLET_COLORS, false /* default*/);
88
- boolean showViewSimulation = GwtHttpRequestUtils.getBooleanParameter(RaceBoardViewConfiguration.PARAM_VIEW_SHOW_SIMULATION, true /* default*/);
89
- String activeCompetitorsFilterSetName = GwtHttpRequestUtils.getStringParameter(RaceBoardViewConfiguration.PARAM_VIEW_COMPETITOR_FILTER, null /* default*/);
90
- final boolean canReplayWhileLiveIsPossible = GwtHttpRequestUtils.getBooleanParameter(RaceBoardViewConfiguration.PARAM_CAN_REPLAY_DURING_LIVE_RACES, false);
91
- final boolean autoSelectMedia = GwtHttpRequestUtils.getBooleanParameter(RaceBoardViewConfiguration.PARAM_AUTOSELECT_MEDIA, true);
92
- final String defaultMedia = GwtHttpRequestUtils.getStringParameter(RaceBoardViewConfiguration.PARAM_DEFAULT_MEDIA, null /* default*/);
93
- final boolean showMapControls = GwtHttpRequestUtils.getBooleanParameter(RaceBoardViewConfiguration.PARAM_VIEW_SHOW_MAPCONTROLS, true /* default*/);
94
- raceboardViewConfig = new RaceBoardViewConfiguration(activeCompetitorsFilterSetName, showLeaderboard,
95
- showWindChart, showCompetitorsChart, showViewStreamlets, showViewStreamletColors, showViewSimulation, canReplayWhileLiveIsPossible, autoSelectMedia, defaultMedia);
96
-
97
- sailingService.getRaceboardData(regattaName, raceName, leaderboardName, leaderboardGroupName, eventId, new AsyncCallback<RaceboardDataDTO>() {
98
- @Override
99
- public void onSuccess(RaceboardDataDTO result) {
100
- checkUrlParameters(result, canReplayWhileLiveIsPossible, showMapControls);
101
- }
102
-
103
- @Override
104
- public void onFailure(Throwable caught) {
105
- reportError("Error trying to create the raceboard: " + caught.getMessage());
106
- }
107
- });
108
- }
109
-
110
- private void createErrorPage(String message) {
111
- final DockLayoutPanel vp = new DockLayoutPanel(Unit.PX);
112
- LogoAndTitlePanel logoAndTitlePanel = new LogoAndTitlePanel(getStringMessages(), this, getUserService());
113
- logoAndTitlePanel.addStyleName("LogoAndTitlePanel");
114
- RootLayoutPanel.get().add(vp);
115
- vp.addNorth(logoAndTitlePanel, 100);
116
- vp.add(new Label(message));
117
- }
118
-
119
- private void checkUrlParameters(RaceboardDataDTO raceboardData, boolean canReplayWhileLiveIsPossible, boolean showMapControls) {
120
- if (!raceboardData.isValidLeaderboard()) {
121
- createErrorPage(getStringMessages().noSuchLeaderboard());
122
- return;
123
- }
124
- if (eventId != null && !raceboardData.isValidEvent()) {
125
- createErrorPage(getStringMessages().noSuchEvent());
126
- }
127
- if (leaderboardGroupName != null) {
128
- if(!raceboardData.isValidLeaderboardGroup()) {
129
- createErrorPage(getStringMessages().leaderboardNotContainedInLeaderboardGroup(leaderboardName, leaderboardGroupName));
130
- return;
131
- }
132
- if (eventId != null && raceboardData.isValidLeaderboardGroup() && !raceboardData.isValidEvent()) {
133
- createErrorPage(getStringMessages().leaderboardGroupNotContainedInEvent(leaderboardGroupName, eventId.toString()));
134
- return;
135
- }
136
- }
137
- if (raceboardData.getRace() == null) {
138
- createErrorPage("Could not obtain a race with name " + raceName + " for a regatta with name " + regattaName);
139
- return;
140
- }
141
- selectedRace = raceboardData.getRace();
142
- Window.setTitle(selectedRace.getName());
143
- RaceSelectionModel raceSelectionModel = new RaceSelectionModel();
144
- List<RegattaAndRaceIdentifier> singletonList = Collections.singletonList(selectedRace.getRaceIdentifier());
145
- raceSelectionModel.setSelection(singletonList);
146
- Timer timer = new Timer(PlayModes.Replay, 1000l);
147
- AsyncActionsExecutor asyncActionsExecutor = new AsyncActionsExecutor();
148
- RaceTimesInfoProvider raceTimesInfoProvider = new RaceTimesInfoProvider(sailingService, asyncActionsExecutor, this, singletonList, 5000l /* requestInterval*/);
149
- RaceBoardPanel raceBoardPanel = new RaceBoardPanel(sailingService, mediaService, getUserService(), asyncActionsExecutor,
150
- raceboardData.getCompetitorAndTheirBoats(), timer, raceSelectionModel, leaderboardName, leaderboardGroupName, eventId,
151
- raceboardViewConfig, RaceBoardEntryPoint.this, getStringMessages(), userAgent, raceTimesInfoProvider, showMapControls);
152
-
153
- createRaceBoardInOneScreenMode(raceBoardPanel, raceboardViewConfig);
154
- }
155
-
156
- private FlowPanel createTimePanel(RaceBoardPanel raceBoardPanel) {
157
- FlowPanel timeLineInnerBgPanel = new FlowPanel();
158
- timeLineInnerBgPanel.addStyleName("timeLineInnerBgPanel");
159
- timeLineInnerBgPanel.add(raceBoardPanel.getTimePanel());
160
-
161
- FlowPanel timeLineInnerPanel = new FlowPanel();
162
- timeLineInnerPanel.add(timeLineInnerBgPanel);
163
- timeLineInnerPanel.addStyleName("timeLineInnerPanel");
164
-
165
- FlowPanel timelinePanel = new FlowPanel();
166
- timelinePanel.add(timeLineInnerPanel);
167
- timelinePanel.addStyleName("timeLinePanel");
168
-
169
- return timelinePanel;
170
- }
171
-
172
- private void createRaceBoardInOneScreenMode(final RaceBoardPanel raceBoardPanel,
173
- RaceBoardViewConfiguration raceboardViewConfiguration) {
174
- final DockLayoutPanel p = new DockLayoutPanel(Unit.PX);
175
- RootLayoutPanel.get().add(p);
176
- final FlowPanel timePanel = createTimePanel(raceBoardPanel);
177
- final Button toggleButton = raceBoardPanel.getTimePanel().getAdvancedToggleButton();
178
- toggleButton.addClickHandler(new ClickHandler() {
179
- @Override
180
- public void onClick(ClickEvent event) {
181
- boolean advancedModeShown = raceBoardPanel.getTimePanel().toggleAdvancedMode();
182
- if (advancedModeShown) {
183
- p.setWidgetSize(timePanel, 96);
184
- toggleButton.removeStyleDependentName("Closed");
185
- toggleButton.addStyleDependentName("Open");
186
- } else {
187
- p.setWidgetSize(timePanel, 67);
188
- toggleButton.addStyleDependentName("Closed");
189
- toggleButton.removeStyleDependentName("Open");
190
- }
191
- }
192
- });
193
- p.addSouth(timePanel, 67);
194
- p.add(raceBoardPanel);
195
- p.addStyleName("dockLayoutPanel");
196
- }
197
-}
1
+
2
+package com.sap.sailing.gwt.ui.raceboard;
3
+
4
+import java.util.Collections;
5
+import java.util.List;
6
+import java.util.UUID;
7
+
8
+import com.google.gwt.core.client.GWT;
9
+import com.google.gwt.dom.client.Document;
10
+import com.google.gwt.dom.client.Style.Unit;
11
+import com.google.gwt.event.dom.client.ClickEvent;
12
+import com.google.gwt.event.dom.client.ClickHandler;
13
+import com.google.gwt.user.client.Window;
14
+import com.google.gwt.user.client.rpc.AsyncCallback;
15
+import com.google.gwt.user.client.rpc.ServiceDefTarget;
16
+import com.google.gwt.user.client.ui.Button;
17
+import com.google.gwt.user.client.ui.DockLayoutPanel;
18
+import com.google.gwt.user.client.ui.FlowPanel;
19
+import com.google.gwt.user.client.ui.Label;
20
+import com.google.gwt.user.client.ui.RootLayoutPanel;
21
+import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
22
+import com.sap.sailing.gwt.ui.client.AbstractSailingEntryPoint;
23
+import com.sap.sailing.gwt.ui.client.LogoAndTitlePanel;
24
+import com.sap.sailing.gwt.ui.client.MediaService;
25
+import com.sap.sailing.gwt.ui.client.MediaServiceAsync;
26
+import com.sap.sailing.gwt.ui.client.RaceSelectionModel;
27
+import com.sap.sailing.gwt.ui.client.RaceTimesInfoProvider;
28
+import com.sap.sailing.gwt.ui.client.RemoteServiceMappingConstants;
29
+import com.sap.sailing.gwt.ui.shared.RaceWithCompetitorsDTO;
30
+import com.sap.sailing.gwt.ui.shared.RaceboardDataDTO;
31
+import com.sap.sse.gwt.client.EntryPointHelper;
32
+import com.sap.sse.gwt.client.async.AsyncActionsExecutor;
33
+import com.sap.sse.gwt.client.player.Timer;
34
+import com.sap.sse.gwt.client.player.Timer.PlayModes;
35
+import com.sap.sse.gwt.shared.GwtHttpRequestUtils;
36
+
37
+public class RaceBoardEntryPoint extends AbstractSailingEntryPoint {
38
+ private RaceWithCompetitorsDTO selectedRace;
39
+
40
+ private static final String PARAM_REGATTA_NAME = "regattaName";
41
+ private static final String PARAM_RACE_NAME = "raceName";
42
+ private static final String PARAM_LEADERBOARD_NAME = "leaderboardName";
43
+ private static final String PARAM_LEADERBOARD_GROUP_NAME = "leaderboardGroupName";
44
+ private static final String PARAM_EVENT_ID = "eventId";
45
+
46
+ private String regattaName;
47
+ private String raceName;
48
+ private String leaderboardName;
49
+ private String leaderboardGroupName;
50
+ private UUID eventId;
51
+ private RaceBoardViewConfiguration raceboardViewConfig;
52
+
53
+ private final MediaServiceAsync mediaService = GWT.create(MediaService.class);
54
+
55
+ @Override
56
+ protected void doOnModuleLoad() {
57
+ super.doOnModuleLoad();
58
+ EntryPointHelper.registerASyncService((ServiceDefTarget) mediaService, RemoteServiceMappingConstants.mediaServiceRemotePath);
59
+ // read mandatory parameters
60
+ regattaName = Window.Location.getParameter(PARAM_REGATTA_NAME);
61
+ raceName = Window.Location.getParameter(PARAM_RACE_NAME);
62
+ String leaderboardNameParamValue = Window.Location.getParameter(PARAM_LEADERBOARD_NAME);
63
+ String leaderboardGroupNameParamValue = Window.Location.getParameter(PARAM_LEADERBOARD_GROUP_NAME);
64
+ if (leaderboardNameParamValue != null && !leaderboardNameParamValue.isEmpty()) {
65
+ leaderboardName = leaderboardNameParamValue;
66
+ }
67
+ if (leaderboardGroupNameParamValue != null && !leaderboardGroupNameParamValue.isEmpty()) {
68
+ leaderboardGroupName = leaderboardGroupNameParamValue;
69
+ }
70
+ String eventIdParamValue = Window.Location.getParameter(PARAM_EVENT_ID);
71
+ if (eventIdParamValue != null && !eventIdParamValue.isEmpty()) {
72
+ eventId = UUID.fromString(eventIdParamValue);
73
+ }
74
+ if (leaderboardGroupNameParamValue != null && !leaderboardGroupNameParamValue.isEmpty()) {
75
+ leaderboardGroupName = leaderboardGroupNameParamValue;
76
+ }
77
+ if (regattaName == null || regattaName.isEmpty() || raceName == null || raceName.isEmpty() ||
78
+ leaderboardName == null || leaderboardName.isEmpty()) {
79
+ createErrorPage("This page requires a valid regatta name, race name and leaderboard name.");
80
+ return;
81
+ }
82
+
83
+ // read optional parameters
84
+ boolean showLeaderboard = GwtHttpRequestUtils.getBooleanParameter(RaceBoardViewConfiguration.PARAM_VIEW_SHOW_LEADERBOARD, true /* default*/);
85
+ boolean showWindChart = GwtHttpRequestUtils.getBooleanParameter(RaceBoardViewConfiguration.PARAM_VIEW_SHOW_WINDCHART, false /* default*/);
86
+ boolean showCompetitorsChart = GwtHttpRequestUtils.getBooleanParameter(RaceBoardViewConfiguration.PARAM_VIEW_SHOW_COMPETITORSCHART, false /* default*/);
87
+ boolean showViewStreamlets = GwtHttpRequestUtils.getBooleanParameter(RaceBoardViewConfiguration.PARAM_VIEW_SHOW_STREAMLETS, false /* default*/);
88
+ boolean showViewStreamletColors = GwtHttpRequestUtils.getBooleanParameter(RaceBoardViewConfiguration.PARAM_VIEW_SHOW_STREAMLET_COLORS, false /* default*/);
89
+ boolean showViewSimulation = GwtHttpRequestUtils.getBooleanParameter(RaceBoardViewConfiguration.PARAM_VIEW_SHOW_SIMULATION, true /* default*/);
90
+ String activeCompetitorsFilterSetName = GwtHttpRequestUtils.getStringParameter(RaceBoardViewConfiguration.PARAM_VIEW_COMPETITOR_FILTER, null /* default*/);
91
+ final boolean canReplayWhileLiveIsPossible = GwtHttpRequestUtils.getBooleanParameter(RaceBoardViewConfiguration.PARAM_CAN_REPLAY_DURING_LIVE_RACES, false);
92
+ final boolean autoSelectMedia = GwtHttpRequestUtils.getBooleanParameter(RaceBoardViewConfiguration.PARAM_AUTOSELECT_MEDIA, true);
93
+ final String defaultMedia = GwtHttpRequestUtils.getStringParameter(RaceBoardViewConfiguration.PARAM_DEFAULT_MEDIA, null /* default*/);
94
+ final boolean showMapControls = GwtHttpRequestUtils.getBooleanParameter(RaceBoardViewConfiguration.PARAM_VIEW_SHOW_MAPCONTROLS, true /* default*/);
95
+ raceboardViewConfig = new RaceBoardViewConfiguration(activeCompetitorsFilterSetName, showLeaderboard,
96
+ showWindChart, showCompetitorsChart, showViewStreamlets, showViewStreamletColors, showViewSimulation, canReplayWhileLiveIsPossible, autoSelectMedia, defaultMedia);
97
+
98
+ sailingService.getRaceboardData(regattaName, raceName, leaderboardName, leaderboardGroupName, eventId, new AsyncCallback<RaceboardDataDTO>() {
99
+ @Override
100
+ public void onSuccess(RaceboardDataDTO result) {
101
+ // Determine if the screen is large enough to display charts such as the competitor chart or the wind chart.
102
+ // This decision is made once based on the initial screen height. Resizing the window afterwards will have
103
+ // no impact on the chart support, i.e. they are available/unavailable based on the initial decision.
104
+ boolean isScreenLargeEnoughToOfferChartSupport = Document.get().getClientHeight() >= 600;
105
+ checkUrlParameters(result, canReplayWhileLiveIsPossible, showMapControls,
106
+ isScreenLargeEnoughToOfferChartSupport);
107
+ }
108
+
109
+ @Override
110
+ public void onFailure(Throwable caught) {
111
+ reportError("Error trying to create the raceboard: " + caught.getMessage());
112
+ }
113
+ });
114
+ }
115
+
116
+ private void createErrorPage(String message) {
117
+ final DockLayoutPanel vp = new DockLayoutPanel(Unit.PX);
118
+ LogoAndTitlePanel logoAndTitlePanel = new LogoAndTitlePanel(getStringMessages(), this, getUserService());
119
+ logoAndTitlePanel.addStyleName("LogoAndTitlePanel");
120
+ RootLayoutPanel.get().add(vp);
121
+ vp.addNorth(logoAndTitlePanel, 100);
122
+ vp.add(new Label(message));
123
+ }
124
+
125
+ private void checkUrlParameters(RaceboardDataDTO raceboardData, boolean canReplayWhileLiveIsPossible, boolean showMapControls,
126
+ boolean isScreenLargeEnoughToOfferChartSupport) {
127
+ if (!raceboardData.isValidLeaderboard()) {
128
+ createErrorPage(getStringMessages().noSuchLeaderboard());
129
+ return;
130
+ }
131
+ if (eventId != null && !raceboardData.isValidEvent()) {
132
+ createErrorPage(getStringMessages().noSuchEvent());
133
+ }
134
+ if (leaderboardGroupName != null) {
135
+ if(!raceboardData.isValidLeaderboardGroup()) {
136
+ createErrorPage(getStringMessages().leaderboardNotContainedInLeaderboardGroup(leaderboardName, leaderboardGroupName));
137
+ return;
138
+ }
139
+ if (eventId != null && raceboardData.isValidLeaderboardGroup() && !raceboardData.isValidEvent()) {
140
+ createErrorPage(getStringMessages().leaderboardGroupNotContainedInEvent(leaderboardGroupName, eventId.toString()));
141
+ return;
142
+ }
143
+ }
144
+ if (raceboardData.getRace() == null) {
145
+ createErrorPage("Could not obtain a race with name " + raceName + " for a regatta with name " + regattaName);
146
+ return;
147
+ }
148
+ selectedRace = raceboardData.getRace();
149
+ Window.setTitle(selectedRace.getName());
150
+ RaceSelectionModel raceSelectionModel = new RaceSelectionModel();
151
+ List<RegattaAndRaceIdentifier> singletonList = Collections.singletonList(selectedRace.getRaceIdentifier());
152
+ raceSelectionModel.setSelection(singletonList);
153
+ Timer timer = new Timer(PlayModes.Replay, 1000l);
154
+ AsyncActionsExecutor asyncActionsExecutor = new AsyncActionsExecutor();
155
+ RaceTimesInfoProvider raceTimesInfoProvider = new RaceTimesInfoProvider(sailingService, asyncActionsExecutor, this, singletonList, 5000l /* requestInterval*/);
156
+ RaceBoardPanel raceBoardPanel = new RaceBoardPanel(sailingService, mediaService, getUserService(), asyncActionsExecutor,
157
+ raceboardData.getCompetitorAndTheirBoats(), timer, raceSelectionModel, leaderboardName, leaderboardGroupName, eventId,
158
+ raceboardViewConfig, RaceBoardEntryPoint.this, getStringMessages(), userAgent, raceTimesInfoProvider, showMapControls,
159
+ isScreenLargeEnoughToOfferChartSupport);
160
+
161
+ createRaceBoardInOneScreenMode(raceBoardPanel, raceboardViewConfig);
162
+ }
163
+
164
+ private FlowPanel createTimePanel(RaceBoardPanel raceBoardPanel) {
165
+ FlowPanel timeLineInnerBgPanel = new ResizableFlowPanel();
166
+ timeLineInnerBgPanel.addStyleName("timeLineInnerBgPanel");
167
+ timeLineInnerBgPanel.add(raceBoardPanel.getTimePanel());
168
+
169
+ FlowPanel timeLineInnerPanel = new ResizableFlowPanel();
170
+ timeLineInnerPanel.add(timeLineInnerBgPanel);
171
+ timeLineInnerPanel.addStyleName("timeLineInnerPanel");
172
+
173
+ FlowPanel timelinePanel = new ResizableFlowPanel();
174
+ timelinePanel.add(timeLineInnerPanel);
175
+ timelinePanel.addStyleName("timeLinePanel");
176
+
177
+ return timelinePanel;
178
+ }
179
+
180
+ private void createRaceBoardInOneScreenMode(final RaceBoardPanel raceBoardPanel,
181
+ RaceBoardViewConfiguration raceboardViewConfiguration) {
182
+ final DockLayoutPanel p = new DockLayoutPanel(Unit.PX);
183
+ RootLayoutPanel.get().add(p);
184
+ final FlowPanel timePanel = createTimePanel(raceBoardPanel);
185
+ final Button toggleButton = raceBoardPanel.getTimePanel().getAdvancedToggleButton();
186
+ toggleButton.addClickHandler(new ClickHandler() {
187
+ @Override
188
+ public void onClick(ClickEvent event) {
189
+ boolean advancedModeShown = raceBoardPanel.getTimePanel().toggleAdvancedMode();
190
+ if (advancedModeShown) {
191
+ p.setWidgetSize(timePanel, 96);
192
+ toggleButton.removeStyleDependentName("Closed");
193
+ toggleButton.addStyleDependentName("Open");
194
+ } else {
195
+ p.setWidgetSize(timePanel, 67);
196
+ toggleButton.addStyleDependentName("Closed");
197
+ toggleButton.removeStyleDependentName("Open");
198
+ }
199
+ }
200
+ });
201
+ p.addSouth(timePanel, 67);
202
+ p.add(raceBoardPanel);
203
+ p.addStyleName("dockLayoutPanel");
204
+ }
205
+}
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardPanel.java
... ...
@@ -1,437 +1,483 @@
1
-package com.sap.sailing.gwt.ui.raceboard;
2
-
3
-import java.util.ArrayList;
4
-import java.util.Collections;
5
-import java.util.HashMap;
6
-import java.util.List;
7
-import java.util.Map;
8
-import java.util.UUID;
9
-
10
-import com.google.gwt.core.shared.GWT;
11
-import com.google.gwt.dom.client.Document;
12
-import com.google.gwt.dom.client.Style.Unit;
13
-import com.google.gwt.i18n.client.DateTimeFormat;
14
-import com.google.gwt.user.client.Command;
15
-import com.google.gwt.user.client.Window;
16
-import com.google.gwt.user.client.ui.Anchor;
17
-import com.google.gwt.user.client.ui.FlowPanel;
18
-import com.google.gwt.user.client.ui.Label;
19
-import com.google.gwt.user.client.ui.MenuBar;
20
-import com.google.gwt.user.client.ui.SimplePanel;
21
-import com.google.gwt.user.client.ui.UIObject;
22
-import com.sap.sailing.domain.common.LeaderboardNameConstants;
23
-import com.sap.sailing.domain.common.RaceIdentifier;
24
-import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
25
-import com.sap.sailing.domain.common.dto.BoatDTO;
26
-import com.sap.sailing.domain.common.dto.CompetitorDTO;
27
-import com.sap.sailing.domain.common.dto.FleetDTO;
28
-import com.sap.sailing.domain.common.dto.LeaderboardDTO;
29
-import com.sap.sailing.domain.common.dto.RaceColumnDTO;
30
-import com.sap.sailing.domain.common.dto.RaceDTO;
31
-import com.sap.sailing.gwt.ui.client.CompetitorColorProvider;
32
-import com.sap.sailing.gwt.ui.client.CompetitorColorProviderImpl;
33
-import com.sap.sailing.gwt.ui.client.CompetitorSelectionModel;
34
-import com.sap.sailing.gwt.ui.client.CompetitorSelectionProvider;
35
-import com.sap.sailing.gwt.ui.client.EntryPointLinkFactory;
36
-import com.sap.sailing.gwt.ui.client.GlobalNavigationPanel;
37
-import com.sap.sailing.gwt.ui.client.LeaderboardUpdateListener;
38
-import com.sap.sailing.gwt.ui.client.MediaServiceAsync;
39
-import com.sap.sailing.gwt.ui.client.RaceSelectionChangeListener;
40
-import com.sap.sailing.gwt.ui.client.RaceSelectionProvider;
41
-import com.sap.sailing.gwt.ui.client.RaceTimePanel;
42
-import com.sap.sailing.gwt.ui.client.RaceTimesInfoProvider;
43
-import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
44
-import com.sap.sailing.gwt.ui.client.StringMessages;
45
-import com.sap.sailing.gwt.ui.client.media.MediaPlayerManagerComponent;
46
-import com.sap.sailing.gwt.ui.client.media.PopupPositionProvider;
47
-import com.sap.sailing.gwt.ui.client.shared.charts.EditMarkPassingsPanel;
48
-import com.sap.sailing.gwt.ui.client.shared.charts.MultiCompetitorRaceChart;
49
-import com.sap.sailing.gwt.ui.client.shared.charts.WindChart;
50
-import com.sap.sailing.gwt.ui.client.shared.charts.WindChartSettings;
51
-import com.sap.sailing.gwt.ui.client.shared.filter.FilterWithUI;
52
-import com.sap.sailing.gwt.ui.client.shared.filter.LeaderboardFetcher;
53
-import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMap;
54
-import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMapResources;
55
-import com.sap.sailing.gwt.ui.leaderboard.CompetitorFilterPanel;
56
-import com.sap.sailing.gwt.ui.leaderboard.ExplicitRaceColumnSelectionWithPreselectedRace;
57
-import com.sap.sailing.gwt.ui.leaderboard.LeaderboardPanel;
58
-import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettings;
59
-import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettingsFactory;
60
-import com.sap.sse.common.filter.FilterSet;
61
-import com.sap.sse.common.settings.AbstractSettings;
62
-import com.sap.sse.gwt.client.ErrorReporter;
63
-import com.sap.sse.gwt.client.URLEncoder;
64
-import com.sap.sse.gwt.client.async.AsyncActionsExecutor;
65
-import com.sap.sse.gwt.client.player.TimeRangeWithZoomModel;
66
-import com.sap.sse.gwt.client.player.Timer;
67
-import com.sap.sse.gwt.client.shared.components.Component;
68
-import com.sap.sse.gwt.client.shared.components.ComponentViewer;
69
-import com.sap.sse.gwt.client.shared.components.SettingsDialog;
70
-import com.sap.sse.gwt.client.useragent.UserAgentDetails;
71
-import com.sap.sse.security.ui.client.UserService;
72
-
73
-/**
74
- * A view showing a list of components visualizing a race from the regattas announced by calls to {@link #fillRegattas(List)}.
75
- * The race selection is provided by a {@link RaceSelectionProvider} for which this is a {@link RaceSelectionChangeListener listener}.
76
- * {@link RaceIdentifier}-based race selection changes are converted to {@link RaceDTO} objects using the {@link #racesByIdentifier}
77
- * map maintained during {@link #fillRegattas(List)}. The race selection provider is expected to be single selection only.
78
- *
79
- * @author Frank Mittag, Axel Uhl (d043530)
80
- *
81
- */
82
-public class RaceBoardPanel extends SimplePanel implements RaceSelectionChangeListener, LeaderboardUpdateListener, PopupPositionProvider {
83
- private final SailingServiceAsync sailingService;
84
- private final MediaServiceAsync mediaService;
85
- private final UUID eventId;
86
- private final StringMessages stringMessages;
87
- private final ErrorReporter errorReporter;
88
- private final RaceBoardViewConfiguration raceboardViewConfiguration;
89
- private String raceBoardName;
90
-
91
- private final List<ComponentViewer> componentViewers;
92
- private RaceTimePanel timePanel;
93
- private final Timer timer;
94
- private final RaceSelectionProvider raceSelectionProvider;
95
- private final UserAgentDetails userAgent;
96
- private final CompetitorSelectionProvider competitorSelectionProvider;
97
- private final TimeRangeWithZoomModel timeRangeWithZoomModel;
98
- private final RegattaAndRaceIdentifier selectedRaceIdentifier;
99
-
100
- private final String leaderboardName;
101
- private final LeaderboardPanel leaderboardPanel;
102
- private WindChart windChart;
103
- private MultiCompetitorRaceChart competitorChart;
104
- private EditMarkPassingsPanel editMarkPassingPanel;
105
-
106
- /**
107
- * The component viewer in <code>ONESCREEN</code> view mode. <code>null</code> if in <code>CASCADE</code> view mode
108
- */
109
- private SideBySideComponentViewer leaderboardAndMapViewer;
110
-
111
- private final AsyncActionsExecutor asyncActionsExecutor;
112
-
113
- private final RaceTimesInfoProvider raceTimesInfoProvider;
114
- private RaceMap raceMap;
115
-
116
- private final FlowPanel raceInformationHeader;
117
- private final FlowPanel regattaAndRaceTimeInformationHeader;
118
- private boolean currentRaceHasBeenSelectedOnce;
119
-
120
- private static final RaceMapResources raceMapResources = GWT.create(RaceMapResources.class);
121
-
122
- /**
123
- * @param eventId
124
- * an optional event that can be used for "back"-navigation in case the race board shows a race in the
125
- * context of an event; may be <code>null</code>.
126
- */
127
- public RaceBoardPanel(SailingServiceAsync sailingService, MediaServiceAsync mediaService,
128
- UserService userService, AsyncActionsExecutor asyncActionsExecutor, Map<CompetitorDTO, BoatDTO> competitorsAndTheirBoats,
129
- Timer timer, RaceSelectionProvider theRaceSelectionProvider, String leaderboardName,
130
- String leaderboardGroupName, UUID eventId, RaceBoardViewConfiguration raceboardViewConfiguration,
131
- ErrorReporter errorReporter, final StringMessages stringMessages,
132
- UserAgentDetails userAgent, RaceTimesInfoProvider raceTimesInfoProvider, boolean showMapControls) {
133
- this.sailingService = sailingService;
134
- this.mediaService = mediaService;
135
- this.stringMessages = stringMessages;
136
- this.raceboardViewConfiguration = raceboardViewConfiguration;
137
- this.raceSelectionProvider = theRaceSelectionProvider;
138
- this.raceTimesInfoProvider = raceTimesInfoProvider;
139
- this.errorReporter = errorReporter;
140
- this.userAgent = userAgent;
141
- this.timer = timer;
142
- this.eventId = eventId;
143
- this.currentRaceHasBeenSelectedOnce = false;
144
- this.leaderboardName = leaderboardName;
145
- raceSelectionProvider.addRaceSelectionChangeListener(this);
146
- selectedRaceIdentifier = raceSelectionProvider.getSelectedRaces().iterator().next();
147
- this.setRaceBoardName(selectedRaceIdentifier.getRaceName());
148
- this.asyncActionsExecutor = asyncActionsExecutor;
149
- FlowPanel mainPanel = new FlowPanel();
150
- mainPanel.setSize("100%", "100%");
151
- setWidget(mainPanel);
152
- raceInformationHeader = new FlowPanel();
153
- raceInformationHeader.setStyleName("RegattaRaceInformation-Header");
154
- regattaAndRaceTimeInformationHeader = new FlowPanel();
155
- regattaAndRaceTimeInformationHeader.setStyleName("RegattaAndRaceTime-Header");
156
- timeRangeWithZoomModel = new TimeRangeWithZoomModel();
157
- componentViewers = new ArrayList<ComponentViewer>();
158
- final CompetitorColorProvider colorProvider = new CompetitorColorProviderImpl(selectedRaceIdentifier, competitorsAndTheirBoats);
159
- competitorSelectionProvider = new CompetitorSelectionModel(/* hasMultiSelection */ true, colorProvider);
160
-
161
- raceMapResources.combinedWindPanelStyle().ensureInjected();
162
- raceMap = new RaceMap(sailingService, asyncActionsExecutor, errorReporter, timer,
163
- competitorSelectionProvider, stringMessages, showMapControls, getConfiguration().isShowViewStreamlets(), getConfiguration().isShowViewStreamletColors(), getConfiguration().isShowViewSimulation(),
164
- selectedRaceIdentifier, raceMapResources.combinedWindPanelStyle(), /* showHeaderPanel */ true);
165
- CompetitorFilterPanel competitorSearchTextBox = new CompetitorFilterPanel(competitorSelectionProvider, stringMessages, raceMap,
166
- new LeaderboardFetcher() {
167
- @Override
168
- public LeaderboardDTO getLeaderboard() {
169
- return leaderboardPanel.getLeaderboard();
170
- }
171
- }, selectedRaceIdentifier);
172
- raceMap.getLeftHeaderPanel().add(raceInformationHeader);
173
- raceMap.getRightHeaderPanel().add(regattaAndRaceTimeInformationHeader);
174
-
175
- leaderboardPanel = createLeaderboardPanel(leaderboardName, leaderboardGroupName, competitorSearchTextBox);
176
- leaderboardPanel.setTitle(stringMessages.leaderboard());
177
- leaderboardPanel.getElement().getStyle().setMarginLeft(6, Unit.PX);
178
- leaderboardPanel.getElement().getStyle().setMarginTop(10, Unit.PX);
179
- createOneScreenView(leaderboardName, leaderboardGroupName, eventId, mainPanel, showMapControls, raceMap, userService); // initializes the raceMap field
180
- leaderboardPanel.addLeaderboardUpdateListener(this);
181
- // in case the URL configuration contains the name of a competitors filter set we try to activate it
182
- // FIXME the competitorsFilterSets has now moved to CompetitorSearchTextBox (which should probably be renamed); pass on the parameters to the LeaderboardPanel and see what it does with it
183
- if (raceboardViewConfiguration.getActiveCompetitorsFilterSetName() != null) {
184
- for (FilterSet<CompetitorDTO, FilterWithUI<CompetitorDTO>> filterSet : competitorSearchTextBox.getCompetitorsFilterSets()
185
- .getFilterSets()) {
186
- if (filterSet.getName().equals(raceboardViewConfiguration.getActiveCompetitorsFilterSetName())) {
187
- competitorSearchTextBox.getCompetitorsFilterSets().setActiveFilterSet(filterSet);
188
- break;
189
- }
190
- }
191
- }
192
- timePanel = new RaceTimePanel(timer, timeRangeWithZoomModel, stringMessages, raceTimesInfoProvider,
193
- raceboardViewConfiguration.isCanReplayDuringLiveRaces());
194
- timeRangeWithZoomModel.addTimeZoomChangeListener(timePanel);
195
- raceTimesInfoProvider.addRaceTimesInfoProviderListener(timePanel);
196
- raceSelectionProvider.addRaceSelectionChangeListener(timePanel);
197
- timePanel.onRaceSelectionChange(raceSelectionProvider.getSelectedRaces());
198
- }
199
-
200
- /**
201
- * @param event an optional event; may be <code>null</code> or else can be used to show some context information in
202
- * the {@link GlobalNavigationPanel}.
203
- */
204
- private void createOneScreenView(String leaderboardName, String leaderboardGroupName, UUID event, FlowPanel mainPanel,
205
- boolean showMapControls, RaceMap raceMap, UserService userService) {
206
- // create the default leaderboard and select the right race
207
- raceTimesInfoProvider.addRaceTimesInfoProviderListener(raceMap);
208
- raceMap.onRaceSelectionChange(Collections.singletonList(selectedRaceIdentifier));
209
- List<Component<?>> components = new ArrayList<Component<?>>();
210
- competitorChart = new MultiCompetitorRaceChart(sailingService, asyncActionsExecutor, competitorSelectionProvider, raceSelectionProvider,
211
- timer, timeRangeWithZoomModel, stringMessages, errorReporter, true, true, leaderboardGroupName, leaderboardName);
212
- competitorChart.onRaceSelectionChange(raceSelectionProvider.getSelectedRaces());
213
- competitorChart.getEntryWidget().setTitle(stringMessages.competitorCharts());
214
- competitorChart.setVisible(false);
215
- components.add(competitorChart);
216
- windChart = new WindChart(sailingService, raceSelectionProvider, timer, timeRangeWithZoomModel, new WindChartSettings(),
217
- stringMessages, asyncActionsExecutor, errorReporter, /* compactChart */ true);
218
- windChart.setVisible(false);
219
- windChart.onRaceSelectionChange(raceSelectionProvider.getSelectedRaces());
220
- windChart.getEntryWidget().setTitle(stringMessages.windChart());
221
- components.add(windChart);
222
- editMarkPassingPanel = new EditMarkPassingsPanel(sailingService, selectedRaceIdentifier,
223
- stringMessages, competitorSelectionProvider, errorReporter, timer);
224
- editMarkPassingPanel.setLeaderboard(leaderboardPanel.getLeaderboard());
225
- editMarkPassingPanel.getEntryWidget().setTitle(stringMessages.editMarkPassings());
226
- components.add(editMarkPassingPanel);
227
- boolean autoSelectMedia = getConfiguration().isAutoSelectMedia();
228
- MediaPlayerManagerComponent mediaPlayerManagerComponent = new MediaPlayerManagerComponent(
229
- selectedRaceIdentifier, raceTimesInfoProvider, timer, mediaService, userService, stringMessages,
230
- errorReporter, userAgent, this, autoSelectMedia);
231
- leaderboardAndMapViewer = new SideBySideComponentViewer(leaderboardPanel, raceMap, mediaPlayerManagerComponent, components, stringMessages, userService, editMarkPassingPanel);
232
- componentViewers.add(leaderboardAndMapViewer);
233
- for (ComponentViewer componentViewer : componentViewers) {
234
- mainPanel.add(componentViewer.getViewerWidget());
235
- }
236
- boolean showLeaderboard = getConfiguration().isShowLeaderboard();
237
- if (Document.get().getClientWidth() <= 1024) {
238
- showLeaderboard = false;
239
- }
240
- setLeaderboardVisible(showLeaderboard);
241
- setWindChartVisible(getConfiguration().isShowWindChart());
242
- setCompetitorChartVisible(getConfiguration().isShowCompetitorsChart());
243
- // make sure to load leaderboard data for filtering to work
244
- if (!showLeaderboard) {
245
- leaderboardPanel.setVisible(true);
246
- leaderboardPanel.setVisible(false);
247
- }
248
- }
249
-
250
- @SuppressWarnings("unused")
251
- private <SettingsType extends AbstractSettings> void addSettingsMenuItem(MenuBar settingsMenu, final Component<SettingsType> component) {
252
- if (component.hasSettings()) {
253
- settingsMenu.addItem(component.getLocalizedShortName(), new Command() {
254
- public void execute() {
255
- new SettingsDialog<SettingsType>(component, stringMessages).show();
256
- }
257
- });
258
- }
259
- }
260
-
261
- private LeaderboardPanel createLeaderboardPanel(String leaderboardName, String leaderboardGroupName, CompetitorFilterPanel competitorSearchTextBox) {
262
- LeaderboardSettings leaderBoardSettings = LeaderboardSettingsFactory.getInstance()
263
- .createNewSettingsForPlayMode(timer.getPlayMode(),
264
- /* nameOfRaceToSort */ selectedRaceIdentifier.getRaceName(),
265
- /* nameOfRaceColumnToShow */ null, /* nameOfRaceToShow */ selectedRaceIdentifier.getRaceName(),
266
- new ExplicitRaceColumnSelectionWithPreselectedRace(selectedRaceIdentifier), /* showRegattaRank */ false,
267
- /*showCompetitorSailIdColumn*/true, /*showCompetitorFullNameColumn*/true);
268
- return new LeaderboardPanel(sailingService, asyncActionsExecutor, leaderBoardSettings, selectedRaceIdentifier != null, selectedRaceIdentifier,
269
- competitorSelectionProvider, timer, leaderboardGroupName, leaderboardName, errorReporter, stringMessages,
270
- userAgent, /* showRaceDetails */ true, competitorSearchTextBox,
271
- /* showSelectionCheckbox */ true, raceTimesInfoProvider, /* autoExpandLastRaceColumn */ false,
272
- /* don't adjust the timer's delay from the leaderboard; control it solely from the RaceTimesInfoProvider */ false,
273
- /*autoApplyTopNFilter*/ false, /*showCompetitorFilterStatus*/ false);
274
- }
275
-
276
- private void setComponentVisible(ComponentViewer componentViewer, Component<?> component, boolean visible) {
277
- component.setVisible(visible);
278
- componentViewer.forceLayout();
279
- }
280
-
281
- /**
282
- * Sets the collapsable panel for the leaderboard open or close, if in <code>CASCADE</code> view mode.<br />
283
- * Displays or hides the leaderboard, if in <code>ONESCREEN</code> view mode.<br /><br />
284
- *
285
- * The race board should be completely rendered before this method is called, or a few exceptions could be thrown.
286
- *
287
- * @param visible <code>true</code> if the leaderboard shall be open/visible
288
- */
289
- public void setLeaderboardVisible(boolean visible) {
290
- setComponentVisible(leaderboardAndMapViewer, leaderboardPanel, visible);
291
- }
292
-
293
- /**
294
- * Sets the collapsable panel for the wind chart open or close, if in <code>CASCADE</code> view mode.<br />
295
- * Displays or hides the wind chart, if in <code>ONESCREEN</code> view mode.<br /><br />
296
- *
297
- * The race board should be completely rendered before this method is called, or a few exceptions could be thrown.
298
- *
299
- * @param visible <code>true</code> if the wind chart shall be open/visible
300
- */
301
- public void setWindChartVisible(boolean visible) {
302
- setComponentVisible(leaderboardAndMapViewer, windChart, visible);
303
- }
304
-
305
- /**
306
- * Sets the collapsable panel for the competitor chart open or close, if in <code>CASCADE</code> view mode.<br />
307
- * Displays or hides the competitor chart, if in <code>ONESCREEN</code> view mode.<br /><br />
308
- *
309
- * The race board should be completely rendered before this method is called, or a few exceptions could be thrown.
310
- *
311
- * @param visible <code>true</code> if the competitor chart shall be open/visible
312
- */
313
- public void setCompetitorChartVisible(boolean visible) {
314
- setComponentVisible(leaderboardAndMapViewer, competitorChart, visible);
315
- }
316
-
317
- public RaceTimePanel getTimePanel() {
318
- return timePanel;
319
- }
320
-
321
- protected SailingServiceAsync getSailingService() {
322
- return sailingService;
323
- }
324
-
325
- protected String getRaceBoardName() {
326
- return raceBoardName;
327
- }
328
-
329
- protected void setRaceBoardName(String raceBoardName) {
330
- this.raceBoardName = raceBoardName;
331
- }
332
-
333
- protected ErrorReporter getErrorReporter() {
334
- return errorReporter;
335
- }
336
-
337
- @Override
338
- public void onRaceSelectionChange(List<RegattaAndRaceIdentifier> selectedRaces) {
339
- }
340
-
341
- public RaceBoardViewConfiguration getConfiguration() {
342
- return raceboardViewConfiguration;
343
- }
344
-
345
- @Override
346
- public void updatedLeaderboard(LeaderboardDTO leaderboard) {
347
- leaderboardAndMapViewer.setLeftComponentWidth(leaderboardPanel.getContentPanel().getOffsetWidth());
348
- editMarkPassingPanel.setLeaderboard(leaderboard);
349
- }
350
-
351
- @Override
352
- public void currentRaceSelected(RaceIdentifier raceIdentifier, RaceColumnDTO raceColumn) {
353
- if (!currentRaceHasBeenSelectedOnce) {
354
- FleetDTO fleet = raceColumn.getFleet(raceIdentifier);
355
- String seriesName = raceColumn.getSeriesName();
356
- if (LeaderboardNameConstants.DEFAULT_SERIES_NAME.equals(seriesName)) {
357
- seriesName = "";
358
- }
359
- String fleetForRaceName = fleet==null?"":fleet.getName();
360
- if (fleetForRaceName.equals(LeaderboardNameConstants.DEFAULT_FLEET_NAME)) {
361
- fleetForRaceName = "";
362
- } else {
363
- fleetForRaceName = " - "+fleetForRaceName;
364
- }
365
- final Label raceNameLabel = new Label(stringMessages.race() + " " + raceColumn.getRaceColumnName());
366
- raceNameLabel.setStyleName("RaceName-Label");
367
- final Label raceAdditionalInformationLabel = new Label(seriesName + fleetForRaceName);
368
- raceAdditionalInformationLabel.setStyleName("RaceSeriesAndFleet-Label");
369
- raceInformationHeader.clear();
370
- raceInformationHeader.add(raceNameLabel);
371
- raceInformationHeader.add(raceAdditionalInformationLabel);
372
- final Anchor regattaNameAnchor = new Anchor(raceIdentifier.getRegattaName());
373
- if (eventId != null) {
374
- // we don't use the EntryPointLinkFactory here, because of lacking support for Places
375
- String debugParam = Window.Location.getParameter("gwt.codesvr");
376
- String link = "/gwt/Home.html";
377
- if (debugParam != null && !debugParam.isEmpty()) {
378
- link += "?gwt.codesvr=" + debugParam;
379
- }
380
- link += "#EventPlace:eventId="+eventId.toString();
381
- link += "&navigationTab=Regatta&leaderboardName=" + URLEncoder.encode(leaderboardName);
382
- regattaNameAnchor.setHref(link);
383
- } else {
384
- String leaderboardGroupNameParam = Window.Location.getParameter("leaderboardGroupName");
385
- if(leaderboardGroupNameParam != null) {
386
- Map<String, String> leaderboardGroupLinkParameters = new HashMap<String, String>();
387
- leaderboardGroupLinkParameters.put("showRaceDetails", "true");
388
- leaderboardGroupLinkParameters.put("leaderboardGroupName", leaderboardGroupNameParam);
389
- String leaderBoardGroupLink = EntryPointLinkFactory.createLeaderboardGroupLink(leaderboardGroupLinkParameters);
390
- regattaNameAnchor.setHref(leaderBoardGroupLink);
391
- } else {
392
- // fallback
393
- regattaNameAnchor.setHref("javascript:window.history.back();");
394
- }
395
- }
396
- regattaNameAnchor.setStyleName("RegattaName-Anchor");
397
-
398
- // TODO: Strange behavior... check
399
-// Window.addResizeHandler(new ResizeHandler() {
400
-// @Override
401
-// public void onResize(ResizeEvent event) {
402
-// int headerPanelWidth = raceMap.getRightHeaderPanel().getOffsetWidth() - 150; // 150px is the width of the sapLogo
403
-// int raceNameAndFleetLabelWidth = raceInformationHeader.getOffsetWidth();
404
-// int regattaAnchorWidth = regattaNameAnchor.getOffsetWidth();
405
-// boolean overlap = raceNameAndFleetLabelWidth + regattaAnchorWidth > headerPanelWidth;
406
-// raceInformationHeader.setVisible(!overlap);
407
-// }
408
-// });
409
-
410
- Label raceTimeLabel = computeRaceInformation(raceColumn, fleet);
411
- raceTimeLabel.setStyleName("RaceTime-Label");
412
- regattaAndRaceTimeInformationHeader.clear();
413
- regattaAndRaceTimeInformationHeader.add(regattaNameAnchor);
414
- regattaAndRaceTimeInformationHeader.add(raceTimeLabel);
415
- currentRaceHasBeenSelectedOnce = true;
416
- }
417
- }
418
-
419
- @Override
420
- public UIObject getXPositionUiObject() {
421
- return timePanel;
422
- }
423
-
424
- @Override
425
- public UIObject getYPositionUiObject() {
426
- return timePanel;
427
- }
428
-
429
- private Label computeRaceInformation(RaceColumnDTO raceColumn, FleetDTO fleet) {
430
- Label raceInformationLabel = new Label();
431
- raceInformationLabel.setStyleName("Race-Time-Label");
432
- DateTimeFormat formatter = DateTimeFormat.getFormat("E d/M/y");
433
- raceInformationLabel.setText(formatter.format(raceColumn.getStartDate(fleet)));
434
- return raceInformationLabel;
435
- }
436
-}
437
-
1
+package com.sap.sailing.gwt.ui.raceboard;
2
+
3
+import java.util.ArrayList;
4
+import java.util.Collections;
5
+import java.util.HashMap;
6
+import java.util.List;
7
+import java.util.Map;
8
+import java.util.UUID;
9
+
10
+import com.google.gwt.core.client.Scheduler;
11
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
12
+import com.google.gwt.core.shared.GWT;
13
+import com.google.gwt.dom.client.Document;
14
+import com.google.gwt.dom.client.Style.Unit;
15
+import com.google.gwt.i18n.client.DateTimeFormat;
16
+import com.google.gwt.user.client.Command;
17
+import com.google.gwt.user.client.Window;
18
+import com.google.gwt.user.client.ui.Anchor;
19
+import com.google.gwt.user.client.ui.FlowPanel;
20
+import com.google.gwt.user.client.ui.Label;
21
+import com.google.gwt.user.client.ui.MenuBar;
22
+import com.google.gwt.user.client.ui.RequiresResize;
23
+import com.google.gwt.user.client.ui.SimplePanel;
24
+import com.google.gwt.user.client.ui.UIObject;
25
+import com.google.gwt.user.client.ui.Widget;
26
+import com.sap.sailing.domain.common.LeaderboardNameConstants;
27
+import com.sap.sailing.domain.common.RaceIdentifier;
28
+import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
29
+import com.sap.sailing.domain.common.dto.BoatDTO;
30
+import com.sap.sailing.domain.common.dto.CompetitorDTO;
31
+import com.sap.sailing.domain.common.dto.FleetDTO;
32
+import com.sap.sailing.domain.common.dto.LeaderboardDTO;
33
+import com.sap.sailing.domain.common.dto.RaceColumnDTO;
34
+import com.sap.sailing.domain.common.dto.RaceDTO;
35
+import com.sap.sailing.gwt.ui.client.CompetitorColorProvider;
36
+import com.sap.sailing.gwt.ui.client.CompetitorColorProviderImpl;
37
+import com.sap.sailing.gwt.ui.client.CompetitorSelectionModel;
38
+import com.sap.sailing.gwt.ui.client.CompetitorSelectionProvider;
39
+import com.sap.sailing.gwt.ui.client.EntryPointLinkFactory;
40
+import com.sap.sailing.gwt.ui.client.GlobalNavigationPanel;
41
+import com.sap.sailing.gwt.ui.client.LeaderboardUpdateListener;
42
+import com.sap.sailing.gwt.ui.client.MediaServiceAsync;
43
+import com.sap.sailing.gwt.ui.client.RaceSelectionChangeListener;
44
+import com.sap.sailing.gwt.ui.client.RaceSelectionProvider;
45
+import com.sap.sailing.gwt.ui.client.RaceTimePanel;
46
+import com.sap.sailing.gwt.ui.client.RaceTimesInfoProvider;
47
+import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
48
+import com.sap.sailing.gwt.ui.client.StringMessages;
49
+import com.sap.sailing.gwt.ui.client.media.MediaPlayerManagerComponent;
50
+import com.sap.sailing.gwt.ui.client.media.PopupPositionProvider;
51
+import com.sap.sailing.gwt.ui.client.shared.charts.EditMarkPassingsPanel;
52
+import com.sap.sailing.gwt.ui.client.shared.charts.MultiCompetitorRaceChart;
53
+import com.sap.sailing.gwt.ui.client.shared.charts.WindChart;
54
+import com.sap.sailing.gwt.ui.client.shared.charts.WindChartSettings;
55
+import com.sap.sailing.gwt.ui.client.shared.filter.FilterWithUI;
56
+import com.sap.sailing.gwt.ui.client.shared.filter.LeaderboardFetcher;
57
+import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMap;
58
+import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMapResources;
59
+import com.sap.sailing.gwt.ui.leaderboard.CompetitorFilterPanel;
60
+import com.sap.sailing.gwt.ui.leaderboard.ExplicitRaceColumnSelectionWithPreselectedRace;
61
+import com.sap.sailing.gwt.ui.leaderboard.LeaderboardPanel;
62
+import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettings;
63
+import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettingsFactory;
64
+import com.sap.sse.common.filter.FilterSet;
65
+import com.sap.sse.common.settings.AbstractSettings;
66
+import com.sap.sse.gwt.client.ErrorReporter;
67
+import com.sap.sse.gwt.client.URLEncoder;
68
+import com.sap.sse.gwt.client.async.AsyncActionsExecutor;
69
+import com.sap.sse.gwt.client.player.TimeRangeWithZoomModel;
70
+import com.sap.sse.gwt.client.player.Timer;
71
+import com.sap.sse.gwt.client.shared.components.Component;
72
+import com.sap.sse.gwt.client.shared.components.ComponentViewer;
73
+import com.sap.sse.gwt.client.shared.components.SettingsDialog;
74
+import com.sap.sse.gwt.client.useragent.UserAgentDetails;
75
+import com.sap.sse.security.ui.client.UserService;
76
+
77
+/**
78
+ * A view showing a list of components visualizing a race from the regattas announced by calls to {@link #fillRegattas(List)}.
79
+ * The race selection is provided by a {@link RaceSelectionProvider} for which this is a {@link RaceSelectionChangeListener listener}.
80
+ * {@link RaceIdentifier}-based race selection changes are converted to {@link RaceDTO} objects using the {@link #racesByIdentifier}
81
+ * map maintained during {@link #fillRegattas(List)}. The race selection provider is expected to be single selection only.
82
+ *
83
+ * @author Frank Mittag, Axel Uhl (d043530)
84
+ *
85
+ */
86
+public class RaceBoardPanel extends SimplePanel implements RaceSelectionChangeListener,
87
+ LeaderboardUpdateListener, PopupPositionProvider, RequiresResize {
88
+ private final SailingServiceAsync sailingService;
89
+ private final MediaServiceAsync mediaService;
90
+ private final UUID eventId;
91
+ private final StringMessages stringMessages;
92
+ private final ErrorReporter errorReporter;
93
+ private final RaceBoardViewConfiguration raceboardViewConfiguration;
94
+ private String raceBoardName;
95
+
96
+ private final List<ComponentViewer> componentViewers;
97
+ private RaceTimePanel timePanel;
98
+ private final Timer timer;
99
+ private final RaceSelectionProvider raceSelectionProvider;
100
+ private final UserAgentDetails userAgent;
101
+ private final CompetitorSelectionProvider competitorSelectionProvider;
102
+ private final TimeRangeWithZoomModel timeRangeWithZoomModel;
103
+ private final RegattaAndRaceIdentifier selectedRaceIdentifier;
104
+
105
+ private final String leaderboardName;
106
+ private final LeaderboardPanel leaderboardPanel;
107
+ private WindChart windChart;
108
+ private MultiCompetitorRaceChart competitorChart;
109
+ private EditMarkPassingsPanel editMarkPassingPanel;
110
+
111
+ /**
112
+ * The component viewer in <code>ONESCREEN</code> view mode. <code>null</code> if in <code>CASCADE</code> view mode
113
+ */
114
+ private SideBySideComponentViewer leaderboardAndMapViewer;
115
+
116
+ private final AsyncActionsExecutor asyncActionsExecutor;
117
+
118
+ private final RaceTimesInfoProvider raceTimesInfoProvider;
119
+ private RaceMap raceMap;
120
+
121
+ private final FlowPanel raceInformationHeader;
122
+ private final FlowPanel regattaAndRaceTimeInformationHeader;
123
+ private boolean currentRaceHasBeenSelectedOnce;
124
+
125
+ private static final RaceMapResources raceMapResources = GWT.create(RaceMapResources.class);
126
+
127
+ /**
128
+ * @param eventId
129
+ * an optional event that can be used for "back"-navigation in case the race board shows a race in the
130
+ * context of an event; may be <code>null</code>.
131
+ * @param isScreenLargeEnoughToOfferChartSupport
132
+ * if the screen is large enough to display charts such as the competitor chart or the wind chart, a
133
+ * padding is provided for the RaceTimePanel that aligns its right border with that of the charts, and
134
+ * the charts are created. This decision is made once on startup in the {@link RaceBoardEntryPoint} class.
135
+ */
136
+ public RaceBoardPanel(SailingServiceAsync sailingService, MediaServiceAsync mediaService,
137
+ UserService userService, AsyncActionsExecutor asyncActionsExecutor, Map<CompetitorDTO, BoatDTO> competitorsAndTheirBoats,
138
+ Timer timer, RaceSelectionProvider theRaceSelectionProvider, String leaderboardName,
139
+ String leaderboardGroupName, UUID eventId, RaceBoardViewConfiguration raceboardViewConfiguration,
140
+ ErrorReporter errorReporter, final StringMessages stringMessages,
141
+ UserAgentDetails userAgent, RaceTimesInfoProvider raceTimesInfoProvider, boolean showMapControls, boolean isScreenLargeEnoughToOfferChartSupport) {
142
+ this.sailingService = sailingService;
143
+ this.mediaService = mediaService;
144
+ this.stringMessages = stringMessages;
145
+ this.raceboardViewConfiguration = raceboardViewConfiguration;
146
+ this.raceSelectionProvider = theRaceSelectionProvider;
147
+ this.raceTimesInfoProvider = raceTimesInfoProvider;
148
+ this.errorReporter = errorReporter;
149
+ this.userAgent = userAgent;
150
+ this.timer = timer;
151
+ this.eventId = eventId;
152
+ this.currentRaceHasBeenSelectedOnce = false;
153
+ this.leaderboardName = leaderboardName;
154
+ raceSelectionProvider.addRaceSelectionChangeListener(this);
155
+ selectedRaceIdentifier = raceSelectionProvider.getSelectedRaces().iterator().next();
156
+ this.setRaceBoardName(selectedRaceIdentifier.getRaceName());
157
+ this.asyncActionsExecutor = asyncActionsExecutor;
158
+ FlowPanel mainPanel = new ResizableFlowPanel();
159
+ mainPanel.setSize("100%", "100%");
160
+ setWidget(mainPanel);
161
+ raceInformationHeader = new FlowPanel();
162
+ raceInformationHeader.setStyleName("RegattaRaceInformation-Header");
163
+ regattaAndRaceTimeInformationHeader = new FlowPanel();
164
+ regattaAndRaceTimeInformationHeader.setStyleName("RegattaAndRaceTime-Header");
165
+ timeRangeWithZoomModel = new TimeRangeWithZoomModel();
166
+ componentViewers = new ArrayList<ComponentViewer>();
167
+ final CompetitorColorProvider colorProvider = new CompetitorColorProviderImpl(selectedRaceIdentifier, competitorsAndTheirBoats);
168
+ competitorSelectionProvider = new CompetitorSelectionModel(/* hasMultiSelection */ true, colorProvider);
169
+
170
+ raceMapResources.combinedWindPanelStyle().ensureInjected();
171
+ raceMap = new RaceMap(sailingService, asyncActionsExecutor, errorReporter, timer,
172
+ competitorSelectionProvider, stringMessages, showMapControls, getConfiguration().isShowViewStreamlets(), getConfiguration().isShowViewStreamletColors(), getConfiguration().isShowViewSimulation(),
173
+ selectedRaceIdentifier, raceMapResources.combinedWindPanelStyle(), /* showHeaderPanel */ true) {
174
+ private static final String INDENT_SMALL_CONTROL_STYLE = "indentsmall";
175
+ private static final String INDENT_BIG_CONTROL_STYLE = "indentbig";
176
+ @Override
177
+ public void onResize() {
178
+ super.onResize();
179
+ Scheduler.get().scheduleDeferred(new ScheduledCommand() {
180
+ @Override
181
+ public void execute() {
182
+ // Show/hide the leaderboard panels toggle button text based on the race map height
183
+ leaderboardAndMapViewer.setLeftComponentToggleButtonTextVisibilityAndDraggerPosition(raceMap.getOffsetHeight() > 400);
184
+ }
185
+ });
186
+ }
187
+
188
+ @Override
189
+ protected String getLeftControlsIndentStyle() {
190
+ // Calculate style name for left control indent based on race map height an leaderboard panel visibility
191
+ if (raceMap.getOffsetHeight() <= 300) {
192
+ return INDENT_BIG_CONTROL_STYLE;
193
+ }
194
+ if (leaderboardPanel.isVisible() && raceMap.getOffsetHeight() <= 500) {
195
+ return INDENT_SMALL_CONTROL_STYLE;
196
+ }
197
+ return super.getLeftControlsIndentStyle();
198
+ }
199
+ };
200
+ CompetitorFilterPanel competitorSearchTextBox = new CompetitorFilterPanel(competitorSelectionProvider, stringMessages, raceMap,
201
+ new LeaderboardFetcher() {
202
+ @Override
203
+ public LeaderboardDTO getLeaderboard() {
204
+ return leaderboardPanel.getLeaderboard();
205
+ }
206
+ }, selectedRaceIdentifier);
207
+ raceMap.getLeftHeaderPanel().add(raceInformationHeader);
208
+ raceMap.getRightHeaderPanel().add(regattaAndRaceTimeInformationHeader);
209
+
210
+ // Determine if the screen is large enough to initially display the leaderboard panel on the left side of the
211
+ // map based on the initial screen width. Afterwards, the leaderboard panel visibility can be toggled as usual.
212
+ boolean isScreenLargeEnoughToInitiallyDisplayLeaderboard = Document.get().getClientWidth() >= 1024;
213
+ leaderboardPanel = createLeaderboardPanel(leaderboardName, leaderboardGroupName, competitorSearchTextBox, isScreenLargeEnoughToInitiallyDisplayLeaderboard);
214
+ leaderboardPanel.setTitle(stringMessages.leaderboard());
215
+ leaderboardPanel.getElement().getStyle().setMarginLeft(6, Unit.PX);
216
+ leaderboardPanel.getElement().getStyle().setMarginTop(10, Unit.PX);
217
+ createOneScreenView(leaderboardName, leaderboardGroupName, eventId, mainPanel, showMapControls, isScreenLargeEnoughToOfferChartSupport, isScreenLargeEnoughToInitiallyDisplayLeaderboard, raceMap, userService); // initializes the raceMap field
218
+ leaderboardPanel.addLeaderboardUpdateListener(this);
219
+ // in case the URL configuration contains the name of a competitors filter set we try to activate it
220
+ // FIXME the competitorsFilterSets has now moved to CompetitorSearchTextBox (which should probably be renamed); pass on the parameters to the LeaderboardPanel and see what it does with it
221
+ if (raceboardViewConfiguration.getActiveCompetitorsFilterSetName() != null) {
222
+ for (FilterSet<CompetitorDTO, FilterWithUI<CompetitorDTO>> filterSet : competitorSearchTextBox.getCompetitorsFilterSets()
223
+ .getFilterSets()) {
224
+ if (filterSet.getName().equals(raceboardViewConfiguration.getActiveCompetitorsFilterSetName())) {
225
+ competitorSearchTextBox.getCompetitorsFilterSets().setActiveFilterSet(filterSet);
226
+ break;
227
+ }
228
+ }
229
+ }
230
+ timePanel = new RaceTimePanel(timer, timeRangeWithZoomModel, stringMessages, raceTimesInfoProvider,
231
+ raceboardViewConfiguration.isCanReplayDuringLiveRaces(), isScreenLargeEnoughToOfferChartSupport);
232
+ timeRangeWithZoomModel.addTimeZoomChangeListener(timePanel);
233
+ raceTimesInfoProvider.addRaceTimesInfoProviderListener(timePanel);
234
+ raceSelectionProvider.addRaceSelectionChangeListener(timePanel);
235
+ timePanel.onRaceSelectionChange(raceSelectionProvider.getSelectedRaces());
236
+ }
237
+
238
+ /**
239
+ * @param event
240
+ * an optional event; may be <code>null</code> or else can be used to show some context information in
241
+ * the {@link GlobalNavigationPanel}.
242
+ * @param isScreenLargeEnoughToOfferChartSupport
243
+ * if the screen is large enough to display charts such as the competitor chart or the wind chart, a
244
+ * padding is provided for the RaceTimePanel that aligns its right border with that of the charts, and
245
+ * the charts are created.
246
+ * @param isScreenLargeEnoughToInitiallyDisplayLeaderboard TODO
247
+ */
248
+ private void createOneScreenView(String leaderboardName, String leaderboardGroupName, UUID event, FlowPanel mainPanel,
249
+ boolean showMapControls, boolean isScreenLargeEnoughToOfferChartSupport, boolean isScreenLargeEnoughToInitiallyDisplayLeaderboard, RaceMap raceMap, UserService userService) {
250
+ // create the default leaderboard and select the right race
251
+ raceTimesInfoProvider.addRaceTimesInfoProviderListener(raceMap);
252
+ raceMap.onRaceSelectionChange(Collections.singletonList(selectedRaceIdentifier));
253
+ List<Component<?>> components = new ArrayList<Component<?>>();
254
+ if (isScreenLargeEnoughToOfferChartSupport) {
255
+ competitorChart = new MultiCompetitorRaceChart(sailingService, asyncActionsExecutor, competitorSelectionProvider, raceSelectionProvider,
256
+ timer, timeRangeWithZoomModel, stringMessages, errorReporter, true, true, leaderboardGroupName, leaderboardName);
257
+ competitorChart.onRaceSelectionChange(raceSelectionProvider.getSelectedRaces());
258
+ competitorChart.getEntryWidget().setTitle(stringMessages.competitorCharts());
259
+ competitorChart.setVisible(false);
260
+ components.add(competitorChart);
261
+ windChart = new WindChart(sailingService, raceSelectionProvider, timer, timeRangeWithZoomModel, new WindChartSettings(),
262
+ stringMessages, asyncActionsExecutor, errorReporter, /* compactChart */ true);
263
+ windChart.setVisible(false);
264
+ windChart.onRaceSelectionChange(raceSelectionProvider.getSelectedRaces());
265
+ windChart.getEntryWidget().setTitle(stringMessages.windChart());
266
+ components.add(windChart);
267
+ editMarkPassingPanel = new EditMarkPassingsPanel(sailingService, selectedRaceIdentifier,
268
+ stringMessages, competitorSelectionProvider, errorReporter, timer);
269
+ editMarkPassingPanel.setLeaderboard(leaderboardPanel.getLeaderboard());
270
+ editMarkPassingPanel.getEntryWidget().setTitle(stringMessages.editMarkPassings());
271
+ components.add(editMarkPassingPanel);
272
+ }
273
+ boolean autoSelectMedia = getConfiguration().isAutoSelectMedia();
274
+ MediaPlayerManagerComponent mediaPlayerManagerComponent = new MediaPlayerManagerComponent(
275
+ selectedRaceIdentifier, raceTimesInfoProvider, timer, mediaService, userService, stringMessages,
276
+ errorReporter, userAgent, this, autoSelectMedia);
277
+ leaderboardAndMapViewer = new SideBySideComponentViewer(leaderboardPanel, raceMap, mediaPlayerManagerComponent, components, stringMessages, userService, editMarkPassingPanel);
278
+ componentViewers.add(leaderboardAndMapViewer);
279
+ for (ComponentViewer componentViewer : componentViewers) {
280
+ mainPanel.add(componentViewer.getViewerWidget());
281
+ }
282
+ boolean showLeaderboard = getConfiguration().isShowLeaderboard() && isScreenLargeEnoughToInitiallyDisplayLeaderboard;
283
+ setLeaderboardVisible(showLeaderboard);
284
+ if (isScreenLargeEnoughToOfferChartSupport) {
285
+ setWindChartVisible(getConfiguration().isShowWindChart());
286
+ setCompetitorChartVisible(getConfiguration().isShowCompetitorsChart());
287
+ }
288
+ // make sure to load leaderboard data for filtering to work
289
+ if (!showLeaderboard) {
290
+ leaderboardPanel.setVisible(true);
291
+ leaderboardPanel.setVisible(false);
292
+ }
293
+ }
294
+
295
+ @SuppressWarnings("unused")
296
+ private <SettingsType extends AbstractSettings> void addSettingsMenuItem(MenuBar settingsMenu, final Component<SettingsType> component) {
297
+ if (component.hasSettings()) {
298
+ settingsMenu.addItem(component.getLocalizedShortName(), new Command() {
299
+ public void execute() {
300
+ new SettingsDialog<SettingsType>(component, stringMessages).show();
301
+ }
302
+ });
303
+ }
304
+ }
305
+
306
+ private LeaderboardPanel createLeaderboardPanel(String leaderboardName, String leaderboardGroupName,
307
+ CompetitorFilterPanel competitorSearchTextBox, boolean isScreenLargeEnoughToInitiallyDisplayLeaderboard) {
308
+ LeaderboardSettings leaderBoardSettings = LeaderboardSettingsFactory.getInstance()
309
+ .createNewSettingsForPlayMode(timer.getPlayMode(),
310
+ /* nameOfRaceToSort */ selectedRaceIdentifier.getRaceName(),
311
+ /* nameOfRaceColumnToShow */ null, /* nameOfRaceToShow */ selectedRaceIdentifier.getRaceName(),
312
+ new ExplicitRaceColumnSelectionWithPreselectedRace(selectedRaceIdentifier), /* showRegattaRank */ false,
313
+ /*showCompetitorSailIdColumn*/true,
314
+ /* don't showCompetitorFullNameColumn in case screen is so small that we don't
315
+ * even display the leaderboard initially */ isScreenLargeEnoughToInitiallyDisplayLeaderboard);
316
+ return new LeaderboardPanel(sailingService, asyncActionsExecutor, leaderBoardSettings, selectedRaceIdentifier != null, selectedRaceIdentifier,
317
+ competitorSelectionProvider, timer, leaderboardGroupName, leaderboardName, errorReporter, stringMessages,
318
+ userAgent, /* showRaceDetails */ true, competitorSearchTextBox,
319
+ /* showSelectionCheckbox */ true, raceTimesInfoProvider, /* autoExpandLastRaceColumn */ false,
320
+ /* don't adjust the timer's delay from the leaderboard; control it solely from the RaceTimesInfoProvider */ false,
321
+ /*autoApplyTopNFilter*/ false, /*showCompetitorFilterStatus*/ false);
322
+ }
323
+
324
+ private void setComponentVisible(ComponentViewer componentViewer, Component<?> component, boolean visible) {
325
+ component.setVisible(visible);
326
+ componentViewer.forceLayout();
327
+ }
328
+
329
+ /**
330
+ * Sets the collapsable panel for the leaderboard open or close, if in <code>CASCADE</code> view mode.<br />
331
+ * Displays or hides the leaderboard, if in <code>ONESCREEN</code> view mode.<br /><br />
332
+ *
333
+ * The race board should be completely rendered before this method is called, or a few exceptions could be thrown.
334
+ *
335
+ * @param visible <code>true</code> if the leaderboard shall be open/visible
336
+ */
337
+ public void setLeaderboardVisible(boolean visible) {
338
+ setComponentVisible(leaderboardAndMapViewer, leaderboardPanel, visible);
339
+ }
340
+
341
+ /**
342
+ * Sets the collapsable panel for the wind chart open or close, if in <code>CASCADE</code> view mode.<br />
343
+ * Displays or hides the wind chart, if in <code>ONESCREEN</code> view mode.<br /><br />
344
+ *
345
+ * The race board should be completely rendered before this method is called, or a few exceptions could be thrown.
346
+ *
347
+ * @param visible <code>true</code> if the wind chart shall be open/visible
348
+ */
349
+ public void setWindChartVisible(boolean visible) {
350
+ setComponentVisible(leaderboardAndMapViewer, windChart, visible);
351
+ }
352
+
353
+ /**
354
+ * Sets the collapsable panel for the competitor chart open or close, if in <code>CASCADE</code> view mode.<br />
355
+ * Displays or hides the competitor chart, if in <code>ONESCREEN</code> view mode.<br /><br />
356
+ *
357
+ * The race board should be completely rendered before this method is called, or a few exceptions could be thrown.
358
+ *
359
+ * @param visible <code>true</code> if the competitor chart shall be open/visible
360
+ */
361
+ public void setCompetitorChartVisible(boolean visible) {
362
+ setComponentVisible(leaderboardAndMapViewer, competitorChart, visible);
363
+ }
364
+
365
+ public RaceTimePanel getTimePanel() {
366
+ return timePanel;
367
+ }
368
+
369
+ protected SailingServiceAsync getSailingService() {
370
+ return sailingService;
371
+ }
372
+
373
+ protected String getRaceBoardName() {
374
+ return raceBoardName;
375
+ }
376
+
377
+ protected void setRaceBoardName(String raceBoardName) {
378
+ this.raceBoardName = raceBoardName;
379
+ }
380
+
381
+ protected ErrorReporter getErrorReporter() {
382
+ return errorReporter;
383
+ }
384
+
385
+ @Override
386
+ public void onRaceSelectionChange(List<RegattaAndRaceIdentifier> selectedRaces) {
387
+ }
388
+
389
+ public RaceBoardViewConfiguration getConfiguration() {
390
+ return raceboardViewConfiguration;
391
+ }
392
+
393
+ @Override
394
+ public void updatedLeaderboard(LeaderboardDTO leaderboard) {
395
+ leaderboardAndMapViewer.setLeftComponentWidth(leaderboardPanel.getContentPanel().getOffsetWidth());
396
+ if (editMarkPassingPanel != null) {
397
+ editMarkPassingPanel.setLeaderboard(leaderboard);
398
+ }
399
+ }
400
+
401
+ @Override
402
+ public void currentRaceSelected(RaceIdentifier raceIdentifier, RaceColumnDTO raceColumn) {
403
+ if (!currentRaceHasBeenSelectedOnce) {
404
+ FleetDTO fleet = raceColumn.getFleet(raceIdentifier);
405
+ String seriesName = raceColumn.getSeriesName();
406
+ if (LeaderboardNameConstants.DEFAULT_SERIES_NAME.equals(seriesName)) {
407
+ seriesName = "";
408
+ }
409
+ String fleetForRaceName = fleet==null?"":fleet.getName();
410
+ if (fleetForRaceName.equals(LeaderboardNameConstants.DEFAULT_FLEET_NAME)) {
411
+ fleetForRaceName = "";
412
+ } else {
413
+ fleetForRaceName = (seriesName.isEmpty() ? "" : " - ") + fleetForRaceName;
414
+ }
415
+ final Label raceNameLabel = new Label(stringMessages.race() + " " + raceColumn.getRaceColumnName());
416
+ raceNameLabel.setStyleName("RaceName-Label");
417
+ final Label raceAdditionalInformationLabel = new Label(seriesName + fleetForRaceName);
418
+ raceAdditionalInformationLabel.setStyleName("RaceSeriesAndFleet-Label");
419
+ raceInformationHeader.clear();
420
+ raceInformationHeader.add(raceNameLabel);
421
+ raceInformationHeader.add(raceAdditionalInformationLabel);
422
+ final Anchor regattaNameAnchor = new Anchor(raceIdentifier.getRegattaName());
423
+ regattaNameAnchor.setTitle(raceIdentifier.getRegattaName());
424
+ if (eventId != null) {
425
+ // we don't use the EntryPointLinkFactory here, because of lacking support for Places
426
+ String debugParam = Window.Location.getParameter("gwt.codesvr");
427
+ String link = "/gwt/Home.html";
428
+ if (debugParam != null && !debugParam.isEmpty()) {
429
+ link += "?gwt.codesvr=" + debugParam;
430
+ }
431
+ link += "#EventPlace:eventId="+eventId.toString();
432
+ link += "&navigationTab=Regatta&leaderboardName=" + URLEncoder.encode(leaderboardName);
433
+ regattaNameAnchor.setHref(link);
434
+ } else {
435
+ String leaderboardGroupNameParam = Window.Location.getParameter("leaderboardGroupName");
436
+ if(leaderboardGroupNameParam != null) {
437
+ Map<String, String> leaderboardGroupLinkParameters = new HashMap<String, String>();
438
+ leaderboardGroupLinkParameters.put("showRaceDetails", "true");
439
+ leaderboardGroupLinkParameters.put("leaderboardGroupName", leaderboardGroupNameParam);
440
+ String leaderBoardGroupLink = EntryPointLinkFactory.createLeaderboardGroupLink(leaderboardGroupLinkParameters);
441
+ regattaNameAnchor.setHref(leaderBoardGroupLink);
442
+ } else {
443
+ // fallback
444
+ regattaNameAnchor.setHref("javascript:window.history.back();");
445
+ }
446
+ }
447
+ regattaNameAnchor.setStyleName("RegattaName-Anchor");
448
+ Label raceTimeLabel = computeRaceInformation(raceColumn, fleet);
449
+ raceTimeLabel.setStyleName("RaceTime-Label");
450
+ regattaAndRaceTimeInformationHeader.clear();
451
+ regattaAndRaceTimeInformationHeader.add(regattaNameAnchor);
452
+ regattaAndRaceTimeInformationHeader.add(raceTimeLabel);
453
+ currentRaceHasBeenSelectedOnce = true;
454
+ }
455
+ }
456
+
457
+ @Override
458
+ public UIObject getXPositionUiObject() {
459
+ return timePanel;
460
+ }
461
+
462
+ @Override
463
+ public UIObject getYPositionUiObject() {
464
+ return timePanel;
465
+ }
466
+
467
+ private Label computeRaceInformation(RaceColumnDTO raceColumn, FleetDTO fleet) {
468
+ Label raceInformationLabel = new Label();
469
+ raceInformationLabel.setStyleName("Race-Time-Label");
470
+ DateTimeFormat formatter = DateTimeFormat.getFormat("E d/M/y");
471
+ raceInformationLabel.setText(formatter.format(raceColumn.getStartDate(fleet)));
472
+ return raceInformationLabel;
473
+ }
474
+
475
+ @Override
476
+ public void onResize() {
477
+ Widget child = getWidget();
478
+ if (child != null && child instanceof RequiresResize) {
479
+ ((RequiresResize) child).onResize();
480
+ }
481
+ }
482
+}
483
+
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/ResizableFlowPanel.java
... ...
@@ -0,0 +1,23 @@
1
+package com.sap.sailing.gwt.ui.raceboard;
2
+
3
+import com.google.gwt.user.client.ui.FlowPanel;
4
+import com.google.gwt.user.client.ui.RequiresResize;
5
+import com.google.gwt.user.client.ui.Widget;
6
+import com.google.gwt.user.client.ui.WidgetCollection;
7
+
8
+/**
9
+ * Utility class extending {@link FlowPanel} and implementing {@link RequiresResize} interface. All
10
+ * {@link #getChildren() children} also implementing {@link RequiresResize} are informed by calling their
11
+ * {@link RequiresResize#onResize()} method.
12
+ */
13
+class ResizableFlowPanel extends FlowPanel implements RequiresResize {
14
+ @Override
15
+ public void onResize() {
16
+ WidgetCollection children = getChildren();
17
+ for (Widget widget : children) {
18
+ if (widget instanceof RequiresResize) {
19
+ ((RequiresResize) widget).onResize();
20
+ }
21
+ }
22
+ }
23
+}
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/SideBySideComponentViewer.java
... ...
@@ -1,305 +1,334 @@
1
-package com.sap.sailing.gwt.ui.raceboard;
2
-
3
-import java.util.ArrayList;
4
-import java.util.List;
5
-
6
-import com.google.gwt.dom.client.Style.Unit;
7
-import com.google.gwt.event.dom.client.ClickEvent;
8
-import com.google.gwt.event.dom.client.ClickHandler;
9
-import com.google.gwt.user.client.ui.AbsolutePanel;
10
-import com.google.gwt.user.client.ui.Button;
11
-import com.google.gwt.user.client.ui.DockLayoutPanel.Direction;
12
-import com.google.gwt.user.client.ui.LayoutPanel;
13
-import com.google.gwt.user.client.ui.Panel;
14
-import com.google.gwt.user.client.ui.RequiresResize;
15
-import com.google.gwt.user.client.ui.ScrollPanel;
16
-import com.google.gwt.user.client.ui.Widget;
17
-import com.google.gwt.user.client.ui.WidgetCollection;
18
-import com.sap.sailing.domain.common.security.Roles;
19
-import com.sap.sailing.gwt.ui.client.StringMessages;
20
-import com.sap.sailing.gwt.ui.client.media.MediaManagementControl;
21
-import com.sap.sailing.gwt.ui.client.media.MediaPlayerManager;
22
-import com.sap.sailing.gwt.ui.client.media.MediaPlayerManager.PlayerChangeListener;
23
-import com.sap.sailing.gwt.ui.client.media.MediaPlayerManagerComponent;
24
-import com.sap.sailing.gwt.ui.client.media.MediaSingleSelectionControl;
25
-import com.sap.sailing.gwt.ui.client.shared.charts.EditMarkPassingsPanel;
26
-import com.sap.sailing.gwt.ui.leaderboard.LeaderboardPanel;
27
-import com.sap.sse.common.Util.Pair;
28
-import com.sap.sse.common.settings.AbstractSettings;
29
-import com.sap.sse.gwt.client.shared.components.Component;
30
-import com.sap.sse.gwt.client.shared.components.ComponentViewer;
31
-import com.sap.sse.gwt.client.shared.components.SettingsDialog;
32
-import com.sap.sse.security.shared.DefaultRoles;
33
-import com.sap.sse.security.ui.client.UserService;
34
-import com.sap.sse.security.ui.client.UserStatusEventHandler;
35
-import com.sap.sse.security.ui.shared.UserDTO;
36
-
37
-/**
38
- * Component Viewer that uses a {@link TouchSplitLayoutPanel} to display its components.
39
- *
40
- * TODO: Refactor to make sure it is really only performing operations that are related to view. Currently it is digging
41
- * too deep into components and setting titles or even creating video buttons.
42
- */
43
-public class SideBySideComponentViewer implements ComponentViewer, UserStatusEventHandler {
44
-
45
- private static final int DEFAULT_SOUTH_SPLIT_PANEL_HEIGHT = 200;
46
- private final int MIN_LEADERBOARD_WIDTH = 432; // works well for 505 and ESS
47
-
48
- /**
49
- * Absolute Panel that informs its children about a resize
50
- */
51
- class ResizableAbsolutePanel extends AbsolutePanel implements RequiresResize {
52
- public void onResize() {
53
- WidgetCollection children = getChildren();
54
- for (Widget widget : children) {
55
- if (widget instanceof RequiresResize) {
56
- ((RequiresResize) widget).onResize();
57
- }
58
- }
59
- }
60
- }
61
-
62
- private final LeaderboardPanel leftComponent;
63
- private final Component<?> rightComponent;
64
- private final List<Component<?>> components;
65
- private final ScrollPanel leftScrollPanel;
66
- private final StringMessages stringMessages;
67
- private final Button mediaSelectionButton;
68
- private final Button mediaManagementButton;
69
- private final EditMarkPassingsPanel markPassingsPanel;
70
-
71
- private LayoutPanel mainPanel;
72
-
73
- private TouchSplitLayoutPanel splitLayoutPanel;
74
- private int savedSplitPosition = -1;
75
- private boolean layoutForLeftComponentForcedOnce = false;
76
-
77
- public SideBySideComponentViewer(final LeaderboardPanel leftComponentP, final Component<?> rightComponentP,
78
- final MediaPlayerManagerComponent mediaPlayerManagerComponent, List<Component<?>> components,
79
- final StringMessages stringMessages, UserService userService, EditMarkPassingsPanel markPassingsPanel) {
80
- this.stringMessages = stringMessages;
81
- this.leftComponent = leftComponentP;
82
- this.rightComponent = rightComponentP;
83
- this.components = components;
84
- this.mediaSelectionButton = createMediaSelectionButton(mediaPlayerManagerComponent);
85
- this.mediaManagementButton = createMediaManagementButton(mediaPlayerManagerComponent);
86
- this.markPassingsPanel = markPassingsPanel;
87
-
88
- userService.addUserStatusEventHandler(this);
89
-
90
- mediaPlayerManagerComponent.setPlayerChangeListener(new PlayerChangeListener() {
91
-
92
- public void notifyStateChange(){
93
- String caption;
94
- String tooltip;
95
- switch (mediaPlayerManagerComponent.getAssignedMediaTracks().size()) {
96
- case 0:
97
- caption = stringMessages.mediaNoVideosCaption();
98
- tooltip = caption;
99
- mediaSelectionButton.setVisible(false);
100
- break;
101
- case 1:
102
- mediaSelectionButton.setVisible(true);
103
- if(mediaPlayerManagerComponent.isPlaying()){
104
- caption = stringMessages.mediaHideVideoCaption();
105
- tooltip = stringMessages.mediaHideVideoTooltip();
106
- }
107
- else{
108
- caption = stringMessages.mediaShowVideoCaption();
109
- tooltip = stringMessages.mediaShowVideoTooltip(mediaPlayerManagerComponent.getAssignedMediaTracks().iterator().next().title);
110
- }
111
- break;
112
- default:
113
- mediaSelectionButton.setVisible(true);
114
- caption = stringMessages.mediaSelectVideoCaption(mediaPlayerManagerComponent.getAssignedMediaTracks().size());
115
- tooltip = stringMessages.mediaSelectVideoTooltip();
116
- break;
117
- }
118
- if (mediaPlayerManagerComponent.isPlaying()) {
119
- mediaSelectionButton.addStyleDependentName("mediaplaying");
120
- } else {
121
- mediaSelectionButton.removeStyleDependentName("mediaplaying");
122
- }
123
- mediaSelectionButton.setText(caption);
124
- mediaSelectionButton.setTitle(tooltip);
125
- mediaManagementButton.setVisible(mediaPlayerManagerComponent.allowsEditing());
126
- }
127
-
128
- });
129
- this.leftScrollPanel = new ScrollPanel();
130
- this.leftScrollPanel.add(leftComponentP.getEntryWidget());
131
- this.leftScrollPanel.setTitle(leftComponentP.getEntryWidget().getTitle());
132
- this.mainPanel = new LayoutPanel();
133
- this.mainPanel.setSize("100%", "100%");
134
- this.mainPanel.getElement().getStyle().setMarginTop(-12, Unit.PX);
135
- this.mainPanel.setStyleName("SideBySideComponentViewer-MainPanel");
136
- this.splitLayoutPanel = new TouchSplitLayoutPanel(/* horizontal splitter width */3, /* vertical splitter height */
137
- 25);
138
- this.mainPanel.add(splitLayoutPanel);
139
-
140
- // initialize components - they need to be added before other widgets to get the right width
141
- initializeComponents();
142
-
143
- // initialize the leaderboard component
144
- savedSplitPosition = MIN_LEADERBOARD_WIDTH;
145
- splitLayoutPanel.insert(leftScrollPanel, leftComponent, Direction.WEST, savedSplitPosition);
146
-
147
- // create a panel that will contain the horizontal toggle buttons
148
- ResizableAbsolutePanel panelForMapAndHorizontalToggleButtons = new ResizableAbsolutePanel();
149
- panelForMapAndHorizontalToggleButtons.add(rightComponent.getEntryWidget());
150
- splitLayoutPanel.insert(panelForMapAndHorizontalToggleButtons, rightComponent, Direction.CENTER, 0);
151
-
152
- // add additional toggle buttons panel that currently only contains the video button
153
- List<Pair<Button, String>> additionalVerticalButtons = new ArrayList<Pair<Button, String>>();
154
- additionalVerticalButtons.add(new Pair<Button, String>(mediaSelectionButton,
155
- mediaPlayerManagerComponent.getDependentCssClassName()));
156
- if (/* TODO check for correct role; was: user != null */ true) {
157
- additionalVerticalButtons.add(new Pair<Button, String>(mediaManagementButton,
158
- "managemedia"));
159
- }
160
- onUserStatusChange(userService.getCurrentUser());
161
-
162
- // ensure that toggle buttons are positioned right
163
- splitLayoutPanel.lastComponentHasBeenAdded(this, panelForMapAndHorizontalToggleButtons,
164
- additionalVerticalButtons);
165
- }
166
-
167
- /**
168
- * Create the video control button that shows or hides the video popup
169
- *
170
- * @param userAgent
171
- */
172
- private Button createMediaSelectionButton(final MediaPlayerManager mediaPlayerManager) {
173
- final Button result = new Button();
174
- final MediaSingleSelectionControl multiSelectionControl = new MediaSingleSelectionControl(mediaPlayerManager,
175
- result, stringMessages);
176
- result.addClickHandler(new ClickHandler() {
177
-
178
- @Override
179
- public void onClick(ClickEvent event) {
180
- if (mediaPlayerManager.getAssignedMediaTracks().size() == 1) {
181
- if (mediaPlayerManager.isPlaying()) {
182
- mediaPlayerManager.stopAll();
183
- } else {
184
- mediaPlayerManager.playDefault();
185
-
186
- }
187
- } else {
188
- multiSelectionControl.show();
189
- }
190
- }
191
- });
192
-
193
- // hide button initially as we defer showing the button to the asynchroneous
194
- // task that gets launched by the media service to get video tracks
195
- result.setVisible(false);
196
- return result;
197
- }
198
-
199
- /**
200
- * Create the video control button that shows or hides the video popup
201
- *
202
- * @param userAgent
203
- */
204
- private Button createMediaManagementButton(final MediaPlayerManager mediaPlayerManager) {
205
- final Button result = new Button(stringMessages.mediaManageMediaCaption());
206
- result.setTitle(stringMessages.mediaManageMediaTooltip());
207
- // onClick
208
- final MediaManagementControl multiSelectionControl = new MediaManagementControl(mediaPlayerManager,
209
- result, stringMessages);
210
- result.addClickHandler(new ClickHandler() {
211
-
212
- @Override
213
- public void onClick(ClickEvent event) {
214
- multiSelectionControl.show();
215
- }
216
- });
217
-
218
- // hide button initially as we defer showing the button to the asynchroneous
219
- // task that gets launched by the media service to get video tracks
220
- result.setVisible(false);
221
- return result;
222
- }
223
-
224
- private void initializeComponents() {
225
- for (final Component<?> component : components) {
226
- splitLayoutPanel.insert(component.getEntryWidget(), component, Direction.SOUTH, 200);
227
- }
228
- }
229
-
230
- public <SettingsType extends AbstractSettings> void showSettingsDialog(Component<SettingsType> component) {
231
- if (component.hasSettings()) {
232
- new SettingsDialog<SettingsType>(component, stringMessages).show();
233
- }
234
- }
235
-
236
- /**
237
- * Called whenever the layout of {@link TouchSplitLayoutPanel} or other components change. Controls the visibility
238
- * based on the {@link Component}s visibility. Each {@link Component} is in charge to not display any data or update
239
- * itself when it is not visible.
240
- */
241
- public void forceLayout() {
242
- if (!leftComponent.isVisible() && rightComponent.isVisible()) {
243
- // the leaderboard is not visible, but the map is
244
- if (isWidgetInSplitPanel(leftScrollPanel)) {
245
- if (leftScrollPanel.getOffsetWidth() > 0) {
246
- savedSplitPosition = leftScrollPanel.getOffsetWidth();
247
- }
248
- splitLayoutPanel.setWidgetVisibility(leftScrollPanel, leftComponent, /* hidden */true,
249
- savedSplitPosition);
250
- }
251
- } else if (leftComponent.isVisible() && rightComponent.isVisible()) {
252
- // the leaderboard and the map are visible
253
- splitLayoutPanel.setWidgetVisibility(leftScrollPanel, leftComponent, /* hidden */false, savedSplitPosition);
254
- } else if (!leftComponent.isVisible() && !rightComponent.isVisible()) {
255
- }
256
-
257
- for (Component<?> component : components) {
258
- final boolean isComponentVisible = component.isVisible();
259
- splitLayoutPanel.setWidgetVisibility(component.getEntryWidget(), component, !isComponentVisible,
260
- DEFAULT_SOUTH_SPLIT_PANEL_HEIGHT);
261
- }
262
- splitLayoutPanel.forceLayout();
263
- }
264
-
265
- private boolean isWidgetInSplitPanel(Widget widget) {
266
- int widgetIndex = splitLayoutPanel.getWidgetIndex(widget);
267
- return widgetIndex >= 0;
268
- }
269
-
270
- public Panel getViewerWidget() {
271
- return mainPanel;
272
- }
273
-
274
- public Component<?> getRootComponent() {
275
- return null;
276
- }
277
-
278
- public String getViewerName() {
279
- return "";
280
- }
281
-
282
- public void setLeftComponentWidth(int width) {
283
- // TODO: The information provided by width is wrong
284
- // need to find a way to get the correct information
285
- if (!layoutForLeftComponentForcedOnce) {
286
- savedSplitPosition = MIN_LEADERBOARD_WIDTH;
287
- forceLayout();
288
- }
289
- layoutForLeftComponentForcedOnce = true;
290
- }
291
-
292
- @Override
293
- public void onUserStatusChange(UserDTO user) {
294
- Button toggleButton = splitLayoutPanel.getAssociatedSplitter(markPassingsPanel).getToggleButton();
295
- if (user != null
296
- && (user.hasRole(DefaultRoles.ADMIN.getRolename()) || user.hasRole(Roles.eventmanager.getRolename()))) {
297
- toggleButton.setVisible(true);
298
- forceLayout();
299
- } else {
300
- markPassingsPanel.setVisible(false);
301
- toggleButton.setVisible(false);
302
- forceLayout();
303
- }
304
- }
305
-}
1
+package com.sap.sailing.gwt.ui.raceboard;
2
+
3
+import java.util.ArrayList;
4
+import java.util.List;
5
+
6
+import com.google.gwt.dom.client.Style;
7
+import com.google.gwt.dom.client.Style.Unit;
8
+import com.google.gwt.event.dom.client.ClickEvent;
9
+import com.google.gwt.event.dom.client.ClickHandler;
10
+import com.google.gwt.user.client.Window;
11
+import com.google.gwt.user.client.ui.AbsolutePanel;
12
+import com.google.gwt.user.client.ui.Button;
13
+import com.google.gwt.user.client.ui.DockLayoutPanel.Direction;
14
+import com.google.gwt.user.client.ui.LayoutPanel;
15
+import com.google.gwt.user.client.ui.Panel;
16
+import com.google.gwt.user.client.ui.RequiresResize;
17
+import com.google.gwt.user.client.ui.ScrollPanel;
18
+import com.google.gwt.user.client.ui.Widget;
19
+import com.google.gwt.user.client.ui.WidgetCollection;
20
+import com.sap.sailing.domain.common.security.Roles;
21
+import com.sap.sailing.gwt.ui.client.StringMessages;
22
+import com.sap.sailing.gwt.ui.client.media.MediaManagementControl;
23
+import com.sap.sailing.gwt.ui.client.media.MediaPlayerManager;
24
+import com.sap.sailing.gwt.ui.client.media.MediaPlayerManager.PlayerChangeListener;
25
+import com.sap.sailing.gwt.ui.client.media.MediaPlayerManagerComponent;
26
+import com.sap.sailing.gwt.ui.client.media.MediaSingleSelectionControl;
27
+import com.sap.sailing.gwt.ui.client.shared.charts.EditMarkPassingsPanel;
28
+import com.sap.sailing.gwt.ui.leaderboard.LeaderboardPanel;
29
+import com.sap.sailing.gwt.ui.raceboard.TouchSplitLayoutPanel.Splitter;
30
+import com.sap.sse.common.Util.Pair;
31
+import com.sap.sse.common.settings.AbstractSettings;
32
+import com.sap.sse.gwt.client.shared.components.Component;
33
+import com.sap.sse.gwt.client.shared.components.ComponentViewer;
34
+import com.sap.sse.gwt.client.shared.components.SettingsDialog;
35
+import com.sap.sse.security.shared.DefaultRoles;
36
+import com.sap.sse.security.ui.client.UserService;
37
+import com.sap.sse.security.ui.client.UserStatusEventHandler;
38
+import com.sap.sse.security.ui.shared.UserDTO;
39
+
40
+/**
41
+ * Component Viewer that uses a {@link TouchSplitLayoutPanel} to display its components.
42
+ *
43
+ * TODO: Refactor to make sure it is really only performing operations that are related to view. Currently it is digging
44
+ * too deep into components and setting titles or even creating video buttons.
45
+ */
46
+public class SideBySideComponentViewer implements ComponentViewer, UserStatusEventHandler {
47
+
48
+ private static final int DEFAULT_SOUTH_SPLIT_PANEL_HEIGHT = 200;
49
+ private final int MIN_LEADERBOARD_WIDTH = Math.min(432, Window.getClientWidth() - 40); // fallback value "432" works well for 505 and ESS
50
+
51
+ /**
52
+ * Absolute Panel that informs its children about a resize
53
+ */
54
+ class ResizableAbsolutePanel extends AbsolutePanel implements RequiresResize {
55
+ public void onResize() {
56
+ WidgetCollection children = getChildren();
57
+ for (Widget widget : children) {
58
+ if (widget instanceof RequiresResize) {
59
+ ((RequiresResize) widget).onResize();
60
+ }
61
+ }
62
+ }
63
+ }
64
+
65
+ private final LeaderboardPanel leftComponent;
66
+ private final Component<?> rightComponent;
67
+ private final List<Component<?>> components;
68
+ private final ScrollPanel leftScrollPanel;
69
+ private final StringMessages stringMessages;
70
+ private final Button mediaSelectionButton;
71
+ private final Button mediaManagementButton;
72
+ private final EditMarkPassingsPanel markPassingsPanel;
73
+
74
+ private LayoutPanel mainPanel;
75
+
76
+ private TouchSplitLayoutPanel splitLayoutPanel;
77
+ private int savedSplitPosition = -1;
78
+ private boolean layoutForLeftComponentForcedOnce = false;
79
+
80
+ public SideBySideComponentViewer(final LeaderboardPanel leftComponentP, final Component<?> rightComponentP,
81
+ final MediaPlayerManagerComponent mediaPlayerManagerComponent, List<Component<?>> components,
82
+ final StringMessages stringMessages, UserService userService, EditMarkPassingsPanel markPassingsPanel) {
83
+ this.stringMessages = stringMessages;
84
+ this.leftComponent = leftComponentP;
85
+ this.rightComponent = rightComponentP;
86
+ this.components = components;
87
+ this.mediaSelectionButton = createMediaSelectionButton(mediaPlayerManagerComponent);
88
+ this.mediaManagementButton = createMediaManagementButton(mediaPlayerManagerComponent);
89
+ this.markPassingsPanel = markPassingsPanel;
90
+ userService.addUserStatusEventHandler(this);
91
+ mediaPlayerManagerComponent.setPlayerChangeListener(new PlayerChangeListener() {
92
+ public void notifyStateChange() {
93
+ String caption;
94
+ String tooltip;
95
+ switch (mediaPlayerManagerComponent.getAssignedMediaTracks().size()) {
96
+ case 0:
97
+ caption = stringMessages.mediaNoVideosCaption();
98
+ tooltip = caption;
99
+ mediaSelectionButton.setVisible(false);
100
+ break;
101
+ case 1:
102
+ mediaSelectionButton.setVisible(true);
103
+ if(mediaPlayerManagerComponent.isPlaying()){
104
+ caption = stringMessages.mediaHideVideoCaption();
105
+ tooltip = stringMessages.mediaHideVideoTooltip();
106
+ }
107
+ else{
108
+ caption = stringMessages.mediaShowVideoCaption();
109
+ tooltip = stringMessages.mediaShowVideoTooltip(mediaPlayerManagerComponent.getAssignedMediaTracks().iterator().next().title);
110
+ }
111
+ break;
112
+ default:
113
+ mediaSelectionButton.setVisible(true);
114
+ caption = stringMessages.mediaSelectVideoCaption(mediaPlayerManagerComponent.getAssignedMediaTracks().size());
115
+ tooltip = stringMessages.mediaSelectVideoTooltip();
116
+ break;
117
+ }
118
+ if (mediaPlayerManagerComponent.isPlaying()) {
119
+ mediaSelectionButton.addStyleDependentName("mediaplaying");
120
+ } else {
121
+ mediaSelectionButton.removeStyleDependentName("mediaplaying");
122
+ }
123
+ mediaSelectionButton.setText(caption);
124
+ mediaSelectionButton.setTitle(tooltip);
125
+ mediaManagementButton.setVisible(mediaPlayerManagerComponent.allowsEditing());
126
+ }
127
+ });
128
+ this.leftScrollPanel = new ScrollPanel();
129
+ this.leftScrollPanel.add(leftComponentP.getEntryWidget());
130
+ this.leftScrollPanel.setTitle(leftComponentP.getEntryWidget().getTitle());
131
+ this.mainPanel = new LayoutPanel() {
132
+ @Override
133
+ public void onResize() {
134
+ int leftWidth = leftScrollPanel.getOffsetWidth();
135
+ // The left scroll panel is potentially resized to ensure it is not too wide when the screen gets narrower,
136
+ // e.g. when resizing browser window or changing mobile device orientation. An offset of 40px is used, so
137
+ // the panels size slider and its toggle button is always accessable if it is open.
138
+ savedSplitPosition = Math.min(leftWidth > 0 ? leftWidth : savedSplitPosition, Window.getClientWidth() - 40);
139
+ splitLayoutPanel.setWidgetSize(leftScrollPanel, savedSplitPosition);
140
+ super.onResize();
141
+ }
142
+ };
143
+ this.mainPanel.setSize("100%", "100%");
144
+ this.mainPanel.getElement().getStyle().setMarginTop(-12, Unit.PX);
145
+ this.mainPanel.setStyleName("SideBySideComponentViewer-MainPanel");
146
+ this.splitLayoutPanel = new TouchSplitLayoutPanel(/* horizontal splitter width */3, /* vertical splitter height */ 25);
147
+ this.mainPanel.add(splitLayoutPanel);
148
+
149
+ // initialize components - they need to be added before other widgets to get the right width
150
+ initializeComponents();
151
+
152
+ // initialize the leaderboard component
153
+ savedSplitPosition = MIN_LEADERBOARD_WIDTH;
154
+ splitLayoutPanel.insert(leftScrollPanel, leftComponent, Direction.WEST, savedSplitPosition);
155
+
156
+ // create a panel that will contain the horizontal toggle buttons
157
+ ResizableAbsolutePanel panelForMapAndHorizontalToggleButtons = new ResizableAbsolutePanel();
158
+ panelForMapAndHorizontalToggleButtons.add(rightComponent.getEntryWidget());
159
+ splitLayoutPanel.insert(panelForMapAndHorizontalToggleButtons, rightComponent, Direction.CENTER, 0);
160
+
161
+ // add additional toggle buttons panel that currently only contains the video button
162
+ List<Pair<Button, String>> additionalVerticalButtons = new ArrayList<Pair<Button, String>>();
163
+ additionalVerticalButtons.add(new Pair<Button, String>(mediaSelectionButton,
164
+ mediaPlayerManagerComponent.getDependentCssClassName()));
165
+ if (/* TODO check for correct role; was: user != null */ true) {
166
+ additionalVerticalButtons.add(new Pair<Button, String>(mediaManagementButton,
167
+ "managemedia"));
168
+ }
169
+ onUserStatusChange(userService.getCurrentUser());
170
+ // ensure that toggle buttons are positioned right
171
+ splitLayoutPanel.lastComponentHasBeenAdded(this, panelForMapAndHorizontalToggleButtons,
172
+ additionalVerticalButtons);
173
+ }
174
+
175
+ /**
176
+ * Create the video control button that shows or hides the video popup
177
+ *
178
+ * @param userAgent
179
+ */
180
+ private Button createMediaSelectionButton(final MediaPlayerManager mediaPlayerManager) {
181
+ final Button result = new Button();
182
+ final MediaSingleSelectionControl multiSelectionControl = new MediaSingleSelectionControl(mediaPlayerManager,
183
+ result, stringMessages);
184
+ result.addClickHandler(new ClickHandler() {
185
+
186
+ @Override
187
+ public void onClick(ClickEvent event) {
188
+ if (mediaPlayerManager.getAssignedMediaTracks().size() == 1) {
189
+ if (mediaPlayerManager.isPlaying()) {
190
+ mediaPlayerManager.stopAll();
191
+ } else {
192
+ mediaPlayerManager.playDefault();
193
+
194
+ }
195
+ } else {
196
+ multiSelectionControl.show();
197
+ }
198
+ }
199
+ });
200
+
201
+ // hide button initially as we defer showing the button to the asynchroneous
202
+ // task that gets launched by the media service to get video tracks
203
+ result.setVisible(false);
204
+ return result;
205
+ }
206
+
207
+ /**
208
+ * Create the video control button that shows or hides the video popup
209
+ *
210
+ * @param userAgent
211
+ */
212
+ private Button createMediaManagementButton(final MediaPlayerManager mediaPlayerManager) {
213
+ final Button result = new Button(stringMessages.mediaManageMediaCaption());
214
+ result.setTitle(stringMessages.mediaManageMediaTooltip());
215
+ // onClick
216
+ final MediaManagementControl multiSelectionControl = new MediaManagementControl(mediaPlayerManager,
217
+ result, stringMessages);
218
+ result.addClickHandler(new ClickHandler() {
219
+ @Override
220
+ public void onClick(ClickEvent event) {
221
+ multiSelectionControl.show();
222
+ }
223
+ });
224
+ // hide button initially as we defer showing the button to the asynchronous
225
+ // task that gets launched by the media service to get video tracks
226
+ result.setVisible(false);
227
+ return result;
228
+ }
229
+
230
+ private void initializeComponents() {
231
+ for (final Component<?> component : components) {
232
+ splitLayoutPanel.insert(component.getEntryWidget(), component, Direction.SOUTH, 200);
233
+ }
234
+ }
235
+
236
+ public <SettingsType extends AbstractSettings> void showSettingsDialog(Component<SettingsType> component) {
237
+ if (component.hasSettings()) {
238
+ new SettingsDialog<SettingsType>(component, stringMessages).show();
239
+ }
240
+ }
241
+
242
+ /**
243
+ * Called whenever the layout of {@link TouchSplitLayoutPanel} or other components change. Controls the visibility
244
+ * based on the {@link Component}s visibility. Each {@link Component} is in charge to not display any data or update
245
+ * itself when it is not visible.
246
+ */
247
+ public void forceLayout() {
248
+ if (!leftComponent.isVisible() && rightComponent.isVisible()) {
249
+ // the leaderboard is not visible, but the map is
250
+ if (isWidgetInSplitPanel(leftScrollPanel)) {
251
+ if (leftScrollPanel.getOffsetWidth() > 0) {
252
+ savedSplitPosition = Math.min(savedSplitPosition, leftScrollPanel.getOffsetWidth());
253
+ }
254
+ splitLayoutPanel.setWidgetVisibility(leftScrollPanel, leftComponent, /* hidden */true,
255
+ savedSplitPosition);
256
+ }
257
+ } else if (leftComponent.isVisible() && rightComponent.isVisible()) {
258
+ // the leaderboard and the map are visible
259
+ splitLayoutPanel.setWidgetVisibility(leftScrollPanel, leftComponent, /* hidden */false, savedSplitPosition);
260
+ } else if (!leftComponent.isVisible() && !rightComponent.isVisible()) {
261
+ }
262
+
263
+ for (Component<?> component : components) {
264
+ final boolean isComponentVisible = component.isVisible();
265
+ splitLayoutPanel.setWidgetVisibility(component.getEntryWidget(), component, !isComponentVisible,
266
+ DEFAULT_SOUTH_SPLIT_PANEL_HEIGHT);
267
+ }
268
+ splitLayoutPanel.forceLayout();
269
+ }
270
+
271
+ private boolean isWidgetInSplitPanel(Widget widget) {
272
+ int widgetIndex = splitLayoutPanel.getWidgetIndex(widget);
273
+ return widgetIndex >= 0;
274
+ }
275
+
276
+ public Panel getViewerWidget() {
277
+ return mainPanel;
278
+ }
279
+
280
+ public Component<?> getRootComponent() {
281
+ return null;
282
+ }
283
+
284
+ public String getViewerName() {
285
+ return "";
286
+ }
287
+
288
+ public void setLeftComponentWidth(int width) {
289
+ // TODO: The information provided by width is wrong
290
+ // need to find a way to get the correct information
291
+ if (!layoutForLeftComponentForcedOnce) {
292
+ savedSplitPosition = MIN_LEADERBOARD_WIDTH;
293
+ forceLayout();
294
+ }
295
+ layoutForLeftComponentForcedOnce = true;
296
+ }
297
+
298
+ @Override
299
+ public void onUserStatusChange(UserDTO user) {
300
+ final Splitter associatedSplitter = splitLayoutPanel.getAssociatedSplitter(markPassingsPanel);
301
+ if (associatedSplitter != null) { // if the panel is not present, the splitter may not be found
302
+ final Button toggleButton = associatedSplitter.getToggleButton();
303
+ if (user != null
304
+ && (user.hasRole(DefaultRoles.ADMIN.getRolename()) ||
305
+ user.hasRole(Roles.eventmanager.getRolename()))) {
306
+ toggleButton.setVisible(true);
307
+ forceLayout();
308
+ } else {
309
+ markPassingsPanel.setVisible(false);
310
+ toggleButton.setVisible(false);
311
+ forceLayout();
312
+ }
313
+ }
314
+ }
315
+
316
+ /**
317
+ * Shows/hides the text on left components toggle button by modifying CSS <code>font-size</code> property and adjust
318
+ * the dragger position by modifying CSS <code>margin-top</code> property.
319
+ *
320
+ * @param visible
321
+ * <code>true</code> to show the button text, <code>false</code> to hide it
322
+ */
323
+ void setLeftComponentToggleButtonTextVisibilityAndDraggerPosition(final boolean visible) {
324
+ Splitter leftScrollPanelSplitter = splitLayoutPanel.getAssociatedSplitter(leftScrollPanel);
325
+ if (leftScrollPanelSplitter != null) {
326
+ Style toggleButtonStyle = leftScrollPanelSplitter.getToggleButton().getElement().getStyle();
327
+ if (visible) toggleButtonStyle.clearFontSize();
328
+ else toggleButtonStyle.setFontSize(0, Unit.PX);
329
+ Style drapperStyle = leftScrollPanelSplitter.getDragger().getElement().getStyle();
330
+ if (visible) drapperStyle.clearMarginTop();
331
+ else drapperStyle.setMarginTop(-25, Unit.PX);
332
+ }
333
+ }
334
+}
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/TouchSplitLayoutPanel.java
... ...
@@ -613,7 +613,7 @@ public class TouchSplitLayoutPanel extends DockLayoutPanel {
613 613
if (lastVerticalSplitter != null) {
614 614
Panel panel = createToggleButtonPanel(allVerticalSplitters,
615 615
"gwt-SplitLayoutPanel-NorthSouthToggleButton-Panel", "gwt-SplitLayoutPanel-NorthSouthToggleButton",
616
- cm, additionalVerticalButtonsAndStyles);
616
+ cm, additionalVerticalButtonsAndStyles); // TODO additional buttons will only be displayed if at least one vertical splitter is... on purpose?
617 617
lastVerticalSplitter.addToogleButtons(panel);
618 618
lastVerticalSplitter.setVisible(true);
619 619
lastVerticalSplitter.setDraggerVisible(false);
... ...
@@ -621,7 +621,7 @@ public class TouchSplitLayoutPanel extends DockLayoutPanel {
621 621
ensureVerticalToggleButtonPosition();
622 622
if (lastHorizontalSplitter != null) {
623 623
Panel horizontalButtonsPanel = createToggleButtonPanel(allHorizontalSplitters,
624
- "gwt-SplitLayoutPanel-EastToggleButton-Panel", "gwt-SplitLayoutPanel-EastToggleButton", cm, null);
624
+ "gwt-SplitLayoutPanel-EastToggleButton-Panel", "gwt-SplitLayoutPanel-EastToggleButton", cm, /* additional buttons and styles */ null);
625 625
panelForHorizontalButtons.add(horizontalButtonsPanel);
626 626
lastHorizontalSplitter.setVisible(false);
627 627
lastHorizontalSplitter.setDraggerVisible(false);
... ...
@@ -842,6 +842,24 @@ public class TouchSplitLayoutPanel extends DockLayoutPanel {
842 842
}
843 843
}
844 844
}
845
+
846
+ /**
847
+ * Sets the size of the given {@link Widget} if it is {@link Widget#isVisible() visible}. Otherwise, the given size
848
+ * will be persisted as the widgets {@link LayoutData#oldSize old size}.
849
+ *
850
+ * @param widget the {@link Widget} to resize
851
+ * @param size the new size
852
+ */
853
+ public void setWidgetSize(Widget widget, int size) {
854
+ final Splitter splitter = getAssociatedSplitter(widget);
855
+ if (splitter != null) {
856
+ if (widget.isVisible()) {
857
+ splitter.setAssociatedWidgetSize(size, /* defer */false);
858
+ } else {
859
+ ((LayoutData) widget.getLayoutData()).oldSize = size;
860
+ }
861
+ }
862
+ }
845 863
846 864
private void assertIsChild2(Widget widget) {
847 865
assert (widget == null) || (widget.getParent() == this) : "The specified widget is not a child of this panel";
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java
... ...
@@ -233,6 +233,7 @@ import com.sap.sailing.domain.leaderboard.RegattaLeaderboard;
233 233
import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule;
234 234
import com.sap.sailing.domain.leaderboard.caching.LeaderboardDTOCalculationReuseCache;
235 235
import com.sap.sailing.domain.leaderboard.caching.LiveLeaderboardUpdater;
236
+import com.sap.sailing.domain.leaderboard.meta.MetaLeaderboardColumn;
236 237
import com.sap.sailing.domain.persistence.DomainObjectFactory;
237 238
import com.sap.sailing.domain.persistence.MongoObjectFactory;
238 239
import com.sap.sailing.domain.persistence.MongoRaceLogStoreFactory;
... ...
@@ -444,12 +445,7 @@ import com.sap.sse.common.Util.Triple;
444 445
import com.sap.sse.common.impl.MillisecondsTimePoint;
445 446
import com.sap.sse.common.impl.TimeRangeImpl;
446 447
import com.sap.sse.common.mail.MailException;
447
-import com.sap.sse.common.media.ImageDescriptor;
448
-import com.sap.sse.common.media.ImageDescriptorImpl;
449
-import com.sap.sse.common.media.MediaUtils;
450 448
import com.sap.sse.common.media.MimeType;
451
-import com.sap.sse.common.media.VideoDescriptor;
452
-import com.sap.sse.common.media.VideoDescriptorImpl;
453 449
import com.sap.sse.filestorage.FileStorageService;
454 450
import com.sap.sse.filestorage.InvalidPropertiesException;
455 451
import com.sap.sse.gwt.client.ServerInfoDTO;
... ...
@@ -465,6 +461,11 @@ import com.sap.sse.replication.ReplicationMasterDescriptor;
465 461
import com.sap.sse.replication.ReplicationService;
466 462
import com.sap.sse.replication.impl.ReplicaDescriptor;
467 463
import com.sap.sse.security.SessionUtils;
464
+import com.sap.sse.shared.media.ImageDescriptor;
465
+import com.sap.sse.shared.media.MediaUtils;
466
+import com.sap.sse.shared.media.VideoDescriptor;
467
+import com.sap.sse.shared.media.impl.ImageDescriptorImpl;
468
+import com.sap.sse.shared.media.impl.VideoDescriptorImpl;
468 469
import com.sap.sse.util.ServiceTrackerFactory;
469 470
import com.sapsailing.xrr.structureimport.eventimport.RegattaJSON;
470 471
... ...
@@ -853,7 +854,8 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
853 854
final RaceColumnDTO raceColumnDTO = columnFactory.createRaceColumnDTO(raceColumn.getName(),
854 855
raceColumn.isMedalRace(), raceColumn.getExplicitFactor(),
855 856
raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getRegatta().getName() : null,
856
- raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getSeries().getName() : null);
857
+ raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getSeries().getName() : null,
858
+ raceColumn instanceof MetaLeaderboardColumn);
857 859
raceColumnDTOs.add(raceColumnDTO);
858 860
}
859 861
return raceColumnDTOs;
... ...
@@ -1023,7 +1025,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
1023 1025
@Override
1024 1026
public void trackWithTracTrac(RegattaIdentifier regattaToAddTo, Iterable<TracTracRaceRecordDTO> rrs, String liveURI, String storedURI,
1025 1027
String courseDesignUpdateURI, boolean trackWind, final boolean correctWindByDeclination,
1026
- final boolean simulateWithStartTimeNow, final boolean useInternalMarkPassingAlgorithm, String tracTracUsername, String tracTracPassword)
1028
+ final Duration offsetToStartTimeOfSimulatedRace, final boolean useInternalMarkPassingAlgorithm, String tracTracUsername, String tracTracPassword)
1027 1029
throws Exception {
1028 1030
logger.info("tracWithTracTrac for regatta " + regattaToAddTo + " for race records " + rrs + " with liveURI " + liveURI
1029 1031
+ " and storedURI " + storedURI);
... ...
@@ -1054,7 +1056,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
1054 1056
new URI(courseDesignUpdateURI), new MillisecondsTimePoint(record.getTrackingStartTime().asMillis()),
1055 1057
new MillisecondsTimePoint(record.getTrackingEndTime().asMillis()),
1056 1058
getRaceLogStore(), getRegattaLogStore(),
1057
- RaceTracker.TIMEOUT_FOR_RECEIVING_RACE_DEFINITION_IN_MILLISECONDS, simulateWithStartTimeNow,
1059
+ RaceTracker.TIMEOUT_FOR_RECEIVING_RACE_DEFINITION_IN_MILLISECONDS, offsetToStartTimeOfSimulatedRace,
1058 1060
useInternalMarkPassingAlgorithm, tracTracUsername, tracTracPassword, record.getRaceStatus(), record.getRaceVisibility());
1059 1061
if (trackWind) {
1060 1062
new Thread("Wind tracking starter for race " + record.getEventName() + "/" + record.getName()) {
... ...
@@ -1946,7 +1948,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
1946 1948
result.totalLegsCount = trackedRace.getRace().getCourse().getLegs().size();
1947 1949
result.currentLegNumber = trackedRace.getLastLegStarted(dateAsTimePoint);
1948 1950
result.marks = new HashSet<MarkDTO>();
1949
- result.course = convertToRaceCourseDTO(trackedRace.getRace().getCourse(), trackedRace, dateAsTimePoint);
1951
+ result.course = convertToRaceCourseDTO(trackedRace.getRace().getCourse(), new TrackedRaceMarkPositionFinder(trackedRace), dateAsTimePoint);
1950 1952
// now make sure we don't duplicate the MarkDTO objects but instead use the ones from the RaceCourseDTO
1951 1953
// object and amend them with the Position
1952 1954
result.waypointPositions = new ArrayList<>();
... ...
@@ -2020,7 +2022,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
2020 2022
for (Waypoint waypoint : course.getWaypoints()) {
2021 2023
ControlPointDTO controlPointDTO = controlPointCache.get(waypoint.getControlPoint().getId());
2022 2024
if (controlPointDTO == null) {
2023
- controlPointDTO = convertToControlPointDTO(waypoint.getControlPoint(), trackedRace, dateAsTimePoint);
2025
+ controlPointDTO = convertToControlPointDTO(waypoint.getControlPoint(), new TrackedRaceMarkPositionFinder(trackedRace), dateAsTimePoint);
2024 2026
controlPointCache.put(waypoint.getControlPoint().getId(), controlPointDTO);
2025 2027
}
2026 2028
WaypointDTO waypointDTO = new WaypointDTO(waypoint.getName(), controlPointDTO,
... ...
@@ -2031,20 +2033,44 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
2031 2033
return new RaceCourseDTO(waypointDTOs, allMarks);
2032 2034
}
2033 2035
2034
- private ControlPointDTO convertToControlPointDTO(ControlPoint controlPoint, TrackedRace trackedRace, TimePoint timePoint) {
2036
+ class TrackedRaceMarkPositionFinder implements MarkPositionFinder{
2037
+ private TrackedRace trackedRace;
2038
+
2039
+ public TrackedRaceMarkPositionFinder(TrackedRace trackedRace) {
2040
+ this.trackedRace = trackedRace;
2041
+ }
2042
+
2043
+ @Override
2044
+ public Position find(Mark mark, TimePoint at) {
2045
+ final TimePoint timePointToUse = trackedRace == null ? null :
2046
+ at == null ? MillisecondsTimePoint.now().minus(trackedRace.getDelayToLiveInMillis()) : at;
2047
+ final Position result;
2048
+ if (timePointToUse == null) {
2049
+ result = null;
2050
+ } else {
2051
+ result = trackedRace.getOrCreateTrack(mark).getEstimatedPosition(timePointToUse, /* extrapolate */ false);
2052
+ }
2053
+ return result;
2054
+ }
2055
+ }
2056
+
2057
+ private interface MarkPositionFinder {
2058
+ Position find(Mark mark, TimePoint at);
2059
+ }
2060
+
2061
+ private ControlPointDTO convertToControlPointDTO(ControlPoint controlPoint, MarkPositionFinder positionFinder, TimePoint timePoint) {
2035 2062
ControlPointDTO result;
2036
- final TimePoint timePointToUse = trackedRace == null ? null :
2037
- timePoint == null ? MillisecondsTimePoint.now().minus(trackedRace.getDelayToLiveInMillis()) : timePoint;
2063
+
2038 2064
if (controlPoint instanceof ControlPointWithTwoMarks) {
2039 2065
final Mark left = ((ControlPointWithTwoMarks) controlPoint).getLeft();
2040
- final Position leftPos = timePointToUse == null ? null : trackedRace.getOrCreateTrack(left).getEstimatedPosition(timePointToUse, /* extrapolate */ false);
2066
+ final Position leftPos = positionFinder.find(left, timePoint);
2041 2067
final Mark right = ((ControlPointWithTwoMarks) controlPoint).getRight();
2042
- final Position rightPos = timePointToUse == null ? null : trackedRace.getOrCreateTrack(right).getEstimatedPosition(timePointToUse, /* extrapolate */ false);
2068
+ final Position rightPos = positionFinder.find(right, timePoint);
2043 2069
result = new GateDTO(controlPoint.getId().toString(), controlPoint.getName(), convertToMarkDTO(left, leftPos), convertToMarkDTO(right, rightPos));
2044 2070
} else {
2045
- final Position posOfFirst = timePointToUse == null ? null : trackedRace.getOrCreateTrack(controlPoint.getMarks().iterator().next()).
2046
- getEstimatedPosition(timePointToUse, /* extrapolate */ false);
2047
- result = new MarkDTO(controlPoint.getId().toString(), controlPoint.getName(), posOfFirst == null ? -1 : posOfFirst.getLatDeg(), posOfFirst == null ? -1 : posOfFirst.getLngDeg());
2071
+ Mark mark = controlPoint.getMarks().iterator().next();
2072
+ final Position position = positionFinder.find(mark, timePoint);
2073
+ result = convertToMarkDTO(mark, position);
2048 2074
}
2049 2075
return result;
2050 2076
}
... ...
@@ -2384,7 +2410,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
2384 2410
RaceColumnDTO raceColumnDTO = leaderboardDTO.addRace(raceColumn.getName(), raceColumn.getExplicitFactor(), raceColumn.getFactor(),
2385 2411
raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getRegatta().getName() : null,
2386 2412
raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getSeries().getName() : null,
2387
- fleetDTO, raceColumn.isMedalRace(), raceIdentifier, raceDTO);
2413
+ fleetDTO, raceColumn.isMedalRace(), raceIdentifier, raceDTO, raceColumn instanceof MetaLeaderboardColumn);
2388 2414
RaceLog raceLog = raceColumn.getRaceLog(fleet);
2389 2415
RaceLogTrackingState raceLogTrackingState = raceLog == null ? RaceLogTrackingState.NOT_A_RACELOG_TRACKED_RACE :
2390 2416
new RaceLogTrackingStateAnalyzer(raceLog).analyze();
... ...
@@ -5156,10 +5182,10 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
5156 5182
* the mark positions and attach to the {@link MarkDTO}s. If <code>null<c/code>, the current time point
5157 5183
* will be used as default.
5158 5184
*/
5159
- private WaypointDTO convertToWaypointDTO(Waypoint waypoint, Map<Serializable, ControlPointDTO> controlPointCache, TrackedRace trackedRace, TimePoint timePoint) {
5185
+ private WaypointDTO convertToWaypointDTO(Waypoint waypoint, Map<Serializable, ControlPointDTO> controlPointCache, MarkPositionFinder positionFinder, TimePoint timePoint) {
5160 5186
ControlPointDTO cp = controlPointCache.get(waypoint.getControlPoint().getId());
5161 5187
if (cp == null) {
5162
- cp = convertToControlPointDTO(waypoint.getControlPoint(), trackedRace, timePoint);
5188
+ cp = convertToControlPointDTO(waypoint.getControlPoint(), positionFinder, timePoint);
5163 5189
controlPointCache.put(waypoint.getControlPoint().getId(), cp);
5164 5190
}
5165 5191
return new WaypointDTO(waypoint.getName(), cp, waypoint.getPassingInstructions());
... ...
@@ -5175,13 +5201,13 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
5175 5201
* the mark positions and attach to the {@link MarkDTO}s. If <code>null<c/code>, the current time point
5176 5202
* will be used as default.
5177 5203
*/
5178
- private RaceCourseDTO convertToRaceCourseDTO(CourseBase course, TrackedRace trackedRace, TimePoint timePoint) {
5204
+ private RaceCourseDTO convertToRaceCourseDTO(CourseBase course, MarkPositionFinder positionFinder, TimePoint timePoint) {
5179 5205
final RaceCourseDTO result;
5180 5206
if (course != null) {
5181 5207
List<WaypointDTO> waypointDTOs = new ArrayList<WaypointDTO>();
5182 5208
Map<Serializable, ControlPointDTO> controlPointCache = new HashMap<>();
5183 5209
for (Waypoint waypoint : course.getWaypoints()) {
5184
- waypointDTOs.add(convertToWaypointDTO(waypoint, controlPointCache, trackedRace, timePoint));
5210
+ waypointDTOs.add(convertToWaypointDTO(waypoint, controlPointCache, positionFinder, timePoint));
5185 5211
}
5186 5212
result = new RaceCourseDTO(waypointDTOs);
5187 5213
} else {
... ...
@@ -5191,13 +5217,18 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
5191 5217
}
5192 5218
5193 5219
@Override
5194
- public RaceCourseDTO getLastCourseDefinitionInRaceLog(String leaderboardName, String raceColumnName, String fleetName) {
5195
- RaceLog raceLog = getRaceLog(leaderboardName, raceColumnName, fleetName);
5220
+ public RaceCourseDTO getLastCourseDefinitionInRaceLog(final String leaderboardName, String raceColumnName, String fleetName) {
5221
+ final RaceLog raceLog = getRaceLog(leaderboardName, raceColumnName, fleetName);
5196 5222
CourseBase lastPublishedCourse = new LastPublishedCourseDesignFinder(raceLog).analyze();
5197 5223
if (lastPublishedCourse == null) {
5198 5224
lastPublishedCourse = new CourseDataImpl("");
5199 5225
}
5200
- return convertToRaceCourseDTO(lastPublishedCourse, /* trackedRace */ null, /* timePoint */ null);
5226
+ return convertToRaceCourseDTO(lastPublishedCourse, new MarkPositionFinder() {
5227
+ @Override
5228
+ public Position find(Mark mark, TimePoint at) {
5229
+ return getService().getMarkPosition(mark, (LeaderboardThatHasRegattaLike) getService().getLeaderboardByName(leaderboardName), at, raceLog);
5230
+ }
5231
+ }, /* timePoint */ MillisecondsTimePoint.now());
5201 5232
}
5202 5233
5203 5234
@Override
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/SimulatorMainPanel.java
... ...
@@ -309,7 +309,9 @@ public class SimulatorMainPanel extends SimplePanel {
309 309
timer.setTime(windParams.getStartTime().getTime());
310 310
int secondsTimeStep = (int) windParams.getTimeStep().asSeconds();
311 311
timer.setPlaySpeedFactor(secondsTimeStep);
312
- timePanel = new TimePanel<TimePanelSettings>(timer, timeRangeProvider, stringMessages, false);
312
+ timePanel = new TimePanel<TimePanelSettings>(timer, timeRangeProvider, stringMessages, false,
313
+ /* isScreenLargeEnoughToOfferChartSupport: no wind or competitor chart is shown; use full horizontal
314
+ * extension of time panel */ false);
313 315
busyIndicator = new SimpleBusyIndicator(false, 0.8f);
314 316
simulatorMap = new SimulatorMap(simulatorSvc, stringMessages, errorReporter, xRes, yRes, border, streamletPars,
315 317
timer, timePanel, windParams, busyIndicator, mode, this, showMapControls,
java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/solutions/SailingAnalyticsNotes.html
... ...
@@ -4,6 +4,26 @@
4 4
<div id="mainContent">
5 5
<h4 class="articleHeadline">What's New - SAP Sailing Analytics</h4>
6 6
<div class="innerContent">
7
+ <h5 class="articleSubheadline">December 2015</h5>
8
+
9
+ <ul class="bulletList">
10
+ <li>New line hover behavior in race map. When the mouse is moved onto a line, for example the start line, a transparent,
11
+ wider line is activated behind it that acts as a buffer for the thin line, where the text displayed by the hover is still shown.
12
+ This makes it easier to hover over the lines. These wider lines can be made semi transparent instead of transparent
13
+ by unticking the corresponding checkbox in the settings of the race map. Furthermore the stroke weight of the wider lines can be
14
+ configured in the same settings dialog.
15
+ </li>
16
+ <li>The race viewer (RaceBoard.html) now intelligently chooses a layout that suits the screen size.
17
+ When only little horizontal space is available, only the sail number and nationality but not the
18
+ competitor's long name is displayed by default. When vertical space is scarce, competitor and wind
19
+ charts are disabled and more horizontal space can be given to the time slider at the bottom. The
20
+ leaderboard section can then be scaled to smaller widths and will reduce the column widths better
21
+ than in the past.
22
+ </li>
23
+ <li>With the changes described above, the RaceBoard is now also accessible from the races page on mobile devices.
24
+ </li>
25
+ </ul>
26
+
7 27
<h5 class="articleSubheadline">November 2015</h5>
8 28
9 29
<ul class="bulletList">
java/com.sap.sailing.mongodb.test/META-INF/MANIFEST.MF
... ...
@@ -27,7 +27,8 @@ Require-Bundle: com.sap.sailing.domain,
27 27
com.sap.sse.operationaltransformation,
28 28
com.sap.sse.replication,
29 29
com.sap.sse,
30
- com.sap.sse.filestorage
30
+ com.sap.sse.filestorage,
31
+ com.sap.sse.shared.android
31 32
Import-Package: com.sap.sailing.domain.common,
32 33
com.sap.sailing.domain.common.impl,
33 34
com.sap.sailing.domain.common.racelog,
java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndLoadingEventsAndRegattas.java
... ...
@@ -1,836 +1,836 @@
1
-package com.sap.sailing.mongodb.test;
2
-
3
-import static org.junit.Assert.assertEquals;
4
-import static org.junit.Assert.assertFalse;
5
-import static org.junit.Assert.assertNotNull;
6
-import static org.junit.Assert.assertNotSame;
7
-import static org.junit.Assert.assertNull;
8
-import static org.junit.Assert.assertSame;
9
-import static org.junit.Assert.assertTrue;
10
-
11
-import java.io.Serializable;
12
-import java.net.MalformedURLException;
13
-import java.net.URL;
14
-import java.net.UnknownHostException;
15
-import java.util.ArrayList;
16
-import java.util.Arrays;
17
-import java.util.Calendar;
18
-import java.util.Collections;
19
-import java.util.Iterator;
20
-import java.util.List;
21
-import java.util.Locale;
22
-import java.util.UUID;
23
-import java.util.logging.Logger;
24
-
25
-import org.junit.Test;
26
-
27
-import com.mongodb.MongoException;
28
-import com.sap.sailing.domain.base.BoatClass;
29
-import com.sap.sailing.domain.base.Competitor;
30
-import com.sap.sailing.domain.base.Course;
31
-import com.sap.sailing.domain.base.CourseArea;
32
-import com.sap.sailing.domain.base.DomainFactory;
33
-import com.sap.sailing.domain.base.Event;
34
-import com.sap.sailing.domain.base.Fleet;
35
-import com.sap.sailing.domain.base.RaceColumn;
36
-import com.sap.sailing.domain.base.RaceColumnInSeries;
37
-import com.sap.sailing.domain.base.RaceDefinition;
38
-import com.sap.sailing.domain.base.Regatta;
39
-import com.sap.sailing.domain.base.Series;
40
-import com.sap.sailing.domain.base.Venue;
41
-import com.sap.sailing.domain.base.Waypoint;
42
-import com.sap.sailing.domain.base.configuration.impl.RegattaConfigurationImpl;
43
-import com.sap.sailing.domain.base.impl.CompetitorImpl;
44
-import com.sap.sailing.domain.base.impl.CourseImpl;
45
-import com.sap.sailing.domain.base.impl.EventImpl;
46
-import com.sap.sailing.domain.base.impl.FleetImpl;
47
-import com.sap.sailing.domain.base.impl.RaceColumnInSeriesImpl;
48
-import com.sap.sailing.domain.base.impl.RaceDefinitionImpl;
49
-import com.sap.sailing.domain.base.impl.RegattaImpl;
50
-import com.sap.sailing.domain.base.impl.SeriesImpl;
51
-import com.sap.sailing.domain.base.impl.VenueImpl;
52
-import com.sap.sailing.domain.common.MaxPointsReason;
53
-import com.sap.sailing.domain.common.RaceIdentifier;
54
-import com.sap.sailing.domain.common.RankingMetrics;
55
-import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
56
-import com.sap.sailing.domain.common.RegattaNameAndRaceName;
57
-import com.sap.sailing.domain.common.ScoringSchemeType;
58
-import com.sap.sailing.domain.common.racelog.RacingProcedureType;
59
-import com.sap.sailing.domain.leaderboard.EventResolver;
60
-import com.sap.sailing.domain.leaderboard.Leaderboard;
61
-import com.sap.sailing.domain.leaderboard.LeaderboardGroup;
62
-import com.sap.sailing.domain.leaderboard.LeaderboardGroupResolver;
63
-import com.sap.sailing.domain.leaderboard.RegattaLeaderboard;
64
-import com.sap.sailing.domain.leaderboard.ScoringScheme;
65
-import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule;
66
-import com.sap.sailing.domain.leaderboard.impl.HighPoint;
67
-import com.sap.sailing.domain.leaderboard.impl.LeaderboardGroupImpl;
68
-import com.sap.sailing.domain.leaderboard.impl.LowPoint;
69
-import com.sap.sailing.domain.leaderboard.impl.ThresholdBasedResultDiscardingRuleImpl;
70
-import com.sap.sailing.domain.persistence.DomainObjectFactory;
71
-import com.sap.sailing.domain.persistence.MongoObjectFactory;
72
-import com.sap.sailing.domain.persistence.PersistenceFactory;
73
-import com.sap.sailing.domain.persistence.impl.CollectionNames;
74
-import com.sap.sailing.domain.persistence.media.MediaDBFactory;
75
-import com.sap.sailing.domain.racelog.tracking.EmptyGPSFixStore;
76
-import com.sap.sailing.domain.ranking.OneDesignRankingMetric;
77
-import com.sap.sailing.domain.ranking.RankingMetricConstructor;
78
-import com.sap.sailing.domain.ranking.TimeOnTimeAndDistanceRankingMetric;
79
-import com.sap.sailing.domain.test.AbstractLeaderboardTest;
80
-import com.sap.sailing.domain.test.mock.MockedTrackedRaceWithFixedRank;
81
-import com.sap.sailing.domain.tracking.DynamicTrackedRace;
82
-import com.sap.sailing.domain.tracking.DynamicTrackedRegatta;
83
-import com.sap.sailing.domain.tracking.impl.DynamicTrackedRegattaImpl;
84
-import com.sap.sailing.domain.tracking.impl.EmptyWindStore;
85
-import com.sap.sailing.server.RacingEventService;
86
-import com.sap.sailing.server.impl.RacingEventServiceImpl;
87
-import com.sap.sailing.server.operationaltransformation.ConnectTrackedRaceToLeaderboardColumn;
88
-import com.sap.sailing.server.operationaltransformation.UpdateLeaderboardMaxPointsReason;
89
-import com.sap.sse.common.Color;
90
-import com.sap.sse.common.TimePoint;
91
-import com.sap.sse.common.Util;
92
-import com.sap.sse.common.impl.MillisecondsTimePoint;
93
-import com.sap.sse.common.media.ImageDescriptor;
94
-import com.sap.sse.common.media.ImageDescriptorImpl;
95
-import com.sap.sse.common.media.MediaTagConstants;
96
-import com.sap.sse.common.media.MimeType;
97
-import com.sap.sse.common.media.VideoDescriptor;
98
-import com.sap.sse.common.media.VideoDescriptorImpl;
99
-
100
-public class TestStoringAndLoadingEventsAndRegattas extends AbstractMongoDBTest {
101
- private static final Logger logger = Logger.getLogger(TestStoringAndLoadingEventsAndRegattas.class.getName());
102
-
103
- private final TimePoint eventStartDate;
104
- private final TimePoint eventEndDate;
105
- private final TimePoint regattaStartDate;
106
- private final TimePoint regattaEndDate;
107
-
108
- public TestStoringAndLoadingEventsAndRegattas() throws UnknownHostException, MongoException {
109
- super();
110
-
111
- Calendar cal = Calendar.getInstance();
112
-
113
- cal.set(2012, 12, 1);
114
- eventStartDate = new MillisecondsTimePoint(cal.getTimeInMillis());
115
- cal.set(2012, 12, 5);
116
- eventEndDate = new MillisecondsTimePoint(cal.getTimeInMillis());
117
-
118
- cal.set(2012, 12, 2);
119
- regattaStartDate = new MillisecondsTimePoint(cal.getTimeInMillis());
120
- cal.set(2012, 12, 3);
121
- regattaEndDate = new MillisecondsTimePoint(cal.getTimeInMillis());
122
- }
123
-
124
- private LeaderboardGroup createLeaderboardGroup(String name) {
125
- return new LeaderboardGroupImpl(name, "Description for "+name, /* displayName */ null, /* displayInReverseOrder */ false, Collections.<Leaderboard>emptyList());
126
- }
127
-
128
- @Test
129
- public void testLoadStoreSimpleEventWithLinkToLeaderboardGroups() throws MalformedURLException {
130
- final String eventName = "Event Name";
131
- final String eventDescription = "Event Description";
132
- final String venueName = "Venue Name";
133
- final String[] courseAreaNames = new String[] { "Alpha", "Bravo", "Charlie", "Delta", "Echo", "Foxtrott" };
134
- final URL officialWebsiteURL = new URL("http://official.website.com");
135
- final URL sailorsInfoWebsiteURL = new URL("http://sailorsinfo.website.com");
136
- final Venue venue = new VenueImpl(venueName);
137
-
138
- for (String courseAreaName : courseAreaNames) {
139
- CourseArea courseArea = DomainFactory.INSTANCE.getOrCreateCourseArea(UUID.randomUUID(), courseAreaName);
140
- venue.addCourseArea(courseArea);
141
- }
142
- MongoObjectFactory mof = PersistenceFactory.INSTANCE.getMongoObjectFactory(getMongoService());
143
- final Event event = new EventImpl(eventName, eventStartDate, eventEndDate, venue, /*isPublic*/ true, UUID.randomUUID());
144
- final LeaderboardGroup lg1 = createLeaderboardGroup("lg1");
145
- final LeaderboardGroup lg2 = createLeaderboardGroup("lg2");
146
- event.addLeaderboardGroup(lg1);
147
- event.addLeaderboardGroup(lg2);
148
- event.setDescription(eventDescription);
149
- event.setOfficialWebsiteURL(officialWebsiteURL);
150
- event.setSailorsInfoWebsiteURL(sailorsInfoWebsiteURL);
151
- mof.storeEvent(event);
152
-
153
- DomainObjectFactory dof = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE);
154
- final Event loadedEvent = dof.loadEvent(eventName);
155
- dof.loadLeaderboardGroupLinksForEvents(new EventResolver() {
156
- @Override
157
- public Event getEvent(Serializable id) {
158
- return id.equals(loadedEvent.getId()) ? loadedEvent : null;
159
- }
160
- }, new LeaderboardGroupResolver() {
161
- @Override
162
- public LeaderboardGroup getLeaderboardGroupByName(String leaderboardGroupName) {
163
- return leaderboardGroupName.equals(lg1.getName()) ? lg1 : leaderboardGroupName.equals(lg2.getName()) ? lg2 : null;
164
- }
165
-
166
- @Override
167
- public LeaderboardGroup getLeaderboardGroupByID(UUID leaderboardGroupID) {
168
- return leaderboardGroupID.equals(lg1.getId()) ? lg1 : leaderboardGroupID.equals(lg2.getId()) ? lg2 : null;
169
- }
170
- });
171
- assertNotNull(loadedEvent);
172
- assertEquals(eventName, loadedEvent.getName());
173
- assertEquals(eventDescription, loadedEvent.getDescription());
174
- assertEquals(event.getOfficialWebsiteURL(), loadedEvent.getOfficialWebsiteURL());
175
- assertEquals(event.getSailorsInfoWebsiteURL(), loadedEvent.getSailorsInfoWebsiteURL());
176
- assertEquals(2, Util.size(loadedEvent.getLeaderboardGroups()));
177
- Iterator<LeaderboardGroup> lgIter = loadedEvent.getLeaderboardGroups().iterator();
178
- assertSame(lg1, lgIter.next());
179
- assertSame(lg2, lgIter.next());
180
- final Venue loadedVenue = loadedEvent.getVenue();
181
- assertNotNull(loadedVenue);
182
- assertEquals(venueName, loadedVenue.getName());
183
- assertEquals(courseAreaNames.length, Util.size(loadedVenue.getCourseAreas()));
184
- int i=0;
185
- for (CourseArea loadedCourseArea : loadedVenue.getCourseAreas()) {
186
- assertEquals(courseAreaNames[i++], loadedCourseArea.getName());
187
- }
188
- }
189
-
190
- @Test
191
- public void testLoadStoreSimpleEventAndRegattaWithCourseArea() {
192
- final String eventName = "Event Name";
193
- final String venueName = "Venue Name";
194
- final String courseAreaName = "Alpha";
195
- final Venue venue = new VenueImpl(venueName);
196
- CourseArea courseArea = DomainFactory.INSTANCE.getOrCreateCourseArea(UUID.randomUUID(), courseAreaName);
197
- venue.addCourseArea(courseArea);
198
-
199
- MongoObjectFactory mof = PersistenceFactory.INSTANCE.getMongoObjectFactory(getMongoService());
200
- Event event = new EventImpl(eventName, eventStartDate, eventEndDate, venue, /*isPublic*/ true, UUID.randomUUID());
201
- mof.storeEvent(event);
202
-
203
- final String regattaBaseName = "Kieler Woche";
204
- BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("29erXX", /* typicallyStartsUpwind */ true);
205
- Regatta regatta = createRegatta(RegattaImpl.getDefaultName(regattaBaseName, boatClass.getName()), boatClass,
206
- regattaStartDate, regattaEndDate, /* persistent */ true, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), courseArea, OneDesignRankingMetric::new);
207
- mof.storeRegatta(regatta);
208
-
209
- DomainObjectFactory dof = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE);
210
- Regatta loadedRegatta = dof.loadRegatta(regatta.getName(), /* trackedRegattaRegistry */ null);
211
- assertNotNull(loadedRegatta);
212
- assertEquals(regatta.getName(), loadedRegatta.getName());
213
- assertEquals(Util.size(regatta.getSeries()), Util.size(loadedRegatta.getSeries()));
214
- assertNotNull(loadedRegatta.getDefaultCourseArea());
215
- assertEquals(loadedRegatta.getDefaultCourseArea().getId(), courseArea.getId());
216
- assertEquals(loadedRegatta.getDefaultCourseArea().getName(), courseArea.getName());
217
- assertEquals(loadedRegatta.getStartDate(), regattaStartDate);
218
- assertEquals(loadedRegatta.getEndDate(), regattaEndDate);
219
- }
220
-
221
- @Test
222
- public void testLoadStoreSimpleEventWithImages() throws MalformedURLException {
223
- final URL imageURL = new URL("http://some.host/with/some/file2.jpg");
224
- final String copyright = "copyright by Alex";
225
- final String imageTitle = "My image title";
226
- final String imageSubtitle = "My image subtitle";
227
- final Integer imageWidth = 500;
228
- final Integer imageHeight = 300;
229
- final TimePoint createdAt = MillisecondsTimePoint.now();
230
- final String eventName = "Event Name";
231
- final String venueName = "Venue Name";
232
- final String courseAreaName = "Alpha";
233
- final Venue venue = new VenueImpl(venueName);
234
- CourseArea courseArea = DomainFactory.INSTANCE.getOrCreateCourseArea(UUID.randomUUID(), courseAreaName);
235
- venue.addCourseArea(courseArea);
236
-
237
- MongoObjectFactory mof = PersistenceFactory.INSTANCE.getMongoObjectFactory(getMongoService());
238
- Event event = new EventImpl(eventName, eventStartDate, eventEndDate, venue, /*isPublic*/ true, UUID.randomUUID());
239
-
240
- ImageDescriptor image1 = new ImageDescriptorImpl(imageURL, createdAt);
241
- image1.setCopyright(copyright);
242
- image1.setSize(imageWidth, imageHeight);
243
- image1.setTitle(imageTitle);
244
- image1.setSubtitle(imageSubtitle);
245
- image1.addTag("Tag1");
246
- image1.addTag("Tag2");
247
- image1.addTag("Tag3");
248
- event.addImage(image1);
249
-
250
- ImageDescriptor image2 = new ImageDescriptorImpl(new URL("http://some.host/with/some/file2.jpg"), MillisecondsTimePoint.now());
251
- image2.setCopyright("copyright");
252
- event.addImage(image2);
253
-
254
- mof.storeEvent(event);
255
-
256
- DomainObjectFactory dof = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE);
257
- final Event loadedEvent = dof.loadEvent(eventName);
258
- assertEquals(2, Util.size(loadedEvent.getImages()));
259
- ImageDescriptor loadedImage1 = loadedEvent.getImages().iterator().next();
260
- assertNotNull(loadedImage1);
261
- assertEquals(imageURL, loadedImage1.getURL());
262
- assertEquals(copyright, loadedImage1.getCopyright());
263
- assertEquals(copyright, loadedImage1.getCopyright());
264
- assertEquals(imageTitle, loadedImage1.getTitle());
265
- assertEquals(imageSubtitle, loadedImage1.getSubtitle());
266
- assertEquals(createdAt, loadedImage1.getCreatedAtDate());
267
- assertEquals(imageWidth, loadedImage1.getWidthInPx());
268
- assertEquals(imageHeight, loadedImage1.getHeightInPx());
269
- assertEquals(3, Util.size(loadedImage1.getTags()));
270
- }
271
-
272
- @Test
273
- public void testLoadStoreSimpleEventWithVideos() throws MalformedURLException {
274
- final URL videoURL = new URL("http://some.host/with/some/video.mpg");
275
- final URL videoThumbnailURL = new URL("http://some.host/with/some/video_thumbnail.jpg");
276
- final Locale locale = Locale.GERMAN;
277
- final Integer videoLengthInSeconds = 2 * 60 * 60 * 1000; // 2h
278
- final MimeType mimeType = MimeType.mp4;
279
- final String copyright = "copyright by Don";
280
- final String videoTitle = "My video title";
281
- final String videoSubtitle = "My video subtitle";
282
- final TimePoint createdAt = MillisecondsTimePoint.now();
283
- final String eventName = "Event Name";
284
- final String venueName = "Venue Name";
285
- final String courseAreaName = "Alpha";
286
- final Venue venue = new VenueImpl(venueName);
287
- CourseArea courseArea = DomainFactory.INSTANCE.getOrCreateCourseArea(UUID.randomUUID(), courseAreaName);
288
- venue.addCourseArea(courseArea);
289
-
290
- MongoObjectFactory mof = PersistenceFactory.INSTANCE.getMongoObjectFactory(getMongoService());
291
- Event event = new EventImpl(eventName, eventStartDate, eventEndDate, venue, /*isPublic*/ true, UUID.randomUUID());
292
-
293
- VideoDescriptor video1 = new VideoDescriptorImpl(videoURL, mimeType, createdAt);
294
- video1.setCopyright(copyright);
295
- video1.setTitle(videoTitle);
296
- video1.setLocale(locale);
297
- video1.setSubtitle(videoSubtitle);
298
- video1.setThumbnailURL(videoThumbnailURL);
299
- video1.setLengthInSeconds(videoLengthInSeconds);
300
- video1.addTag("Tag1");
301
- video1.addTag("Tag2");
302
- video1.addTag("Tag3");
303
- event.addVideo(video1);
304
-
305
- VideoDescriptor video2 = new VideoDescriptorImpl(new URL("http://some.host/with/some/file2.ogg"), MimeType.ogg, MillisecondsTimePoint.now());
306
- video2.setCopyright("copyright");
307
- event.addVideo(video2);
308
-
309
- mof.storeEvent(event);
310
-
311
- DomainObjectFactory dof = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE);
312
- final Event loadedEvent = dof.loadEvent(eventName);
313
- assertEquals(2, Util.size(loadedEvent.getVideos()));
314
- VideoDescriptor loadedVideo1 = loadedEvent.getVideos().iterator().next();
315
- assertNotNull(loadedVideo1);
316
- assertEquals(videoURL, loadedVideo1.getURL());
317
- assertEquals(videoThumbnailURL, loadedVideo1.getThumbnailURL());
318
- assertEquals(videoLengthInSeconds, loadedVideo1.getLengthInSeconds());
319
- assertEquals(copyright, loadedVideo1.getCopyright());
320
- assertEquals(locale, loadedVideo1.getLocale());
321
- assertEquals(videoTitle, loadedVideo1.getTitle());
322
- assertEquals(videoSubtitle, loadedVideo1.getSubtitle());
323
- assertEquals(createdAt, loadedVideo1.getCreatedAtDate());
324
- assertEquals(3, Util.size(loadedVideo1.getTags()));
325
- }
326
-
327
- @SuppressWarnings("deprecation")
328
- @Test
329
- /**
330
- * We expected that the migration code creates also an image URL for each image we create.
331
- * Images with the 'Sponsor' tag should create a corresponding sponsor image URL
332
- * Videos should create a video URL.
333
- */
334
- public void testLoadStoreSimpleEventWithImageAndVideoURLMigration() throws MalformedURLException {
335
- final URL imageURL = new URL("http://some.host/with/some/bla.jpg");
336
- final URL sponsorImageURL = new URL("http://some.host/with/some/sponsor.jpg");
337
- final URL videoURL = new URL("http://some.host/with/some/video.mpg");
338
- final TimePoint createdAt = MillisecondsTimePoint.now();
339
- final String eventName = "Event Name";
340
- final Venue venue = new VenueImpl("My Venue");
341
- CourseArea courseArea = DomainFactory.INSTANCE.getOrCreateCourseArea(UUID.randomUUID(), "Alfa");
342
- venue.addCourseArea(courseArea);
343
-
344
- MongoObjectFactory mof = PersistenceFactory.INSTANCE.getMongoObjectFactory(getMongoService());
345
- Event event = new EventImpl(eventName, eventStartDate, eventEndDate, venue, /*isPublic*/ true, UUID.randomUUID());
346
-
347
- ImageDescriptor image1 = new ImageDescriptorImpl(imageURL, createdAt);
348
- image1.addTag(MediaTagConstants.GALLERY);
349
- event.addImage(image1);
350
-
351
- ImageDescriptor image2 = new ImageDescriptorImpl(sponsorImageURL, createdAt);
352
- event.addImage(image2);
353
- image2.addTag(MediaTagConstants.SPONSOR);
354
-
355
- VideoDescriptor video1 = new VideoDescriptorImpl(videoURL, MimeType.mp4, createdAt);
356
- event.addVideo(video1);
357
-
358
- mof.storeEvent(event);
359
-
360
- DomainObjectFactory dof = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE);
361
- final Event loadedEvent = dof.loadEvent(eventName);
362
- assertEquals(2, Util.size(loadedEvent.getImages()));
363
- assertEquals(1, Util.size(loadedEvent.getVideos()));
364
- assertEquals(1, Util.size(loadedEvent.getImageURLs()));
365
- assertEquals(1, Util.size(loadedEvent.getSponsorImageURLs()));
366
- assertEquals(1, Util.size(loadedEvent.getVideoURLs()));
367
- }
368
-
369
-
370
- @Test
371
- public void testLoadStoreRegattaConfiguration() {
372
-
373
- RegattaConfigurationImpl configuration = new RegattaConfigurationImpl();
374
- configuration.setDefaultRacingProcedureType(RacingProcedureType.BASIC);
375
-
376
- BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("ESS40", false);
377
- Regatta regatta = createRegattaAndAddRaceColumns(1, 1, RegattaImpl.getDefaultName("RR", boatClass.getName()), boatClass,
378
- regattaStartDate, regattaEndDate, false, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.HIGH_POINT), OneDesignRankingMetric::new);
379
- regatta.setRegattaConfiguration(configuration);
380
-
381
- MongoObjectFactory mof = PersistenceFactory.INSTANCE.getMongoObjectFactory(getMongoService());
382
- mof.storeRegatta(regatta);
383
- DomainObjectFactory dof = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE);
384
- Regatta loadedRegatta = dof.loadRegatta(regatta.getName(), null);
385
-
386
- assertNotNull(loadedRegatta.getRegattaConfiguration());
387
- assertEquals(RacingProcedureType.BASIC, loadedRegatta.getRegattaConfiguration().getDefaultRacingProcedureType());
388
- }
389
-
390
- @Test
391
- public void testLoadStoreSimpleRegattaLeaderboard() {
392
- RacingEventService res = new RacingEventServiceImpl(PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE), PersistenceFactory.INSTANCE
393
- .getMongoObjectFactory(getMongoService()), MediaDBFactory.INSTANCE.getMediaDB(getMongoService()), EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE);
394
- final int numberOfQualifyingRaces = 5;
395
- final int numberOfFinalRaces = 7;
396
- final String regattaBaseName = "Kieler Woche";
397
- BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("29erXX", /* typicallyStartsUpwind */ true);
398
- Regatta regattaProxy = createRegatta(RegattaImpl.getDefaultName(regattaBaseName, boatClass.getName()), boatClass,
399
- regattaStartDate, regattaEndDate, /* persistent */ true, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null, OneDesignRankingMetric::new);
400
- final String regattaName = regattaProxy.getName();
401
- Regatta regatta = res.createRegatta(regattaName, regattaProxy.getBoatClass().getName(), regattaStartDate, regattaEndDate,
402
- "123", regattaProxy.getSeries(), regattaProxy.isPersistent(), DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null, /* useStartTimeInference */ true, OneDesignRankingMetric::new);
403
- addRaceColumns(numberOfQualifyingRaces, numberOfFinalRaces, regatta);
404
- res.addRegattaLeaderboard(regatta.getRegattaIdentifier(), null, new int[] { 3, 5 });
405
- DomainObjectFactory dof = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE);
406
- Regatta loadedRegatta = dof.loadRegatta(regatta.getName(), /* trackedRegattaRegistry */ null);
407
- assertNotNull(loadedRegatta);
408
- assertEquals(regatta.getName(), loadedRegatta.getName());
409
- assertEquals(Util.size(regatta.getSeries()), Util.size(loadedRegatta.getSeries()));
410
-
411
- Leaderboard loadedLeaderboard = dof.loadLeaderboard(regatta.getName(), res);
412
- assertNotNull(loadedLeaderboard);
413
- assertTrue(loadedLeaderboard instanceof RegattaLeaderboard);
414
- RegattaLeaderboard loadedRegattaLeaderboard = (RegattaLeaderboard) loadedLeaderboard;
415
- assertSame(regatta, loadedRegattaLeaderboard.getRegatta());
416
- }
417
-
418
- @Test
419
- public void testLoadStoreRegattaLeaderboardWithScoreCorrections() {
420
- // for some reason the dropping of collections doesn't work reliably on Linux... explicitly drop those collections that we depend on
421
- getMongoService().getDB().getCollection(CollectionNames.LEADERBOARDS.name()).drop();
422
- getMongoService().getDB().getCollection(CollectionNames.REGATTAS.name()).drop();
423
- Competitor hasso = AbstractLeaderboardTest.createCompetitor("Dr. Hasso Plattner");
424
- BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("29erXX", /* typicallyStartsUpwind */ true);
425
- final DynamicTrackedRegatta[] trackedRegatta = new DynamicTrackedRegatta[1];
426
- final DynamicTrackedRace q2YellowTrackedRace = new MockedTrackedRaceWithFixedRank(hasso, /* rank */ 1, /* started */ false, boatClass) {
427
- private static final long serialVersionUID = 1234L;
428
- @Override
429
- public RegattaAndRaceIdentifier getRaceIdentifier() {
430
- return new RegattaNameAndRaceName("Kieler Woche (29ERXX)", "Yellow Race 2");
431
- }
432
- @Override
433
- public DynamicTrackedRegatta getTrackedRegatta() {
434
- return trackedRegatta[0];
435
- }
436
- };
437
- RacingEventService res = createRacingEventServiceWithOneMockedTrackedRace(q2YellowTrackedRace);
438
- final int numberOfQualifyingRaces = 5;
439
- final int numberOfFinalRaces = 7;
440
- final String regattaBaseName = "Kieler Woche";
441
-
442
- Regatta regattaProxy = createRegatta(RegattaImpl.getDefaultName(regattaBaseName, boatClass.getName()), boatClass, regattaStartDate, regattaEndDate, /* persistent */ true, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null, OneDesignRankingMetric::new);
443
- Regatta regatta = res.createRegatta(regattaProxy.getName(), regattaProxy.getBoatClass().getName(), regattaProxy.getStartDate(), regattaProxy.getEndDate(),
444
- "123", regattaProxy.getSeries(), regattaProxy.isPersistent(), DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null, /* useStartTimeInference */ true, OneDesignRankingMetric::new);
445
- trackedRegatta[0] = new DynamicTrackedRegattaImpl(regatta);
446
- addRaceColumns(numberOfQualifyingRaces, numberOfFinalRaces, regatta);
447
- logColumnsInRegatta(regatta);
448
- RegattaLeaderboard regattaLeaderboard = res.addRegattaLeaderboard(regatta.getRegattaIdentifier(), null, new int[] { 3, 5 });
449
- assertSame(regatta, regattaLeaderboard.getRegatta());
450
- final RaceColumnInSeries q2 = regatta.getSeriesByName("Qualifying").getRaceColumnByName("Q2");
451
- final Fleet yellow = q2.getFleetByName("Yellow");
452
- logColumnsInRegatta(regatta);
453
- logColumnsInRegattaLeaderboard(regattaLeaderboard);
454
- assertNotNull(regattaLeaderboard.getRaceColumnByName(q2.getName()));
455
- res.apply(new ConnectTrackedRaceToLeaderboardColumn(regattaLeaderboard.getName(), q2.getName(), yellow
456
- .getName(), q2YellowTrackedRace.getRaceIdentifier()));
457
- res.apply(new UpdateLeaderboardMaxPointsReason(regattaLeaderboard.getName(), q2.getName(), hasso.getId().toString(),
458
- MaxPointsReason.DNF, MillisecondsTimePoint.now()));
459
-
460
- // load new RacingEventService including regatta and leaderboard
461
- RacingEventService resForLoading = createRacingEventServiceWithOneMockedTrackedRace(q2YellowTrackedRace);
462
- Regatta loadedRegatta = resForLoading.getRegattaByName("Kieler Woche (29ERXX)");
463
- assertNotNull(loadedRegatta);
464
- assertEquals(regatta.getName(), loadedRegatta.getName());
465
- assertEquals(Util.size(regatta.getSeries()), Util.size(loadedRegatta.getSeries()));
466
- Leaderboard loadedLeaderboard = resForLoading.getLeaderboardByName(loadedRegatta.getName());
467
- assertNotNull(loadedLeaderboard);
468
- assertEquals(((ThresholdBasedResultDiscardingRule) regattaLeaderboard.getResultDiscardingRule()).getDiscardIndexResultsStartingWithHowManyRaces().length,
469
- ((ThresholdBasedResultDiscardingRule) loadedLeaderboard.getResultDiscardingRule()).getDiscardIndexResultsStartingWithHowManyRaces().length);
470
- assertTrue(Arrays.equals(((ThresholdBasedResultDiscardingRule) regattaLeaderboard.getResultDiscardingRule()).getDiscardIndexResultsStartingWithHowManyRaces(),
471
- ((ThresholdBasedResultDiscardingRule) loadedLeaderboard.getResultDiscardingRule()).getDiscardIndexResultsStartingWithHowManyRaces()));
472
- assertTrue(loadedLeaderboard instanceof RegattaLeaderboard);
473
- RegattaLeaderboard loadedRegattaLeaderboard = (RegattaLeaderboard) loadedLeaderboard;
474
- assertSame(loadedRegatta, loadedRegattaLeaderboard.getRegatta());
475
- // now re-associate the tracked race to let score correction "snap" to competitor:
476
- final RaceColumnInSeries loadedQ2 = loadedRegatta.getSeriesByName("Qualifying").getRaceColumnByName("Q2");
477
- final Fleet loadedYellow = loadedQ2.getFleetByName("Yellow");
478
- // adjust tracked regatta for tracked race:
479
- trackedRegatta[0] = new DynamicTrackedRegattaImpl(loadedRegatta);
480
- resForLoading.apply(new ConnectTrackedRaceToLeaderboardColumn(loadedLeaderboard.getName(), loadedQ2.getName(), loadedYellow
481
- .getName(), q2YellowTrackedRace.getRaceIdentifier()));
482
- MaxPointsReason hassosLoadedMaxPointsReason = loadedLeaderboard.getScoreCorrection().getMaxPointsReason(hasso, loadedQ2, MillisecondsTimePoint.now());
483
- assertEquals(MaxPointsReason.DNF, hassosLoadedMaxPointsReason);
484
- }
485
-
486
- private void logColumnsInRegattaLeaderboard(RegattaLeaderboard regattaLeaderboard) {
487
- StringBuilder rlbrcNames = new StringBuilder();
488
- for (RaceColumn rlbrc : regattaLeaderboard.getRaceColumns()) {
489
- rlbrcNames.append("; ");
490
- rlbrcNames.append(rlbrc.getName());
491
- }
492
- logger.info("columns in regatta leaderboard for regatta "+regattaLeaderboard.getRegatta().getName()+" ("+
493
- regattaLeaderboard.getRegatta().hashCode()+"): "+rlbrcNames);
494
- logColumnsInRegatta(regattaLeaderboard.getRegatta());
495
- }
496
-
497
- private void logColumnsInRegatta(Regatta regatta) {
498
- StringBuilder rrcNames = new StringBuilder();
499
- for (Series series : regatta.getSeries()) {
500
- for (RaceColumn raceColumn : series.getRaceColumns()) {
501
- rrcNames.append("; ");
502
- rrcNames.append(raceColumn.getName());
503
- }
504
- }
505
- logger.info("columns in regatta "+regatta.getName()+" ("+regatta.hashCode()+") : "+rrcNames);
506
- }
507
-
508
- private RacingEventServiceImpl createRacingEventServiceWithOneMockedTrackedRace(final DynamicTrackedRace q2YellowTrackedRace) {
509
- return new RacingEventServiceImpl(PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE), PersistenceFactory.INSTANCE
510
- .getMongoObjectFactory(getMongoService()), MediaDBFactory.INSTANCE.getMediaDB(getMongoService()), EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE) {
511
- @Override
512
- public DynamicTrackedRace getExistingTrackedRace(RegattaAndRaceIdentifier raceIdentifier) {
513
- return q2YellowTrackedRace;
514
- }
515
- };
516
- }
517
-
518
- @Test
519
- public void testLoadStoreSimpleRegatta() {
520
- final int numberOfQualifyingRaces = 5;
521
- final int numberOfFinalRaces = 7;
522
- final String regattaBaseName = "Kieler Woche";
523
- BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("29erXX", /* typicallyStartsUpwind */true);
524
- final String regattaName = RegattaImpl.getDefaultName(regattaBaseName, boatClass.getName());
525
- Regatta regatta = createRegattaAndAddRaceColumns(numberOfQualifyingRaces, numberOfFinalRaces, regattaName,
526
- boatClass, regattaStartDate, regattaEndDate, /* persistent */false,
527
- DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), OneDesignRankingMetric::new);
528
- MongoObjectFactory mof = PersistenceFactory.INSTANCE.getMongoObjectFactory(getMongoService());
529
- mof.storeRegatta(regatta);
530
-
531
- DomainObjectFactory dof = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE);
532
- Regatta loadedRegatta = dof.loadRegatta(regatta.getName(), /* trackedRegattaRegistry */ null);
533
- assertSame(LowPoint.class, loadedRegatta.getScoringScheme().getClass());
534
- assertEquals(regattaName, loadedRegatta.getName());
535
- Iterator<? extends Series> seriesIter = loadedRegatta.getSeries().iterator();
536
- Series loadedQualifyingSeries = seriesIter.next();
537
- assertEquals(numberOfQualifyingRaces, Util.size(loadedQualifyingSeries.getRaceColumns()));
538
- assertEquals(0, loadedQualifyingSeries.getFleetByName("Yellow").compareTo(loadedQualifyingSeries.getFleetByName("Blue")));
539
- Series loadedFinalSeries = seriesIter.next();
540
- assertEquals(numberOfFinalRaces, Util.size(loadedFinalSeries.getRaceColumns()));
541
- assertTrue(loadedFinalSeries.getFleetByName("Silver").compareTo(loadedFinalSeries.getFleetByName("Gold")) > 0);
542
- Series loadedMedalSeries = seriesIter.next();
543
- assertEquals(1, Util.size(loadedMedalSeries.getRaceColumns()));
544
- assertEquals(loadedRegatta.getStartDate(), regattaStartDate);
545
- assertEquals(loadedRegatta.getEndDate(), regattaEndDate);
546
- assertEquals(RankingMetrics.ONE_DESIGN, loadedRegatta.getRankingMetricType());
547
- }
548
-
549
- @Test
550
- public void testLoadStoreRegattaWithHandicapRanking() {
551
- final int numberOfQualifyingRaces = 5;
552
- final int numberOfFinalRaces = 7;
553
- final String regattaBaseName = "Kieler Woche";
554
- BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("29erXX", /* typicallyStartsUpwind */true);
555
- final String regattaName = RegattaImpl.getDefaultName(regattaBaseName, boatClass.getName());
556
- Regatta regatta = createRegattaAndAddRaceColumns(numberOfQualifyingRaces, numberOfFinalRaces, regattaName,
557
- boatClass, regattaStartDate, regattaEndDate, /* persistent */false,
558
- DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), TimeOnTimeAndDistanceRankingMetric::new);
559
- assertEquals(RankingMetrics.TIME_ON_TIME_AND_DISTANCE, regatta.getRankingMetricType());
560
- MongoObjectFactory mof = PersistenceFactory.INSTANCE.getMongoObjectFactory(getMongoService());
561
- mof.storeRegatta(regatta);
562
-
563
- DomainObjectFactory dof = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE);
564
- Regatta loadedRegatta = dof.loadRegatta(regatta.getName(), /* trackedRegattaRegistry */ null);
565
- assertSame(LowPoint.class, loadedRegatta.getScoringScheme().getClass());
566
- assertEquals(regattaName, loadedRegatta.getName());
567
- Iterator<? extends Series> seriesIter = loadedRegatta.getSeries().iterator();
568
- Series loadedQualifyingSeries = seriesIter.next();
569
- assertEquals(numberOfQualifyingRaces, Util.size(loadedQualifyingSeries.getRaceColumns()));
570
- assertEquals(0, loadedQualifyingSeries.getFleetByName("Yellow").compareTo(loadedQualifyingSeries.getFleetByName("Blue")));
571
- Series loadedFinalSeries = seriesIter.next();
572
- assertEquals(numberOfFinalRaces, Util.size(loadedFinalSeries.getRaceColumns()));
573
- assertTrue(loadedFinalSeries.getFleetByName("Silver").compareTo(loadedFinalSeries.getFleetByName("Gold")) > 0);
574
- Series loadedMedalSeries = seriesIter.next();
575
- assertEquals(1, Util.size(loadedMedalSeries.getRaceColumns()));
576
- assertEquals(loadedRegatta.getStartDate(), regattaStartDate);
577
- assertEquals(loadedRegatta.getEndDate(), regattaEndDate);
578
- assertEquals(RankingMetrics.TIME_ON_TIME_AND_DISTANCE, loadedRegatta.getRankingMetricType());
579
- }
580
-
581
- @Test
582
- public void testLoadStoreSimpleRegattaWithEmptyStartAndEndDate() {
583
- final int numberOfQualifyingRaces = 1;
584
- final int numberOfFinalRaces = 1;
585
- final String regattaBaseName = "Kieler Woche";
586
- BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("29erXX", /* typicallyStartsUpwind */true);
587
- final String regattaName = RegattaImpl.getDefaultName(regattaBaseName, boatClass.getName());
588
- Regatta regatta = createRegattaAndAddRaceColumns(numberOfQualifyingRaces, numberOfFinalRaces, regattaName,
589
- boatClass, null, null, /* persistent */false,
590
- DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), OneDesignRankingMetric::new);
591
- MongoObjectFactory mof = PersistenceFactory.INSTANCE.getMongoObjectFactory(getMongoService());
592
- mof.storeRegatta(regatta);
593
-
594
- DomainObjectFactory dof = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE);
595
- Regatta loadedRegatta = dof.loadRegatta(regatta.getName(), /* trackedRegattaRegistry */ null);
596
- assertEquals(regattaName, loadedRegatta.getName());
597
- assertEquals(loadedRegatta.getStartDate(), null);
598
- assertEquals(loadedRegatta.getEndDate(), null);
599
- }
600
-
601
- @Test
602
- public void testLoadStoreSimpleRegattaWithSeriesScoringScheme() {
603
- final int numberOfQualifyingRaces = 5;
604
- final int numberOfFinalRaces = 7;
605
- final String regattaBaseName = "Kieler Woche";
606
- BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("29erXX", /* typicallyStartsUpwind */ true);
607
- Regatta regatta = createRegattaAndAddRaceColumns(numberOfQualifyingRaces, numberOfFinalRaces, RegattaImpl.getDefaultName(regattaBaseName, boatClass.getName()),
608
- boatClass, regattaStartDate, regattaEndDate, /* persistent */false, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), OneDesignRankingMetric::new);
609
- regatta.getSeriesByName("Qualifying").setResultDiscardingRule(new ThresholdBasedResultDiscardingRuleImpl(new int[] { 1, 2, 3 }));
610
- MongoObjectFactory mof = PersistenceFactory.INSTANCE.getMongoObjectFactory(getMongoService());
611
- mof.storeRegatta(regatta);
612
-
613
- DomainObjectFactory dof = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE);
614
- Regatta loadedRegatta = dof.loadRegatta(regatta.getName(), /* trackedRegattaRegistry */ null);
615
- assertTrue(Arrays.equals(new int[] { 1, 2, 3 },
616
- loadedRegatta.getSeriesByName("Qualifying").getResultDiscardingRule().getDiscardIndexResultsStartingWithHowManyRaces()));
617
- }
618
-
619
- @Test
620
- public void testLoadStoreSimpleRegattaWithScoreForMedalStartingWithZero() {
621
- final int numberOfQualifyingRaces = 5;
622
- final int numberOfFinalRaces = 7;
623
- final String regattaBaseName = "Kieler Woche";
624
- BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("29erXX", /* typicallyStartsUpwind */ true);
625
- Regatta regatta = createRegattaAndAddRaceColumns(numberOfQualifyingRaces, numberOfFinalRaces, RegattaImpl.getDefaultName(regattaBaseName, boatClass.getName()),
626
- boatClass, regattaStartDate, regattaEndDate, /* persistent */false, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), OneDesignRankingMetric::new);
627
- regatta.getSeriesByName("Medal").setStartsWithZeroScore(true);
628
- MongoObjectFactory mof = PersistenceFactory.INSTANCE.getMongoObjectFactory(getMongoService());
629
- mof.storeRegatta(regatta);
630
-
631
- DomainObjectFactory dof = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE);
632
- Regatta loadedRegatta = dof.loadRegatta(regatta.getName(), /* trackedRegattaRegistry */ null);
633
- assertFalse(loadedRegatta.getSeriesByName("Qualifying").isStartsWithZeroScore());
634
- assertTrue(loadedRegatta.getSeriesByName("Medal").isStartsWithZeroScore());
635
- }
636
-
637
- @Test
638
- public void testLoadStoreSimpleRegattaWithHighPointScoringScheme() {
639
- final int numberOfQualifyingRaces = 5;
640
- final int numberOfFinalRaces = 7;
641
- final String regattaBaseName = "ESS40 Cardiff 2012";
642
- BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("ESS40", /* typicallyStartsUpwind */ false);
643
- Regatta regatta = createRegattaAndAddRaceColumns(numberOfQualifyingRaces, numberOfFinalRaces, RegattaImpl.getDefaultName(regattaBaseName, boatClass.getName()),
644
- boatClass, regattaStartDate, regattaEndDate, /* persistent */false, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.HIGH_POINT), OneDesignRankingMetric::new);
645
- MongoObjectFactory mof = PersistenceFactory.INSTANCE.getMongoObjectFactory(getMongoService());
646
- mof.storeRegatta(regatta);
647
-
648
- DomainObjectFactory dof = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE);
649
- Regatta loadedRegatta = dof.loadRegatta(regatta.getName(), /* trackedRegattaRegistry */ null);
650
- assertSame(HighPoint.class, loadedRegatta.getScoringScheme().getClass());
651
- }
652
-
653
- @Test
654
- public void testLoadStoreRegattaWithFleetsEnsuringIdenticalFleetsInSeriesAndRaceColumns() {
655
- final int numberOfQualifyingRaces = 5;
656
- final int numberOfFinalRaces = 7;
657
- final String regattaBaseName = "Kieler Woche";
658
- BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("29erXX", /* typicallyStartsUpwind */ true);
659
- final String regattaName = RegattaImpl.getDefaultName(regattaBaseName, boatClass.getName());
660
- Regatta regatta = createRegattaAndAddRaceColumns(numberOfQualifyingRaces, numberOfFinalRaces,
661
- regattaName, boatClass, regattaStartDate, regattaEndDate,
662
- /* persistent */false, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), OneDesignRankingMetric::new);
663
- MongoObjectFactory mof = PersistenceFactory.INSTANCE.getMongoObjectFactory(getMongoService());
664
- mof.storeRegatta(regatta);
665
-
666
- DomainObjectFactory dof = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE);
667
- Regatta loadedRegatta = dof.loadRegatta(regatta.getName(), /* trackedRegattaRegistry */ null);
668
- assertEquals(regattaName, loadedRegatta.getName());
669
- Iterator<? extends Series> seriesIter = loadedRegatta.getSeries().iterator();
670
- Series loadedQualifyingSeries = seriesIter.next();
671
- int i=1;
672
- for (RaceColumn raceColumn : loadedQualifyingSeries.getRaceColumns()) {
673
- assertTrue(raceColumn instanceof RaceColumnInSeriesImpl);
674
- assertEquals("Q"+i, raceColumn.getName());
675
- assertTrue(Util.equals(loadedQualifyingSeries.getFleets(), raceColumn.getFleets()));
676
- i++;
677
- }
678
- Series loadedFinalSeries = seriesIter.next();
679
- i=1;
680
- for (RaceColumn raceColumn : loadedFinalSeries.getRaceColumns()) {
681
- assertTrue(raceColumn instanceof RaceColumnInSeriesImpl);
682
- assertEquals("F"+i, raceColumn.getName());
683
- assertTrue(Util.equals(loadedFinalSeries.getFleets(), raceColumn.getFleets()));
684
- i++;
685
- }
686
- Series loadedMedalSeries = seriesIter.next();
687
- for (RaceColumn raceColumn : loadedMedalSeries.getRaceColumns()) {
688
- assertTrue(raceColumn instanceof RaceColumnInSeriesImpl);
689
- assertEquals("M", raceColumn.getName());
690
- assertTrue(Util.equals(loadedMedalSeries.getFleets(), raceColumn.getFleets()));
691
- }
692
- }
693
-
694
- @Test
695
- public void testLoadStoreRegattaWithFleetsEnsuringFleetOrdering() {
696
- final String regattaBaseName = "Kieler Woche";
697
- BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("29erXX", /* typicallyStartsUpwind */ true);
698
- final String regattaName = RegattaImpl.getDefaultName(regattaBaseName, boatClass.getName());
699
- Regatta regatta = createRegatta(regattaName, boatClass, regattaStartDate, regattaEndDate,
700
- /* persistent */ false, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null, OneDesignRankingMetric::new);
701
- MongoObjectFactory mof = PersistenceFactory.INSTANCE.getMongoObjectFactory(getMongoService());
702
- mof.storeRegatta(regatta);
703
-
704
- DomainObjectFactory dof = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE);
705
- Regatta loadedRegatta = dof.loadRegatta(regatta.getName(), /* trackedRegattaRegistry */ null);
706
- assertEquals(regattaName, loadedRegatta.getName());
707
-
708
- Iterator<? extends Series> seriesIter = loadedRegatta.getSeries().iterator();
709
- Series loadedQualifyingSeries = seriesIter.next();
710
-
711
- Iterator<? extends Fleet> qualiFleetIt = loadedQualifyingSeries.getFleets().iterator();
712
- Fleet qualiFleet1 = qualiFleetIt.next();
713
- assertEquals(qualiFleet1.getName(), "Yellow");
714
- Fleet qualiFleet2 = qualiFleetIt.next();
715
- assertEquals(qualiFleet2.getName(), "Blue");
716
-
717
- Series loadedFinalSeries = seriesIter.next();
718
- Iterator<? extends Fleet> finalFleetIt = loadedFinalSeries.getFleets().iterator();
719
- Fleet finalFleet1 = finalFleetIt.next();
720
- assertEquals(finalFleet1.getName(), "Gold");
721
- Fleet finalFleet2 = finalFleetIt.next();
722
- assertEquals(finalFleet2.getName(), "Silver");
723
- }
724
-
725
- @Test
726
- public void testStorageOfRaceIdentifiersOnRaceColumnInSeries() {
727
- final int numberOfQualifyingRaces = 5;
728
- final int numberOfFinalRaces = 7;
729
- final String regattaBaseName = "Kieler Woche";
730
- BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("29erXX", /* typicallyStartsUpwind */ true);
731
- Regatta regatta = createRegattaAndAddRaceColumns(numberOfQualifyingRaces, numberOfFinalRaces,
732
- RegattaImpl.getDefaultName(regattaBaseName, boatClass.getName()), boatClass, regattaStartDate, regattaEndDate,
733
- /* persistent */false, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), OneDesignRankingMetric::new);
734
- Series qualifyingSeries = regatta.getSeries().iterator().next();
735
- RaceColumn q2 = qualifyingSeries.getRaceColumnByName("Q2");
736
- final RegattaNameAndRaceName q2TrackedRaceIdentifier = new RegattaNameAndRaceName(regatta.getName(), "Q2 TracTrac");
737
- q2.setRaceIdentifier(qualifyingSeries.getFleetByName("Yellow"), q2TrackedRaceIdentifier);
738
- MongoObjectFactory mof = PersistenceFactory.INSTANCE.getMongoObjectFactory(getMongoService());
739
- mof.storeRegatta(regatta);
740
-
741
- DomainObjectFactory dof = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE);
742
- Regatta loadedRegatta = dof.loadRegatta(regatta.getName(), /* trackedRegattaRegistry */ null);
743
- Series loadedQualifyingSeries = loadedRegatta.getSeries().iterator().next();
744
- RaceColumn loadedQ2 = loadedQualifyingSeries.getRaceColumnByName("Q2");
745
- RaceIdentifier loadedQ2TrackedRaceIdentifier = loadedQ2.getRaceIdentifier(loadedQualifyingSeries.getFleetByName("Yellow"));
746
- assertEquals(q2TrackedRaceIdentifier, loadedQ2TrackedRaceIdentifier);
747
- assertNotSame(q2TrackedRaceIdentifier, loadedQ2TrackedRaceIdentifier);
748
- assertNull(loadedQualifyingSeries.getRaceColumnByName("Q1").getRaceIdentifier(loadedQualifyingSeries.getFleetByName("Yellow")));
749
- assertNull(loadedQualifyingSeries.getRaceColumnByName("Q2").getRaceIdentifier(loadedQualifyingSeries.getFleetByName("Blue")));
750
- }
751
-
752
- private Regatta createRegattaAndAddRaceColumns(final int numberOfQualifyingRaces, final int numberOfFinalRaces,
753
- final String regattaName, BoatClass boatClass, TimePoint startDate, TimePoint endDate, boolean persistent,
754
- ScoringScheme scoringScheme, RankingMetricConstructor rankingMetricConstructor) {
755
- Regatta regatta = createRegatta(regattaName, boatClass, startDate, endDate, persistent, scoringScheme, null,
756
- rankingMetricConstructor);
757
- addRaceColumns(numberOfQualifyingRaces, numberOfFinalRaces, regatta);
758
- return regatta;
759
- }
760
-
761
- private void addRaceColumns(final int numberOfQualifyingRaces, final int numberOfFinalRaces, Regatta regatta) {
762
- List<String> finalRaceColumnNames = new ArrayList<String>();
763
- for (int i=1; i<=numberOfFinalRaces; i++) {
764
- finalRaceColumnNames.add("F"+i);
765
- }
766
- List<String> qualifyingRaceColumnNames = new ArrayList<String>();
767
- for (int i=1; i<=numberOfQualifyingRaces; i++) {
768
- qualifyingRaceColumnNames.add("Q"+i);
769
- }
770
- List<String> medalRaceColumnNames = new ArrayList<String>();
771
- medalRaceColumnNames.add("M");
772
- addRaceColumnsToSeries(qualifyingRaceColumnNames, regatta.getSeriesByName("Qualifying"));
773
- addRaceColumnsToSeries(finalRaceColumnNames, regatta.getSeriesByName("Final"));
774
- addRaceColumnsToSeries(medalRaceColumnNames, regatta.getSeriesByName("Medal"));
775
- }
776
-
777
- private Regatta createRegatta(final String regattaName, BoatClass boatClass, TimePoint startDate,
778
- TimePoint endDate, boolean persistent, ScoringScheme scoringScheme, CourseArea courseArea,
779
- RankingMetricConstructor rankingMetricConstructor) {
780
- List<String> emptyRaceColumnNames = Collections.emptyList();
781
- List<Series> series = new ArrayList<Series>();
782
-
783
- // -------- qualifying series ------------
784
- List<Fleet> qualifyingFleets = new ArrayList<Fleet>();
785
- qualifyingFleets.add(new FleetImpl("Yellow"));
786
- qualifyingFleets.add(new FleetImpl("Blue"));
787
- Series qualifyingSeries = new SeriesImpl("Qualifying", /* isMedal */false, qualifyingFleets,
788
- emptyRaceColumnNames, /* trackedRegattaRegistry */ null);
789
- series.add(qualifyingSeries);
790
-
791
- // -------- final series ------------
792
- List<Fleet> finalFleets = new ArrayList<Fleet>();
793
- finalFleets.add(new FleetImpl("Gold", 1));
794
- finalFleets.add(new FleetImpl("Silver", 2));
795
- Series finalSeries = new SeriesImpl("Final", /* isMedal */ false, finalFleets, emptyRaceColumnNames, /* trackedRegattaRegistry */ null);
796
- series.add(finalSeries);
797
-
798
- // ------------ medal --------------
799
- List<Fleet> medalFleets = new ArrayList<Fleet>();
800
- medalFleets.add(new FleetImpl("Medal"));
801
- Series medalSeries = new SeriesImpl("Medal", /* isMedal */ true, medalFleets, emptyRaceColumnNames, /* trackedRegattaRegistry */ null);
802
- series.add(medalSeries);
803
- Regatta regatta = new RegattaImpl(regattaName, boatClass, startDate, endDate, series, persistent, scoringScheme, "123", courseArea, rankingMetricConstructor);
804
- return regatta;
805
- }
806
-
807
- private void addRaceColumnsToSeries(List<String> finalRaceColumnNames, Series finalSeries) {
808
- for (String raceColumnName : finalRaceColumnNames) {
809
- finalSeries.addRaceColumn(raceColumnName, /* trackedRegattaRegistry */ null);
810
- }
811
- }
812
-
813
- @Test
814
- public void testRegattaRaceAssociationStore() throws Exception {
815
- BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("112er", /* typicallyStartsUpwind */ true);
816
- Regatta regatta = createRegatta(RegattaImpl.getDefaultName("Cologne Masters", boatClass.getName()), boatClass,
817
- regattaStartDate, regattaEndDate, /* persistent */ true, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null, OneDesignRankingMetric::new);
818
-
819
- List<Competitor> competitors = new ArrayList<Competitor>();
820
- competitors.add(new CompetitorImpl("Axel", "Axel Uhl", Color.RED, null, null, null, null, /* timeOnTimeFactor */ null, /* timeOnDistanceAllowancePerNauticalMile */ null));
821
- Iterable<Waypoint> waypoints = Collections.emptyList();
822
- Course course = new CourseImpl("Course", waypoints);
823
-
824
- RaceDefinition racedef = new RaceDefinitionImpl("M1", course, boatClass, competitors);
825
- regatta.addRace(racedef);
826
-
827
- RacingEventServiceImpl evs = new RacingEventServiceImpl(PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE), PersistenceFactory.INSTANCE
828
- .getMongoObjectFactory(getMongoService()), MediaDBFactory.INSTANCE.getMediaDB(getMongoService()), EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE);
829
- assertNull(evs.getRememberedRegattaForRace(racedef.getId()));
830
- evs.raceAdded(regatta, racedef);
831
- assertNotNull(evs.getRememberedRegattaForRace(racedef.getId()));
832
- evs.removeRegatta(regatta);
833
- assertNull(evs.getRememberedRegattaForRace(racedef.getId()));
834
- }
835
-
836
-}
1
+package com.sap.sailing.mongodb.test;
2
+
3
+import static org.junit.Assert.assertEquals;
4
+import static org.junit.Assert.assertFalse;
5
+import static org.junit.Assert.assertNotNull;
6
+import static org.junit.Assert.assertNotSame;
7
+import static org.junit.Assert.assertNull;
8
+import static org.junit.Assert.assertSame;
9
+import static org.junit.Assert.assertTrue;
10
+
11
+import java.io.Serializable;
12
+import java.net.MalformedURLException;
13
+import java.net.URL;
14
+import java.net.UnknownHostException;
15
+import java.util.ArrayList;
16
+import java.util.Arrays;
17
+import java.util.Calendar;
18
+import java.util.Collections;
19
+import java.util.Iterator;
20
+import java.util.List;
21
+import java.util.Locale;
22
+import java.util.UUID;
23
+import java.util.logging.Logger;
24
+
25
+import org.junit.Test;
26
+
27
+import com.mongodb.MongoException;
28
+import com.sap.sailing.domain.base.BoatClass;
29
+import com.sap.sailing.domain.base.Competitor;
30
+import com.sap.sailing.domain.base.Course;
31
+import com.sap.sailing.domain.base.CourseArea;
32
+import com.sap.sailing.domain.base.DomainFactory;
33
+import com.sap.sailing.domain.base.Event;
34
+import com.sap.sailing.domain.base.Fleet;
35
+import com.sap.sailing.domain.base.RaceColumn;
36
+import com.sap.sailing.domain.base.RaceColumnInSeries;
37
+import com.sap.sailing.domain.base.RaceDefinition;
38
+import com.sap.sailing.domain.base.Regatta;
39
+import com.sap.sailing.domain.base.Series;
40
+import com.sap.sailing.domain.base.Venue;
41
+import com.sap.sailing.domain.base.Waypoint;
42
+import com.sap.sailing.domain.base.configuration.impl.RegattaConfigurationImpl;
43
+import com.sap.sailing.domain.base.impl.CompetitorImpl;
44
+import com.sap.sailing.domain.base.impl.CourseImpl;
45
+import com.sap.sailing.domain.base.impl.EventImpl;
46
+import com.sap.sailing.domain.base.impl.FleetImpl;
47
+import com.sap.sailing.domain.base.impl.RaceColumnInSeriesImpl;
48
+import com.sap.sailing.domain.base.impl.RaceDefinitionImpl;
49
+import com.sap.sailing.domain.base.impl.RegattaImpl;
50
+import com.sap.sailing.domain.base.impl.SeriesImpl;
51
+import com.sap.sailing.domain.base.impl.VenueImpl;
52
+import com.sap.sailing.domain.common.MaxPointsReason;
53
+import com.sap.sailing.domain.common.RaceIdentifier;
54
+import com.sap.sailing.domain.common.RankingMetrics;
55
+import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
56
+import com.sap.sailing.domain.common.RegattaNameAndRaceName;
57
+import com.sap.sailing.domain.common.ScoringSchemeType;
58
+import com.sap.sailing.domain.common.racelog.RacingProcedureType;
59
+import com.sap.sailing.domain.leaderboard.EventResolver;
60
+import com.sap.sailing.domain.leaderboard.Leaderboard;
61
+import com.sap.sailing.domain.leaderboard.LeaderboardGroup;
62
+import com.sap.sailing.domain.leaderboard.LeaderboardGroupResolver;
63
+import com.sap.sailing.domain.leaderboard.RegattaLeaderboard;
64
+import com.sap.sailing.domain.leaderboard.ScoringScheme;
65
+import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule;
66
+import com.sap.sailing.domain.leaderboard.impl.HighPoint;
67
+import com.sap.sailing.domain.leaderboard.impl.LeaderboardGroupImpl;
68
+import com.sap.sailing.domain.leaderboard.impl.LowPoint;
69
+import com.sap.sailing.domain.leaderboard.impl.ThresholdBasedResultDiscardingRuleImpl;
70
+import com.sap.sailing.domain.persistence.DomainObjectFactory;
71
+import com.sap.sailing.domain.persistence.MongoObjectFactory;
72
+import com.sap.sailing.domain.persistence.PersistenceFactory;
73
+import com.sap.sailing.domain.persistence.impl.CollectionNames;
74
+import com.sap.sailing.domain.persistence.media.MediaDBFactory;
75
+import com.sap.sailing.domain.racelog.tracking.EmptyGPSFixStore;
76
+import com.sap.sailing.domain.ranking.OneDesignRankingMetric;
77
+import com.sap.sailing.domain.ranking.RankingMetricConstructor;
78
+import com.sap.sailing.domain.ranking.TimeOnTimeAndDistanceRankingMetric;
79
+import com.sap.sailing.domain.test.AbstractLeaderboardTest;
80
+import com.sap.sailing.domain.test.mock.MockedTrackedRaceWithFixedRank;
81
+import com.sap.sailing.domain.tracking.DynamicTrackedRace;
82
+import com.sap.sailing.domain.tracking.DynamicTrackedRegatta;
83
+import com.sap.sailing.domain.tracking.impl.DynamicTrackedRegattaImpl;
84
+import com.sap.sailing.domain.tracking.impl.EmptyWindStore;
85
+import com.sap.sailing.server.RacingEventService;
86
+import com.sap.sailing.server.impl.RacingEventServiceImpl;
87
+import com.sap.sailing.server.operationaltransformation.ConnectTrackedRaceToLeaderboardColumn;
88
+import com.sap.sailing.server.operationaltransformation.UpdateLeaderboardMaxPointsReason;
89
+import com.sap.sse.common.Color;
90
+import com.sap.sse.common.TimePoint;
91
+import com.sap.sse.common.Util;
92
+import com.sap.sse.common.impl.MillisecondsTimePoint;
93
+import com.sap.sse.common.media.MediaTagConstants;
94
+import com.sap.sse.common.media.MimeType;
95
+import com.sap.sse.shared.media.ImageDescriptor;
96
+import com.sap.sse.shared.media.VideoDescriptor;
97
+import com.sap.sse.shared.media.impl.ImageDescriptorImpl;
98
+import com.sap.sse.shared.media.impl.VideoDescriptorImpl;
99
+
100
+public class TestStoringAndLoadingEventsAndRegattas extends AbstractMongoDBTest {
101
+ private static final Logger logger = Logger.getLogger(TestStoringAndLoadingEventsAndRegattas.class.getName());
102
+
103
+ private final TimePoint eventStartDate;
104
+ private final TimePoint eventEndDate;
105
+ private final TimePoint regattaStartDate;
106
+ private final TimePoint regattaEndDate;
107
+
108
+ public TestStoringAndLoadingEventsAndRegattas() throws UnknownHostException, MongoException {
109
+ super();
110
+
111
+ Calendar cal = Calendar.getInstance();
112
+
113
+ cal.set(2012, 12, 1);
114
+ eventStartDate = new MillisecondsTimePoint(cal.getTimeInMillis());
115
+ cal.set(2012, 12, 5);
116
+ eventEndDate = new MillisecondsTimePoint(cal.getTimeInMillis());
117
+
118
+ cal.set(2012, 12, 2);
119
+ regattaStartDate = new MillisecondsTimePoint(cal.getTimeInMillis());
120
+ cal.set(2012, 12, 3);
121
+ regattaEndDate = new MillisecondsTimePoint(cal.getTimeInMillis());
122
+ }
123
+
124
+ private LeaderboardGroup createLeaderboardGroup(String name) {
125
+ return new LeaderboardGroupImpl(name, "Description for "+name, /* displayName */ null, /* displayInReverseOrder */ false, Collections.<Leaderboard>emptyList());
126
+ }
127
+
128
+ @Test
129
+ public void testLoadStoreSimpleEventWithLinkToLeaderboardGroups() throws MalformedURLException {
130
+ final String eventName = "Event Name";
131
+ final String eventDescription = "Event Description";
132
+ final String venueName = "Venue Name";
133
+ final String[] courseAreaNames = new String[] { "Alpha", "Bravo", "Charlie", "Delta", "Echo", "Foxtrott" };
134
+ final URL officialWebsiteURL = new URL("http://official.website.com");
135
+ final URL sailorsInfoWebsiteURL = new URL("http://sailorsinfo.website.com");
136
+ final Venue venue = new VenueImpl(venueName);
137
+
138
+ for (String courseAreaName : courseAreaNames) {
139
+ CourseArea courseArea = DomainFactory.INSTANCE.getOrCreateCourseArea(UUID.randomUUID(), courseAreaName);
140
+ venue.addCourseArea(courseArea);
141
+ }
142
+ MongoObjectFactory mof = PersistenceFactory.INSTANCE.getMongoObjectFactory(getMongoService());
143
+ final Event event = new EventImpl(eventName, eventStartDate, eventEndDate, venue, /*isPublic*/ true, UUID.randomUUID());
144
+ final LeaderboardGroup lg1 = createLeaderboardGroup("lg1");
145
+ final LeaderboardGroup lg2 = createLeaderboardGroup("lg2");
146
+ event.addLeaderboardGroup(lg1);
147
+ event.addLeaderboardGroup(lg2);
148
+ event.setDescription(eventDescription);
149
+ event.setOfficialWebsiteURL(officialWebsiteURL);
150
+ event.setSailorsInfoWebsiteURL(sailorsInfoWebsiteURL);
151
+ mof.storeEvent(event);
152
+
153
+ DomainObjectFactory dof = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE);
154
+ final Event loadedEvent = dof.loadEvent(eventName);
155
+ dof.loadLeaderboardGroupLinksForEvents(new EventResolver() {
156
+ @Override
157
+ public Event getEvent(Serializable id) {
158
+ return id.equals(loadedEvent.getId()) ? loadedEvent : null;
159
+ }
160
+ }, new LeaderboardGroupResolver() {
161
+ @Override
162
+ public LeaderboardGroup getLeaderboardGroupByName(String leaderboardGroupName) {
163
+ return leaderboardGroupName.equals(lg1.getName()) ? lg1 : leaderboardGroupName.equals(lg2.getName()) ? lg2 : null;
164
+ }
165
+
166
+ @Override
167
+ public LeaderboardGroup getLeaderboardGroupByID(UUID leaderboardGroupID) {
168
+ return leaderboardGroupID.equals(lg1.getId()) ? lg1 : leaderboardGroupID.equals(lg2.getId()) ? lg2 : null;
169
+ }
170
+ });
171
+ assertNotNull(loadedEvent);
172
+ assertEquals(eventName, loadedEvent.getName());
173
+ assertEquals(eventDescription, loadedEvent.getDescription());
174
+ assertEquals(event.getOfficialWebsiteURL(), loadedEvent.getOfficialWebsiteURL());
175
+ assertEquals(event.getSailorsInfoWebsiteURL(), loadedEvent.getSailorsInfoWebsiteURL());
176
+ assertEquals(2, Util.size(loadedEvent.getLeaderboardGroups()));
177
+ Iterator<LeaderboardGroup> lgIter = loadedEvent.getLeaderboardGroups().iterator();
178
+ assertSame(lg1, lgIter.next());
179
+ assertSame(lg2, lgIter.next());
180
+ final Venue loadedVenue = loadedEvent.getVenue();
181
+ assertNotNull(loadedVenue);
182
+ assertEquals(venueName, loadedVenue.getName());
183
+ assertEquals(courseAreaNames.length, Util.size(loadedVenue.getCourseAreas()));
184
+ int i=0;
185
+ for (CourseArea loadedCourseArea : loadedVenue.getCourseAreas()) {
186
+ assertEquals(courseAreaNames[i++], loadedCourseArea.getName());
187
+ }
188
+ }
189
+
190
+ @Test
191
+ public void testLoadStoreSimpleEventAndRegattaWithCourseArea() {
192
+ final String eventName = "Event Name";
193
+ final String venueName = "Venue Name";
194
+ final String courseAreaName = "Alpha";
195
+ final Venue venue = new VenueImpl(venueName);
196
+ CourseArea courseArea = DomainFactory.INSTANCE.getOrCreateCourseArea(UUID.randomUUID(), courseAreaName);
197
+ venue.addCourseArea(courseArea);
198
+
199
+ MongoObjectFactory mof = PersistenceFactory.INSTANCE.getMongoObjectFactory(getMongoService());
200
+ Event event = new EventImpl(eventName, eventStartDate, eventEndDate, venue, /*isPublic*/ true, UUID.randomUUID());
201
+ mof.storeEvent(event);
202
+
203
+ final String regattaBaseName = "Kieler Woche";
204
+ BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("29erXX", /* typicallyStartsUpwind */ true);
205
+ Regatta regatta = createRegatta(RegattaImpl.getDefaultName(regattaBaseName, boatClass.getName()), boatClass,
206
+ regattaStartDate, regattaEndDate, /* persistent */ true, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), courseArea, OneDesignRankingMetric::new);
207
+ mof.storeRegatta(regatta);
208
+
209
+ DomainObjectFactory dof = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE);
210
+ Regatta loadedRegatta = dof.loadRegatta(regatta.getName(), /* trackedRegattaRegistry */ null);
211
+ assertNotNull(loadedRegatta);
212
+ assertEquals(regatta.getName(), loadedRegatta.getName());
213
+ assertEquals(Util.size(regatta.getSeries()), Util.size(loadedRegatta.getSeries()));
214
+ assertNotNull(loadedRegatta.getDefaultCourseArea());
215
+ assertEquals(loadedRegatta.getDefaultCourseArea().getId(), courseArea.getId());
216
+ assertEquals(loadedRegatta.getDefaultCourseArea().getName(), courseArea.getName());
217
+ assertEquals(loadedRegatta.getStartDate(), regattaStartDate);
218
+ assertEquals(loadedRegatta.getEndDate(), regattaEndDate);
219
+ }
220
+
221
+ @Test
222
+ public void testLoadStoreSimpleEventWithImages() throws MalformedURLException {
223
+ final URL imageURL = new URL("http://some.host/with/some/file2.jpg");
224
+ final String copyright = "copyright by Alex";
225
+ final String imageTitle = "My image title";
226
+ final String imageSubtitle = "My image subtitle";
227
+ final Integer imageWidth = 500;
228
+ final Integer imageHeight = 300;
229
+ final TimePoint createdAt = MillisecondsTimePoint.now();
230
+ final String eventName = "Event Name";
231
+ final String venueName = "Venue Name";
232
+ final String courseAreaName = "Alpha";
233
+ final Venue venue = new VenueImpl(venueName);
234
+ CourseArea courseArea = DomainFactory.INSTANCE.getOrCreateCourseArea(UUID.randomUUID(), courseAreaName);
235
+ venue.addCourseArea(courseArea);
236
+
237
+ MongoObjectFactory mof = PersistenceFactory.INSTANCE.getMongoObjectFactory(getMongoService());
238
+ Event event = new EventImpl(eventName, eventStartDate, eventEndDate, venue, /*isPublic*/ true, UUID.randomUUID());
239
+
240
+ ImageDescriptor image1 = new ImageDescriptorImpl(imageURL, createdAt);
241
+ image1.setCopyright(copyright);
242
+ image1.setSize(imageWidth, imageHeight);
243
+ image1.setTitle(imageTitle);
244
+ image1.setSubtitle(imageSubtitle);
245
+ image1.addTag("Tag1");
246
+ image1.addTag("Tag2");
247
+ image1.addTag("Tag3");
248
+ event.addImage(image1);
249
+
250
+ ImageDescriptor image2 = new ImageDescriptorImpl(new URL("http://some.host/with/some/file2.jpg"), MillisecondsTimePoint.now());
251
+ image2.setCopyright("copyright");
252
+ event.addImage(image2);
253
+
254
+ mof.storeEvent(event);
255
+
256
+ DomainObjectFactory dof = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE);
257
+ final Event loadedEvent = dof.loadEvent(eventName);
258
+ assertEquals(2, Util.size(loadedEvent.getImages()));
259
+ ImageDescriptor loadedImage1 = loadedEvent.getImages().iterator().next();
260
+ assertNotNull(loadedImage1);
261
+ assertEquals(imageURL, loadedImage1.getURL());
262
+ assertEquals(copyright, loadedImage1.getCopyright());
263
+ assertEquals(copyright, loadedImage1.getCopyright());
264
+ assertEquals(imageTitle, loadedImage1.getTitle());
265
+ assertEquals(imageSubtitle, loadedImage1.getSubtitle());
266
+ assertEquals(createdAt, loadedImage1.getCreatedAtDate());
267
+ assertEquals(imageWidth, loadedImage1.getWidthInPx());
268
+ assertEquals(imageHeight, loadedImage1.getHeightInPx());
269
+ assertEquals(3, Util.size(loadedImage1.getTags()));
270
+ }
271
+
272
+ @Test
273
+ public void testLoadStoreSimpleEventWithVideos() throws MalformedURLException {
274
+ final URL videoURL = new URL("http://some.host/with/some/video.mpg");
275
+ final URL videoThumbnailURL = new URL("http://some.host/with/some/video_thumbnail.jpg");
276
+ final Locale locale = Locale.GERMAN;
277
+ final Integer videoLengthInSeconds = 2 * 60 * 60 * 1000; // 2h
278
+ final MimeType mimeType = MimeType.mp4;
279
+ final String copyright = "copyright by Don";
280
+ final String videoTitle = "My video title";
281
+ final String videoSubtitle = "My video subtitle";
282
+ final TimePoint createdAt = MillisecondsTimePoint.now();
283
+ final String eventName = "Event Name";
284
+ final String venueName = "Venue Name";
285
+ final String courseAreaName = "Alpha";
286
+ final Venue venue = new VenueImpl(venueName);
287
+ CourseArea courseArea = DomainFactory.INSTANCE.getOrCreateCourseArea(UUID.randomUUID(), courseAreaName);
288
+ venue.addCourseArea(courseArea);
289
+
290
+ MongoObjectFactory mof = PersistenceFactory.INSTANCE.getMongoObjectFactory(getMongoService());
291
+ Event event = new EventImpl(eventName, eventStartDate, eventEndDate, venue, /*isPublic*/ true, UUID.randomUUID());
292
+
293
+ VideoDescriptor video1 = new VideoDescriptorImpl(videoURL, mimeType, createdAt);
294
+ video1.setCopyright(copyright);
295
+ video1.setTitle(videoTitle);
296
+ video1.setLocale(locale);
297
+ video1.setSubtitle(videoSubtitle);
298
+ video1.setThumbnailURL(videoThumbnailURL);
299
+ video1.setLengthInSeconds(videoLengthInSeconds);
300
+ video1.addTag("Tag1");
301
+ video1.addTag("Tag2");
302
+ video1.addTag("Tag3");
303
+ event.addVideo(video1);
304
+
305
+ VideoDescriptor video2 = new VideoDescriptorImpl(new URL("http://some.host/with/some/file2.ogg"), MimeType.ogg, MillisecondsTimePoint.now());
306
+ video2.setCopyright("copyright");
307
+ event.addVideo(video2);
308
+
309
+ mof.storeEvent(event);
310
+
311
+ DomainObjectFactory dof = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE);
312
+ final Event loadedEvent = dof.loadEvent(eventName);
313
+ assertEquals(2, Util.size(loadedEvent.getVideos()));
314
+ VideoDescriptor loadedVideo1 = loadedEvent.getVideos().iterator().next();
315
+ assertNotNull(loadedVideo1);
316
+ assertEquals(videoURL, loadedVideo1.getURL());
317
+ assertEquals(videoThumbnailURL, loadedVideo1.getThumbnailURL());
318
+ assertEquals(videoLengthInSeconds, loadedVideo1.getLengthInSeconds());
319
+ assertEquals(copyright, loadedVideo1.getCopyright());
320
+ assertEquals(locale, loadedVideo1.getLocale());
321
+ assertEquals(videoTitle, loadedVideo1.getTitle());
322
+ assertEquals(videoSubtitle, loadedVideo1.getSubtitle());
323
+ assertEquals(createdAt, loadedVideo1.getCreatedAtDate());
324
+ assertEquals(3, Util.size(loadedVideo1.getTags()));
325
+ }
326
+
327
+ @SuppressWarnings("deprecation")
328
+ @Test
329
+ /**
330
+ * We expected that the migration code creates also an image URL for each image we create.
331
+ * Images with the 'Sponsor' tag should create a corresponding sponsor image URL
332
+ * Videos should create a video URL.
333
+ */
334
+ public void testLoadStoreSimpleEventWithImageAndVideoURLMigration() throws MalformedURLException {
335
+ final URL imageURL = new URL("http://some.host/with/some/bla.jpg");
336
+ final URL sponsorImageURL = new URL("http://some.host/with/some/sponsor.jpg");
337
+ final URL videoURL = new URL("http://some.host/with/some/video.mpg");
338
+ final TimePoint createdAt = MillisecondsTimePoint.now();
339
+ final String eventName = "Event Name";
340
+ final Venue venue = new VenueImpl("My Venue");
341
+ CourseArea courseArea = DomainFactory.INSTANCE.getOrCreateCourseArea(UUID.randomUUID(), "Alfa");
342
+ venue.addCourseArea(courseArea);
343
+
344
+ MongoObjectFactory mof = PersistenceFactory.INSTANCE.getMongoObjectFactory(getMongoService());
345
+ Event event = new EventImpl(eventName, eventStartDate, eventEndDate, venue, /*isPublic*/ true, UUID.randomUUID());
346
+
347
+ ImageDescriptor image1 = new ImageDescriptorImpl(imageURL, createdAt);
348
+ image1.addTag(MediaTagConstants.GALLERY);
349
+ event.addImage(image1);
350
+
351
+ ImageDescriptor image2 = new ImageDescriptorImpl(sponsorImageURL, createdAt);
352
+ event.addImage(image2);
353
+ image2.addTag(MediaTagConstants.SPONSOR);
354
+
355
+ VideoDescriptor video1 = new VideoDescriptorImpl(videoURL, MimeType.mp4, createdAt);
356
+ event.addVideo(video1);
357
+
358
+ mof.storeEvent(event);
359
+
360
+ DomainObjectFactory dof = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE);
361
+ final Event loadedEvent = dof.loadEvent(eventName);
362
+ assertEquals(2, Util.size(loadedEvent.getImages()));
363
+ assertEquals(1, Util.size(loadedEvent.getVideos()));
364
+ assertEquals(1, Util.size(loadedEvent.getImageURLs()));
365
+ assertEquals(1, Util.size(loadedEvent.getSponsorImageURLs()));
366
+ assertEquals(1, Util.size(loadedEvent.getVideoURLs()));
367
+ }
368
+
369
+
370
+ @Test
371
+ public void testLoadStoreRegattaConfiguration() {
372
+
373
+ RegattaConfigurationImpl configuration = new RegattaConfigurationImpl();
374
+ configuration.setDefaultRacingProcedureType(RacingProcedureType.BASIC);
375
+
376
+ BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("ESS40", false);
377
+ Regatta regatta = createRegattaAndAddRaceColumns(1, 1, RegattaImpl.getDefaultName("RR", boatClass.getName()), boatClass,
378
+ regattaStartDate, regattaEndDate, false, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.HIGH_POINT), OneDesignRankingMetric::new);
379
+ regatta.setRegattaConfiguration(configuration);
380
+
381
+ MongoObjectFactory mof = PersistenceFactory.INSTANCE.getMongoObjectFactory(getMongoService());
382
+ mof.storeRegatta(regatta);
383
+ DomainObjectFactory dof = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE);
384
+ Regatta loadedRegatta = dof.loadRegatta(regatta.getName(), null);
385
+
386
+ assertNotNull(loadedRegatta.getRegattaConfiguration());
387
+ assertEquals(RacingProcedureType.BASIC, loadedRegatta.getRegattaConfiguration().getDefaultRacingProcedureType());
388
+ }
389
+
390
+ @Test
391
+ public void testLoadStoreSimpleRegattaLeaderboard() {
392
+ RacingEventService res = new RacingEventServiceImpl(PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE), PersistenceFactory.INSTANCE
393
+ .getMongoObjectFactory(getMongoService()), MediaDBFactory.INSTANCE.getMediaDB(getMongoService()), EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE);
394
+ final int numberOfQualifyingRaces = 5;
395
+ final int numberOfFinalRaces = 7;
396
+ final String regattaBaseName = "Kieler Woche";
397
+ BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("29erXX", /* typicallyStartsUpwind */ true);
398
+ Regatta regattaProxy = createRegatta(RegattaImpl.getDefaultName(regattaBaseName, boatClass.getName()), boatClass,
399
+ regattaStartDate, regattaEndDate, /* persistent */ true, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null, OneDesignRankingMetric::new);
400
+ final String regattaName = regattaProxy.getName();
401
+ Regatta regatta = res.createRegatta(regattaName, regattaProxy.getBoatClass().getName(), regattaStartDate, regattaEndDate,
402
+ "123", regattaProxy.getSeries(), regattaProxy.isPersistent(), DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null, /* useStartTimeInference */ true, OneDesignRankingMetric::new);
403
+ addRaceColumns(numberOfQualifyingRaces, numberOfFinalRaces, regatta);
404
+ res.addRegattaLeaderboard(regatta.getRegattaIdentifier(), null, new int[] { 3, 5 });
405
+ DomainObjectFactory dof = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE);
406
+ Regatta loadedRegatta = dof.loadRegatta(regatta.getName(), /* trackedRegattaRegistry */ null);
407
+ assertNotNull(loadedRegatta);
408
+ assertEquals(regatta.getName(), loadedRegatta.getName());
409
+ assertEquals(Util.size(regatta.getSeries()), Util.size(loadedRegatta.getSeries()));
410
+
411
+ Leaderboard loadedLeaderboard = dof.loadLeaderboard(regatta.getName(), res);
412
+ assertNotNull(loadedLeaderboard);
413
+ assertTrue(loadedLeaderboard instanceof RegattaLeaderboard);
414
+ RegattaLeaderboard loadedRegattaLeaderboard = (RegattaLeaderboard) loadedLeaderboard;
415
+ assertSame(regatta, loadedRegattaLeaderboard.getRegatta());
416
+ }
417
+
418
+ @Test
419
+ public void testLoadStoreRegattaLeaderboardWithScoreCorrections() {
420
+ // for some reason the dropping of collections doesn't work reliably on Linux... explicitly drop those collections that we depend on
421
+ getMongoService().getDB().getCollection(CollectionNames.LEADERBOARDS.name()).drop();
422
+ getMongoService().getDB().getCollection(CollectionNames.REGATTAS.name()).drop();
423
+ Competitor hasso = AbstractLeaderboardTest.createCompetitor("Dr. Hasso Plattner");
424
+ BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("29erXX", /* typicallyStartsUpwind */ true);
425
+ final DynamicTrackedRegatta[] trackedRegatta = new DynamicTrackedRegatta[1];
426
+ final DynamicTrackedRace q2YellowTrackedRace = new MockedTrackedRaceWithFixedRank(hasso, /* rank */ 1, /* started */ false, boatClass) {
427
+ private static final long serialVersionUID = 1234L;
428
+ @Override
429
+ public RegattaAndRaceIdentifier getRaceIdentifier() {
430
+ return new RegattaNameAndRaceName("Kieler Woche (29ERXX)", "Yellow Race 2");
431
+ }
432
+ @Override
433
+ public DynamicTrackedRegatta getTrackedRegatta() {
434
+ return trackedRegatta[0];
435
+ }
436
+ };
437
+ RacingEventService res = createRacingEventServiceWithOneMockedTrackedRace(q2YellowTrackedRace);
438
+ final int numberOfQualifyingRaces = 5;
439
+ final int numberOfFinalRaces = 7;
440
+ final String regattaBaseName = "Kieler Woche";
441
+
442
+ Regatta regattaProxy = createRegatta(RegattaImpl.getDefaultName(regattaBaseName, boatClass.getName()), boatClass, regattaStartDate, regattaEndDate, /* persistent */ true, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null, OneDesignRankingMetric::new);
443
+ Regatta regatta = res.createRegatta(regattaProxy.getName(), regattaProxy.getBoatClass().getName(), regattaProxy.getStartDate(), regattaProxy.getEndDate(),
444
+ "123", regattaProxy.getSeries(), regattaProxy.isPersistent(), DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null, /* useStartTimeInference */ true, OneDesignRankingMetric::new);
445
+ trackedRegatta[0] = new DynamicTrackedRegattaImpl(regatta);
446
+ addRaceColumns(numberOfQualifyingRaces, numberOfFinalRaces, regatta);
447
+ logColumnsInRegatta(regatta);
448
+ RegattaLeaderboard regattaLeaderboard = res.addRegattaLeaderboard(regatta.getRegattaIdentifier(), null, new int[] { 3, 5 });
449
+ assertSame(regatta, regattaLeaderboard.getRegatta());
450
+ final RaceColumnInSeries q2 = regatta.getSeriesByName("Qualifying").getRaceColumnByName("Q2");
451
+ final Fleet yellow = q2.getFleetByName("Yellow");
452
+ logColumnsInRegatta(regatta);
453
+ logColumnsInRegattaLeaderboard(regattaLeaderboard);
454
+ assertNotNull(regattaLeaderboard.getRaceColumnByName(q2.getName()));
455
+ res.apply(new ConnectTrackedRaceToLeaderboardColumn(regattaLeaderboard.getName(), q2.getName(), yellow
456
+ .getName(), q2YellowTrackedRace.getRaceIdentifier()));
457
+ res.apply(new UpdateLeaderboardMaxPointsReason(regattaLeaderboard.getName(), q2.getName(), hasso.getId().toString(),
458
+ MaxPointsReason.DNF, MillisecondsTimePoint.now()));
459
+
460
+ // load new RacingEventService including regatta and leaderboard
461
+ RacingEventService resForLoading = createRacingEventServiceWithOneMockedTrackedRace(q2YellowTrackedRace);
462
+ Regatta loadedRegatta = resForLoading.getRegattaByName("Kieler Woche (29ERXX)");
463
+ assertNotNull(loadedRegatta);
464
+ assertEquals(regatta.getName(), loadedRegatta.getName());
465
+ assertEquals(Util.size(regatta.getSeries()), Util.size(loadedRegatta.getSeries()));
466
+ Leaderboard loadedLeaderboard = resForLoading.getLeaderboardByName(loadedRegatta.getName());
467
+ assertNotNull(loadedLeaderboard);
468
+ assertEquals(((ThresholdBasedResultDiscardingRule) regattaLeaderboard.getResultDiscardingRule()).getDiscardIndexResultsStartingWithHowManyRaces().length,
469
+ ((ThresholdBasedResultDiscardingRule) loadedLeaderboard.getResultDiscardingRule()).getDiscardIndexResultsStartingWithHowManyRaces().length);
470
+ assertTrue(Arrays.equals(((ThresholdBasedResultDiscardingRule) regattaLeaderboard.getResultDiscardingRule()).getDiscardIndexResultsStartingWithHowManyRaces(),
471
+ ((ThresholdBasedResultDiscardingRule) loadedLeaderboard.getResultDiscardingRule()).getDiscardIndexResultsStartingWithHowManyRaces()));
472
+ assertTrue(loadedLeaderboard instanceof RegattaLeaderboard);
473
+ RegattaLeaderboard loadedRegattaLeaderboard = (RegattaLeaderboard) loadedLeaderboard;
474
+ assertSame(loadedRegatta, loadedRegattaLeaderboard.getRegatta());
475
+ // now re-associate the tracked race to let score correction "snap" to competitor:
476
+ final RaceColumnInSeries loadedQ2 = loadedRegatta.getSeriesByName("Qualifying").getRaceColumnByName("Q2");
477
+ final Fleet loadedYellow = loadedQ2.getFleetByName("Yellow");
478
+ // adjust tracked regatta for tracked race:
479
+ trackedRegatta[0] = new DynamicTrackedRegattaImpl(loadedRegatta);
480
+ resForLoading.apply(new ConnectTrackedRaceToLeaderboardColumn(loadedLeaderboard.getName(), loadedQ2.getName(), loadedYellow
481
+ .getName(), q2YellowTrackedRace.getRaceIdentifier()));
482
+ MaxPointsReason hassosLoadedMaxPointsReason = loadedLeaderboard.getScoreCorrection().getMaxPointsReason(hasso, loadedQ2, MillisecondsTimePoint.now());
483
+ assertEquals(MaxPointsReason.DNF, hassosLoadedMaxPointsReason);
484
+ }
485
+
486
+ private void logColumnsInRegattaLeaderboard(RegattaLeaderboard regattaLeaderboard) {
487
+ StringBuilder rlbrcNames = new StringBuilder();
488
+ for (RaceColumn rlbrc : regattaLeaderboard.getRaceColumns()) {
489
+ rlbrcNames.append("; ");
490
+ rlbrcNames.append(rlbrc.getName());
491
+ }
492
+ logger.info("columns in regatta leaderboard for regatta "+regattaLeaderboard.getRegatta().getName()+" ("+
493
+ regattaLeaderboard.getRegatta().hashCode()+"): "+rlbrcNames);
494
+ logColumnsInRegatta(regattaLeaderboard.getRegatta());
495
+ }
496
+
497
+ private void logColumnsInRegatta(Regatta regatta) {
498
+ StringBuilder rrcNames = new StringBuilder();
499
+ for (Series series : regatta.getSeries()) {
500
+ for (RaceColumn raceColumn : series.getRaceColumns()) {
501
+ rrcNames.append("; ");
502
+ rrcNames.append(raceColumn.getName());
503
+ }
504
+ }
505
+ logger.info("columns in regatta "+regatta.getName()+" ("+regatta.hashCode()+") : "+rrcNames);
506
+ }
507
+
508
+ private RacingEventServiceImpl createRacingEventServiceWithOneMockedTrackedRace(final DynamicTrackedRace q2YellowTrackedRace) {
509
+ return new RacingEventServiceImpl(PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE), PersistenceFactory.INSTANCE
510
+ .getMongoObjectFactory(getMongoService()), MediaDBFactory.INSTANCE.getMediaDB(getMongoService()), EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE) {
511
+ @Override
512
+ public DynamicTrackedRace getExistingTrackedRace(RegattaAndRaceIdentifier raceIdentifier) {
513
+ return q2YellowTrackedRace;
514
+ }
515
+ };
516
+ }
517
+
518
+ @Test
519
+ public void testLoadStoreSimpleRegatta() {
520
+ final int numberOfQualifyingRaces = 5;
521
+ final int numberOfFinalRaces = 7;
522
+ final String regattaBaseName = "Kieler Woche";
523
+ BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("29erXX", /* typicallyStartsUpwind */true);
524
+ final String regattaName = RegattaImpl.getDefaultName(regattaBaseName, boatClass.getName());
525
+ Regatta regatta = createRegattaAndAddRaceColumns(numberOfQualifyingRaces, numberOfFinalRaces, regattaName,
526
+ boatClass, regattaStartDate, regattaEndDate, /* persistent */false,
527
+ DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), OneDesignRankingMetric::new);
528
+ MongoObjectFactory mof = PersistenceFactory.INSTANCE.getMongoObjectFactory(getMongoService());
529
+ mof.storeRegatta(regatta);
530
+
531
+ DomainObjectFactory dof = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE);
532
+ Regatta loadedRegatta = dof.loadRegatta(regatta.getName(), /* trackedRegattaRegistry */ null);
533
+ assertSame(LowPoint.class, loadedRegatta.getScoringScheme().getClass());
534
+ assertEquals(regattaName, loadedRegatta.getName());
535
+ Iterator<? extends Series> seriesIter = loadedRegatta.getSeries().iterator();
536
+ Series loadedQualifyingSeries = seriesIter.next();
537
+ assertEquals(numberOfQualifyingRaces, Util.size(loadedQualifyingSeries.getRaceColumns()));
538
+ assertEquals(0, loadedQualifyingSeries.getFleetByName("Yellow").compareTo(loadedQualifyingSeries.getFleetByName("Blue")));
539
+ Series loadedFinalSeries = seriesIter.next();
540
+ assertEquals(numberOfFinalRaces, Util.size(loadedFinalSeries.getRaceColumns()));
541
+ assertTrue(loadedFinalSeries.getFleetByName("Silver").compareTo(loadedFinalSeries.getFleetByName("Gold")) > 0);
542
+ Series loadedMedalSeries = seriesIter.next();
543
+ assertEquals(1, Util.size(loadedMedalSeries.getRaceColumns()));
544
+ assertEquals(loadedRegatta.getStartDate(), regattaStartDate);
545
+ assertEquals(loadedRegatta.getEndDate(), regattaEndDate);
546
+ assertEquals(RankingMetrics.ONE_DESIGN, loadedRegatta.getRankingMetricType());
547
+ }
548
+
549
+ @Test
550
+ public void testLoadStoreRegattaWithHandicapRanking() {
551
+ final int numberOfQualifyingRaces = 5;
552
+ final int numberOfFinalRaces = 7;
553
+ final String regattaBaseName = "Kieler Woche";
554
+ BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("29erXX", /* typicallyStartsUpwind */true);
555
+ final String regattaName = RegattaImpl.getDefaultName(regattaBaseName, boatClass.getName());
556
+ Regatta regatta = createRegattaAndAddRaceColumns(numberOfQualifyingRaces, numberOfFinalRaces, regattaName,
557
+ boatClass, regattaStartDate, regattaEndDate, /* persistent */false,
558
+ DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), TimeOnTimeAndDistanceRankingMetric::new);
559
+ assertEquals(RankingMetrics.TIME_ON_TIME_AND_DISTANCE, regatta.getRankingMetricType());
560
+ MongoObjectFactory mof = PersistenceFactory.INSTANCE.getMongoObjectFactory(getMongoService());
561
+ mof.storeRegatta(regatta);
562
+
563
+ DomainObjectFactory dof = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE);
564
+ Regatta loadedRegatta = dof.loadRegatta(regatta.getName(), /* trackedRegattaRegistry */ null);
565
+ assertSame(LowPoint.class, loadedRegatta.getScoringScheme().getClass());
566
+ assertEquals(regattaName, loadedRegatta.getName());
567
+ Iterator<? extends Series> seriesIter = loadedRegatta.getSeries().iterator();
568
+ Series loadedQualifyingSeries = seriesIter.next();
569
+ assertEquals(numberOfQualifyingRaces, Util.size(loadedQualifyingSeries.getRaceColumns()));
570
+ assertEquals(0, loadedQualifyingSeries.getFleetByName("Yellow").compareTo(loadedQualifyingSeries.getFleetByName("Blue")));
571
+ Series loadedFinalSeries = seriesIter.next();
572
+ assertEquals(numberOfFinalRaces, Util.size(loadedFinalSeries.getRaceColumns()));
573
+ assertTrue(loadedFinalSeries.getFleetByName("Silver").compareTo(loadedFinalSeries.getFleetByName("Gold")) > 0);
574
+ Series loadedMedalSeries = seriesIter.next();
575
+ assertEquals(1, Util.size(loadedMedalSeries.getRaceColumns()));
576
+ assertEquals(loadedRegatta.getStartDate(), regattaStartDate);
577
+ assertEquals(loadedRegatta.getEndDate(), regattaEndDate);
578
+ assertEquals(RankingMetrics.TIME_ON_TIME_AND_DISTANCE, loadedRegatta.getRankingMetricType());
579
+ }
580
+
581
+ @Test
582
+ public void testLoadStoreSimpleRegattaWithEmptyStartAndEndDate() {
583
+ final int numberOfQualifyingRaces = 1;
584
+ final int numberOfFinalRaces = 1;
585
+ final String regattaBaseName = "Kieler Woche";
586
+ BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("29erXX", /* typicallyStartsUpwind */true);
587
+ final String regattaName = RegattaImpl.getDefaultName(regattaBaseName, boatClass.getName());
588
+ Regatta regatta = createRegattaAndAddRaceColumns(numberOfQualifyingRaces, numberOfFinalRaces, regattaName,
589
+ boatClass, null, null, /* persistent */false,
590
+ DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), OneDesignRankingMetric::new);
591
+ MongoObjectFactory mof = PersistenceFactory.INSTANCE.getMongoObjectFactory(getMongoService());
592
+ mof.storeRegatta(regatta);
593
+
594
+ DomainObjectFactory dof = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE);
595
+ Regatta loadedRegatta = dof.loadRegatta(regatta.getName(), /* trackedRegattaRegistry */ null);
596
+ assertEquals(regattaName, loadedRegatta.getName());
597
+ assertEquals(loadedRegatta.getStartDate(), null);
598
+ assertEquals(loadedRegatta.getEndDate(), null);
599
+ }
600
+
601
+ @Test
602
+ public void testLoadStoreSimpleRegattaWithSeriesScoringScheme() {
603
+ final int numberOfQualifyingRaces = 5;
604
+ final int numberOfFinalRaces = 7;
605
+ final String regattaBaseName = "Kieler Woche";
606
+ BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("29erXX", /* typicallyStartsUpwind */ true);
607
+ Regatta regatta = createRegattaAndAddRaceColumns(numberOfQualifyingRaces, numberOfFinalRaces, RegattaImpl.getDefaultName(regattaBaseName, boatClass.getName()),
608
+ boatClass, regattaStartDate, regattaEndDate, /* persistent */false, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), OneDesignRankingMetric::new);
609
+ regatta.getSeriesByName("Qualifying").setResultDiscardingRule(new ThresholdBasedResultDiscardingRuleImpl(new int[] { 1, 2, 3 }));
610
+ MongoObjectFactory mof = PersistenceFactory.INSTANCE.getMongoObjectFactory(getMongoService());
611
+ mof.storeRegatta(regatta);
612
+
613
+ DomainObjectFactory dof = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE);
614
+ Regatta loadedRegatta = dof.loadRegatta(regatta.getName(), /* trackedRegattaRegistry */ null);
615
+ assertTrue(Arrays.equals(new int[] { 1, 2, 3 },
616
+ loadedRegatta.getSeriesByName("Qualifying").getResultDiscardingRule().getDiscardIndexResultsStartingWithHowManyRaces()));
617
+ }
618
+
619
+ @Test
620
+ public void testLoadStoreSimpleRegattaWithScoreForMedalStartingWithZero() {
621
+ final int numberOfQualifyingRaces = 5;
622
+ final int numberOfFinalRaces = 7;
623
+ final String regattaBaseName = "Kieler Woche";
624
+ BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("29erXX", /* typicallyStartsUpwind */ true);
625
+ Regatta regatta = createRegattaAndAddRaceColumns(numberOfQualifyingRaces, numberOfFinalRaces, RegattaImpl.getDefaultName(regattaBaseName, boatClass.getName()),
626
+ boatClass, regattaStartDate, regattaEndDate, /* persistent */false, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), OneDesignRankingMetric::new);
627
+ regatta.getSeriesByName("Medal").setStartsWithZeroScore(true);
628
+ MongoObjectFactory mof = PersistenceFactory.INSTANCE.getMongoObjectFactory(getMongoService());
629
+ mof.storeRegatta(regatta);
630
+
631
+ DomainObjectFactory dof = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE);
632
+ Regatta loadedRegatta = dof.loadRegatta(regatta.getName(), /* trackedRegattaRegistry */ null);
633
+ assertFalse(loadedRegatta.getSeriesByName("Qualifying").isStartsWithZeroScore());
634
+ assertTrue(loadedRegatta.getSeriesByName("Medal").isStartsWithZeroScore());
635
+ }
636
+
637
+ @Test
638
+ public void testLoadStoreSimpleRegattaWithHighPointScoringScheme() {
639
+ final int numberOfQualifyingRaces = 5;
640
+ final int numberOfFinalRaces = 7;
641
+ final String regattaBaseName = "ESS40 Cardiff 2012";
642
+ BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("ESS40", /* typicallyStartsUpwind */ false);
643
+ Regatta regatta = createRegattaAndAddRaceColumns(numberOfQualifyingRaces, numberOfFinalRaces, RegattaImpl.getDefaultName(regattaBaseName, boatClass.getName()),
644
+ boatClass, regattaStartDate, regattaEndDate, /* persistent */false, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.HIGH_POINT), OneDesignRankingMetric::new);
645
+ MongoObjectFactory mof = PersistenceFactory.INSTANCE.getMongoObjectFactory(getMongoService());
646
+ mof.storeRegatta(regatta);
647
+
648
+ DomainObjectFactory dof = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE);
649
+ Regatta loadedRegatta = dof.loadRegatta(regatta.getName(), /* trackedRegattaRegistry */ null);
650
+ assertSame(HighPoint.class, loadedRegatta.getScoringScheme().getClass());
651
+ }
652
+
653
+ @Test
654
+ public void testLoadStoreRegattaWithFleetsEnsuringIdenticalFleetsInSeriesAndRaceColumns() {
655
+ final int numberOfQualifyingRaces = 5;
656
+ final int numberOfFinalRaces = 7;
657
+ final String regattaBaseName = "Kieler Woche";
658
+ BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("29erXX", /* typicallyStartsUpwind */ true);
659
+ final String regattaName = RegattaImpl.getDefaultName(regattaBaseName, boatClass.getName());
660
+ Regatta regatta = createRegattaAndAddRaceColumns(numberOfQualifyingRaces, numberOfFinalRaces,
661
+ regattaName, boatClass, regattaStartDate, regattaEndDate,
662
+ /* persistent */false, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), OneDesignRankingMetric::new);
663
+ MongoObjectFactory mof = PersistenceFactory.INSTANCE.getMongoObjectFactory(getMongoService());
664
+ mof.storeRegatta(regatta);
665
+
666
+ DomainObjectFactory dof = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE);
667
+ Regatta loadedRegatta = dof.loadRegatta(regatta.getName(), /* trackedRegattaRegistry */ null);
668
+ assertEquals(regattaName, loadedRegatta.getName());
669
+ Iterator<? extends Series> seriesIter = loadedRegatta.getSeries().iterator();
670
+ Series loadedQualifyingSeries = seriesIter.next();
671
+ int i=1;
672
+ for (RaceColumn raceColumn : loadedQualifyingSeries.getRaceColumns()) {
673
+ assertTrue(raceColumn instanceof RaceColumnInSeriesImpl);
674
+ assertEquals("Q"+i, raceColumn.getName());
675
+ assertTrue(Util.equals(loadedQualifyingSeries.getFleets(), raceColumn.getFleets()));
676
+ i++;
677
+ }
678
+ Series loadedFinalSeries = seriesIter.next();
679
+ i=1;
680
+ for (RaceColumn raceColumn : loadedFinalSeries.getRaceColumns()) {
681
+ assertTrue(raceColumn instanceof RaceColumnInSeriesImpl);
682
+ assertEquals("F"+i, raceColumn.getName());
683
+ assertTrue(Util.equals(loadedFinalSeries.getFleets(), raceColumn.getFleets()));
684
+ i++;
685
+ }
686
+ Series loadedMedalSeries = seriesIter.next();
687
+ for (RaceColumn raceColumn : loadedMedalSeries.getRaceColumns()) {
688
+ assertTrue(raceColumn instanceof RaceColumnInSeriesImpl);
689
+ assertEquals("M", raceColumn.getName());
690
+ assertTrue(Util.equals(loadedMedalSeries.getFleets(), raceColumn.getFleets()));
691
+ }
692
+ }
693
+
694
+ @Test
695
+ public void testLoadStoreRegattaWithFleetsEnsuringFleetOrdering() {
696
+ final String regattaBaseName = "Kieler Woche";
697
+ BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("29erXX", /* typicallyStartsUpwind */ true);
698
+ final String regattaName = RegattaImpl.getDefaultName(regattaBaseName, boatClass.getName());
699
+ Regatta regatta = createRegatta(regattaName, boatClass, regattaStartDate, regattaEndDate,
700
+ /* persistent */ false, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null, OneDesignRankingMetric::new);
701
+ MongoObjectFactory mof = PersistenceFactory.INSTANCE.getMongoObjectFactory(getMongoService());
702
+ mof.storeRegatta(regatta);
703
+
704
+ DomainObjectFactory dof = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE);
705
+ Regatta loadedRegatta = dof.loadRegatta(regatta.getName(), /* trackedRegattaRegistry */ null);
706
+ assertEquals(regattaName, loadedRegatta.getName());
707
+
708
+ Iterator<? extends Series> seriesIter = loadedRegatta.getSeries().iterator();
709
+ Series loadedQualifyingSeries = seriesIter.next();
710
+
711
+ Iterator<? extends Fleet> qualiFleetIt = loadedQualifyingSeries.getFleets().iterator();
712
+ Fleet qualiFleet1 = qualiFleetIt.next();
713
+ assertEquals(qualiFleet1.getName(), "Yellow");
714
+ Fleet qualiFleet2 = qualiFleetIt.next();
715
+ assertEquals(qualiFleet2.getName(), "Blue");
716
+
717
+ Series loadedFinalSeries = seriesIter.next();
718
+ Iterator<? extends Fleet> finalFleetIt = loadedFinalSeries.getFleets().iterator();
719
+ Fleet finalFleet1 = finalFleetIt.next();
720
+ assertEquals(finalFleet1.getName(), "Gold");
721
+ Fleet finalFleet2 = finalFleetIt.next();
722
+ assertEquals(finalFleet2.getName(), "Silver");
723
+ }
724
+
725
+ @Test
726
+ public void testStorageOfRaceIdentifiersOnRaceColumnInSeries() {
727
+ final int numberOfQualifyingRaces = 5;
728
+ final int numberOfFinalRaces = 7;
729
+ final String regattaBaseName = "Kieler Woche";
730
+ BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("29erXX", /* typicallyStartsUpwind */ true);
731
+ Regatta regatta = createRegattaAndAddRaceColumns(numberOfQualifyingRaces, numberOfFinalRaces,
732
+ RegattaImpl.getDefaultName(regattaBaseName, boatClass.getName()), boatClass, regattaStartDate, regattaEndDate,
733
+ /* persistent */false, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), OneDesignRankingMetric::new);
734
+ Series qualifyingSeries = regatta.getSeries().iterator().next();
735
+ RaceColumn q2 = qualifyingSeries.getRaceColumnByName("Q2");
736
+ final RegattaNameAndRaceName q2TrackedRaceIdentifier = new RegattaNameAndRaceName(regatta.getName(), "Q2 TracTrac");
737
+ q2.setRaceIdentifier(qualifyingSeries.getFleetByName("Yellow"), q2TrackedRaceIdentifier);
738
+ MongoObjectFactory mof = PersistenceFactory.INSTANCE.getMongoObjectFactory(getMongoService());
739
+ mof.storeRegatta(regatta);
740
+
741
+ DomainObjectFactory dof = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE);
742
+ Regatta loadedRegatta = dof.loadRegatta(regatta.getName(), /* trackedRegattaRegistry */ null);
743
+ Series loadedQualifyingSeries = loadedRegatta.getSeries().iterator().next();
744
+ RaceColumn loadedQ2 = loadedQualifyingSeries.getRaceColumnByName("Q2");
745
+ RaceIdentifier loadedQ2TrackedRaceIdentifier = loadedQ2.getRaceIdentifier(loadedQualifyingSeries.getFleetByName("Yellow"));
746
+ assertEquals(q2TrackedRaceIdentifier, loadedQ2TrackedRaceIdentifier);
747
+ assertNotSame(q2TrackedRaceIdentifier, loadedQ2TrackedRaceIdentifier);
748
+ assertNull(loadedQualifyingSeries.getRaceColumnByName("Q1").getRaceIdentifier(loadedQualifyingSeries.getFleetByName("Yellow")));
749
+ assertNull(loadedQualifyingSeries.getRaceColumnByName("Q2").getRaceIdentifier(loadedQualifyingSeries.getFleetByName("Blue")));
750
+ }
751
+
752
+ private Regatta createRegattaAndAddRaceColumns(final int numberOfQualifyingRaces, final int numberOfFinalRaces,
753
+ final String regattaName, BoatClass boatClass, TimePoint startDate, TimePoint endDate, boolean persistent,
754
+ ScoringScheme scoringScheme, RankingMetricConstructor rankingMetricConstructor) {
755
+ Regatta regatta = createRegatta(regattaName, boatClass, startDate, endDate, persistent, scoringScheme, null,
756
+ rankingMetricConstructor);
757
+ addRaceColumns(numberOfQualifyingRaces, numberOfFinalRaces, regatta);
758
+ return regatta;
759
+ }
760
+
761
+ private void addRaceColumns(final int numberOfQualifyingRaces, final int numberOfFinalRaces, Regatta regatta) {
762
+ List<String> finalRaceColumnNames = new ArrayList<String>();
763
+ for (int i=1; i<=numberOfFinalRaces; i++) {
764
+ finalRaceColumnNames.add("F"+i);
765
+ }
766
+ List<String> qualifyingRaceColumnNames = new ArrayList<String>();
767
+ for (int i=1; i<=numberOfQualifyingRaces; i++) {
768
+ qualifyingRaceColumnNames.add("Q"+i);
769
+ }
770
+ List<String> medalRaceColumnNames = new ArrayList<String>();
771
+ medalRaceColumnNames.add("M");
772
+ addRaceColumnsToSeries(qualifyingRaceColumnNames, regatta.getSeriesByName("Qualifying"));
773
+ addRaceColumnsToSeries(finalRaceColumnNames, regatta.getSeriesByName("Final"));
774
+ addRaceColumnsToSeries(medalRaceColumnNames, regatta.getSeriesByName("Medal"));
775
+ }
776
+
777
+ private Regatta createRegatta(final String regattaName, BoatClass boatClass, TimePoint startDate,
778
+ TimePoint endDate, boolean persistent, ScoringScheme scoringScheme, CourseArea courseArea,
779
+ RankingMetricConstructor rankingMetricConstructor) {
780
+ List<String> emptyRaceColumnNames = Collections.emptyList();
781
+ List<Series> series = new ArrayList<Series>();
782
+
783
+ // -------- qualifying series ------------
784
+ List<Fleet> qualifyingFleets = new ArrayList<Fleet>();
785
+ qualifyingFleets.add(new FleetImpl("Yellow"));
786
+ qualifyingFleets.add(new FleetImpl("Blue"));
787
+ Series qualifyingSeries = new SeriesImpl("Qualifying", /* isMedal */false, qualifyingFleets,
788
+ emptyRaceColumnNames, /* trackedRegattaRegistry */ null);
789
+ series.add(qualifyingSeries);
790
+
791
+ // -------- final series ------------
792
+ List<Fleet> finalFleets = new ArrayList<Fleet>();
793
+ finalFleets.add(new FleetImpl("Gold", 1));
794
+ finalFleets.add(new FleetImpl("Silver", 2));
795
+ Series finalSeries = new SeriesImpl("Final", /* isMedal */ false, finalFleets, emptyRaceColumnNames, /* trackedRegattaRegistry */ null);
796
+ series.add(finalSeries);
797
+
798
+ // ------------ medal --------------
799
+ List<Fleet> medalFleets = new ArrayList<Fleet>();
800
+ medalFleets.add(new FleetImpl("Medal"));
801
+ Series medalSeries = new SeriesImpl("Medal", /* isMedal */ true, medalFleets, emptyRaceColumnNames, /* trackedRegattaRegistry */ null);
802
+ series.add(medalSeries);
803
+ Regatta regatta = new RegattaImpl(regattaName, boatClass, startDate, endDate, series, persistent, scoringScheme, "123", courseArea, rankingMetricConstructor);
804
+ return regatta;
805
+ }
806
+
807
+ private void addRaceColumnsToSeries(List<String> finalRaceColumnNames, Series finalSeries) {
808
+ for (String raceColumnName : finalRaceColumnNames) {
809
+ finalSeries.addRaceColumn(raceColumnName, /* trackedRegattaRegistry */ null);
810
+ }
811
+ }
812
+
813
+ @Test
814
+ public void testRegattaRaceAssociationStore() throws Exception {
815
+ BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("112er", /* typicallyStartsUpwind */ true);
816
+ Regatta regatta = createRegatta(RegattaImpl.getDefaultName("Cologne Masters", boatClass.getName()), boatClass,
817
+ regattaStartDate, regattaEndDate, /* persistent */ true, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null, OneDesignRankingMetric::new);
818
+
819
+ List<Competitor> competitors = new ArrayList<Competitor>();
820
+ competitors.add(new CompetitorImpl("Axel", "Axel Uhl", Color.RED, null, null, null, null, /* timeOnTimeFactor */ null, /* timeOnDistanceAllowancePerNauticalMile */ null));
821
+ Iterable<Waypoint> waypoints = Collections.emptyList();
822
+ Course course = new CourseImpl("Course", waypoints);
823
+
824
+ RaceDefinition racedef = new RaceDefinitionImpl("M1", course, boatClass, competitors);
825
+ regatta.addRace(racedef);
826
+
827
+ RacingEventServiceImpl evs = new RacingEventServiceImpl(PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE), PersistenceFactory.INSTANCE
828
+ .getMongoObjectFactory(getMongoService()), MediaDBFactory.INSTANCE.getMediaDB(getMongoService()), EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE);
829
+ assertNull(evs.getRememberedRegattaForRace(racedef.getId()));
830
+ evs.raceAdded(regatta, racedef);
831
+ assertNotNull(evs.getRememberedRegattaForRace(racedef.getId()));
832
+ evs.removeRegatta(regatta);
833
+ assertNull(evs.getRememberedRegattaForRace(racedef.getId()));
834
+ }
835
+
836
+}
java/com.sap.sailing.polars.datamining/src/com/sap/sailing/polars/datamining/data/impl/GPSFixWithPolarContext.java
... ...
@@ -38,7 +38,7 @@ public class GPSFixWithPolarContext implements HasGPSFixPolarContext {
38 38
39 39
@Override
40 40
public ClusterDTO getWindSpeedRange() {
41
- return new ClusterDTO(windSpeedRangeGroup.getClusterFor(wind.getObject()).toString());
41
+ return new ClusterDTO(wind == null || wind.getObject() == null ? "null" : windSpeedRangeGroup.getClusterFor(wind.getObject()).toString());
42 42
}
43 43
44 44
@Override
java/com.sap.sailing.server.gateway.serialization.shared.android/META-INF/MANIFEST.MF
... ...
@@ -8,7 +8,8 @@ Bundle-RequiredExecutionEnvironment: JavaSE-1.7
8 8
Require-Bundle: com.sap.sailing.domain.common,
9 9
com.sap.sailing.domain.shared.android,
10 10
org.json.simple;bundle-version="1.1.0",
11
- com.sap.sse.common
11
+ com.sap.sse.common,
12
+ com.sap.sse.shared.android
12 13
Export-Package: com.sap.sailing.server.gateway.deserialization,
13 14
com.sap.sailing.server.gateway.deserialization.coursedata.impl,
14 15
com.sap.sailing.server.gateway.deserialization.impl,
java/com.sap.sailing.server.gateway.serialization.shared.android/pom.xml
... ...
@@ -1,25 +1,25 @@
1
-<?xml version="1.0" encoding="UTF-8"?>
2
-<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
3
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
4
- <modelVersion>4.0.0</modelVersion>
5
- <parent>
6
- <artifactId>root</artifactId>
7
- <groupId>com.sap.sailing</groupId>
8
- <version>1.0.0-SNAPSHOT</version>
9
- </parent>
10
- <artifactId>com.sap.sailing.server.gateway.serialization.shared.android</artifactId>
11
- <packaging>eclipse-plugin</packaging>
12
- <build>
13
- <plugins>
14
- <plugin>
15
- <groupId>org.eclipse.tycho</groupId>
16
- <artifactId>tycho-compiler-plugin</artifactId>
17
- <version>${tycho-version}</version>
18
- <configuration>
19
- <source>1.7</source>
20
- <target>1.7</target>
21
- </configuration>
22
- </plugin>
23
- </plugins>
24
- </build>
25
-</project>
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
3
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
4
+ <modelVersion>4.0.0</modelVersion>
5
+ <parent>
6
+ <artifactId>root</artifactId>
7
+ <groupId>com.sap.sailing</groupId>
8
+ <version>1.0.0-SNAPSHOT</version>
9
+ </parent>
10
+ <artifactId>com.sap.sailing.server.gateway.serialization.shared.android</artifactId>
11
+ <packaging>eclipse-plugin</packaging>
12
+ <build>
13
+ <plugins>
14
+ <plugin>
15
+ <groupId>org.eclipse.tycho</groupId>
16
+ <artifactId>tycho-compiler-plugin</artifactId>
17
+ <version>${tycho-version}</version>
18
+ <configuration>
19
+ <source>1.7</source>
20
+ <target>1.7</target>
21
+ </configuration>
22
+ </plugin>
23
+ </plugins>
24
+ </build>
25
+</project>
java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/deserialization/impl/EventBaseJsonDeserializer.java
... ...
@@ -23,12 +23,12 @@ import com.sap.sailing.server.gateway.serialization.impl.EventBaseJsonSerializer
23 23
import com.sap.sse.common.TimePoint;
24 24
import com.sap.sse.common.Util;
25 25
import com.sap.sse.common.impl.MillisecondsTimePoint;
26
-import com.sap.sse.common.media.ImageDescriptor;
27
-import com.sap.sse.common.media.ImageDescriptorImpl;
28 26
import com.sap.sse.common.media.ImageSize;
29 27
import com.sap.sse.common.media.MimeType;
30
-import com.sap.sse.common.media.VideoDescriptor;
31
-import com.sap.sse.common.media.VideoDescriptorImpl;
28
+import com.sap.sse.shared.media.ImageDescriptor;
29
+import com.sap.sse.shared.media.VideoDescriptor;
30
+import com.sap.sse.shared.media.impl.ImageDescriptorImpl;
31
+import com.sap.sse.shared.media.impl.VideoDescriptorImpl;
32 32
33 33
public class EventBaseJsonDeserializer implements JsonDeserializer<EventBase> {
34 34
private final JsonDeserializer<Venue> venueDeserializer;
java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/serialization/impl/EventBaseJsonSerializer.java
... ...
@@ -9,8 +9,8 @@ import com.sap.sailing.domain.base.EventBase;
9 9
import com.sap.sailing.domain.base.LeaderboardGroupBase;
10 10
import com.sap.sailing.domain.base.Venue;
11 11
import com.sap.sailing.server.gateway.serialization.JsonSerializer;
12
-import com.sap.sse.common.media.ImageDescriptor;
13
-import com.sap.sse.common.media.VideoDescriptor;
12
+import com.sap.sse.shared.media.ImageDescriptor;
13
+import com.sap.sse.shared.media.VideoDescriptor;
14 14
15 15
public class EventBaseJsonSerializer implements JsonSerializer<EventBase> {
16 16
public static final String FIELD_ID = "id";
java/com.sap.sailing.server.gateway.serialization.test/META-INF/MANIFEST.MF
... ...
@@ -14,4 +14,5 @@ Require-Bundle: com.sap.sailing.domain;bundle-version="1.0.0",
14 14
org.json.simple,
15 15
org.mockito.mockito-core;bundle-version="1.9.5",
16 16
org.objenesis;bundle-version="1.3.0",
17
- com.sap.sse.common
17
+ com.sap.sse.common,
18
+ com.sap.sse.shared.android
java/com.sap.sailing.server.gateway.serialization.test/src/com/sap/sailing/server/gateway/serialization/test/EventDataJsonSerializerTest.java
... ...
@@ -32,8 +32,8 @@ import com.sap.sailing.server.gateway.serialization.impl.LeaderboardGroupBaseJso
32 32
import com.sap.sailing.server.gateway.serialization.impl.VenueJsonSerializer;
33 33
import com.sap.sse.common.TimePoint;
34 34
import com.sap.sse.common.impl.MillisecondsTimePoint;
35
-import com.sap.sse.common.media.ImageDescriptor;
36
-import com.sap.sse.common.media.VideoDescriptor;
35
+import com.sap.sse.shared.media.ImageDescriptor;
36
+import com.sap.sse.shared.media.VideoDescriptor;
37 37
38 38
public class EventDataJsonSerializerTest {
39 39
protected final UUID expectedId = UUID.randomUUID();
java/com.sap.sailing.server.gateway.serialization.test/src/com/sap/sailing/server/gateway/serialization/test/EventDataJsonSerializerWithImagesAndVideosTest.java
... ...
@@ -36,11 +36,11 @@ import com.sap.sailing.server.gateway.serialization.impl.VenueJsonSerializer;
36 36
import com.sap.sse.common.TimePoint;
37 37
import com.sap.sse.common.Util;
38 38
import com.sap.sse.common.impl.MillisecondsTimePoint;
39
-import com.sap.sse.common.media.ImageDescriptor;
40
-import com.sap.sse.common.media.ImageDescriptorImpl;
41 39
import com.sap.sse.common.media.MimeType;
42
-import com.sap.sse.common.media.VideoDescriptor;
43
-import com.sap.sse.common.media.VideoDescriptorImpl;
40
+import com.sap.sse.shared.media.ImageDescriptor;
41
+import com.sap.sse.shared.media.VideoDescriptor;
42
+import com.sap.sse.shared.media.impl.ImageDescriptorImpl;
43
+import com.sap.sse.shared.media.impl.VideoDescriptorImpl;
44 44
45 45
public class EventDataJsonSerializerWithImagesAndVideosTest {
46 46
private final UUID expectedId = UUID.randomUUID();
java/com.sap.sailing.server.gateway.serialization.test/src/com/sap/sailing/server/gateway/serialization/test/EventDataJsonSerializerWithNullValuesTest.java
... ...
@@ -32,8 +32,8 @@ import com.sap.sailing.server.gateway.serialization.impl.LeaderboardGroupBaseJso
32 32
import com.sap.sailing.server.gateway.serialization.impl.VenueJsonSerializer;
33 33
import com.sap.sse.common.TimePoint;
34 34
import com.sap.sse.common.impl.MillisecondsTimePoint;
35
-import com.sap.sse.common.media.ImageDescriptor;
36
-import com.sap.sse.common.media.VideoDescriptor;
35
+import com.sap.sse.shared.media.ImageDescriptor;
36
+import com.sap.sse.shared.media.VideoDescriptor;
37 37
38 38
public class EventDataJsonSerializerWithNullValuesTest {
39 39
protected final UUID expectedId = UUID.randomUUID();
java/com.sap.sailing.server.gateway.serialization.test/src/com/sap/sailing/server/gateway/serialization/test/EventWithNullStartAndEndDataJsonSerializerTest.java
... ...
@@ -31,8 +31,8 @@ import com.sap.sailing.server.gateway.serialization.impl.EventBaseJsonSerializer
31 31
import com.sap.sailing.server.gateway.serialization.impl.LeaderboardGroupBaseJsonSerializer;
32 32
import com.sap.sailing.server.gateway.serialization.impl.VenueJsonSerializer;
33 33
import com.sap.sse.common.TimePoint;
34
-import com.sap.sse.common.media.ImageDescriptor;
35
-import com.sap.sse.common.media.VideoDescriptor;
34
+import com.sap.sse.shared.media.ImageDescriptor;
35
+import com.sap.sse.shared.media.VideoDescriptor;
36 36
37 37
public class EventWithNullStartAndEndDataJsonSerializerTest {
38 38
java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/TrackRaceBoatCompetitorMetadataReplicationTest.java
... ...
@@ -70,7 +70,7 @@ public class TrackRaceBoatCompetitorMetadataReplicationTest extends AbstractServ
70 70
trackingParams = com.sap.sailing.domain.tractracadapter.DomainFactory.INSTANCE
71 71
.createTrackingConnectivityParameters(paramURL, liveURI, storedURI, courseDesignUpdateURI,
72 72
startOfTracking, endOfTracking, /* delayToLiveInMillis */
73
- 0l, /* simulateWithStartTimeNow */false, /*ignoreTracTracMarkPassings*/ false, EmptyRaceLogStore.INSTANCE,
73
+ 0l, /* offsetToStartTimeOfSimulatedRace */null, /*ignoreTracTracMarkPassings*/ false, EmptyRaceLogStore.INSTANCE,
74 74
EmptyRegattaLogStore.INSTANCE, tracTracUsername, tracTracPassword, "", "");
75 75
}
76 76
java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/TrackRaceReplicationTest.java
... ...
@@ -88,7 +88,7 @@ public class TrackRaceReplicationTest extends AbstractServerReplicationTest {
88 88
trackingParams = com.sap.sailing.domain.tractracadapter.DomainFactory.INSTANCE
89 89
.createTrackingConnectivityParameters(paramURL, liveURI, storedURI, courseDesignUpdateURI,
90 90
startOfTracking, endOfTracking, /* delayToLiveInMillis */
91
- 0l, /* simulateWithStartTimeNow */false, /*ignoreTracTracMarkPassings*/ false, EmptyRaceLogStore.INSTANCE,
91
+ 0l, /* offsetToStartTimeOfSimulatedRace */null, /*ignoreTracTracMarkPassings*/ false, EmptyRaceLogStore.INSTANCE,
92 92
EmptyRegattaLogStore.INSTANCE, tracTracUsername, tracTracPassword, "", "");
93 93
}
94 94
java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/MasterDataImportTest.java
... ...
@@ -129,11 +129,11 @@ import com.sap.sse.common.TimePoint;
129 129
import com.sap.sse.common.Util;
130 130
import com.sap.sse.common.impl.MillisecondsDurationImpl;
131 131
import com.sap.sse.common.impl.MillisecondsTimePoint;
132
-import com.sap.sse.common.media.ImageDescriptor;
133 132
import com.sap.sse.common.media.MimeType;
134
-import com.sap.sse.common.media.VideoDescriptor;
135 133
import com.sap.sse.mongodb.MongoDBConfiguration;
136 134
import com.sap.sse.mongodb.MongoDBService;
135
+import com.sap.sse.shared.media.ImageDescriptor;
136
+import com.sap.sse.shared.media.VideoDescriptor;
137 137
138 138
public class MasterDataImportTest {
139 139
java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/SearchServiceTest.java
... ...
@@ -72,10 +72,10 @@ import com.sap.sse.common.Color;
72 72
import com.sap.sse.common.TimePoint;
73 73
import com.sap.sse.common.Util;
74 74
import com.sap.sse.common.impl.MillisecondsTimePoint;
75
-import com.sap.sse.common.media.ImageDescriptor;
76
-import com.sap.sse.common.media.VideoDescriptor;
77 75
import com.sap.sse.common.search.KeywordQuery;
78 76
import com.sap.sse.common.search.Result;
77
+import com.sap.sse.shared.media.ImageDescriptor;
78
+import com.sap.sse.shared.media.VideoDescriptor;
79 79
80 80
public class SearchServiceTest {
81 81
private RacingEventService server;
java/com.sap.sailing.server/src/com/sap/sailing/server/RacingEventService.java
... ...
@@ -1,623 +1,623 @@
1
-package com.sap.sailing.server;
2
-
3
-import java.io.IOException;
4
-import java.io.Serializable;
5
-import java.net.MalformedURLException;
6
-import java.net.SocketException;
7
-import java.net.URI;
8
-import java.net.URL;
9
-import java.util.Collection;
10
-import java.util.ConcurrentModificationException;
11
-import java.util.List;
12
-import java.util.Map;
13
-import java.util.UUID;
14
-import java.util.concurrent.ConcurrentHashMap;
15
-
16
-import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor;
17
-import com.sap.sailing.domain.abstractlog.race.RaceLog;
18
-import com.sap.sailing.domain.abstractlog.race.RaceLogStartTimeEvent;
19
-import com.sap.sailing.domain.abstractlog.race.analyzing.impl.RaceLogResolver;
20
-import com.sap.sailing.domain.base.Competitor;
21
-import com.sap.sailing.domain.base.CompetitorStore;
22
-import com.sap.sailing.domain.base.CourseArea;
23
-import com.sap.sailing.domain.base.DomainFactory;
24
-import com.sap.sailing.domain.base.Event;
25
-import com.sap.sailing.domain.base.EventBase;
26
-import com.sap.sailing.domain.base.Fleet;
27
-import com.sap.sailing.domain.base.LeaderboardSearchResult;
28
-import com.sap.sailing.domain.base.LeaderboardSearchResultBase;
29
-import com.sap.sailing.domain.base.Mark;
30
-import com.sap.sailing.domain.base.RaceColumn;
31
-import com.sap.sailing.domain.base.RaceDefinition;
32
-import com.sap.sailing.domain.base.Regatta;
33
-import com.sap.sailing.domain.base.RegattaRegistry;
34
-import com.sap.sailing.domain.base.RemoteSailingServerReference;
35
-import com.sap.sailing.domain.base.SailingServerConfiguration;
36
-import com.sap.sailing.domain.base.Series;
37
-import com.sap.sailing.domain.base.configuration.DeviceConfiguration;
38
-import com.sap.sailing.domain.base.configuration.DeviceConfigurationIdentifier;
39
-import com.sap.sailing.domain.base.configuration.DeviceConfigurationMatcher;
40
-import com.sap.sailing.domain.base.configuration.RegattaConfiguration;
41
-import com.sap.sailing.domain.common.DataImportProgress;
42
-import com.sap.sailing.domain.common.Position;
43
-import com.sap.sailing.domain.common.RaceFetcher;
44
-import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
45
-import com.sap.sailing.domain.common.RegattaFetcher;
46
-import com.sap.sailing.domain.common.RegattaIdentifier;
47
-import com.sap.sailing.domain.common.RegattaName;
48
-import com.sap.sailing.domain.common.ScoringSchemeType;
49
-import com.sap.sailing.domain.common.media.MediaTrack;
50
-import com.sap.sailing.domain.common.racelog.RacingProcedureType;
51
-import com.sap.sailing.domain.leaderboard.EventResolver;
52
-import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard;
53
-import com.sap.sailing.domain.leaderboard.Leaderboard;
54
-import com.sap.sailing.domain.leaderboard.LeaderboardGroup;
55
-import com.sap.sailing.domain.leaderboard.LeaderboardGroupResolver;
56
-import com.sap.sailing.domain.leaderboard.LeaderboardRegistry;
57
-import com.sap.sailing.domain.leaderboard.RegattaLeaderboard;
58
-import com.sap.sailing.domain.leaderboard.ScoringScheme;
59
-import com.sap.sailing.domain.persistence.DomainObjectFactory;
60
-import com.sap.sailing.domain.persistence.MongoObjectFactory;
61
-import com.sap.sailing.domain.polars.PolarDataService;
62
-import com.sap.sailing.domain.racelog.tracking.GPSFixStore;
63
-import com.sap.sailing.domain.ranking.RankingMetricConstructor;
64
-import com.sap.sailing.domain.regattalike.LeaderboardThatHasRegattaLike;
65
-import com.sap.sailing.domain.tracking.DynamicTrackedRace;
66
-import com.sap.sailing.domain.tracking.RaceListener;
67
-import com.sap.sailing.domain.tracking.RaceTracker;
68
-import com.sap.sailing.domain.tracking.TrackedRace;
69
-import com.sap.sailing.domain.tracking.TrackedRegatta;
70
-import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
71
-import com.sap.sailing.domain.tracking.TrackerManager;
72
-import com.sap.sailing.domain.tracking.WindStore;
73
-import com.sap.sailing.server.masterdata.DataImportLockWithProgress;
74
-import com.sap.sailing.server.simulation.SimulationService;
75
-import com.sap.sse.common.TimePoint;
76
-import com.sap.sse.common.TypeBasedServiceFinderFactory;
77
-import com.sap.sse.common.Util;
78
-import com.sap.sse.common.Util.Triple;
79
-import com.sap.sse.common.media.ImageDescriptor;
80
-import com.sap.sse.common.media.VideoDescriptor;
81
-import com.sap.sse.common.search.KeywordQuery;
82
-import com.sap.sse.common.search.Result;
83
-import com.sap.sse.common.search.Searchable;
84
-import com.sap.sse.filestorage.FileStorageManagementService;
85
-import com.sap.sse.replication.impl.ReplicableWithObjectInputStream;
86
-
87
-/**
88
- * An OSGi service that can be used to track boat races using a TracTrac connector that pushes live GPS boat location,
89
- * waypoint, coarse and mark passing data.
90
- * <p>
91
- *
92
- * If a race/regatta is already being tracked, another {@link #addTracTracRace(URL, URI, URI, WindStore, long)} or
93
- * {@link #addRegatta(URL, URI, URI, WindStore, long)} call will have no effect, even if a different {@link WindStore}
94
- * is requested.
95
- * <p>
96
- *
97
- * When the tracking of a race/regatta is {@link #stopTracking(Regatta, RaceDefinition) stopped}, the next time it's
98
- * started to be tracked, a new {@link TrackedRace} at least will be constructed. This also means that when a
99
- * {@link TrackedRegatta} exists that still holds other {@link TrackedRace}s, the no longer tracked {@link TrackedRace}
100
- * will be removed from the {@link TrackedRegatta}. corresponding information is removed also from the
101
- * {@link DomainFactory}'s caches to ensure that clean, fresh data is received should another tracking request be issued
102
- * later.
103
- * <p>
104
- *
105
- * During receiving the initial load for a replication in {@link #initiallyFillFromInternal(java.io.ObjectInputStream)},
106
- * tracked regattas read from the stream are observed (see {@link RaceListener}) by this object for automatic updates to
107
- * the default leaderboard and for automatic linking to leaderboard columns. It is assumed that no explicit replication
108
- * of these operations will happen based on the changes performed on the replication master.
109
- *
110
- * @author Axel Uhl (d043530)
111
- *
112
- */
113
-public interface RacingEventService extends TrackedRegattaRegistry, RegattaFetcher, RegattaRegistry, RaceFetcher,
114
- LeaderboardRegistry, EventResolver, LeaderboardGroupResolver, TrackerManager, Searchable<LeaderboardSearchResult, KeywordQuery>,
115
- ReplicableWithObjectInputStream<RacingEventService, RacingEventServiceOperation<?>>, RaceLogResolver {
116
- @Override
117
- Regatta getRegatta(RegattaName regattaName);
118
-
119
- @Override
120
- RaceDefinition getRace(RegattaAndRaceIdentifier raceIdentifier);
121
-
122
- DynamicTrackedRace getTrackedRace(Regatta regatta, RaceDefinition race);
123
-
124
- DynamicTrackedRace getTrackedRace(RegattaAndRaceIdentifier raceIdentifier);
125
-
126
- /**
127
- * Obtains an unmodifiable map of the leaderboard configured in this service keyed by their names.
128
- */
129
- Map<String, Leaderboard> getLeaderboards();
130
-
131
- /**
132
- * @return a leaderboard whose {@link Leaderboard#getName()} method returns the value of the <code>name</code>
133
- * parameter, or <code>null</code> if no such leaderboard is known to this service
134
- */
135
- Leaderboard getLeaderboardByName(String name);
136
-
137
- /**
138
- * Looks at the mark tracks in the tracked races attached to the <code>leaderboard</code>. If it doesn't find a
139
- * track for the <code>mark</code> requested there which has fixes before and after <code>timePoint</code> (to
140
- * ensure that no track cropping has taken place, removing the fixes for the interesting time period), looks in the
141
- * leaderboard's regatta log and the specific <code>raceLog</code> (if provided) or all race logs attached to the
142
- * leaderboard (if not provided) for device mappings for the mark and tries to load fixes from the
143
- * {@link GPSFixStore}. The latter is only necessary if the mark isn't found in any tracked race with fixes
144
- * surrounding <code>timePoint</code> because should there be a tracked race in the leaderboard that has the mark
145
- * then it will also have received the fixes from the {@link GPSFixStore} through the regatta log mapping.
146
- * <p>
147
- *
148
- * @return the position obtained by interpolation but never extrapolation from the track identified as described
149
- * above
150
- */
151
- Position getMarkPosition(Mark mark, LeaderboardThatHasRegattaLike leaderboard, TimePoint timePoint, RaceLog raceLog);
152
-
153
- /**
154
- * Stops tracking all races of the regatta specified. This will also stop tracking wind for all races of this regatta.
155
- * See {@link #stopTrackingWind(Regatta, RaceDefinition)}. If there were multiple calls to
156
- * {@link #addTracTracRace(URL, URI, URI, WindStore, long)} with an equal combination of URLs/URIs, the {@link TracTracRaceTracker}
157
- * already tracking the race was re-used. The trackers will be stopped by this call regardless of how many calls
158
- * were made that ensured they were tracking.
159
- */
160
- void stopTracking(Regatta regatta) throws MalformedURLException, IOException, InterruptedException;
161
-
162
- /**
163
- * Removes <code>race</code> and any corresponding {@link #getTrackedRace(Regatta, RaceDefinition) tracked race}
164
- * from this service. If it was the last {@link RaceDefinition} in its {@link Regatta} and the regatta
165
- * {@link Regatta#isPersistent() is not stored persistently}, the <code>regatta</code> is removed as well and will no
166
- * longer be returned by {@link #getAllRegattas()}. The wind tracking is stopped for <code>race</code>.
167
- * <p>
168
- *
169
- * Any {@link RaceTracker} for which <code>race</race> is the last race tracked that is still reachable
170
- * from {@link #getAllRegattas()} will be {@link RaceTracker#stop(boolean) stopped}.
171
- *
172
- * The <code>race</code> will be also removed from all leaderboards containing a column that has <code>race</code>'s
173
- * {@link #getTrackedRace(Regatta, RaceDefinition) corresponding} {@link TrackedRace} as its
174
- * {@link RaceColumn#getTrackedRace(Fleet)}.
175
- *
176
- * @param regatta
177
- * the regatta from which to remove the race
178
- * @param race
179
- * the race to remove
180
- */
181
- void removeRace(Regatta regatta, RaceDefinition race) throws MalformedURLException, IOException,InterruptedException;
182
-
183
- /**
184
- * @param port
185
- * the UDP port on which to listen for incoming messages from Expedition clients
186
- * @param correctByDeclination
187
- * An optional service to convert the wind bearings (which the receiver may
188
- * believe to be true bearings) from magnetic to true bearings.
189
- * @throws SocketException
190
- * thrown, e.g., in case there is already another listener on the port requested
191
- */
192
- void startTrackingWind(Regatta regatta, RaceDefinition race, boolean correctByDeclination) throws Exception;
193
-
194
- void stopTrackingWind(Regatta regatta, RaceDefinition race) throws SocketException, IOException;
195
-
196
- /**
197
- * The {@link Triple#getC() third component} of the triples returned is a wind tracker-specific
198
- * comment where a wind tracker may provide information such as its type name or, if applicable,
199
- * connectivity information such as the network port on which it receives wind information.
200
- */
201
- Iterable<Util.Triple<Regatta, RaceDefinition, String>> getWindTrackedRaces();
202
-
203
- /**
204
- * Creates a new leaderboard with the <code>name</code> specified.
205
- * @param discardThresholds
206
- * Tells the thresholds from which on a next higher number of worst races will be discarded per
207
- * competitor. Example: <code>[3, 6]</code> means that starting from three races the single worst race
208
- * will be discarded; starting from six races, the two worst races per competitor are discarded.
209
- *
210
- * @return the leaderboard created
211
- */
212
- FlexibleLeaderboard addFlexibleLeaderboard(String leaderboardName, String leaderboardDisplayName, int[] discardThresholds, ScoringScheme scoringScheme, Serializable courseAreaId);
213
-
214
- RegattaLeaderboard addRegattaLeaderboard(RegattaIdentifier regattaIdentifier, String leaderboardDisplayName, int[] discardThresholds);
215
-
216
- void removeLeaderboard(String leaderboardName);
217
-
218
- /**
219
- * Renames a leaderboard. If a leaderboard by the name <code>oldName</code> does not exist in {@link #getLeaderboards()},
220
- * or if a leaderboard with the name <code>newName</code> already exists, an {@link IllegalArgumentException} is thrown.
221
- * If the method completes normally, the rename has been successful, and the leaderboard previously obtained by calling
222
- * {@link #getLeaderboardByName(String) getLeaderboardByName(oldName)} can now be obtained by calling
223
- * {@link #getLeaderboardByName(String) getLeaderboardByName(newName)}.
224
- */
225
- void renameLeaderboard(String oldName, String newName);
226
-
227
- RaceColumn addColumnToLeaderboard(String columnName, String leaderboardName, boolean medalRace);
228
-
229
- void moveLeaderboardColumnUp(String leaderboardName, String columnName);
230
-
231
- void moveLeaderboardColumnDown(String leaderboardName, String columnName);
232
-
233
- void removeLeaderboardColumn(String leaderboardName, String columnName);
234
-
235
- void renameLeaderboardColumn(String leaderboardName, String oldColumnName, String newColumnName);
236
-
237
- /**
238
- * @see RaceColumn#setFactor(Double)
239
- */
240
- void updateLeaderboardColumnFactor(String leaderboardName, String columnName, Double factor);
241
-
242
- /**
243
- * Updates the leaderboard data in the persistent store
244
- */
245
- void updateStoredLeaderboard(Leaderboard leaderboard);
246
-
247
- void updateStoredRegatta(Regatta regatta);
248
-
249
- void stopTrackingAndRemove(Regatta regatta) throws MalformedURLException, IOException, InterruptedException;
250
-
251
- /**
252
- * Removes the regatta as well as all regatta leaderboards for that regatta
253
- */
254
- void removeRegatta(Regatta regatta) throws MalformedURLException, IOException, InterruptedException;
255
-
256
- /**
257
- * Removes the given series
258
- */
259
- void removeSeries(Series series) throws MalformedURLException, IOException, InterruptedException;
260
-
261
- DynamicTrackedRace getExistingTrackedRace(RegattaAndRaceIdentifier raceIdentifier);
262
-
263
- /**
264
- * Obtains an unmodifiable map of the leaderboard groups configured in this service keyed by their names.
265
- */
266
- Map<String, LeaderboardGroup> getLeaderboardGroups();
267
-
268
- /**
269
- * Creates a new group with the name <code>groupName</code>, the description <code>desciption</code> and the
270
- * leaderboards with the names in <code>leaderboardNames</code> and saves it in the database.
271
- * @param id TODO
272
- * @param groupName
273
- * The name of the new group
274
- * @param description
275
- * The description of the new group
276
- * @param displayName TODO
277
- * @param displayGroupsInReverseOrder TODO
278
- * @param leaderboardNames
279
- * The names of the leaderboards, which should be contained by the new group.<br />
280
- * If there isn't a leaderboard with one of these names an {@link IllegalArgumentException} is thrown.
281
- * @return The new leaderboard group
282
- */
283
- LeaderboardGroup addLeaderboardGroup(UUID id, String groupName, String description,
284
- String displayName, boolean displayGroupsInReverseOrder, List<String> leaderboardNames, int[] overallLeaderboardDiscardThresholds, ScoringSchemeType overallLeaderboardScoringSchemeType);
285
-
286
- /**
287
- * Removes the group with the name <code>groupName</code> from the service and the database.
288
- * @param groupName The name of the group which shall be removed.
289
- */
290
- void removeLeaderboardGroup(String groupName);
291
-
292
- /**
293
- * Renames the group with the name <code>oldName</code> to the <code>newName</code>.<br />
294
- * If there's no group with the name <code>oldName</code> or there's already a group with the name
295
- * <code>newName</code> a {@link IllegalArgumentException} is thrown.
296
- *
297
- * @param oldName The old name of the group
298
- * @param newName The new name of the group
299
- */
300
- void renameLeaderboardGroup(String oldName, String newName);
301
-
302
- /**
303
- * Updates the group data in the persistant store.
304
- */
305
- void updateStoredLeaderboardGroup(LeaderboardGroup leaderboardGroup);
306
-
307
- DynamicTrackedRace createTrackedRace(RegattaAndRaceIdentifier raceIdentifier, WindStore windStore, GPSFixStore gpsFixStore,
308
- long delayToLiveInMillis, long millisecondsOverWhichToAverageWind, long millisecondsOverWhichToAverageSpeed, boolean useMarkPassingCalculator);
309
-
310
- Regatta getOrCreateDefaultRegatta(String name, String boatClassName, Serializable id);
311
-
312
- /**
313
- * @param series the series must not have any {@link RaceColumn}s yet
314
- */
315
- Regatta createRegatta(String regattaName, String boatClassName, TimePoint startDate, TimePoint endDate, Serializable id, Iterable<? extends Series> series,
316
- boolean persistent, ScoringScheme scoringScheme, Serializable defaultCourseAreaId,
317
- boolean useStartTimeInference, RankingMetricConstructor rankingMetricConstructor);
318
-
319
- Regatta updateRegatta(RegattaIdentifier regattaIdentifier, TimePoint startDate, TimePoint endDate, Serializable newDefaultCourseAreaId, RegattaConfiguration regattaConfiguration, Iterable<? extends Series> series, boolean useStartTimeInference);
320
-
321
- /**
322
- * Adds <code>raceDefinition</code> to the {@link Regatta} such that it will appear in {@link Regatta#getAllRaces()}
323
- * and {@link Regatta#getRaceByName(String)}.
324
- *
325
- * @param addToRegatta identifier of an regatta that must exist already
326
- */
327
- void addRace(RegattaIdentifier addToRegatta, RaceDefinition raceDefinition);
328
-
329
- void updateLeaderboardGroup(String oldName, String newName, String description, String displayName,
330
- List<String> leaderboardNames, int[] overallLeaderboardDiscardThresholds, ScoringSchemeType overallLeaderboardScoringSchemeType);
331
-
332
- /**
333
- * @return a thread-safe copy of the events currently known by the service; it's safe for callers to iterate over
334
- * the iterable returned, and no risk of a {@link ConcurrentModificationException} exists
335
- */
336
- Iterable<Event> getAllEvents();
337
-
338
- /**
339
- * Creates a new event with the name <code>eventName</code>, the venue <code>venue</code> and the regattas with the
340
- * names in <code>regattaNames</code>, saves it in the database and replicates it. Use for TESTING only!
341
- *
342
- * @param eventName
343
- * The name of the new event
344
- * @param eventDescription TODO
345
- * @param startDate
346
- * The start date of the event
347
- * @param endDate
348
- * The end date of the event
349
- * @param isPublic
350
- * Indicates whether the event is public accessible via the publication URL or not
351
- * @param id
352
- * The id of the new event
353
- * @param venue
354
- * The name of the venue of the new event
355
- * @return The new event
356
- */
357
- Event addEvent(String eventName, String eventDescription, TimePoint startDate, TimePoint endDate, String venueName, boolean isPublic, UUID id);
358
-
359
- /**
360
- * Updates a sailing event with the name <code>eventName</code>, the venue<code>venue</code> and the regattas with
361
- * the names in <code>regattaNames</code> and updates it in the database.
362
- * @param eventName
363
- * The name of the event to update
364
- * @param startDate
365
- * The start date of the event
366
- * @param endDate
367
- * The end date of the event
368
- * @param venueName
369
- * The name of the venue of the event
370
- * @param isPublic
371
- * Indicates whether the event is public accessible via the publication URL or not
372
- * @return The new event
373
- */
374
- void updateEvent(UUID id, String eventName, String eventDescription, TimePoint startDate, TimePoint endDate,
375
- String venueName, boolean isPublic, Iterable<UUID> leaderboardGroupIds, URL officialWebsiteURL, URL sailorsInfoWebsiteURL,
376
- Iterable<ImageDescriptor> images, Iterable<VideoDescriptor> videos);
377
-
378
- /**
379
- * Renames a sailing event. If a sailing event by the name <code>oldName</code> does not exist in {@link #getEvents()},
380
- * or if a event with the name <code>newName</code> already exists, an {@link IllegalArgumentException} is thrown.
381
- * If the method completes normally, the rename has been successful, and the event previously obtained by calling
382
- * {@link #getEventByName(String) getEventByName(oldName)} can now be obtained by calling
383
- * {@link #getEventByName(String) getEventByName(newName)}.
384
- */
385
- void renameEvent(UUID id, String newEventName);
386
-
387
- void removeEvent(UUID id);
388
-
389
-
390
- /**
391
- * @return a thread-safe copy of the events (or the exception that occurred trying to obtain the events; arranged in
392
- * a {@link Util.Pair}) of from all sailing server instances currently known by the service; it's safe for
393
- * callers to iterate over the iterable returned, and no risk of a {@link ConcurrentModificationException}
394
- * exists
395
- */
396
- Map<RemoteSailingServerReference, Util.Pair<Iterable<EventBase>, Exception>> getPublicEventsOfAllSailingServers();
397
-
398
- RemoteSailingServerReference addRemoteSailingServerReference(String name, URL url);
399
-
400
- void removeRemoteSailingServerReference(String name);
401
-
402
-
403
- CourseArea[] addCourseAreas(UUID eventId, String[] courseAreaNames, UUID[] courseAreaIds);
404
-
405
- com.sap.sailing.domain.base.DomainFactory getBaseDomainFactory();
406
-
407
- CourseArea getCourseArea(Serializable courseAreaId);
408
-
409
- /**
410
- * Adds the specified mediaTrack to the in-memory media library.
411
- * Important note: Only if mediaTrack.dbId != null the mediaTrack will be persisted in the the database.
412
- * @param mediaTrack
413
- */
414
- void mediaTrackAdded(MediaTrack mediaTrack);
415
-
416
- /**
417
- * Calling mediaTrackAdded for every entry in the specified collection.
418
- * @param mediaTracks
419
- */
420
- void mediaTracksAdded(Collection<MediaTrack> mediaTracks);
421
-
422
- void mediaTrackTitleChanged(MediaTrack mediaTrack);
423
-
424
- void mediaTrackUrlChanged(MediaTrack mediaTrack);
425
-
426
- void mediaTrackStartTimeChanged(MediaTrack mediaTrack);
427
-
428
- void mediaTrackDurationChanged(MediaTrack mediaTrack);
429
-
430
- void mediaTrackAssignedRacesChanged(MediaTrack mediaTrack);
431
-
432
- void mediaTrackDeleted(MediaTrack mediaTrack);
433
-
434
- /**
435
- * In contrast to mediaTracksAdded, this method takes mediaTracks with a given dbId.
436
- * Checks if the track already exists in the library and the database and adds/stores it
437
- * accordingly. If a track already exists and override, its properties are checked for changes
438
- * @param mediaTrack
439
- * @param override If true, track properties (title, url, start time, duration, not mime type!) will be
440
- * overwritten with the values from the track to be imported.
441
- */
442
- void mediaTracksImported(Collection<MediaTrack> mediaTracksToImport, boolean override);
443
-
444
- Collection<MediaTrack> getMediaTracksForRace(RegattaAndRaceIdentifier regattaAndRaceIdentifier);
445
-
446
- Collection<MediaTrack> getMediaTracksInTimeRange(RegattaAndRaceIdentifier regattaAndRaceIdentifier);
447
-
448
- Collection<MediaTrack> getAllMediaTracks();
449
-
450
- void reloadRaceLog(String leaderboardName, String raceColumnName, String fleetName);
451
-
452
- RaceLog getRaceLog(String leaderboardName, String raceColumnName, String fleetName);
453
-
454
- /**
455
- * @param rankingMetricConstructor TODO
456
- * @return a pair with the found or created regatta, and a boolean that tells whether the regatta was created during
457
- * the call
458
- */
459
- Util.Pair<Regatta, Boolean> getOrCreateRegattaWithoutReplication(String fullRegattaName, String boatClassName,
460
- TimePoint startDate, TimePoint endDate, Serializable id,
461
- Iterable<? extends Series> series, boolean persistent, ScoringScheme scoringScheme,
462
- Serializable defaultCourseAreaId, boolean useStartTimeInference, RankingMetricConstructor rankingMetricConstructor);
463
-
464
- /**
465
- * @return map where keys are the toString() representation of the {@link RaceDefinition#getId() IDs} of races passed to
466
- * {@link #setRegattaForRace(Regatta, RaceDefinition)}. It helps remember the connection between races and regattas.
467
- */
468
- ConcurrentHashMap<String, Regatta> getPersistentRegattasForRaceIDs();
469
-
470
- Event createEventWithoutReplication(String eventName, String eventDescription, TimePoint startDate, TimePoint endDate, String venue,
471
- boolean isPublic, UUID id, URL officialWebsiteURL, URL sailorsInfoWebsiteURL, Iterable<ImageDescriptor> images,
472
- Iterable<VideoDescriptor> videos);
473
-
474
- void setRegattaForRace(Regatta regatta, String raceIdAsString);
475
-
476
- CourseArea[] addCourseAreasWithoutReplication(UUID eventId, UUID[] courseAreaIds, String[] courseAreaNames);
477
-
478
- CourseArea[] removeCourseAreaWithoutReplication(UUID eventId, UUID[] courseAreaIds);
479
-
480
- /**
481
- * Returns a mobile device's configuration.
482
- * @param identifier of the client (may include event)
483
- * @return the {@link DeviceConfiguration}
484
- */
485
- DeviceConfiguration getDeviceConfiguration(DeviceConfigurationIdentifier identifier);
486
-
487
- /**
488
- * Adds a device configuration.
489
- * @param matcher defining for which the configuration applies.
490
- * @param configuration of the device.
491
- */
492
- void createOrUpdateDeviceConfiguration(DeviceConfigurationMatcher matcher, DeviceConfiguration configuration);
493
-
494
- /**
495
- * Removes a configuration by its matching object.
496
- * @param matcher
497
- */
498
- void removeDeviceConfiguration(DeviceConfigurationMatcher matcher);
499
-
500
- /**
501
- * Returns all configurations and their matching objects.
502
- * @return the {@link DeviceConfiguration}s.
503
- */
504
- Map<DeviceConfigurationMatcher, DeviceConfiguration> getAllDeviceConfigurations();
505
-
506
- /**
507
- * Forces a new start time on the RaceLog identified by the passed parameters.
508
- * @param leaderboardName name of the RaceLog's leaderboard.
509
- * @param raceColumnName name of the RaceLog's column
510
- * @param fleetName name of the RaceLog's fleet
511
- * @param authorName name of the {@link AbstractLogEventAuthor} the {@link RaceLogStartTimeEvent} will be created with
512
- * @param authorPriority priority of the author.
513
- * @param passId Pass identifier of the new start time event.
514
- * @param logicalTimePoint logical {@link TimePoint} of the new event.
515
- * @param startTime the new Start-Time
516
- * @return
517
- */
518
- TimePoint setStartTimeAndProcedure(String leaderboardName, String raceColumnName, String fleetName, String authorName,
519
- int authorPriority, int passId, TimePoint logicalTimePoint, TimePoint startTime, RacingProcedureType racingProcedure);
520
-
521
- /**
522
- * Gets the start time, pass identifier and racing procedure for the queried race. Start time might be <code>null</code>.
523
- */
524
- Util.Triple<TimePoint, Integer, RacingProcedureType> getStartTimeAndProcedure(String leaderboardName, String raceColumnName, String fleetName);
525
-
526
- MongoObjectFactory getMongoObjectFactory();
527
-
528
- DomainObjectFactory getDomainObjectFactory();
529
-
530
- WindStore getWindStore();
531
-
532
- PolarDataService getPolarDataService();
533
-
534
- SimulationService getSimulationService();
535
-
536
- GPSFixStore getGPSFixStore();
537
-
538
- RaceTracker getRaceTrackerById(Object id);
539
-
540
- AbstractLogEventAuthor getServerAuthor();
541
-
542
- CompetitorStore getCompetitorStore();
543
-
544
- TypeBasedServiceFinderFactory getTypeBasedServiceFinderFactory();
545
-
546
- /**
547
- * This lock exists to allow only one master data import at a time to avoid situation where multiple Imports
548
- * override each other in unpredictable fashion
549
- */
550
- DataImportLockWithProgress getDataImportLock();
551
-
552
- DataImportProgress createOrUpdateDataImportProgressWithReplication(UUID importOperationId,
553
- double overallProgressPct,
554
- String subProgressName, double subProgressPct);
555
-
556
- DataImportProgress createOrUpdateDataImportProgressWithoutReplication(UUID importOperationId,
557
- double overallProgressPct,
558
- String subProgressName, double subProgressPct);
559
-
560
- void setDataImportFailedWithReplication(UUID importOperationId, String errorMessage);
561
-
562
- void setDataImportFailedWithoutReplication(UUID importOperationId, String errorMessage);
563
-
564
- void setDataImportDeleteProgressFromMapTimerWithReplication(UUID importOperationId);
565
-
566
- void setDataImportDeleteProgressFromMapTimerWithoutReplication(UUID importOperationId);
567
-
568
- /**
569
- * For the reference to a remote sailing server, updates its events cache and returns the event list
570
- * or, if fetching the event list from the remote server did fail, the exception for which it failed.
571
- */
572
- Util.Pair<Iterable<EventBase>, Exception> updateRemoteServerEventCacheSynchronously(RemoteSailingServerReference ref);
573
-
574
- /**
575
- * Searches the content of this server, not that of any remote servers referenced by any {@link RemoteSailingServerReference}s.
576
- */
577
- @Override
578
- Result<LeaderboardSearchResult> search(KeywordQuery query);
579
-
580
- /**
581
- * Searches a specific remote server whose reference has the {@link RemoteSailingServerReference#getName() name}
582
- * <code>remoteServerReferenceName</code>. If a remote server reference with that name is not known,
583
- * <code>null</code> is returned. Otherwise, a non-<code>null</code> and possibly empty search result set is
584
- * returned.
585
- */
586
- Result<LeaderboardSearchResultBase> searchRemotely(String remoteServerReferenceName, KeywordQuery query);
587
-
588
- /**
589
- * Gets the configuration of the local sailing server instances.
590
- */
591
- SailingServerConfiguration getSailingServerConfiguration();
592
-
593
- void updateServerConfiguration(SailingServerConfiguration serverConfiguration);
594
-
595
- /**
596
- * References to remote servers may be dead or alive. This is internally determined by regularly polling those
597
- * servers for their events list. If the events list cannot be successfully retrieved, the server is considered "dead."
598
- * This method returns the "live" server references.
599
- */
600
- Iterable<RemoteSailingServerReference> getLiveRemoteServerReferences();
601
-
602
- RemoteSailingServerReference getRemoteServerReferenceByName(String remoteServerReferenceName);
603
-
604
- void addRegattaWithoutReplication(Regatta regatta);
605
-
606
- void addEventWithoutReplication(Event event);
607
-
608
- /**
609
- * Adds the leaderboard group to this service; if the group has an overall leaderboard, the overall leaderboard
610
- * is added to this service as well. For both, the group and the overall leaderboard, any previously existing
611
- * objects by the same name of that type will be replaced.
612
- */
613
- void addLeaderboardGroupWithoutReplication(LeaderboardGroup leaderboardGroup);
614
-
615
- /**
616
- * @return {@code null} if no service can be found in the OSGi registry
617
- */
618
- FileStorageManagementService getFileStorageManagementService();
619
-
620
- ClassLoader getCombinedMasterDataClassLoader();
621
-
622
- Iterable<Competitor> getCompetitorInOrderOfWindwardDistanceTraveledFarthestFirst(TrackedRace trackedRace, TimePoint timePoint);
623
-}
1
+package com.sap.sailing.server;
2
+
3
+import java.io.IOException;
4
+import java.io.Serializable;
5
+import java.net.MalformedURLException;
6
+import java.net.SocketException;
7
+import java.net.URI;
8
+import java.net.URL;
9
+import java.util.Collection;
10
+import java.util.ConcurrentModificationException;
11
+import java.util.List;
12
+import java.util.Map;
13
+import java.util.UUID;
14
+import java.util.concurrent.ConcurrentHashMap;
15
+
16
+import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor;
17
+import com.sap.sailing.domain.abstractlog.race.RaceLog;
18
+import com.sap.sailing.domain.abstractlog.race.RaceLogStartTimeEvent;
19
+import com.sap.sailing.domain.abstractlog.race.analyzing.impl.RaceLogResolver;
20
+import com.sap.sailing.domain.base.Competitor;
21
+import com.sap.sailing.domain.base.CompetitorStore;
22
+import com.sap.sailing.domain.base.CourseArea;
23
+import com.sap.sailing.domain.base.DomainFactory;
24
+import com.sap.sailing.domain.base.Event;
25
+import com.sap.sailing.domain.base.EventBase;
26
+import com.sap.sailing.domain.base.Fleet;
27
+import com.sap.sailing.domain.base.LeaderboardSearchResult;
28
+import com.sap.sailing.domain.base.LeaderboardSearchResultBase;
29
+import com.sap.sailing.domain.base.Mark;
30
+import com.sap.sailing.domain.base.RaceColumn;
31
+import com.sap.sailing.domain.base.RaceDefinition;
32
+import com.sap.sailing.domain.base.Regatta;
33
+import com.sap.sailing.domain.base.RegattaRegistry;
34
+import com.sap.sailing.domain.base.RemoteSailingServerReference;
35
+import com.sap.sailing.domain.base.SailingServerConfiguration;
36
+import com.sap.sailing.domain.base.Series;
37
+import com.sap.sailing.domain.base.configuration.DeviceConfiguration;
38
+import com.sap.sailing.domain.base.configuration.DeviceConfigurationIdentifier;
39
+import com.sap.sailing.domain.base.configuration.DeviceConfigurationMatcher;
40
+import com.sap.sailing.domain.base.configuration.RegattaConfiguration;
41
+import com.sap.sailing.domain.common.DataImportProgress;
42
+import com.sap.sailing.domain.common.Position;
43
+import com.sap.sailing.domain.common.RaceFetcher;
44
+import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
45
+import com.sap.sailing.domain.common.RegattaFetcher;
46
+import com.sap.sailing.domain.common.RegattaIdentifier;
47
+import com.sap.sailing.domain.common.RegattaName;
48
+import com.sap.sailing.domain.common.ScoringSchemeType;
49
+import com.sap.sailing.domain.common.media.MediaTrack;
50
+import com.sap.sailing.domain.common.racelog.RacingProcedureType;
51
+import com.sap.sailing.domain.leaderboard.EventResolver;
52
+import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard;
53
+import com.sap.sailing.domain.leaderboard.Leaderboard;
54
+import com.sap.sailing.domain.leaderboard.LeaderboardGroup;
55
+import com.sap.sailing.domain.leaderboard.LeaderboardGroupResolver;
56
+import com.sap.sailing.domain.leaderboard.LeaderboardRegistry;
57
+import com.sap.sailing.domain.leaderboard.RegattaLeaderboard;
58
+import com.sap.sailing.domain.leaderboard.ScoringScheme;
59
+import com.sap.sailing.domain.persistence.DomainObjectFactory;
60
+import com.sap.sailing.domain.persistence.MongoObjectFactory;
61
+import com.sap.sailing.domain.polars.PolarDataService;
62
+import com.sap.sailing.domain.racelog.tracking.GPSFixStore;
63
+import com.sap.sailing.domain.ranking.RankingMetricConstructor;
64
+import com.sap.sailing.domain.regattalike.LeaderboardThatHasRegattaLike;
65
+import com.sap.sailing.domain.tracking.DynamicTrackedRace;
66
+import com.sap.sailing.domain.tracking.RaceListener;
67
+import com.sap.sailing.domain.tracking.RaceTracker;
68
+import com.sap.sailing.domain.tracking.TrackedRace;
69
+import com.sap.sailing.domain.tracking.TrackedRegatta;
70
+import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
71
+import com.sap.sailing.domain.tracking.TrackerManager;
72
+import com.sap.sailing.domain.tracking.WindStore;
73
+import com.sap.sailing.server.masterdata.DataImportLockWithProgress;
74
+import com.sap.sailing.server.simulation.SimulationService;
75
+import com.sap.sse.common.TimePoint;
76
+import com.sap.sse.common.TypeBasedServiceFinderFactory;
77
+import com.sap.sse.common.Util;
78
+import com.sap.sse.common.Util.Triple;
79
+import com.sap.sse.common.search.KeywordQuery;
80
+import com.sap.sse.common.search.Result;
81
+import com.sap.sse.common.search.Searchable;
82
+import com.sap.sse.filestorage.FileStorageManagementService;
83
+import com.sap.sse.replication.impl.ReplicableWithObjectInputStream;
84
+import com.sap.sse.shared.media.ImageDescriptor;
85
+import com.sap.sse.shared.media.VideoDescriptor;
86
+
87
+/**
88
+ * An OSGi service that can be used to track boat races using a TracTrac connector that pushes live GPS boat location,
89
+ * waypoint, coarse and mark passing data.
90
+ * <p>
91
+ *
92
+ * If a race/regatta is already being tracked, another {@link #addTracTracRace(URL, URI, URI, WindStore, long)} or
93
+ * {@link #addRegatta(URL, URI, URI, WindStore, long)} call will have no effect, even if a different {@link WindStore}
94
+ * is requested.
95
+ * <p>
96
+ *
97
+ * When the tracking of a race/regatta is {@link #stopTracking(Regatta, RaceDefinition) stopped}, the next time it's
98
+ * started to be tracked, a new {@link TrackedRace} at least will be constructed. This also means that when a
99
+ * {@link TrackedRegatta} exists that still holds other {@link TrackedRace}s, the no longer tracked {@link TrackedRace}
100
+ * will be removed from the {@link TrackedRegatta}. corresponding information is removed also from the
101
+ * {@link DomainFactory}'s caches to ensure that clean, fresh data is received should another tracking request be issued
102
+ * later.
103
+ * <p>
104
+ *
105
+ * During receiving the initial load for a replication in {@link #initiallyFillFromInternal(java.io.ObjectInputStream)},
106
+ * tracked regattas read from the stream are observed (see {@link RaceListener}) by this object for automatic updates to
107
+ * the default leaderboard and for automatic linking to leaderboard columns. It is assumed that no explicit replication
108
+ * of these operations will happen based on the changes performed on the replication master.
109
+ *
110
+ * @author Axel Uhl (d043530)
111
+ *
112
+ */
113
+public interface RacingEventService extends TrackedRegattaRegistry, RegattaFetcher, RegattaRegistry, RaceFetcher,
114
+ LeaderboardRegistry, EventResolver, LeaderboardGroupResolver, TrackerManager, Searchable<LeaderboardSearchResult, KeywordQuery>,
115
+ ReplicableWithObjectInputStream<RacingEventService, RacingEventServiceOperation<?>>, RaceLogResolver {
116
+ @Override
117
+ Regatta getRegatta(RegattaName regattaName);
118
+
119
+ @Override
120
+ RaceDefinition getRace(RegattaAndRaceIdentifier raceIdentifier);
121
+
122
+ DynamicTrackedRace getTrackedRace(Regatta regatta, RaceDefinition race);
123
+
124
+ DynamicTrackedRace getTrackedRace(RegattaAndRaceIdentifier raceIdentifier);
125
+
126
+ /**
127
+ * Obtains an unmodifiable map of the leaderboard configured in this service keyed by their names.
128
+ */
129
+ Map<String, Leaderboard> getLeaderboards();
130
+
131
+ /**
132
+ * @return a leaderboard whose {@link Leaderboard#getName()} method returns the value of the <code>name</code>
133
+ * parameter, or <code>null</code> if no such leaderboard is known to this service
134
+ */
135
+ Leaderboard getLeaderboardByName(String name);
136
+
137
+ /**
138
+ * Looks at the mark tracks in the tracked races attached to the <code>leaderboard</code>. If it doesn't find a
139
+ * track for the <code>mark</code> requested there which has fixes before and after <code>timePoint</code> (to
140
+ * ensure that no track cropping has taken place, removing the fixes for the interesting time period), looks in the
141
+ * leaderboard's regatta log and the specific <code>raceLog</code> (if provided) or all race logs attached to the
142
+ * leaderboard (if not provided) for device mappings for the mark and tries to load fixes from the
143
+ * {@link GPSFixStore}. The latter is only necessary if the mark isn't found in any tracked race with fixes
144
+ * surrounding <code>timePoint</code> because should there be a tracked race in the leaderboard that has the mark
145
+ * then it will also have received the fixes from the {@link GPSFixStore} through the regatta log mapping.
146
+ * <p>
147
+ *
148
+ * @return the position obtained by interpolation but never extrapolation from the track identified as described
149
+ * above
150
+ */
151
+ Position getMarkPosition(Mark mark, LeaderboardThatHasRegattaLike leaderboard, TimePoint timePoint, RaceLog raceLog);
152
+
153
+ /**
154
+ * Stops tracking all races of the regatta specified. This will also stop tracking wind for all races of this regatta.
155
+ * See {@link #stopTrackingWind(Regatta, RaceDefinition)}. If there were multiple calls to
156
+ * {@link #addTracTracRace(URL, URI, URI, WindStore, long)} with an equal combination of URLs/URIs, the {@link TracTracRaceTracker}
157
+ * already tracking the race was re-used. The trackers will be stopped by this call regardless of how many calls
158
+ * were made that ensured they were tracking.
159
+ */
160
+ void stopTracking(Regatta regatta) throws MalformedURLException, IOException, InterruptedException;
161
+
162
+ /**
163
+ * Removes <code>race</code> and any corresponding {@link #getTrackedRace(Regatta, RaceDefinition) tracked race}
164
+ * from this service. If it was the last {@link RaceDefinition} in its {@link Regatta} and the regatta
165
+ * {@link Regatta#isPersistent() is not stored persistently}, the <code>regatta</code> is removed as well and will no
166
+ * longer be returned by {@link #getAllRegattas()}. The wind tracking is stopped for <code>race</code>.
167
+ * <p>
168
+ *
169
+ * Any {@link RaceTracker} for which <code>race</race> is the last race tracked that is still reachable
170
+ * from {@link #getAllRegattas()} will be {@link RaceTracker#stop(boolean) stopped}.
171
+ *
172
+ * The <code>race</code> will be also removed from all leaderboards containing a column that has <code>race</code>'s
173
+ * {@link #getTrackedRace(Regatta, RaceDefinition) corresponding} {@link TrackedRace} as its
174
+ * {@link RaceColumn#getTrackedRace(Fleet)}.
175
+ *
176
+ * @param regatta
177
+ * the regatta from which to remove the race
178
+ * @param race
179
+ * the race to remove
180
+ */
181
+ void removeRace(Regatta regatta, RaceDefinition race) throws MalformedURLException, IOException,InterruptedException;
182
+
183
+ /**
184
+ * @param port
185
+ * the UDP port on which to listen for incoming messages from Expedition clients
186
+ * @param correctByDeclination
187
+ * An optional service to convert the wind bearings (which the receiver may
188
+ * believe to be true bearings) from magnetic to true bearings.
189
+ * @throws SocketException
190
+ * thrown, e.g., in case there is already another listener on the port requested
191
+ */
192
+ void startTrackingWind(Regatta regatta, RaceDefinition race, boolean correctByDeclination) throws Exception;
193
+
194
+ void stopTrackingWind(Regatta regatta, RaceDefinition race) throws SocketException, IOException;
195
+
196
+ /**
197
+ * The {@link Triple#getC() third component} of the triples returned is a wind tracker-specific
198
+ * comment where a wind tracker may provide information such as its type name or, if applicable,
199
+ * connectivity information such as the network port on which it receives wind information.
200
+ */
201
+ Iterable<Util.Triple<Regatta, RaceDefinition, String>> getWindTrackedRaces();
202
+
203
+ /**
204
+ * Creates a new leaderboard with the <code>name</code> specified.
205
+ * @param discardThresholds
206
+ * Tells the thresholds from which on a next higher number of worst races will be discarded per
207
+ * competitor. Example: <code>[3, 6]</code> means that starting from three races the single worst race
208
+ * will be discarded; starting from six races, the two worst races per competitor are discarded.
209
+ *
210
+ * @return the leaderboard created
211
+ */
212
+ FlexibleLeaderboard addFlexibleLeaderboard(String leaderboardName, String leaderboardDisplayName, int[] discardThresholds, ScoringScheme scoringScheme, Serializable courseAreaId);
213
+
214
+ RegattaLeaderboard addRegattaLeaderboard(RegattaIdentifier regattaIdentifier, String leaderboardDisplayName, int[] discardThresholds);
215
+
216
+ void removeLeaderboard(String leaderboardName);
217
+
218
+ /**
219
+ * Renames a leaderboard. If a leaderboard by the name <code>oldName</code> does not exist in {@link #getLeaderboards()},
220
+ * or if a leaderboard with the name <code>newName</code> already exists, an {@link IllegalArgumentException} is thrown.
221
+ * If the method completes normally, the rename has been successful, and the leaderboard previously obtained by calling
222
+ * {@link #getLeaderboardByName(String) getLeaderboardByName(oldName)} can now be obtained by calling
223
+ * {@link #getLeaderboardByName(String) getLeaderboardByName(newName)}.
224
+ */
225
+ void renameLeaderboard(String oldName, String newName);
226
+
227
+ RaceColumn addColumnToLeaderboard(String columnName, String leaderboardName, boolean medalRace);
228
+
229
+ void moveLeaderboardColumnUp(String leaderboardName, String columnName);
230
+
231
+ void moveLeaderboardColumnDown(String leaderboardName, String columnName);
232
+
233
+ void removeLeaderboardColumn(String leaderboardName, String columnName);
234
+
235
+ void renameLeaderboardColumn(String leaderboardName, String oldColumnName, String newColumnName);
236
+
237
+ /**
238
+ * @see RaceColumn#setFactor(Double)
239
+ */
240
+ void updateLeaderboardColumnFactor(String leaderboardName, String columnName, Double factor);
241
+
242
+ /**
243
+ * Updates the leaderboard data in the persistent store
244
+ */
245
+ void updateStoredLeaderboard(Leaderboard leaderboard);
246
+
247
+ void updateStoredRegatta(Regatta regatta);
248
+
249
+ void stopTrackingAndRemove(Regatta regatta) throws MalformedURLException, IOException, InterruptedException;
250
+
251
+ /**
252
+ * Removes the regatta as well as all regatta leaderboards for that regatta
253
+ */
254
+ void removeRegatta(Regatta regatta) throws MalformedURLException, IOException, InterruptedException;
255
+
256
+ /**
257
+ * Removes the given series
258
+ */
259
+ void removeSeries(Series series) throws MalformedURLException, IOException, InterruptedException;
260
+
261
+ DynamicTrackedRace getExistingTrackedRace(RegattaAndRaceIdentifier raceIdentifier);
262
+
263
+ /**
264
+ * Obtains an unmodifiable map of the leaderboard groups configured in this service keyed by their names.
265
+ */
266
+ Map<String, LeaderboardGroup> getLeaderboardGroups();
267
+
268
+ /**
269
+ * Creates a new group with the name <code>groupName</code>, the description <code>desciption</code> and the
270
+ * leaderboards with the names in <code>leaderboardNames</code> and saves it in the database.
271
+ * @param id TODO
272
+ * @param groupName
273
+ * The name of the new group
274
+ * @param description
275
+ * The description of the new group
276
+ * @param displayName TODO
277
+ * @param displayGroupsInReverseOrder TODO
278
+ * @param leaderboardNames
279
+ * The names of the leaderboards, which should be contained by the new group.<br />
280
+ * If there isn't a leaderboard with one of these names an {@link IllegalArgumentException} is thrown.
281
+ * @return The new leaderboard group
282
+ */
283
+ LeaderboardGroup addLeaderboardGroup(UUID id, String groupName, String description,
284
+ String displayName, boolean displayGroupsInReverseOrder, List<String> leaderboardNames, int[] overallLeaderboardDiscardThresholds, ScoringSchemeType overallLeaderboardScoringSchemeType);
285
+
286
+ /**
287
+ * Removes the group with the name <code>groupName</code> from the service and the database.
288
+ * @param groupName The name of the group which shall be removed.
289
+ */
290
+ void removeLeaderboardGroup(String groupName);
291
+
292
+ /**
293
+ * Renames the group with the name <code>oldName</code> to the <code>newName</code>.<br />
294
+ * If there's no group with the name <code>oldName</code> or there's already a group with the name
295
+ * <code>newName</code> a {@link IllegalArgumentException} is thrown.
296
+ *
297
+ * @param oldName The old name of the group
298
+ * @param newName The new name of the group
299
+ */
300
+ void renameLeaderboardGroup(String oldName, String newName);
301
+
302
+ /**
303
+ * Updates the group data in the persistant store.
304
+ */
305
+ void updateStoredLeaderboardGroup(LeaderboardGroup leaderboardGroup);
306
+
307
+ DynamicTrackedRace createTrackedRace(RegattaAndRaceIdentifier raceIdentifier, WindStore windStore, GPSFixStore gpsFixStore,
308
+ long delayToLiveInMillis, long millisecondsOverWhichToAverageWind, long millisecondsOverWhichToAverageSpeed, boolean useMarkPassingCalculator);
309
+
310
+ Regatta getOrCreateDefaultRegatta(String name, String boatClassName, Serializable id);
311
+
312
+ /**
313
+ * @param series the series must not have any {@link RaceColumn}s yet
314
+ */
315
+ Regatta createRegatta(String regattaName, String boatClassName, TimePoint startDate, TimePoint endDate, Serializable id, Iterable<? extends Series> series,
316
+ boolean persistent, ScoringScheme scoringScheme, Serializable defaultCourseAreaId,
317
+ boolean useStartTimeInference, RankingMetricConstructor rankingMetricConstructor);
318
+
319
+ Regatta updateRegatta(RegattaIdentifier regattaIdentifier, TimePoint startDate, TimePoint endDate, Serializable newDefaultCourseAreaId, RegattaConfiguration regattaConfiguration, Iterable<? extends Series> series, boolean useStartTimeInference);
320
+
321
+ /**
322
+ * Adds <code>raceDefinition</code> to the {@link Regatta} such that it will appear in {@link Regatta#getAllRaces()}
323
+ * and {@link Regatta#getRaceByName(String)}.
324
+ *
325
+ * @param addToRegatta identifier of an regatta that must exist already
326
+ */
327
+ void addRace(RegattaIdentifier addToRegatta, RaceDefinition raceDefinition);
328
+
329
+ void updateLeaderboardGroup(String oldName, String newName, String description, String displayName,
330
+ List<String> leaderboardNames, int[] overallLeaderboardDiscardThresholds, ScoringSchemeType overallLeaderboardScoringSchemeType);
331
+
332
+ /**
333
+ * @return a thread-safe copy of the events currently known by the service; it's safe for callers to iterate over
334
+ * the iterable returned, and no risk of a {@link ConcurrentModificationException} exists
335
+ */
336
+ Iterable<Event> getAllEvents();
337
+
338
+ /**
339
+ * Creates a new event with the name <code>eventName</code>, the venue <code>venue</code> and the regattas with the
340
+ * names in <code>regattaNames</code>, saves it in the database and replicates it. Use for TESTING only!
341
+ *
342
+ * @param eventName
343
+ * The name of the new event
344
+ * @param eventDescription TODO
345
+ * @param startDate
346
+ * The start date of the event
347
+ * @param endDate
348
+ * The end date of the event
349
+ * @param isPublic
350
+ * Indicates whether the event is public accessible via the publication URL or not
351
+ * @param id
352
+ * The id of the new event
353
+ * @param venue
354
+ * The name of the venue of the new event
355
+ * @return The new event
356
+ */
357
+ Event addEvent(String eventName, String eventDescription, TimePoint startDate, TimePoint endDate, String venueName, boolean isPublic, UUID id);
358
+
359
+ /**
360
+ * Updates a sailing event with the name <code>eventName</code>, the venue<code>venue</code> and the regattas with
361
+ * the names in <code>regattaNames</code> and updates it in the database.
362
+ * @param eventName
363
+ * The name of the event to update
364
+ * @param startDate
365
+ * The start date of the event
366
+ * @param endDate
367
+ * The end date of the event
368
+ * @param venueName
369
+ * The name of the venue of the event
370
+ * @param isPublic
371
+ * Indicates whether the event is public accessible via the publication URL or not
372
+ * @return The new event
373
+ */
374
+ void updateEvent(UUID id, String eventName, String eventDescription, TimePoint startDate, TimePoint endDate,
375
+ String venueName, boolean isPublic, Iterable<UUID> leaderboardGroupIds, URL officialWebsiteURL, URL sailorsInfoWebsiteURL,
376
+ Iterable<ImageDescriptor> images, Iterable<VideoDescriptor> videos);
377
+
378
+ /**
379
+ * Renames a sailing event. If a sailing event by the name <code>oldName</code> does not exist in {@link #getEvents()},
380
+ * or if a event with the name <code>newName</code> already exists, an {@link IllegalArgumentException} is thrown.
381
+ * If the method completes normally, the rename has been successful, and the event previously obtained by calling
382
+ * {@link #getEventByName(String) getEventByName(oldName)} can now be obtained by calling
383
+ * {@link #getEventByName(String) getEventByName(newName)}.
384
+ */
385
+ void renameEvent(UUID id, String newEventName);
386
+
387
+ void removeEvent(UUID id);
388
+
389
+
390
+ /**
391
+ * @return a thread-safe copy of the events (or the exception that occurred trying to obtain the events; arranged in
392
+ * a {@link Util.Pair}) of from all sailing server instances currently known by the service; it's safe for
393
+ * callers to iterate over the iterable returned, and no risk of a {@link ConcurrentModificationException}
394
+ * exists
395
+ */
396
+ Map<RemoteSailingServerReference, Util.Pair<Iterable<EventBase>, Exception>> getPublicEventsOfAllSailingServers();
397
+
398
+ RemoteSailingServerReference addRemoteSailingServerReference(String name, URL url);
399
+
400
+ void removeRemoteSailingServerReference(String name);
401
+
402
+
403
+ CourseArea[] addCourseAreas(UUID eventId, String[] courseAreaNames, UUID[] courseAreaIds);
404
+
405
+ com.sap.sailing.domain.base.DomainFactory getBaseDomainFactory();
406
+
407
+ CourseArea getCourseArea(Serializable courseAreaId);
408
+
409
+ /**
410
+ * Adds the specified mediaTrack to the in-memory media library.
411
+ * Important note: Only if mediaTrack.dbId != null the mediaTrack will be persisted in the the database.
412
+ * @param mediaTrack
413
+ */
414
+ void mediaTrackAdded(MediaTrack mediaTrack);
415
+
416
+ /**
417
+ * Calling mediaTrackAdded for every entry in the specified collection.
418
+ * @param mediaTracks
419
+ */
420
+ void mediaTracksAdded(Collection<MediaTrack> mediaTracks);
421
+
422
+ void mediaTrackTitleChanged(MediaTrack mediaTrack);
423
+
424
+ void mediaTrackUrlChanged(MediaTrack mediaTrack);
425
+
426
+ void mediaTrackStartTimeChanged(MediaTrack mediaTrack);
427
+
428
+ void mediaTrackDurationChanged(MediaTrack mediaTrack);
429
+
430
+ void mediaTrackAssignedRacesChanged(MediaTrack mediaTrack);
431
+
432
+ void mediaTrackDeleted(MediaTrack mediaTrack);
433
+
434
+ /**
435
+ * In contrast to mediaTracksAdded, this method takes mediaTracks with a given dbId.
436
+ * Checks if the track already exists in the library and the database and adds/stores it
437
+ * accordingly. If a track already exists and override, its properties are checked for changes
438
+ * @param mediaTrack
439
+ * @param override If true, track properties (title, url, start time, duration, not mime type!) will be
440
+ * overwritten with the values from the track to be imported.
441
+ */
442
+ void mediaTracksImported(Collection<MediaTrack> mediaTracksToImport, boolean override);
443
+
444
+ Collection<MediaTrack> getMediaTracksForRace(RegattaAndRaceIdentifier regattaAndRaceIdentifier);
445
+
446
+ Collection<MediaTrack> getMediaTracksInTimeRange(RegattaAndRaceIdentifier regattaAndRaceIdentifier);
447
+
448
+ Collection<MediaTrack> getAllMediaTracks();
449
+
450
+ void reloadRaceLog(String leaderboardName, String raceColumnName, String fleetName);
451
+
452
+ RaceLog getRaceLog(String leaderboardName, String raceColumnName, String fleetName);
453
+
454
+ /**
455
+ * @param rankingMetricConstructor TODO
456
+ * @return a pair with the found or created regatta, and a boolean that tells whether the regatta was created during
457
+ * the call
458
+ */
459
+ Util.Pair<Regatta, Boolean> getOrCreateRegattaWithoutReplication(String fullRegattaName, String boatClassName,
460
+ TimePoint startDate, TimePoint endDate, Serializable id,
461
+ Iterable<? extends Series> series, boolean persistent, ScoringScheme scoringScheme,
462
+ Serializable defaultCourseAreaId, boolean useStartTimeInference, RankingMetricConstructor rankingMetricConstructor);
463
+
464
+ /**
465
+ * @return map where keys are the toString() representation of the {@link RaceDefinition#getId() IDs} of races passed to
466
+ * {@link #setRegattaForRace(Regatta, RaceDefinition)}. It helps remember the connection between races and regattas.
467
+ */
468
+ ConcurrentHashMap<String, Regatta> getPersistentRegattasForRaceIDs();
469
+
470
+ Event createEventWithoutReplication(String eventName, String eventDescription, TimePoint startDate, TimePoint endDate, String venue,
471
+ boolean isPublic, UUID id, URL officialWebsiteURL, URL sailorsInfoWebsiteURL, Iterable<ImageDescriptor> images,
472
+ Iterable<VideoDescriptor> videos);
473
+
474
+ void setRegattaForRace(Regatta regatta, String raceIdAsString);
475
+
476
+ CourseArea[] addCourseAreasWithoutReplication(UUID eventId, UUID[] courseAreaIds, String[] courseAreaNames);
477
+
478
+ CourseArea[] removeCourseAreaWithoutReplication(UUID eventId, UUID[] courseAreaIds);
479
+
480
+ /**
481
+ * Returns a mobile device's configuration.
482
+ * @param identifier of the client (may include event)
483
+ * @return the {@link DeviceConfiguration}
484
+ */
485
+ DeviceConfiguration getDeviceConfiguration(DeviceConfigurationIdentifier identifier);
486
+
487
+ /**
488
+ * Adds a device configuration.
489
+ * @param matcher defining for which the configuration applies.
490
+ * @param configuration of the device.
491
+ */
492
+ void createOrUpdateDeviceConfiguration(DeviceConfigurationMatcher matcher, DeviceConfiguration configuration);
493
+
494
+ /**
495
+ * Removes a configuration by its matching object.
496
+ * @param matcher
497
+ */
498
+ void removeDeviceConfiguration(DeviceConfigurationMatcher matcher);
499
+
500
+ /**
501
+ * Returns all configurations and their matching objects.
502
+ * @return the {@link DeviceConfiguration}s.
503
+ */
504
+ Map<DeviceConfigurationMatcher, DeviceConfiguration> getAllDeviceConfigurations();
505
+
506
+ /**
507
+ * Forces a new start time on the RaceLog identified by the passed parameters.
508
+ * @param leaderboardName name of the RaceLog's leaderboard.
509
+ * @param raceColumnName name of the RaceLog's column
510
+ * @param fleetName name of the RaceLog's fleet
511
+ * @param authorName name of the {@link AbstractLogEventAuthor} the {@link RaceLogStartTimeEvent} will be created with
512
+ * @param authorPriority priority of the author.
513
+ * @param passId Pass identifier of the new start time event.
514
+ * @param logicalTimePoint logical {@link TimePoint} of the new event.
515
+ * @param startTime the new Start-Time
516
+ * @return
517
+ */
518
+ TimePoint setStartTimeAndProcedure(String leaderboardName, String raceColumnName, String fleetName, String authorName,
519
+ int authorPriority, int passId, TimePoint logicalTimePoint, TimePoint startTime, RacingProcedureType racingProcedure);
520
+
521
+ /**
522
+ * Gets the start time, pass identifier and racing procedure for the queried race. Start time might be <code>null</code>.
523
+ */
524
+ Util.Triple<TimePoint, Integer, RacingProcedureType> getStartTimeAndProcedure(String leaderboardName, String raceColumnName, String fleetName);
525
+
526
+ MongoObjectFactory getMongoObjectFactory();
527
+
528
+ DomainObjectFactory getDomainObjectFactory();
529
+
530
+ WindStore getWindStore();
531
+
532
+ PolarDataService getPolarDataService();
533
+
534
+ SimulationService getSimulationService();
535
+
536
+ GPSFixStore getGPSFixStore();
537
+
538
+ RaceTracker getRaceTrackerById(Object id);
539
+
540
+ AbstractLogEventAuthor getServerAuthor();
541
+
542
+ CompetitorStore getCompetitorStore();
543
+
544
+ TypeBasedServiceFinderFactory getTypeBasedServiceFinderFactory();
545
+
546
+ /**
547
+ * This lock exists to allow only one master data import at a time to avoid situation where multiple Imports
548
+ * override each other in unpredictable fashion
549
+ */
550
+ DataImportLockWithProgress getDataImportLock();
551
+
552
+ DataImportProgress createOrUpdateDataImportProgressWithReplication(UUID importOperationId,
553
+ double overallProgressPct,
554
+ String subProgressName, double subProgressPct);
555
+
556
+ DataImportProgress createOrUpdateDataImportProgressWithoutReplication(UUID importOperationId,
557
+ double overallProgressPct,
558
+ String subProgressName, double subProgressPct);
559
+
560
+ void setDataImportFailedWithReplication(UUID importOperationId, String errorMessage);
561
+
562
+ void setDataImportFailedWithoutReplication(UUID importOperationId, String errorMessage);
563
+
564
+ void setDataImportDeleteProgressFromMapTimerWithReplication(UUID importOperationId);
565
+
566
+ void setDataImportDeleteProgressFromMapTimerWithoutReplication(UUID importOperationId);
567
+
568
+ /**
569
+ * For the reference to a remote sailing server, updates its events cache and returns the event list
570
+ * or, if fetching the event list from the remote server did fail, the exception for which it failed.
571
+ */
572
+ Util.Pair<Iterable<EventBase>, Exception> updateRemoteServerEventCacheSynchronously(RemoteSailingServerReference ref);
573
+
574
+ /**
575
+ * Searches the content of this server, not that of any remote servers referenced by any {@link RemoteSailingServerReference}s.
576
+ */
577
+ @Override
578
+ Result<LeaderboardSearchResult> search(KeywordQuery query);
579
+
580
+ /**
581
+ * Searches a specific remote server whose reference has the {@link RemoteSailingServerReference#getName() name}
582
+ * <code>remoteServerReferenceName</code>. If a remote server reference with that name is not known,
583
+ * <code>null</code> is returned. Otherwise, a non-<code>null</code> and possibly empty search result set is
584
+ * returned.
585
+ */
586
+ Result<LeaderboardSearchResultBase> searchRemotely(String remoteServerReferenceName, KeywordQuery query);
587
+
588
+ /**
589
+ * Gets the configuration of the local sailing server instances.
590
+ */
591
+ SailingServerConfiguration getSailingServerConfiguration();
592
+
593
+ void updateServerConfiguration(SailingServerConfiguration serverConfiguration);
594
+
595
+ /**
596
+ * References to remote servers may be dead or alive. This is internally determined by regularly polling those
597
+ * servers for their events list. If the events list cannot be successfully retrieved, the server is considered "dead."
598
+ * This method returns the "live" server references.
599
+ */
600
+ Iterable<RemoteSailingServerReference> getLiveRemoteServerReferences();
601
+
602
+ RemoteSailingServerReference getRemoteServerReferenceByName(String remoteServerReferenceName);
603
+
604
+ void addRegattaWithoutReplication(Regatta regatta);
605
+
606
+ void addEventWithoutReplication(Event event);
607
+
608
+ /**
609
+ * Adds the leaderboard group to this service; if the group has an overall leaderboard, the overall leaderboard
610
+ * is added to this service as well. For both, the group and the overall leaderboard, any previously existing
611
+ * objects by the same name of that type will be replaced.
612
+ */
613
+ void addLeaderboardGroupWithoutReplication(LeaderboardGroup leaderboardGroup);
614
+
615
+ /**
616
+ * @return {@code null} if no service can be found in the OSGi registry
617
+ */
618
+ FileStorageManagementService getFileStorageManagementService();
619
+
620
+ ClassLoader getCombinedMasterDataClassLoader();
621
+
622
+ Iterable<Competitor> getCompetitorInOrderOfWindwardDistanceTraveledFarthestFirst(TrackedRace trackedRace, TimePoint timePoint);
623
+}
java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RacingEventServiceImpl.java
... ...
@@ -1,3344 +1,3344 @@
1
-package com.sap.sailing.server.impl;
2
-
3
-import java.io.BufferedReader;
4
-import java.io.IOException;
5
-import java.io.InputStream;
6
-import java.io.InputStreamReader;
7
-import java.io.ObjectInputStream;
8
-import java.io.ObjectOutputStream;
9
-import java.io.Serializable;
10
-import java.net.MalformedURLException;
11
-import java.net.SocketException;
12
-import java.net.URL;
13
-import java.net.URLConnection;
14
-import java.net.URLEncoder;
15
-import java.util.ArrayList;
16
-import java.util.Arrays;
17
-import java.util.Collection;
18
-import java.util.Collections;
19
-import java.util.Comparator;
20
-import java.util.HashMap;
21
-import java.util.HashSet;
22
-import java.util.Iterator;
23
-import java.util.LinkedHashMap;
24
-import java.util.List;
25
-import java.util.Map;
26
-import java.util.Map.Entry;
27
-import java.util.Set;
28
-import java.util.UUID;
29
-import java.util.concurrent.ConcurrentHashMap;
30
-import java.util.concurrent.Executor;
31
-import java.util.concurrent.Executors;
32
-import java.util.concurrent.LinkedBlockingQueue;
33
-import java.util.concurrent.ScheduledExecutorService;
34
-import java.util.concurrent.ScheduledFuture;
35
-import java.util.concurrent.ThreadPoolExecutor;
36
-import java.util.concurrent.TimeUnit;
37
-import java.util.function.Function;
38
-import java.util.logging.Level;
39
-import java.util.logging.Logger;
40
-
41
-import org.json.simple.JSONArray;
42
-import org.json.simple.JSONObject;
43
-import org.json.simple.parser.JSONParser;
44
-import org.json.simple.parser.ParseException;
45
-import org.osgi.framework.BundleContext;
46
-import org.osgi.framework.ServiceReference;
47
-import org.osgi.util.tracker.ServiceTracker;
48
-
49
-import com.sap.sailing.domain.abstractlog.AbstractLog;
50
-import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor;
51
-import com.sap.sailing.domain.abstractlog.impl.LogEventAuthorImpl;
52
-import com.sap.sailing.domain.abstractlog.race.RaceLog;
53
-import com.sap.sailing.domain.abstractlog.race.RaceLogEvent;
54
-import com.sap.sailing.domain.abstractlog.race.RaceLogEventVisitor;
55
-import com.sap.sailing.domain.abstractlog.race.SimpleRaceLogIdentifier;
56
-import com.sap.sailing.domain.abstractlog.race.analyzing.impl.RaceLogResolver;
57
-import com.sap.sailing.domain.abstractlog.race.state.RaceState;
58
-import com.sap.sailing.domain.abstractlog.race.state.ReadonlyRaceState;
59
-import com.sap.sailing.domain.abstractlog.race.state.impl.RaceStateImpl;
60
-import com.sap.sailing.domain.abstractlog.race.state.impl.ReadonlyRaceStateImpl;
61
-import com.sap.sailing.domain.base.Competitor;
62
-import com.sap.sailing.domain.base.CompetitorStore;
63
-import com.sap.sailing.domain.base.CompetitorStore.CompetitorUpdateListener;
64
-import com.sap.sailing.domain.base.ControlPoint;
65
-import com.sap.sailing.domain.base.CourseArea;
66
-import com.sap.sailing.domain.base.DomainFactory;
67
-import com.sap.sailing.domain.base.Event;
68
-import com.sap.sailing.domain.base.EventBase;
69
-import com.sap.sailing.domain.base.EventFetcher;
70
-import com.sap.sailing.domain.base.Fleet;
71
-import com.sap.sailing.domain.base.LeaderboardSearchResult;
72
-import com.sap.sailing.domain.base.LeaderboardSearchResultBase;
73
-import com.sap.sailing.domain.base.Mark;
74
-import com.sap.sailing.domain.base.RaceColumn;
75
-import com.sap.sailing.domain.base.RaceColumnInSeries;
76
-import com.sap.sailing.domain.base.RaceDefinition;
77
-import com.sap.sailing.domain.base.Regatta;
78
-import com.sap.sailing.domain.base.RegattaListener;
79
-import com.sap.sailing.domain.base.RemoteSailingServerReference;
80
-import com.sap.sailing.domain.base.SailingServerConfiguration;
81
-import com.sap.sailing.domain.base.Series;
82
-import com.sap.sailing.domain.base.Sideline;
83
-import com.sap.sailing.domain.base.Waypoint;
84
-import com.sap.sailing.domain.base.configuration.DeviceConfiguration;
85
-import com.sap.sailing.domain.base.configuration.DeviceConfigurationIdentifier;
86
-import com.sap.sailing.domain.base.configuration.DeviceConfigurationMatcher;
87
-import com.sap.sailing.domain.base.configuration.RegattaConfiguration;
88
-import com.sap.sailing.domain.base.configuration.impl.DeviceConfigurationMapImpl;
89
-import com.sap.sailing.domain.base.impl.DynamicCompetitor;
90
-import com.sap.sailing.domain.base.impl.EventImpl;
91
-import com.sap.sailing.domain.base.impl.RegattaImpl;
92
-import com.sap.sailing.domain.base.impl.RemoteSailingServerReferenceImpl;
93
-import com.sap.sailing.domain.common.DataImportProgress;
94
-import com.sap.sailing.domain.common.Distance;
95
-import com.sap.sailing.domain.common.Position;
96
-import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
97
-import com.sap.sailing.domain.common.RegattaIdentifier;
98
-import com.sap.sailing.domain.common.RegattaName;
99
-import com.sap.sailing.domain.common.Renamable;
100
-import com.sap.sailing.domain.common.ScoringSchemeType;
101
-import com.sap.sailing.domain.common.TrackedRaceStatusEnum;
102
-import com.sap.sailing.domain.common.Wind;
103
-import com.sap.sailing.domain.common.WindSource;
104
-import com.sap.sailing.domain.common.dto.FleetDTO;
105
-import com.sap.sailing.domain.common.dto.RegattaCreationParametersDTO;
106
-import com.sap.sailing.domain.common.dto.SeriesCreationParametersDTO;
107
-import com.sap.sailing.domain.common.impl.DataImportProgressImpl;
108
-import com.sap.sailing.domain.common.media.MediaTrack;
109
-import com.sap.sailing.domain.common.racelog.RacingProcedureType;
110
-import com.sap.sailing.domain.common.tracking.GPSFix;
111
-import com.sap.sailing.domain.common.tracking.GPSFixMoving;
112
-import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard;
113
-import com.sap.sailing.domain.leaderboard.FlexibleRaceColumn;
114
-import com.sap.sailing.domain.leaderboard.Leaderboard;
115
-import com.sap.sailing.domain.leaderboard.LeaderboardGroup;
116
-import com.sap.sailing.domain.leaderboard.LeaderboardRegistry;
117
-import com.sap.sailing.domain.leaderboard.RegattaLeaderboard;
118
-import com.sap.sailing.domain.leaderboard.ScoringScheme;
119
-import com.sap.sailing.domain.leaderboard.impl.FlexibleLeaderboardImpl;
120
-import com.sap.sailing.domain.leaderboard.impl.LeaderboardGroupImpl;
121
-import com.sap.sailing.domain.leaderboard.impl.RegattaLeaderboardImpl;
122
-import com.sap.sailing.domain.leaderboard.impl.ThresholdBasedResultDiscardingRuleImpl;
123
-import com.sap.sailing.domain.leaderboard.meta.LeaderboardGroupMetaLeaderboard;
124
-import com.sap.sailing.domain.persistence.DomainObjectFactory;
125
-import com.sap.sailing.domain.persistence.MongoObjectFactory;
126
-import com.sap.sailing.domain.persistence.MongoRaceLogStoreFactory;
127
-import com.sap.sailing.domain.persistence.MongoRegattaLogStoreFactory;
128
-import com.sap.sailing.domain.persistence.MongoWindStore;
129
-import com.sap.sailing.domain.persistence.MongoWindStoreFactory;
130
-import com.sap.sailing.domain.persistence.PersistenceFactory;
131
-import com.sap.sailing.domain.persistence.media.MediaDB;
132
-import com.sap.sailing.domain.persistence.media.MediaDBFactory;
133
-import com.sap.sailing.domain.persistence.racelog.tracking.MongoGPSFixStoreFactory;
134
-import com.sap.sailing.domain.polars.PolarDataService;
135
-import com.sap.sailing.domain.racelog.RaceLogIdentifier;
136
-import com.sap.sailing.domain.racelog.RaceLogStore;
137
-import com.sap.sailing.domain.racelog.tracking.GPSFixStore;
138
-import com.sap.sailing.domain.racelogtracking.DeviceIdentifier;
139
-import com.sap.sailing.domain.ranking.RankingMetric.CompetitorRankingInfo;
140
-import com.sap.sailing.domain.ranking.RankingMetric.RankingInfo;
141
-import com.sap.sailing.domain.ranking.RankingMetricConstructor;
142
-import com.sap.sailing.domain.regattalike.HasRegattaLike;
143
-import com.sap.sailing.domain.regattalike.IsRegattaLike;
144
-import com.sap.sailing.domain.regattalike.LeaderboardThatHasRegattaLike;
145
-import com.sap.sailing.domain.regattalog.RegattaLogStore;
146
-import com.sap.sailing.domain.tracking.DynamicTrackedRace;
147
-import com.sap.sailing.domain.tracking.DynamicTrackedRegatta;
148
-import com.sap.sailing.domain.tracking.GPSFixTrack;
149
-import com.sap.sailing.domain.tracking.MarkPassing;
150
-import com.sap.sailing.domain.tracking.RaceChangeListener;
151
-import com.sap.sailing.domain.tracking.RaceHandle;
152
-import com.sap.sailing.domain.tracking.RaceListener;
153
-import com.sap.sailing.domain.tracking.RaceTracker;
154
-import com.sap.sailing.domain.tracking.RaceTrackingConnectivityParameters;
155
-import com.sap.sailing.domain.tracking.TrackedRace;
156
-import com.sap.sailing.domain.tracking.TrackedRaceStatus;
157
-import com.sap.sailing.domain.tracking.TrackedRegatta;
158
-import com.sap.sailing.domain.tracking.WindStore;
159
-import com.sap.sailing.domain.tracking.WindTracker;
160
-import com.sap.sailing.domain.tracking.WindTrackerFactory;
161
-import com.sap.sailing.domain.tracking.impl.AbstractRaceChangeListener;
162
-import com.sap.sailing.domain.tracking.impl.DynamicGPSFixTrackImpl;
163
-import com.sap.sailing.domain.tracking.impl.DynamicTrackedRegattaImpl;
164
-import com.sap.sailing.domain.tracking.impl.TrackedRaceImpl;
165
-import com.sap.sailing.expeditionconnector.ExpeditionWindTrackerFactory;
166
-import com.sap.sailing.server.RacingEventService;
167
-import com.sap.sailing.server.Replicator;
168
-import com.sap.sailing.server.gateway.deserialization.impl.CourseAreaJsonDeserializer;
169
-import com.sap.sailing.server.gateway.deserialization.impl.EventBaseJsonDeserializer;
170
-import com.sap.sailing.server.gateway.deserialization.impl.LeaderboardGroupBaseJsonDeserializer;
171
-import com.sap.sailing.server.gateway.deserialization.impl.LeaderboardSearchResultBaseJsonDeserializer;
172
-import com.sap.sailing.server.gateway.deserialization.impl.VenueJsonDeserializer;
173
-import com.sap.sailing.server.masterdata.DataImportLockWithProgress;
174
-import com.sap.sailing.server.operationaltransformation.AddCourseAreas;
175
-import com.sap.sailing.server.operationaltransformation.AddDefaultRegatta;
176
-import com.sap.sailing.server.operationaltransformation.AddMediaTrackOperation;
177
-import com.sap.sailing.server.operationaltransformation.AddRaceDefinition;
178
-import com.sap.sailing.server.operationaltransformation.AddSpecificRegatta;
179
-import com.sap.sailing.server.operationaltransformation.ConnectTrackedRaceToLeaderboardColumn;
180
-import com.sap.sailing.server.operationaltransformation.CreateEvent;
181
-import com.sap.sailing.server.operationaltransformation.CreateOrUpdateDataImportProgress;
182
-import com.sap.sailing.server.operationaltransformation.CreateOrUpdateDeviceConfiguration;
183
-import com.sap.sailing.server.operationaltransformation.CreateTrackedRace;
184
-import com.sap.sailing.server.operationaltransformation.DataImportFailed;
185
-import com.sap.sailing.server.operationaltransformation.RecordCompetitorGPSFix;
186
-import com.sap.sailing.server.operationaltransformation.RecordMarkGPSFix;
187
-import com.sap.sailing.server.operationaltransformation.RecordMarkGPSFixForExistingTrack;
188
-import com.sap.sailing.server.operationaltransformation.RecordMarkGPSFixForNewMarkTrack;
189
-import com.sap.sailing.server.operationaltransformation.RecordWindFix;
190
-import com.sap.sailing.server.operationaltransformation.RemoveDeviceConfiguration;
191
-import com.sap.sailing.server.operationaltransformation.RemoveEvent;
192
-import com.sap.sailing.server.operationaltransformation.RemoveMediaTrackOperation;
193
-import com.sap.sailing.server.operationaltransformation.RemoveWindFix;
194
-import com.sap.sailing.server.operationaltransformation.RenameEvent;
195
-import com.sap.sailing.server.operationaltransformation.SetDataImportDeleteProgressFromMapTimer;
196
-import com.sap.sailing.server.operationaltransformation.TrackRegatta;
197
-import com.sap.sailing.server.operationaltransformation.UpdateCompetitor;
198
-import com.sap.sailing.server.operationaltransformation.UpdateEndOfTracking;
199
-import com.sap.sailing.server.operationaltransformation.UpdateMarkPassings;
200
-import com.sap.sailing.server.operationaltransformation.UpdateMediaTrackDurationOperation;
201
-import com.sap.sailing.server.operationaltransformation.UpdateMediaTrackRacesOperation;
202
-import com.sap.sailing.server.operationaltransformation.UpdateMediaTrackStartTimeOperation;
203
-import com.sap.sailing.server.operationaltransformation.UpdateMediaTrackTitleOperation;
204
-import com.sap.sailing.server.operationaltransformation.UpdateMediaTrackUrlOperation;
205
-import com.sap.sailing.server.operationaltransformation.UpdateRaceDelayToLive;
206
-import com.sap.sailing.server.operationaltransformation.UpdateStartOfTracking;
207
-import com.sap.sailing.server.operationaltransformation.UpdateStartTimeReceived;
208
-import com.sap.sailing.server.operationaltransformation.UpdateTrackedRaceStatus;
209
-import com.sap.sailing.server.operationaltransformation.UpdateWindAveragingTime;
210
-import com.sap.sailing.server.operationaltransformation.UpdateWindSourcesToExclude;
211
-import com.sap.sailing.server.simulation.SimulationService;
212
-import com.sap.sailing.server.simulation.SimulationServiceFactory;
213
-import com.sap.sse.ServerInfo;
214
-import com.sap.sse.common.TimePoint;
215
-import com.sap.sse.common.TypeBasedServiceFinderFactory;
216
-import com.sap.sse.common.Util;
217
-import com.sap.sse.common.Util.Pair;
218
-import com.sap.sse.common.Util.Triple;
219
-import com.sap.sse.common.impl.MillisecondsTimePoint;
220
-import com.sap.sse.common.media.ImageDescriptor;
221
-import com.sap.sse.common.media.VideoDescriptor;
222
-import com.sap.sse.common.search.KeywordQuery;
223
-import com.sap.sse.common.search.Result;
224
-import com.sap.sse.common.search.ResultImpl;
225
-import com.sap.sse.concurrent.LockUtil;
226
-import com.sap.sse.concurrent.NamedReentrantReadWriteLock;
227
-import com.sap.sse.filestorage.FileStorageManagementService;
228
-import com.sap.sse.replication.OperationExecutionListener;
229
-import com.sap.sse.replication.OperationWithResult;
230
-import com.sap.sse.replication.ReplicationMasterDescriptor;
231
-import com.sap.sse.replication.impl.OperationWithResultWithIdWrapper;
232
-import com.sap.sse.util.ClearStateTestSupport;
233
-import com.sap.sse.util.JoinedClassLoader;
234
-import com.sap.sse.util.impl.ThreadFactoryWithPriority;
235
-
236
-public class RacingEventServiceImpl implements RacingEventService, ClearStateTestSupport, RegattaListener,
237
- LeaderboardRegistry, Replicator, EventFetcher {
238
- private static final Logger logger = Logger.getLogger(RacingEventServiceImpl.class.getName());
239
-
240
- /**
241
- * A scheduler for the periodic checks of the paramURL documents for the advent of {@link ControlPoint}s with static
242
- * position information otherwise not available through <code>MarkPassingReceiver</code>'s events.
243
- */
244
- private static final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1, new ThreadFactoryWithPriority(Thread.NORM_PRIORITY, /* daemon */ true));
245
-
246
- private final com.sap.sailing.domain.base.DomainFactory baseDomainFactory;
247
-
248
- /**
249
- * Holds the {@link Event} objects for those event registered with this service. Note that there may be
250
- * {@link Event} objects that exist outside this service for events not (yet) registered here.
251
- */
252
- private final ConcurrentHashMap<Serializable, Event> eventsById;
253
-
254
- private final RemoteSailingServerSet remoteSailingServerSet;
255
-
256
- /**
257
- * Holds the {@link Regatta} objects for those races registered with this service. Note that there may be
258
- * {@link Regatta} objects that exist outside this service for regattas not (yet) registered here.
259
- */
260
- protected final ConcurrentHashMap<String, Regatta> regattasByName;
261
-
262
- private final NamedReentrantReadWriteLock regattasByNameLock;
263
-
264
- private final ConcurrentHashMap<RaceDefinition, CourseChangeReplicator> courseListeners;
265
-
266
- protected final ConcurrentHashMap<Regatta, Set<RaceTracker>> raceTrackersByRegatta;
267
-
268
- /**
269
- * Although {@link #raceTrackersByRegatta} is a concurrent hash map, entering sets as values needs to be
270
- * synchronized using this lock's write lock to avoid two value sets overwriting each other.
271
- */
272
- private final NamedReentrantReadWriteLock raceTrackersByRegattaLock;
273
-
274
- /**
275
- * Remembers the trackers by paramURL/liveURI/storedURI to avoid duplication
276
- */
277
- protected final ConcurrentHashMap<Object, RaceTracker> raceTrackersByID;
278
-
279
- /**
280
- * {@link #addRace(RegattaIdentifier, RaceTrackingConnectivityParameters, long)} will check
281
- * {@link #raceTrackersByID} for the presence of a tracker and won't create a new tracker if one for the
282
- * connectivity parameters' ID already exists. This check and creation and addition to {@link #raceTrackersByID}
283
- * requires locking in the face of concurrent calls to
284
- * {@link #addRace(RegattaIdentifier, RaceTrackingConnectivityParameters, long)}. Using <code>synchronized</code> is
285
- * not ideal due to its coarse-grained locking style which allows for little concurrency. Instead, this map is used
286
- * to keep locks for any ID that any invocation of the
287
- * {@link #addRace(RegattaIdentifier, RaceTrackingConnectivityParameters, long)} method is currently working on.
288
- * Fetching or creating and putting a lock to this map happens in ({@link #getOrCreateRaceTrackersByIdLock}) which
289
- * takes care of managing concurrent access to this concurrent map. When done, the
290
- * {@link #addRace(RegattaIdentifier, RaceTrackingConnectivityParameters, long)} method cleans up by removing the
291
- * lock again from this map, again using a synchronized method (
292
- * {@link #unlockRaceTrackersById(Object, NamedReentrantReadWriteLock)}).
293
- */
294
- private final ConcurrentHashMap<Object, NamedReentrantReadWriteLock> raceTrackersByIDLocks;
295
-
296
- /**
297
- * Leaderboards managed by this racing event service
298
- */
299
- private final ConcurrentHashMap<String, Leaderboard> leaderboardsByName;
300
-
301
- /**
302
- * {@link #leaderboardsByName} is already a concurrent hash map; however, when renaming a leaderboard, this shall
303
- * happen as an atomic transaction, not interruptible by other write accesses on the same map because otherwise
304
- * assumptions made during the rename process wouldn't hold. See, in particular,
305
- * {@link #renameLeaderboard(String, String)}.
306
- */
307
- private final NamedReentrantReadWriteLock leaderboardsByNameLock;
308
-
309
- private final ConcurrentHashMap<String, LeaderboardGroup> leaderboardGroupsByName;
310
-
311
- private final ConcurrentHashMap<UUID, LeaderboardGroup> leaderboardGroupsByID;
312
-
313
- /**
314
- * See {@link #leaderboardsByNameLock}
315
- */
316
- private final NamedReentrantReadWriteLock leaderboardGroupsByNameLock;
317
-
318
- private final CompetitorStore competitorStore;
319
-
320
- /**
321
- * A set based on a concurrent hash map, therefore being thread safe
322
- */
323
- private Set<DynamicTrackedRegatta> regattasObservedForDefaultLeaderboard = Collections
324
- .newSetFromMap(new ConcurrentHashMap<DynamicTrackedRegatta, Boolean>());
325
-
326
- private final MongoObjectFactory mongoObjectFactory;
327
-
328
- private final DomainObjectFactory domainObjectFactory;
329
-
330
- private final ConcurrentHashMap<Regatta, DynamicTrackedRegatta> regattaTrackingCache;
331
-
332
- /**
333
- * Protects write access transactions that do a previous read to {@link #regattaTrackingCache}; read-only access is
334
- * already synchronized by using a concurrent hash map for {@link #regattaTrackingCache}.
335
- */
336
- private final NamedReentrantReadWriteLock regattaTrackingCacheLock;
337
-
338
- private final ConcurrentHashMap<OperationExecutionListener<RacingEventService>, OperationExecutionListener<RacingEventService>> operationExecutionListeners;
339
-
340
- /**
341
- * Keys are the toString() representation of the {@link RaceDefinition#getId() IDs} of races passed to
342
- * {@link #setRegattaForRace(Regatta, RaceDefinition)}.
343
- */
344
- private final ConcurrentHashMap<String, Regatta> persistentRegattasForRaceIDs;
345
-
346
- private final RaceLogReplicator raceLogReplicator;
347
- private final RegattaLogReplicator regattaLogReplicator;
348
-
349
- private final RaceLogScoringReplicator raceLogScoringReplicator;
350
-
351
- private final MediaDB mediaDB;
352
-
353
- private final MediaLibrary mediaLibrary;
354
-
355
- /**
356
- * Currently valid pairs of {@link DeviceConfigurationMatcher}s and {@link DeviceConfiguration}s. The contents of
357
- * this map is persisted and replicated. See {@link DeviceConfigurationMapImpl}.
358
- */
359
- protected final DeviceConfigurationMapImpl configurationMap;
360
-
361
- private final WindStore windStore;
362
- private final GPSFixStore gpsFixStore;
363
-
364
- /**
365
- * This author should be used for server generated race log events
366
- */
367
- private final AbstractLogEventAuthor raceLogEventAuthorForServer = new LogEventAuthorImpl(
368
- RacingEventService.class.getName(), 0);
369
-
370
- private PolarDataService polarDataService;
371
-
372
- private final SimulationService simulationService;
373
-
374
- /**
375
- * Allow only one master data import at a time to avoid situation where multiple Imports override each other in
376
- * unpredictable fashion
377
- */
378
- private final DataImportLockWithProgress dataImportLock;
379
-
380
- /**
381
- * If this service runs in the context of an OSGi environment, the activator should {@link #setBundleContext set the
382
- * bundle context} on this object so that service lookups become possible.
383
- */
384
- private BundleContext bundleContext;
385
-
386
- private TypeBasedServiceFinderFactory serviceFinderFactory;
387
-
388
- /**
389
- * The master from which this replicable is currently replicating, or <code>null</code> if this replicable is not
390
- * currently replicated from any master.
391
- */
392
- private ReplicationMasterDescriptor replicatingFromMaster;
393
-
394
- private Set<OperationWithResultWithIdWrapper<?, ?>> operationsSentToMasterForReplication;
395
-
396
- private ThreadLocal<Boolean> currentlyFillingFromInitialLoadOrApplyingOperationReceivedFromMaster = ThreadLocal
397
- .withInitial(() -> false);
398
-
399
- private final Set<ClassLoader> masterDataClassLoaders = new HashSet<ClassLoader>();
400
-
401
- private final JoinedClassLoader joinedClassLoader;
402
-
403
- private SailingServerConfiguration sailingServerConfiguration;
404
-
405
- /**
406
- * Providing the constructor parameters for a new {@link RacingEventServiceImpl} instance is a bit tricky
407
- * in some cases because containment and initialization order of some types is fairly tightly coupled.
408
- * There is a dependency of many such objects on an instance of {@link RaceLogResolver} which is implemented
409
- * by {@link RacingEventServiceImpl}. However, therefore, this instance only becomes available in the
410
- * innermost constructor.
411
- *
412
- * @author Axel Uhl (d043530)
413
- *
414
- */
415
- public static interface ConstructorParameters {
416
- DomainObjectFactory getDomainObjectFactory();
417
- MongoObjectFactory getMongoObjectFactory();
418
- com.sap.sailing.domain.base.DomainFactory getBaseDomainFactory();
419
- CompetitorStore getCompetitorStore();
420
- }
421
-
422
- /**
423
- * Constructs a {@link DomainFactory base domain factory} that uses this object's {@link #competitorStore competitor
424
- * store} for competitor management. This base domain factory is then also used for the construction of the
425
- * {@link DomainObjectFactory}. This constructor variant initially clears the persistent competitor collection,
426
- * hence removes all previously persistent competitors. This is the default for testing and for backward
427
- * compatibility with prior releases that did not support a persistent competitor collection.
428
- */
429
- public RacingEventServiceImpl() {
430
- this(true, null);
431
- }
432
-
433
- public RacingEventServiceImpl(WindStore windStore, GPSFixStore gpsFixStore,
434
- TypeBasedServiceFinderFactory serviceFinderFactory) {
435
- this(true, windStore, gpsFixStore, serviceFinderFactory);
436
- }
437
-
438
- void setBundleContext(BundleContext bundleContext) {
439
- this.bundleContext = bundleContext;
440
- }
441
-
442
- /**
443
- * Like {@link #RacingEventServiceImpl()}, but allows callers to specify that the persistent competitor collection
444
- * be cleared before the service starts.
445
- *
446
- * @param clearPersistentCompetitorStore
447
- * if <code>true</code>, the {@link PersistentCompetitorStore} is created empty, with the corresponding
448
- * database collection cleared as well. Use with caution! When used with <code>false</code>, competitors
449
- * created and stored during previous service executions will initially be loaded.
450
- */
451
- public RacingEventServiceImpl(boolean clearPersistentCompetitorStore, final TypeBasedServiceFinderFactory serviceFinderFactory) {
452
- this((final RaceLogResolver raceLogResolver)-> {
453
- return new ConstructorParameters() {
454
- private final MongoObjectFactory mongoObjectFactory = PersistenceFactory.INSTANCE.getDefaultMongoObjectFactory(serviceFinderFactory);
455
- private final PersistentCompetitorStore competitorStore = new PersistentCompetitorStore(
456
- PersistenceFactory.INSTANCE.getDefaultMongoObjectFactory(serviceFinderFactory),
457
- clearPersistentCompetitorStore, serviceFinderFactory, raceLogResolver);
458
-
459
- @Override public DomainObjectFactory getDomainObjectFactory() { return competitorStore.getDomainObjectFactory(); }
460
- @Override public MongoObjectFactory getMongoObjectFactory() { return mongoObjectFactory; }
461
- @Override public DomainFactory getBaseDomainFactory() { return competitorStore.getBaseDomainFactory(); }
462
- @Override public CompetitorStore getCompetitorStore() { return competitorStore; }
463
- };
464
- }, MediaDBFactory.INSTANCE.getDefaultMediaDB(), null, null, serviceFinderFactory);
465
- }
466
-
467
- private RacingEventServiceImpl(final boolean clearPersistentCompetitorStore, WindStore windStore,
468
- GPSFixStore gpsFixStore, final TypeBasedServiceFinderFactory serviceFinderFactory) {
469
- this((final RaceLogResolver raceLogResolver)-> {
470
- return new ConstructorParameters() {
471
- private final MongoObjectFactory mongoObjectFactory = PersistenceFactory.INSTANCE.getDefaultMongoObjectFactory(serviceFinderFactory);
472
- private final PersistentCompetitorStore competitorStore = new PersistentCompetitorStore(
473
- mongoObjectFactory,
474
- clearPersistentCompetitorStore, serviceFinderFactory, raceLogResolver);
475
-
476
- @Override public DomainObjectFactory getDomainObjectFactory() { return competitorStore.getDomainObjectFactory(); }
477
- @Override public MongoObjectFactory getMongoObjectFactory() { return mongoObjectFactory; }
478
- @Override public DomainFactory getBaseDomainFactory() { return competitorStore.getBaseDomainFactory(); }
479
- @Override public CompetitorStore getCompetitorStore() { return competitorStore; }
480
- };
481
- }, MediaDBFactory.INSTANCE.getDefaultMediaDB(), windStore, gpsFixStore, serviceFinderFactory);
482
- }
483
-
484
- /**
485
- * Uses the default factories for the tracking adapters and the {@link DomainFactory base domain factory} of the
486
- * {@link PersistenceFactory#getDefaultDomainObjectFactory() default domain object factory}. This constructor should
487
- * be used for testing because it provides a transient {@link CompetitorStore} as required for competitor
488
- * persistence.
489
- */
490
- public RacingEventServiceImpl(Function<RaceLogResolver, DomainObjectFactory> domainObjectFactoryProvider,
491
- final MongoObjectFactory mongoObjectFactory, MediaDB mediaDB, WindStore windStore, GPSFixStore gpsFixStore) {
492
- this((final RaceLogResolver raceLogResolver)-> {
493
- return new ConstructorParameters() {
494
- private final DomainObjectFactory domainObjectFactory = domainObjectFactoryProvider.apply(raceLogResolver);
495
-
496
- @Override public DomainObjectFactory getDomainObjectFactory() { return domainObjectFactory; }
497
- @Override public MongoObjectFactory getMongoObjectFactory() { return mongoObjectFactory; }
498
- @Override public DomainFactory getBaseDomainFactory() { return domainObjectFactory.getBaseDomainFactory(); }
499
- @Override public CompetitorStore getCompetitorStore() { return getBaseDomainFactory().getCompetitorStore(); }
500
- };
501
- }, mediaDB, windStore, gpsFixStore, null);
502
- }
503
-
504
- public RacingEventServiceImpl(final DomainObjectFactory domainObjectFactory, MongoObjectFactory mongoObjectFactory,
505
- MediaDB mediaDB, WindStore windStore, GPSFixStore gpsFixStore) {
506
- this((final RaceLogResolver raceLogResolver)-> {
507
- return new ConstructorParameters() {
508
- @Override public DomainObjectFactory getDomainObjectFactory() { return domainObjectFactory; }
509
- @Override public MongoObjectFactory getMongoObjectFactory() { return mongoObjectFactory; }
510
- @Override public DomainFactory getBaseDomainFactory() { return domainObjectFactory.getBaseDomainFactory(); }
511
- @Override public CompetitorStore getCompetitorStore() { return getBaseDomainFactory().getCompetitorStore(); }
512
- };
513
- }, mediaDB, windStore, gpsFixStore, null);
514
- }
515
-
516
- /**
517
- * @param windStore
518
- * if <code>null</code>, a default {@link MongoWindStore} will be used, based on the persistence set-up
519
- * of this service
520
- * @param serviceFinderFactory
521
- * used to find the services handling specific types of tracking devices, such as the persistent storage
522
- * of {@link DeviceIdentifier}s of specific device types or the managing of the device-to-competitor
523
- * associations per race tracked.
524
- */
525
- public RacingEventServiceImpl(Function<RaceLogResolver, ConstructorParameters> constructorParametersProvider, MediaDB mediaDb,
526
- WindStore windStore, GPSFixStore gpsFixStore, TypeBasedServiceFinderFactory serviceFinderFactory) {
527
- logger.info("Created " + this);
528
- final ConstructorParameters constructorParameters = constructorParametersProvider.apply(this);
529
- this.domainObjectFactory = constructorParameters.getDomainObjectFactory();
530
- this.masterDataClassLoaders.add(this.getClass().getClassLoader());
531
- joinedClassLoader = new JoinedClassLoader(masterDataClassLoaders);
532
- this.operationsSentToMasterForReplication = new HashSet<>();
533
- this.baseDomainFactory = constructorParameters.getBaseDomainFactory();
534
- this.mongoObjectFactory = constructorParameters.getMongoObjectFactory();
535
- this.mediaDB = mediaDb;
536
- this.competitorStore = constructorParameters.getCompetitorStore();
537
- if (windStore == null) {
538
- try {
539
- windStore = MongoWindStoreFactory.INSTANCE.getMongoWindStore(mongoObjectFactory, domainObjectFactory);
540
- } catch (Exception e) {
541
- throw new RuntimeException(e);
542
- }
543
- }
544
- this.competitorStore.addCompetitorUpdateListener(new CompetitorUpdateListener() {
545
- @Override
546
- public void competitorUpdated(Competitor competitor) {
547
- replicate(new UpdateCompetitor(competitor.getId().toString(), competitor.getName(), competitor
548
- .getColor(), competitor.getEmail(), competitor.getBoat().getSailID(), competitor.getTeam().getNationality(),
549
- competitor.getTeam().getImage(), competitor.getFlagImage(),
550
- competitor.getTimeOnTimeFactor(), competitor.getTimeOnDistanceAllowancePerNauticalMile()));
551
- }
552
- });
553
- this.windStore = windStore;
554
- this.dataImportLock = new DataImportLockWithProgress();
555
-
556
- remoteSailingServerSet = new RemoteSailingServerSet(scheduler);
557
- regattasByName = new ConcurrentHashMap<String, Regatta>();
558
- regattasByNameLock = new NamedReentrantReadWriteLock("regattasByName for " + this, /* fair */false);
559
- eventsById = new ConcurrentHashMap<Serializable, Event>();
560
- regattaTrackingCache = new ConcurrentHashMap<>();
561
- regattaTrackingCacheLock = new NamedReentrantReadWriteLock("regattaTrackingCache for " + this, /* fair */false);
562
- raceTrackersByRegatta = new ConcurrentHashMap<>();
563
- raceTrackersByRegattaLock = new NamedReentrantReadWriteLock("raceTrackersByRegatta for " + this, /* fair */
564
- false);
565
- raceTrackersByID = new ConcurrentHashMap<>();
566
- raceTrackersByIDLocks = new ConcurrentHashMap<>();
567
- leaderboardGroupsByName = new ConcurrentHashMap<>();
568
- leaderboardGroupsByID = new ConcurrentHashMap<>();
569
- leaderboardGroupsByNameLock = new NamedReentrantReadWriteLock("leaderboardGroupsByName for " + this, /* fair */
570
- false);
571
- leaderboardsByName = new ConcurrentHashMap<String, Leaderboard>();
572
- leaderboardsByNameLock = new NamedReentrantReadWriteLock("leaderboardsByName for " + this, /* fair */false);
573
- operationExecutionListeners = new ConcurrentHashMap<>();
574
- courseListeners = new ConcurrentHashMap<>();
575
- persistentRegattasForRaceIDs = new ConcurrentHashMap<>();
576
- final int SIMULATION_THREAD_POOL_SIZE = Math.max(Runtime.getRuntime().availableProcessors()/3, 1);
577
- Executor simulatorExecutor = new ThreadPoolExecutor(/* corePoolSize */SIMULATION_THREAD_POOL_SIZE,
578
- /* maximumPoolSize */SIMULATION_THREAD_POOL_SIZE,
579
- /* keepAliveTime */60, TimeUnit.SECONDS,
580
- /* workQueue */new LinkedBlockingQueue<Runnable>());
581
- // TODO: initialize smart-future-cache for simulation-results and add to simulation-service
582
- simulationService = SimulationServiceFactory.INSTANCE.getService(simulatorExecutor, this);
583
- this.raceLogReplicator = new RaceLogReplicator(this);
584
- this.regattaLogReplicator = new RegattaLogReplicator(this);
585
- this.raceLogScoringReplicator = new RaceLogScoringReplicator(this);
586
- this.mediaLibrary = new MediaLibrary();
587
- if (gpsFixStore == null) {
588
- try {
589
- gpsFixStore = MongoGPSFixStoreFactory.INSTANCE.getMongoGPSFixStore(mongoObjectFactory,
590
- domainObjectFactory, serviceFinderFactory);
591
- } catch (Exception e) {
592
- e.printStackTrace();
593
- throw new RuntimeException(e);
594
- }
595
- }
596
- this.gpsFixStore = gpsFixStore;
597
- this.configurationMap = new DeviceConfigurationMapImpl();
598
- this.serviceFinderFactory = serviceFinderFactory;
599
-
600
- sailingServerConfiguration = domainObjectFactory.loadServerConfiguration();
601
- final Iterable<Pair<Event, Boolean>> loadedEventsWithRequireStoreFlag = loadStoredEvents();
602
- loadStoredRegattas();
603
- loadRaceIDToRegattaAssociations();
604
- loadStoredLeaderboardsAndGroups();
605
- loadLinksFromEventsToLeaderboardGroups();
606
- loadMediaLibary();
607
- loadStoredDeviceConfigurations();
608
- loadAllRemoteSailingServersAndSchedulePeriodicEventCacheRefresh();
609
-
610
- // Stores all events which run through a data migration
611
- // Remark: must be called after loadLinksFromEventsToLeaderboardGroups(), otherwise would loose the Event -> LeaderboardGroup relation
612
- for (Pair<Event, Boolean> eventAndRequireStoreFlag : loadedEventsWithRequireStoreFlag) {
613
- if (eventAndRequireStoreFlag.getB()) {
614
- mongoObjectFactory.storeEvent(eventAndRequireStoreFlag.getA());
615
- }
616
- }
617
- }
618
-
619
- @Override
620
- public boolean isCurrentlyFillingFromInitialLoadOrApplyingOperationReceivedFromMaster() {
621
- return currentlyFillingFromInitialLoadOrApplyingOperationReceivedFromMaster.get();
622
- }
623
-
624
- @Override
625
- public void setCurrentlyFillingFromInitialLoadOrApplyingOperationReceivedFromMaster(
626
- boolean currentlyFillingFromInitialLoadOrApplyingOperationReceivedFromMaster) {
627
- this.currentlyFillingFromInitialLoadOrApplyingOperationReceivedFromMaster
628
- .set(currentlyFillingFromInitialLoadOrApplyingOperationReceivedFromMaster);
629
- }
630
-
631
- @Override
632
- public PolarDataService getPolarDataService() {
633
- return polarDataService;
634
- }
635
-
636
- @Override
637
- public SimulationService getSimulationService() {
638
- return simulationService;
639
- }
640
-
641
- @Override
642
- public void clearState() throws Exception {
643
- for (String leaderboardGroupName : new ArrayList<>(this.leaderboardGroupsByName.keySet())) {
644
- removeLeaderboardGroup(leaderboardGroupName);
645
- }
646
- for (String leaderboardName : new ArrayList<>(this.leaderboardsByName.keySet())) {
647
- removeLeaderboard(leaderboardName);
648
- }
649
- for (Regatta regatta : new ArrayList<>(this.regattasByName.values())) {
650
- stopTracking(regatta);
651
- removeRegatta(regatta);
652
- }
653
- for (Event event : new ArrayList<>(this.eventsById.values())) {
654
- removeEvent(event.getId());
655
- }
656
- for (MediaTrack mediaTrack : this.mediaLibrary.allTracks()) {
657
- mediaTrackDeleted(mediaTrack);
658
- }
659
- // TODO clear user store? See bug 2430.
660
- this.competitorStore.clear();
661
- }
662
-
663
- @Override
664
- public com.sap.sailing.domain.base.DomainFactory getBaseDomainFactory() {
665
- return baseDomainFactory;
666
- }
667
-
668
- @Override
669
- public MongoObjectFactory getMongoObjectFactory() {
670
- return mongoObjectFactory;
671
- }
672
-
673
- @Override
674
- public DomainObjectFactory getDomainObjectFactory() {
675
- return domainObjectFactory;
676
- }
677
-
678
- private void loadRaceIDToRegattaAssociations() {
679
- persistentRegattasForRaceIDs.putAll(domainObjectFactory.loadRaceIDToRegattaAssociations(this));
680
- }
681
-
682
- private void loadStoredRegattas() {
683
- LockUtil.lockForWrite(regattasByNameLock);
684
- try {
685
- for (Regatta regatta : domainObjectFactory.loadAllRegattas(this)) {
686
- logger.info("putting regatta " + regatta.getName() + " (" + regatta.hashCode()
687
- + ") into regattasByName");
688
- regattasByName.put(regatta.getName(), regatta);
689
- regatta.addRegattaListener(this);
690
- onRegattaLikeAdded(regatta);
691
- regatta.addRaceColumnListener(raceLogReplicator);
692
- regatta.addRaceColumnListener(raceLogScoringReplicator);
693
- }
694
- } finally {
695
- LockUtil.unlockAfterWrite(regattasByNameLock);
696
- }
697
- }
698
-
699
- private Iterable<Pair<Event, Boolean>> loadStoredEvents() {
700
- Iterable<Pair<Event, Boolean>> loadedEventsWithRequireStoreFlag = domainObjectFactory.loadAllEvents();
701
- for (Pair<Event, Boolean> eventAndFlag : loadedEventsWithRequireStoreFlag) {
702
- Event event = eventAndFlag.getA();
703
- if (event.getId() != null)
704
- eventsById.put(event.getId(), event);
705
- }
706
- return loadedEventsWithRequireStoreFlag;
707
- }
708
-
709
- private void loadLinksFromEventsToLeaderboardGroups() {
710
- domainObjectFactory.loadLeaderboardGroupLinksForEvents(/* eventResolver */this, /* leaderboardGroupResolver */
711
- this);
712
- }
713
-
714
- private void loadAllRemoteSailingServersAndSchedulePeriodicEventCacheRefresh() {
715
- for (RemoteSailingServerReference sailingServer : domainObjectFactory.loadAllRemoteSailingServerReferences()) {
716
- remoteSailingServerSet.add(sailingServer);
717
- }
718
- }
719
-
720
- /**
721
- * Collects media track references from the configured sources (mongo DB by default, ftp folder yet to be
722
- * implemented). The method is expected to be called initially blocking the API until finished.
723
- *
724
- * Subsequent calls (assumed to be triggered from the admin console or in scheduled intervals) don't need to block.
725
- * In that case, the API will simply serve the current state.
726
- *
727
- */
728
- private void loadMediaLibary() {
729
- Collection<MediaTrack> allDbMediaTracks = mediaDB.loadAllMediaTracks();
730
- mediaTracksAdded(allDbMediaTracks);
731
- }
732
-
733
- private void loadStoredDeviceConfigurations() {
734
- for (Entry<DeviceConfigurationMatcher, DeviceConfiguration> entry : domainObjectFactory
735
- .loadAllDeviceConfigurations()) {
736
- configurationMap.put(entry.getKey(), entry.getValue());
737
- }
738
- }
739
-
740
- @Override
741
- public void addLeaderboard(Leaderboard leaderboard) {
742
- LockUtil.lockForWrite(leaderboardsByNameLock);
743
- try {
744
- leaderboardsByName.put(leaderboard.getName(), leaderboard);
745
- } finally {
746
- LockUtil.unlockAfterWrite(leaderboardsByNameLock);
747
- }
748
- // RaceColumns of RegattaLeaderboards are tracked via its Regatta!
749
- if (leaderboard instanceof FlexibleLeaderboard) {
750
- onRegattaLikeAdded(((FlexibleLeaderboard) leaderboard).getRegattaLike());
751
- leaderboard.addRaceColumnListener(raceLogReplicator);
752
- leaderboard.addRaceColumnListener(raceLogScoringReplicator);
753
- }
754
- }
755
-
756
- private void loadStoredLeaderboardsAndGroups() {
757
- logger.info("loading stored leaderboards and groups");
758
- // Loading all leaderboard groups and the contained leaderboards
759
- for (LeaderboardGroup leaderboardGroup : domainObjectFactory.getAllLeaderboardGroups(this, this)) {
760
- logger.info("loaded leaderboard group " + leaderboardGroup.getName() + " into " + this);
761
- LockUtil.lockForWrite(leaderboardGroupsByNameLock);
762
- try {
763
- leaderboardGroupsByName.put(leaderboardGroup.getName(), leaderboardGroup);
764
- leaderboardGroupsByID.put(leaderboardGroup.getId(), leaderboardGroup);
765
- } finally {
766
- LockUtil.unlockAfterWrite(leaderboardGroupsByNameLock);
767
- }
768
- }
769
- // Loading the remaining leaderboards
770
- domainObjectFactory.getLeaderboardsNotInGroup(this, this);
771
- logger.info("done with loading stored leaderboards and groups");
772
- }
773
-
774
- @Override
775
- public FlexibleLeaderboard addFlexibleLeaderboard(String leaderboardName, String leaderboardDisplayName,
776
- int[] discardThresholds, ScoringScheme scoringScheme, Serializable courseAreaId) {
777
- logger.info("adding flexible leaderboard " + leaderboardName);
778
- CourseArea courseArea = getCourseArea(courseAreaId);
779
- FlexibleLeaderboard result = new FlexibleLeaderboardImpl(getRaceLogStore(), getRegattaLogStore(),
780
- leaderboardName, new ThresholdBasedResultDiscardingRuleImpl(discardThresholds), scoringScheme,
781
- courseArea);
782
- result.setDisplayName(leaderboardDisplayName);
783
- if (getLeaderboardByName(leaderboardName) != null) {
784
- throw new IllegalArgumentException("Leaderboard with name " + leaderboardName + " already exists");
785
- }
786
- addLeaderboard(result);
787
- mongoObjectFactory.storeLeaderboard(result);
788
- return result;
789
- }
790
-
791
- @Override
792
- public CourseArea getCourseArea(Serializable courseAreaId) {
793
- for (Event event : getAllEvents()) {
794
- for (CourseArea courseArea : event.getVenue().getCourseAreas()) {
795
- if (courseArea.getId().equals(courseAreaId)) {
796
- return courseArea;
797
- }
798
- }
799
- }
800
- return null;
801
- }
802
-
803
- @Override
804
- public RegattaLeaderboard addRegattaLeaderboard(RegattaIdentifier regattaIdentifier, String leaderboardDisplayName,
805
- int[] discardThresholds) {
806
- Regatta regatta = getRegatta(regattaIdentifier);
807
- logger.info("adding regatta leaderboard for regatta "
808
- + (regatta == null ? "null" : (regatta.getName() + " (" + regatta.hashCode() + ")")) + " to " + this);
809
- RegattaLeaderboard result = null;
810
- if (regatta != null) {
811
- result = new RegattaLeaderboardImpl(regatta, new ThresholdBasedResultDiscardingRuleImpl(discardThresholds));
812
- result.setDisplayName(leaderboardDisplayName);
813
- if (getLeaderboardByName(result.getName()) != null) {
814
- throw new IllegalArgumentException("Leaderboard with name " + result.getName() + " already exists in "
815
- + this);
816
- }
817
- addLeaderboard(result);
818
- mongoObjectFactory.storeLeaderboard(result);
819
- } else {
820
- logger.warning("Cannot find regatta " + regattaIdentifier
821
- + ". Hence, cannot create regatta leaderboard for it.");
822
- }
823
- return result;
824
- }
825
-
826
- @Override
827
- public RaceColumn addColumnToLeaderboard(String columnName, String leaderboardName, boolean medalRace) {
828
- Leaderboard leaderboard = getLeaderboardByName(leaderboardName);
829
- if (leaderboard != null) {
830
- if (leaderboard instanceof FlexibleLeaderboard) {
831
- // uses the default fleet as the single fleet for the new column
832
- RaceColumn result = ((FlexibleLeaderboard) leaderboard).addRaceColumn(columnName, medalRace);
833
- updateStoredLeaderboard((FlexibleLeaderboard) leaderboard);
834
- return result;
835
- } else {
836
- throw new IllegalArgumentException("Leaderboard named " + leaderboardName
837
- + " is not a FlexibleLeaderboard");
838
- }
839
- } else {
840
- throw new IllegalArgumentException("Leaderboard named " + leaderboardName + " not found");
841
- }
842
- }
843
-
844
- @Override
845
- public void moveLeaderboardColumnUp(String leaderboardName, String columnName) {
846
- Leaderboard leaderboard = getLeaderboardByName(leaderboardName);
847
- if (leaderboard != null && leaderboard instanceof FlexibleLeaderboard) {
848
- ((FlexibleLeaderboard) leaderboard).moveRaceColumnUp(columnName);
849
- updateStoredLeaderboard((FlexibleLeaderboard) leaderboard);
850
- } else {
851
- throw new IllegalArgumentException("Leaderboard named " + leaderboardName + " not found");
852
- }
853
- }
854
-
855
- @Override
856
- public void moveLeaderboardColumnDown(String leaderboardName, String columnName) {
857
- Leaderboard leaderboard = getLeaderboardByName(leaderboardName);
858
- if (leaderboard != null && leaderboard instanceof FlexibleLeaderboard) {
859
- ((FlexibleLeaderboard) leaderboard).moveRaceColumnDown(columnName);
860
- updateStoredLeaderboard((FlexibleLeaderboard) leaderboard);
861
- } else {
862
- throw new IllegalArgumentException("Leaderboard named " + leaderboardName + " not found");
863
- }
864
- }
865
-
866
- @Override
867
- public void removeLeaderboardColumn(String leaderboardName, String columnName) {
868
- Leaderboard leaderboard = getLeaderboardByName(leaderboardName);
869
- if (leaderboard == null) {
870
- throw new IllegalArgumentException("Leaderboard named " + leaderboardName + " not found");
871
- } else if (!(leaderboard instanceof FlexibleLeaderboard)) {
872
- throw new IllegalArgumentException("Columns cannot be removed from Leaderboard named " + leaderboardName);
873
- } else {
874
- ((FlexibleLeaderboard) leaderboard).removeRaceColumn(columnName);
875
- updateStoredLeaderboard((FlexibleLeaderboard) leaderboard);
876
- }
877
- }
878
-
879
- @Override
880
- public void renameLeaderboardColumn(String leaderboardName, String oldColumnName, String newColumnName) {
881
- Leaderboard leaderboard = getLeaderboardByName(leaderboardName);
882
- if (leaderboard != null) {
883
- final RaceColumn raceColumn = leaderboard.getRaceColumnByName(oldColumnName);
884
- if (raceColumn instanceof FlexibleRaceColumn) {
885
- // remove race log under old identifier; the race log identifier changes
886
- for (Fleet fleet : raceColumn.getFleets()) {
887
- getMongoObjectFactory().removeRaceLog(raceColumn.getRaceLogIdentifier(fleet));
888
- }
889
- ((FlexibleRaceColumn) raceColumn).setName(newColumnName);
890
- // store the race logs again under the new identifiers
891
- storeRaceLogs(raceColumn);
892
- updateStoredLeaderboard(leaderboard);
893
- } else {
894
- throw new IllegalArgumentException("Race column " + oldColumnName + " cannot be renamed");
895
- }
896
- } else {
897
- throw new IllegalArgumentException("Leaderboard named " + leaderboardName + " not found");
898
- }
899
- }
900
-
901
- /**
902
- * When a race column is renamed, its race log identifiers change. Therefore, the race logs need to be stored under
903
- * the new identifier again to be consistent with the in-memory image again.
904
- */
905
- private void storeRaceLogs(RaceColumn raceColumn) {
906
- for (Fleet fleet : raceColumn.getFleets()) {
907
- RaceLogIdentifier identifier = raceColumn.getRaceLogIdentifier(fleet);
908
- RaceLogEventVisitor storeVisitor = MongoRaceLogStoreFactory.INSTANCE.getMongoRaceLogStoreVisitor(
909
- identifier, getMongoObjectFactory());
910
- RaceLog raceLog = raceColumn.getRaceLog(fleet);
911
- raceLog.lockForRead();
912
- try {
913
- for (RaceLogEvent e : raceLog.getRawFixes()) {
914
- e.accept(storeVisitor);
915
- }
916
- } finally {
917
- raceLog.unlockAfterRead();
918
- }
919
- }
920
- }
921
-
922
- @Override
923
- public void updateLeaderboardColumnFactor(String leaderboardName, String columnName, Double factor) {
924
- Leaderboard leaderboard = getLeaderboardByName(leaderboardName);
925
- if (leaderboard != null) {
926
- final RaceColumn raceColumn = leaderboard.getRaceColumnByName(columnName);
927
- if (raceColumn != null) {
928
- raceColumn.setFactor(factor);
929
- updateStoredLeaderboard(leaderboard);
930
- } else {
931
- throw new IllegalArgumentException("Race column " + columnName + " not found in leaderboard "
932
- + leaderboardName);
933
- }
934
- } else {
935
- throw new IllegalArgumentException("Leaderboard named " + leaderboardName + " not found");
936
- }
937
- }
938
-
939
- @Override
940
- public void renameLeaderboard(String oldName, String newName) {
941
- final Leaderboard toRename = leaderboardsByName.get(oldName);
942
- LockUtil.lockForWrite(leaderboardsByNameLock);
943
- try {
944
- if (toRename == null) {
945
- throw new IllegalArgumentException("No leaderboard with name " + oldName + " found");
946
- }
947
- if (leaderboardsByName.containsKey(newName)) {
948
- throw new IllegalArgumentException("Leaderboard with name " + newName + " already exists");
949
- }
950
- if (toRename instanceof Renamable) {
951
- ((Renamable) toRename).setName(newName);
952
- leaderboardsByName.remove(oldName);
953
- leaderboardsByName.put(newName, toRename);
954
- } else {
955
- throw new IllegalArgumentException("Leaderboard with name " + newName + " is of type "
956
- + toRename.getClass().getSimpleName() + " and therefore cannot be renamed");
957
- }
958
- } finally {
959
- LockUtil.unlockAfterWrite(leaderboardsByNameLock);
960
- }
961
- // don't need the lock anymore to update DB
962
- if (toRename instanceof Renamable) {
963
- mongoObjectFactory.renameLeaderboard(oldName, newName);
964
- syncGroupsAfterLeaderboardChange(toRename, true);
965
- }
966
- }
967
-
968
- @Override
969
- public void updateStoredLeaderboard(Leaderboard leaderboard) {
970
- getMongoObjectFactory().storeLeaderboard(leaderboard);
971
- syncGroupsAfterLeaderboardChange(leaderboard, true);
972
- }
973
-
974
- @Override
975
- public void updateStoredRegatta(Regatta regatta) {
976
- if (regatta.isPersistent()) {
977
- mongoObjectFactory.storeRegatta(regatta);
978
- }
979
- }
980
-
981
- /**
982
- * Checks all groups, if they contain a leaderboard with the name of the <code>updatedLeaderboard</code> and
983
- * replaces the one in the group with the updated one.<br />
984
- * This synchronizes things like the RaceIdentifier in the leaderboard columns.
985
- */
986
- private void syncGroupsAfterLeaderboardChange(Leaderboard updatedLeaderboard, boolean doDatabaseUpdate) {
987
- boolean groupNeedsUpdate = false;
988
- for (LeaderboardGroup leaderboardGroup : leaderboardGroupsByName.values()) {
989
- for (Leaderboard leaderboard : leaderboardGroup.getLeaderboards()) {
990
- if (leaderboard == updatedLeaderboard) {
991
- int index = leaderboardGroup.getIndexOf(leaderboard);
992
- leaderboardGroup.removeLeaderboard(leaderboard);
993
- leaderboardGroup.addLeaderboardAt(updatedLeaderboard, index);
994
- groupNeedsUpdate = true;
995
- // TODO we assume that the leaderboard names are unique, so we can break the inner loop here
996
- break;
997
- }
998
- }
999
-
1000
- if (doDatabaseUpdate && groupNeedsUpdate) {
1001
- mongoObjectFactory.storeLeaderboardGroup(leaderboardGroup);
1002
- }
1003
- groupNeedsUpdate = false;
1004
- }
1005
- }
1006
-
1007
- @Override
1008
- public void removeLeaderboard(String leaderboardName) {
1009
- Leaderboard leaderboard = removeLeaderboardFromLeaderboardsByName(leaderboardName);
1010
- if (leaderboard != null) {
1011
- leaderboard.removeRaceColumnListener(raceLogReplicator);
1012
- leaderboard.removeRaceColumnListener(raceLogScoringReplicator);
1013
- mongoObjectFactory.removeLeaderboard(leaderboardName);
1014
- syncGroupsAfterLeaderboardRemove(leaderboardName, true);
1015
- if (leaderboard instanceof FlexibleLeaderboard) {
1016
- onRegattaLikeRemoved(((FlexibleLeaderboard) leaderboard).getRegattaLike());
1017
- }
1018
- leaderboard.destroy();
1019
- }
1020
- }
1021
-
1022
- private Leaderboard removeLeaderboardFromLeaderboardsByName(String leaderboardName) {
1023
- LockUtil.lockForWrite(leaderboardsByNameLock);
1024
- try {
1025
- return leaderboardsByName.remove(leaderboardName);
1026
- } finally {
1027
- LockUtil.unlockAfterWrite(leaderboardsByNameLock);
1028
- }
1029
- }
1030
-
1031
- /**
1032
- * Checks all groups, if they contain a leaderboard with the <code>removedLeaderboardName</code> and removes it from
1033
- * the group.
1034
- *
1035
- * @param removedLeaderboardName
1036
- */
1037
- private void syncGroupsAfterLeaderboardRemove(String removedLeaderboardName, boolean doDatabaseUpdate) {
1038
- boolean groupNeedsUpdate = false;
1039
- for (LeaderboardGroup leaderboardGroup : leaderboardGroupsByName.values()) {
1040
- for (Leaderboard leaderboard : leaderboardGroup.getLeaderboards()) {
1041
- if (leaderboard.getName().equals(removedLeaderboardName)) {
1042
- leaderboardGroup.removeLeaderboard(leaderboard);
1043
- groupNeedsUpdate = true;
1044
- // TODO we assume that the leaderboard names are unique, so we can break the inner loop here
1045
- break;
1046
- }
1047
- }
1048
-
1049
- if (doDatabaseUpdate && groupNeedsUpdate) {
1050
- mongoObjectFactory.storeLeaderboardGroup(leaderboardGroup);
1051
- }
1052
- groupNeedsUpdate = false;
1053
- }
1054
- }
1055
-
1056
- @Override
1057
- public Leaderboard getLeaderboardByName(String name) {
1058
- return leaderboardsByName.get(name);
1059
- }
1060
-
1061
- @Override
1062
- public Position getMarkPosition(Mark mark, LeaderboardThatHasRegattaLike leaderboard, TimePoint timePoint, RaceLog raceLog) {
1063
- GPSFixTrack<Mark, GPSFix> track = null;
1064
- // If no spanning track is found, the fix closest to the time point requested is used instead
1065
- GPSFix nonSpanningFallback = null;
1066
- for (TrackedRace trackedRace : leaderboard.getTrackedRaces()) {
1067
- GPSFixTrack<Mark, GPSFix> trackCandidate = trackedRace.getTrack(mark);
1068
- if (trackCandidate != null) {
1069
- if (spansTimePoint(trackCandidate, timePoint)) {
1070
- track = trackCandidate;
1071
- break;
1072
- } else {
1073
- nonSpanningFallback = improveTimewiseClosestFix(nonSpanningFallback, trackCandidate, timePoint);
1074
- }
1075
- }
1076
- }
1077
- if (track == null) { // no spanning track found in any tracked race, or no tracked races found
1078
- // try to load from store
1079
- DynamicGPSFixTrackImpl<Mark> loadedTrack = new DynamicGPSFixTrackImpl<Mark>(mark, 0);
1080
- track = loadedTrack;
1081
- Set<AbstractLog<?, ?>> logs = new HashSet<>();
1082
- logs.add(leaderboard.getRegattaLike().getRegattaLog());
1083
- if (raceLog == null) { // no race log explicitly provided --> use all race logs
1084
- for (RaceColumn raceColumn : leaderboard.getRaceColumns()) {
1085
- for (Fleet fleet : raceColumn.getFleets()) {
1086
- logs.add(raceColumn.getRaceLog(fleet));
1087
- }
1088
- }
1089
- } else {
1090
- logs.add(raceLog);
1091
- }
1092
- for (AbstractLog<?, ?> log : logs) {
1093
- try {
1094
- getGPSFixStore().loadMarkTrack(loadedTrack, log, mark);
1095
- } catch (Exception e) {
1096
- logger.log(Level.WARNING, "Couldn't load mark track for mark " + mark + " from log " + log, e);
1097
- }
1098
- }
1099
- }
1100
- Position result = track.getEstimatedPosition(timePoint, /* extrapolate */ false);
1101
- if (result == null) {
1102
- result = nonSpanningFallback == null ? null : nonSpanningFallback.getPosition();
1103
- }
1104
- return result;
1105
- }
1106
-
1107
- private GPSFix improveTimewiseClosestFix(GPSFix nonSpanningFallback, GPSFixTrack<Mark, GPSFix> track, final TimePoint timePoint) {
1108
- GPSFix lastAtOrBefore = track.getLastFixAtOrBefore(timePoint);
1109
- GPSFix firstAtOrAfter = track.getFirstFixAtOrAfter(timePoint);
1110
- // find the fix closes to timePoint, sorting null values to the end and fixes near timePoint to the beginning
1111
- final List<GPSFix> list = Arrays.asList(nonSpanningFallback, lastAtOrBefore, firstAtOrAfter);
1112
- list.sort(new Comparator<GPSFix>() {
1113
- @Override
1114
- public int compare(GPSFix o1, GPSFix o2) {
1115
- final int result;
1116
- if (o1 == null) {
1117
- if (o2 == null) {
1118
- result = 0;
1119
- } else {
1120
- result = 1;
1121
- }
1122
- } else if (o2 == null) {
1123
- result = -1;
1124
- } else {
1125
- result = new Long(Math.abs(o1.getTimePoint().until(timePoint).asMillis())).compareTo(
1126
- Math.abs(o2.getTimePoint().until(timePoint).asMillis()));
1127
- }
1128
- return result;
1129
- }
1130
- });
1131
- return list.get(0);
1132
- }
1133
-
1134
- private boolean spansTimePoint(GPSFixTrack<Mark, GPSFix> track, TimePoint timePoint) {
1135
- return track.getLastFixAtOrBefore(timePoint) != null && track.getFirstFixAtOrAfter(timePoint) != null;
1136
- }
1137
-
1138
- @Override
1139
- public Map<String, Leaderboard> getLeaderboards() {
1140
- return Collections.unmodifiableMap(new HashMap<String, Leaderboard>(leaderboardsByName));
1141
- }
1142
-
1143
- @Override
1144
- public SailingServerConfiguration getSailingServerConfiguration() {
1145
- return sailingServerConfiguration;
1146
- }
1147
-
1148
- @Override
1149
- public void updateServerConfiguration(SailingServerConfiguration serverConfiguration) {
1150
- this.sailingServerConfiguration = serverConfiguration;
1151
- mongoObjectFactory.storeServerConfiguration(serverConfiguration);
1152
- }
1153
-
1154
- @Override
1155
- public Map<RemoteSailingServerReference, com.sap.sse.common.Util.Pair<Iterable<EventBase>, Exception>> getPublicEventsOfAllSailingServers() {
1156
- return remoteSailingServerSet.getCachedEventsForRemoteSailingServers(); // FIXME should probably add our own
1157
- // stuff here... Is it enough to pass on
1158
- // the remote reference URL to the
1159
- // client for leaderboard group URL
1160
- // construction?
1161
- }
1162
-
1163
- @Override
1164
- public RemoteSailingServerReference addRemoteSailingServerReference(String name, URL url) {
1165
- RemoteSailingServerReference result = new RemoteSailingServerReferenceImpl(name, url);
1166
- remoteSailingServerSet.add(result);
1167
- mongoObjectFactory.storeSailingServer(result);
1168
- return result;
1169
- }
1170
-
1171
- @Override
1172
- public Iterable<RemoteSailingServerReference> getLiveRemoteServerReferences() {
1173
- return remoteSailingServerSet.getLiveRemoteServerReferences();
1174
- }
1175
-
1176
- @Override
1177
- public RemoteSailingServerReference getRemoteServerReferenceByName(String remoteServerReferenceName) {
1178
- return remoteSailingServerSet.getServerReferenceByName(remoteServerReferenceName);
1179
- }
1180
-
1181
- @Override
1182
- public com.sap.sse.common.Util.Pair<Iterable<EventBase>, Exception> updateRemoteServerEventCacheSynchronously(
1183
- RemoteSailingServerReference ref) {
1184
- return remoteSailingServerSet.getEventsOrException(ref);
1185
- }
1186
-
1187
- @Override
1188
- public void removeRemoteSailingServerReference(String name) {
1189
- remoteSailingServerSet.remove(name);
1190
- mongoObjectFactory.removeSailingServer(name);
1191
- }
1192
-
1193
- @Override
1194
- public Iterable<Event> getAllEvents() {
1195
- return Collections.unmodifiableCollection(new ArrayList<Event>(eventsById.values()));
1196
- }
1197
-
1198
- @Override
1199
- public Event getEvent(Serializable id) {
1200
- return id == null ? null : eventsById.get(id);
1201
- }
1202
-
1203
- @Override
1204
- public Iterable<Regatta> getAllRegattas() {
1205
- return Collections.unmodifiableCollection(new ArrayList<Regatta>(regattasByName.values()));
1206
- }
1207
-
1208
- @Override
1209
- public boolean isRaceBeingTracked(Regatta regattaContext, RaceDefinition r) {
1210
- Set<RaceTracker> trackers = raceTrackersByRegatta.get(regattaContext);
1211
- if (trackers != null) {
1212
- for (RaceTracker tracker : trackers) {
1213
- final Set<RaceDefinition> races = tracker.getRaces();
1214
- if (races != null && races.contains(r)) {
1215
- return true;
1216
- }
1217
- }
1218
- }
1219
- return false;
1220
- }
1221
-
1222
- @Override
1223
- public Regatta getRegattaByName(String name) {
1224
- return name == null ? null : regattasByName.get(name);
1225
- }
1226
-
1227
- @Override
1228
- public Regatta getOrCreateDefaultRegatta(String name, String boatClassName, Serializable id) {
1229
- Regatta result = regattasByName.get(name);
1230
- if (result == null) {
1231
- result = new RegattaImpl(getRaceLogStore(), getRegattaLogStore(), name, getBaseDomainFactory()
1232
- .getOrCreateBoatClass(boatClassName), /* startDate */null, /* endDate */null, this,
1233
- getBaseDomainFactory().createScoringScheme(ScoringSchemeType.LOW_POINT), id, null);
1234
- logger.info("Created default regatta " + result.getName() + " (" + hashCode() + ") on " + this);
1235
- onRegattaLikeAdded(result);
1236
- cacheAndReplicateDefaultRegatta(result);
1237
- }
1238
- return result;
1239
- }
1240
-
1241
- private void onRegattaLikeAdded(IsRegattaLike isRegattaLike) {
1242
- isRegattaLike.addListener(regattaLogReplicator);
1243
- }
1244
-
1245
- private void onRegattaLikeRemoved(IsRegattaLike isRegattaLike) {
1246
- isRegattaLike.removeListener(regattaLogReplicator);
1247
- getRegattaLogStore().removeRegattaLog(isRegattaLike.getRegattaLikeIdentifier());
1248
- }
1249
-
1250
- @Override
1251
- public Regatta createRegatta(String fullRegattaName, String boatClassName, TimePoint startDate, TimePoint endDate,
1252
- Serializable id, Iterable<? extends Series> series, boolean persistent, ScoringScheme scoringScheme,
1253
- Serializable defaultCourseAreaId, boolean useStartTimeInference, RankingMetricConstructor rankingMetricConstructor) {
1254
- com.sap.sse.common.Util.Pair<Regatta, Boolean> regattaWithCreatedFlag = getOrCreateRegattaWithoutReplication(
1255
- fullRegattaName, boatClassName, startDate, endDate, id, series, persistent, scoringScheme,
1256
- defaultCourseAreaId, useStartTimeInference, rankingMetricConstructor);
1257
- Regatta regatta = regattaWithCreatedFlag.getA();
1258
- if (regattaWithCreatedFlag.getB()) {
1259
- onRegattaLikeAdded(regatta);
1260
- replicateSpecificRegattaWithoutRaceColumns(regatta);
1261
- }
1262
- return regatta;
1263
- }
1264
-
1265
- @Override
1266
- public void addRegattaWithoutReplication(Regatta regatta) {
1267
- UUID defaultCourseAreaId = null;
1268
- if (regatta.getDefaultCourseArea() != null) {
1269
- defaultCourseAreaId = regatta.getDefaultCourseArea().getId();
1270
- }
1271
- boolean wasAdded = addAndConnectRegatta(regatta.isPersistent(), defaultCourseAreaId, regatta);
1272
- if (!wasAdded) {
1273
- logger.info("Regatta with name " + regatta.getName() + " already existed, so it hasn't been added.");
1274
- }
1275
- }
1276
-
1277
- private RaceLogStore getRaceLogStore() {
1278
- return MongoRaceLogStoreFactory.INSTANCE.getMongoRaceLogStore(mongoObjectFactory, domainObjectFactory);
1279
- }
1280
-
1281
- private RegattaLogStore getRegattaLogStore() {
1282
- return MongoRegattaLogStoreFactory.INSTANCE.getMongoRegattaLogStore(mongoObjectFactory, domainObjectFactory);
1283
- }
1284
-
1285
- @Override
1286
- public com.sap.sse.common.Util.Pair<Regatta, Boolean> getOrCreateRegattaWithoutReplication(String fullRegattaName,
1287
- String boatClassName, TimePoint startDate, TimePoint endDate, Serializable id,
1288
- Iterable<? extends Series> series, boolean persistent, ScoringScheme scoringScheme,
1289
- Serializable defaultCourseAreaId, boolean useStartTimeInference, RankingMetricConstructor rankingMetricConstructor) {
1290
- CourseArea courseArea = getCourseArea(defaultCourseAreaId);
1291
- Regatta regatta = new RegattaImpl(getRaceLogStore(), getRegattaLogStore(), fullRegattaName,
1292
- getBaseDomainFactory().getOrCreateBoatClass(boatClassName), startDate, endDate, series, persistent,
1293
- scoringScheme, id, courseArea, useStartTimeInference, rankingMetricConstructor);
1294
- boolean wasCreated = addAndConnectRegatta(persistent, defaultCourseAreaId, regatta);
1295
- if (wasCreated) {
1296
- logger.info("Created regatta " + regatta.getName() + " (" + hashCode() + ") on " + this);
1297
- }
1298
- return new com.sap.sse.common.Util.Pair<Regatta, Boolean>(regatta, wasCreated);
1299
- }
1300
-
1301
- private boolean addAndConnectRegatta(boolean persistent, Serializable defaultCourseAreaId, Regatta regatta) {
1302
- boolean wasCreated = false;
1303
- // try a quick read protected by the concurrent hash map implementation
1304
- if (!regattasByName.containsKey(regatta.getName())) {
1305
- LockUtil.lockForWrite(regattasByNameLock);
1306
- try {
1307
- // check again, now that we hold the exclusive write lock
1308
- if (!regattasByName.containsKey(regatta.getName())) {
1309
- wasCreated = true;
1310
- logger.info("putting regatta " + regatta.getName() + " (" + regatta.hashCode()
1311
- + ") into regattasByName of " + this);
1312
- regattasByName.put(regatta.getName(), regatta);
1313
- regatta.addRegattaListener(this);
1314
- regatta.addRaceColumnListener(raceLogReplicator);
1315
- regatta.addRaceColumnListener(raceLogScoringReplicator);
1316
- }
1317
- } finally {
1318
- LockUtil.unlockAfterWrite(regattasByNameLock);
1319
- }
1320
- }
1321
- if (persistent) {
1322
- updateStoredRegatta(regatta);
1323
- }
1324
- for (Event event : getAllEvents()) {
1325
- for (CourseArea eventCourseArea : event.getVenue().getCourseAreas()) {
1326
- if (defaultCourseAreaId != null && eventCourseArea.getId().equals(defaultCourseAreaId)) {
1327
- event.addRegatta(regatta);
1328
- }
1329
- }
1330
- }
1331
- return wasCreated;
1332
- }
1333
-
1334
- @Override
1335
- public void addRace(RegattaIdentifier addToRegatta, RaceDefinition raceDefinition) {
1336
- Regatta regatta = getRegatta(addToRegatta);
1337
- regatta.addRace(raceDefinition); // will trigger the raceAdded operation because this service is listening on
1338
- // all its regattas
1339
- }
1340
-
1341
- /**
1342
- * If the <code>regatta</code> {@link Regatta#isPersistent() is a persistent one}, the association of the race with
1343
- * the regatta is remembered persistently so that {@link #getRememberedRegattaForRace(Serializable)} will provide
1344
- * it.
1345
- */
1346
- @Override
1347
- public void raceAdded(Regatta regatta, RaceDefinition raceDefinition) {
1348
- if (regatta.isPersistent()) {
1349
- setRegattaForRace(regatta, raceDefinition);
1350
- }
1351
- final CourseChangeReplicator listener = new CourseChangeReplicator(this, regatta, raceDefinition);
1352
- courseListeners.put(raceDefinition, listener);
1353
- raceDefinition.getCourse().addCourseListener(listener);
1354
- replicate(new AddRaceDefinition(regatta.getRegattaIdentifier(), raceDefinition));
1355
- }
1356
-
1357
- @Override
1358
- public void raceRemoved(Regatta regatta, RaceDefinition raceDefinition) {
1359
- raceDefinition.getCourse().removeCourseListener(courseListeners.remove(raceDefinition));
1360
- }
1361
-
1362
- private NamedReentrantReadWriteLock lockRaceTrackersById(Object trackerId) {
1363
- NamedReentrantReadWriteLock lock;
1364
- synchronized (raceTrackersByIDLocks) {
1365
- lock = raceTrackersByIDLocks.get(trackerId);
1366
- if (lock == null) {
1367
- lock = new NamedReentrantReadWriteLock("raceTrackersByIDLock for " + trackerId, /* fair */false);
1368
- raceTrackersByIDLocks.put(trackerId, lock);
1369
- }
1370
- }
1371
- LockUtil.lockForWrite(lock);
1372
- return lock;
1373
- }
1374
-
1375
- /**
1376
- * @param lock
1377
- * need to pass the lock obtained from {@link #lockRaceTrackersById(Object)} because a competing thread
1378
- * may already have removed the lock from the {@link #raceTrackersByIDLocks} map
1379
- */
1380
- private void unlockRaceTrackersById(Object trackerId, NamedReentrantReadWriteLock lock) {
1381
- LockUtil.unlockAfterWrite(lock);
1382
- synchronized (raceTrackersByIDLocks) {
1383
- raceTrackersByIDLocks.remove(trackerId);
1384
- }
1385
- }
1386
-
1387
- @Override
1388
- public RaceHandle addRace(RegattaIdentifier regattaToAddTo, RaceTrackingConnectivityParameters params,
1389
- long timeoutInMilliseconds) throws Exception {
1390
- final Object trackerID = params.getTrackerID();
1391
- NamedReentrantReadWriteLock raceTrackersByIdLock = lockRaceTrackersById(trackerID);
1392
- try {
1393
- RaceTracker tracker = raceTrackersByID.get(trackerID);
1394
- if (tracker == null) {
1395
- Regatta regatta = regattaToAddTo == null ? null : getRegatta(regattaToAddTo);
1396
- if (regatta == null) {
1397
- // create tracker and use an existing or create a default regatta
1398
- tracker = params.createRaceTracker(this, windStore, gpsFixStore, /* raceLogResolver */ this);
1399
- } else {
1400
- // use the regatta selected by the RaceIdentifier regattaToAddTo
1401
- tracker = params.createRaceTracker(regatta, this, windStore, gpsFixStore, /* raceLogResolver */ this);
1402
- assert tracker.getRegatta() == regatta;
1403
- }
1404
- LockUtil.lockForWrite(raceTrackersByRegattaLock);
1405
- try {
1406
- raceTrackersByID.put(trackerID, tracker);
1407
- Set<RaceTracker> trackers = raceTrackersByRegatta.get(tracker.getRegatta());
1408
- if (trackers == null) {
1409
- trackers = Collections.newSetFromMap(new ConcurrentHashMap<RaceTracker, Boolean>());
1410
- raceTrackersByRegatta.put(tracker.getRegatta(), trackers);
1411
- }
1412
- trackers.add(tracker);
1413
- } finally {
1414
- LockUtil.unlockAfterWrite(raceTrackersByRegattaLock);
1415
- }
1416
- // TODO we assume here that the regatta name is unique which necessitates adding the boat class name to
1417
- // it in RegattaImpl constructor
1418
- String regattaName = tracker.getRegatta().getName();
1419
- Regatta regattaWithName = regattasByName.get(regattaName);
1420
- // TODO we assume here that the regatta name is unique which necessitates adding the boat class name to
1421
- // it in RegattaImpl constructor
1422
- if (regattaWithName != null) {
1423
- if (regattaWithName != tracker.getRegatta()) {
1424
- if (Util.isEmpty(regattaWithName.getAllRaces())) {
1425
- // probably, tracker removed the last races from the old regatta and created a new one
1426
- LockUtil.lockForWrite(regattasByNameLock);
1427
- try {
1428
- regattasByName.remove(regattaName);
1429
- cacheAndReplicateDefaultRegatta(tracker.getRegatta());
1430
- } finally {
1431
- LockUtil.unlockAfterWrite(regattasByNameLock);
1432
- }
1433
- } else {
1434
- throw new RuntimeException("Internal error. Two regatta objects with equal name "
1435
- + regattaName);
1436
- }
1437
- }
1438
- } else {
1439
- cacheAndReplicateDefaultRegatta(tracker.getRegatta());
1440
- }
1441
- } else {
1442
- logger.warning("Race tracker with ID "+trackerID+" already found; not tracking twice to avoid race duplication");
1443
- WindStore existingTrackersWindStore = tracker.getWindStore();
1444
- if (!existingTrackersWindStore.equals(windStore)) {
1445
- logger.warning("Wind store mismatch. Requested wind store: " + windStore
1446
- + ". Wind store in use by existing tracker: " + existingTrackersWindStore);
1447
- }
1448
- GPSFixStore existingTrackersGPSFixStore = tracker.getGPSFixStore();
1449
- if (!existingTrackersGPSFixStore.equals(gpsFixStore)) {
1450
- logger.warning("GPSFix store mismatch. Requested GPSFix store: " + gpsFixStore
1451
- + ". GPSFix store in use by existing tracker: " + existingTrackersGPSFixStore);
1452
- }
1453
- }
1454
- if (timeoutInMilliseconds != -1) {
1455
- scheduleAbortTrackerAfterInitialTimeout(tracker, timeoutInMilliseconds);
1456
- }
1457
- return tracker.getRacesHandle();
1458
- } finally {
1459
- unlockRaceTrackersById(trackerID, raceTrackersByIdLock);
1460
- }
1461
- }
1462
-
1463
- /**
1464
- * The regatta and all its contained {@link Regatta#getAllRaces() races} are replicated to all replicas.
1465
- *
1466
- * @param regatta
1467
- * the series of this regatta must not have any {@link Series#getRaceColumns() race columns associated
1468
- * (yet)}.
1469
- */
1470
- private void replicateSpecificRegattaWithoutRaceColumns(Regatta regatta) {
1471
- Serializable courseAreaId = null;
1472
- if (regatta.getDefaultCourseArea() != null) {
1473
- courseAreaId = regatta.getDefaultCourseArea().getId();
1474
- }
1475
- replicate(new AddSpecificRegatta(regatta.getName(), regatta.getBoatClass() == null ? null : regatta
1476
- .getBoatClass().getName(), regatta.getStartDate(), regatta.getEndDate(), regatta.getId(),
1477
- getSeriesWithoutRaceColumnsConstructionParametersAsMap(regatta), regatta.isPersistent(),
1478
- regatta.getScoringScheme(), courseAreaId, regatta.useStartTimeInference(), regatta.getRankingMetricType()));
1479
- RegattaIdentifier regattaIdentifier = regatta.getRegattaIdentifier();
1480
- for (RaceDefinition race : regatta.getAllRaces()) {
1481
- replicate(new AddRaceDefinition(regattaIdentifier, race));
1482
- }
1483
- }
1484
-
1485
- private RegattaCreationParametersDTO getSeriesWithoutRaceColumnsConstructionParametersAsMap(Regatta regatta) {
1486
- LinkedHashMap<String, SeriesCreationParametersDTO> result = new LinkedHashMap<String, SeriesCreationParametersDTO>();
1487
- for (Series s : regatta.getSeries()) {
1488
- assert Util.isEmpty(s.getRaceColumns());
1489
- List<FleetDTO> fleetNamesAndOrdering = new ArrayList<FleetDTO>();
1490
- for (Fleet f : s.getFleets()) {
1491
- fleetNamesAndOrdering.add(getBaseDomainFactory().convertToFleetDTO(f));
1492
- }
1493
- result.put(
1494
- s.getName(),
1495
- new SeriesCreationParametersDTO(fleetNamesAndOrdering, s.isMedal(), s.isStartsWithZeroScore(), s
1496
- .isFirstColumnIsNonDiscardableCarryForward(), s.getResultDiscardingRule() == null ? null
1497
- : s.getResultDiscardingRule().getDiscardIndexResultsStartingWithHowManyRaces(), s
1498
- .hasSplitFleetContiguousScoring()));
1499
- }
1500
- return new RegattaCreationParametersDTO(result);
1501
- }
1502
-
1503
- /**
1504
- * If <code>regatta</code> is not yet in {@link #regattasByName}, it is added, this service is
1505
- * {@link Regatta#addRegattaListener(RegattaListener) added} as regatta listener, and the regatta and all its
1506
- * contained {@link Regatta#getAllRaces() races} are replicated to all replica.
1507
- */
1508
- private void cacheAndReplicateDefaultRegatta(Regatta regatta) {
1509
- // try a quick read first, protected by regattasByName being a concurrent hash set
1510
- if (!regattasByName.containsKey(regatta.getName())) {
1511
- // now we need to obtain exclusive write access; in between, some other thread may have added a regatta by
1512
- // that
1513
- // name, so we need to check again:
1514
- LockUtil.lockForWrite(regattasByNameLock);
1515
- try {
1516
- if (!regattasByName.containsKey(regatta.getName())) {
1517
- logger.info("putting regatta " + regatta.getName() + " (" + regatta.hashCode()
1518
- + ") into regattasByName of " + this);
1519
- regattasByName.put(regatta.getName(), regatta);
1520
- regatta.addRegattaListener(this);
1521
- regatta.addRaceColumnListener(raceLogReplicator);
1522
- regatta.addRaceColumnListener(raceLogScoringReplicator);
1523
-
1524
- replicate(new AddDefaultRegatta(regatta.getName(), regatta.getBoatClass() == null ? null : regatta
1525
- .getBoatClass().getName(), regatta.getStartDate(), regatta.getEndDate(), regatta.getId()));
1526
- RegattaIdentifier regattaIdentifier = regatta.getRegattaIdentifier();
1527
- for (RaceDefinition race : regatta.getAllRaces()) {
1528
- replicate(new AddRaceDefinition(regattaIdentifier, race));
1529
- }
1530
- }
1531
- } finally {
1532
- LockUtil.unlockAfterWrite(regattasByNameLock);
1533
- }
1534
- }
1535
- }
1536
-
1537
- @Override
1538
- public DynamicTrackedRace createTrackedRace(RegattaAndRaceIdentifier raceIdentifier, WindStore windStore,
1539
- GPSFixStore gpsFixStore, long delayToLiveInMillis, long millisecondsOverWhichToAverageWind,
1540
- long millisecondsOverWhichToAverageSpeed, boolean useMarkPassingCalculator) {
1541
- DynamicTrackedRegatta trackedRegatta = getOrCreateTrackedRegatta(getRegatta(raceIdentifier));
1542
- RaceDefinition race = getRace(raceIdentifier);
1543
- return trackedRegatta.createTrackedRace(race, Collections.<Sideline> emptyList(), windStore, gpsFixStore,
1544
- delayToLiveInMillis, millisecondsOverWhichToAverageWind, millisecondsOverWhichToAverageSpeed,
1545
- /* raceDefinitionSetToUpdate */null, useMarkPassingCalculator, /* raceLogResolver */ this);
1546
- }
1547
-
1548
- private void ensureRegattaIsObservedForDefaultLeaderboardAndAutoLeaderboardLinking(
1549
- DynamicTrackedRegatta trackedRegatta) {
1550
- if (regattasObservedForDefaultLeaderboard.add(trackedRegatta)) {
1551
- trackedRegatta.addRaceListener(new RaceAdditionListener());
1552
- }
1553
- }
1554
-
1555
- private void stopObservingRegattaForRedaultLeaderboardAndAutoLeaderboardLinking(DynamicTrackedRegatta trackedRegatta) {
1556
- regattasObservedForDefaultLeaderboard.remove(trackedRegatta);
1557
- }
1558
-
1559
- /**
1560
- * A listener class used to ensure that when a tracked race is added to any {@link TrackedRegatta} managed by this
1561
- * service, the service adds the tracked race to the default leaderboard and links it to the leaderboard columns
1562
- * that were previously connected to it. Additionally, a {@link RaceChangeListener} is added to the
1563
- * {@link TrackedRace} which is responsible for triggering the replication of all relevant changes to the tracked
1564
- * race. When a tracked race is removed, the {@link TrackedRaceReplicator} that was added as listener to that
1565
- * tracked race is removed again.
1566
- *
1567
- * A {@link PolarFixCacheUpdater} is added to every race so that polar fixes are aggregated when new GPS fixes
1568
- * arrive.
1569
- *
1570
- * @author Axel Uhl (d043530)
1571
- *
1572
- */
1573
- private class RaceAdditionListener implements RaceListener, Serializable {
1574
- private static final long serialVersionUID = 1036955460477000265L;
1575
-
1576
- private final Map<TrackedRace, TrackedRaceReplicator> trackedRaceReplicators;
1577
-
1578
- private final Map<TrackedRace, PolarFixCacheUpdater> polarFixCacheUpdaters;
1579
-
1580
- public RaceAdditionListener() {
1581
- this.trackedRaceReplicators = new HashMap<TrackedRace, TrackedRaceReplicator>();
1582
- this.polarFixCacheUpdaters = new HashMap<TrackedRace, PolarFixCacheUpdater>();
1583
- }
1584
-
1585
- @Override
1586
- public void raceRemoved(TrackedRace trackedRace) {
1587
- TrackedRaceReplicator trackedRaceReplicator = trackedRaceReplicators.remove(trackedRace);
1588
- if (trackedRaceReplicator != null) {
1589
- trackedRace.removeListener(trackedRaceReplicator);
1590
- }
1591
- PolarFixCacheUpdater polarFixCacheUpdater = polarFixCacheUpdaters.remove(trackedRace);
1592
- if (polarFixCacheUpdater != null) {
1593
- trackedRace.removeListener(polarFixCacheUpdater);
1594
- }
1595
- }
1596
-
1597
- @Override
1598
- public void raceAdded(TrackedRace trackedRace) {
1599
- // replicate the addition of the tracked race:
1600
- CreateTrackedRace op = new CreateTrackedRace(trackedRace.getRaceIdentifier(), trackedRace.getWindStore(),
1601
- trackedRace.getGPSFixStore(), trackedRace.getDelayToLiveInMillis(),
1602
- trackedRace.getMillisecondsOverWhichToAverageWind(),
1603
- trackedRace.getMillisecondsOverWhichToAverageSpeed());
1604
- replicate(op);
1605
- linkRaceToConfiguredLeaderboardColumns(trackedRace);
1606
- TrackedRaceReplicator trackedRaceReplicator = new TrackedRaceReplicator(trackedRace);
1607
- trackedRaceReplicators.put(trackedRace, trackedRaceReplicator);
1608
- trackedRace.addListener(trackedRaceReplicator, /* fire wind already loaded */true, true);
1609
-
1610
- PolarFixCacheUpdater polarFixCacheUpdater = new PolarFixCacheUpdater(trackedRace);
1611
- polarFixCacheUpdaters.put(trackedRace, polarFixCacheUpdater);
1612
- trackedRace.addListener(polarFixCacheUpdater);
1613
-
1614
- if (polarDataService != null) {
1615
- trackedRace.setPolarDataService(polarDataService);
1616
- }
1617
- }
1618
- }
1619
-
1620
- private class PolarFixCacheUpdater extends AbstractRaceChangeListener {
1621
-
1622
- private final TrackedRace race;
1623
-
1624
- public PolarFixCacheUpdater(TrackedRace race) {
1625
- this.race = race;
1626
- }
1627
-
1628
- @Override
1629
- public void competitorPositionChanged(GPSFixMoving fix, Competitor item) {
1630
- if (polarDataService != null) {
1631
- polarDataService.competitorPositionChanged(fix, item, race);
1632
- }
1633
- }
1634
-
1635
- @Override
1636
- public void statusChanged(TrackedRaceStatus newStatus, TrackedRaceStatus oldStatus) {
1637
- if (oldStatus.getStatus() == TrackedRaceStatusEnum.LOADING
1638
- && newStatus.getStatus() != TrackedRaceStatusEnum.LOADING) {
1639
- if (polarDataService != null) {
1640
- polarDataService.raceFinishedLoading(race);
1641
- }
1642
- }
1643
- }
1644
-
1645
- }
1646
-
1647
- private class TrackedRaceReplicator implements RaceChangeListener {
1648
- private final TrackedRace trackedRace;
1649
-
1650
- public TrackedRaceReplicator(TrackedRace trackedRace) {
1651
- this.trackedRace = trackedRace;
1652
- }
1653
-
1654
- @Override
1655
- public void windSourcesToExcludeChanged(Iterable<? extends WindSource> windSourcesToExclude) {
1656
- replicate(new UpdateWindSourcesToExclude(getRaceIdentifier(), windSourcesToExclude));
1657
- }
1658
-
1659
- @Override
1660
- public void startOfTrackingChanged(TimePoint startOfTracking) {
1661
- replicate(new UpdateStartOfTracking(getRaceIdentifier(), startOfTracking));
1662
- }
1663
-
1664
- @Override
1665
- public void endOfTrackingChanged(TimePoint endOfTracking) {
1666
- replicate(new UpdateEndOfTracking(getRaceIdentifier(), endOfTracking));
1667
- }
1668
-
1669
- @Override
1670
- public void startTimeReceivedChanged(TimePoint startTimeReceived) {
1671
- replicate(new UpdateStartTimeReceived(getRaceIdentifier(), startTimeReceived));
1672
- }
1673
-
1674
- @Override
1675
- public void startOfRaceChanged(TimePoint oldStartOfRace, TimePoint newStartOfRace) {
1676
- // no action required; the update signaled by this call is implicit; for explicit updates
1677
- // see raceTimesChanged(TimePoint, TimePoint, TimePoint).
1678
- }
1679
-
1680
- @Override
1681
- public void waypointAdded(int zeroBasedIndex, Waypoint waypointThatGotAdded) {
1682
- // no-op; the course change is replicated by the separate CourseChangeReplicator
1683
- }
1684
-
1685
- @Override
1686
- public void waypointRemoved(int zeroBasedIndex, Waypoint waypointThatGotRemoved) {
1687
- // no-op; the course change is replicated by the separate CourseChangeReplicator
1688
- }
1689
-
1690
- @Override
1691
- public void delayToLiveChanged(long delayToLiveInMillis) {
1692
- replicate(new UpdateRaceDelayToLive(getRaceIdentifier(), delayToLiveInMillis));
1693
- }
1694
-
1695
- @Override
1696
- public void windDataReceived(Wind wind, WindSource windSource) {
1697
- replicate(new RecordWindFix(getRaceIdentifier(), windSource, wind));
1698
- }
1699
-
1700
- @Override
1701
- public void windDataRemoved(Wind wind, WindSource windSource) {
1702
- replicate(new RemoveWindFix(getRaceIdentifier(), windSource, wind));
1703
- }
1704
-
1705
- @Override
1706
- public void windAveragingChanged(long oldMillisecondsOverWhichToAverage, long newMillisecondsOverWhichToAverage) {
1707
- replicate(new UpdateWindAveragingTime(getRaceIdentifier(), newMillisecondsOverWhichToAverage));
1708
- }
1709
-
1710
- @Override
1711
- public void competitorPositionChanged(GPSFixMoving fix, Competitor competitor) {
1712
- replicate(new RecordCompetitorGPSFix(getRaceIdentifier(), competitor, fix));
1713
- }
1714
-
1715
- @Override
1716
- public void statusChanged(TrackedRaceStatus newStatus, TrackedRaceStatus oldStatus) {
1717
- replicate(new UpdateTrackedRaceStatus(getRaceIdentifier(), newStatus));
1718
- }
1719
-
1720
- @Override
1721
- public void markPositionChanged(GPSFix fix, Mark mark, boolean firstInTrack) {
1722
- final RecordMarkGPSFix operation;
1723
- if (firstInTrack) {
1724
- operation = new RecordMarkGPSFixForNewMarkTrack(getRaceIdentifier(), mark, fix);
1725
- } else {
1726
- operation = new RecordMarkGPSFixForExistingTrack(getRaceIdentifier(), mark, fix);
1727
- }
1728
- replicate(operation);
1729
- }
1730
-
1731
- @Override
1732
- public void markPassingReceived(Competitor competitor, Map<Waypoint, MarkPassing> oldMarkPassings,
1733
- Iterable<MarkPassing> markPassings) {
1734
- replicate(new UpdateMarkPassings(getRaceIdentifier(), competitor, markPassings));
1735
- }
1736
-
1737
- @Override
1738
- public void speedAveragingChanged(long oldMillisecondsOverWhichToAverage, long newMillisecondsOverWhichToAverage) {
1739
- replicate(new UpdateWindAveragingTime(getRaceIdentifier(), newMillisecondsOverWhichToAverage));
1740
- }
1741
-
1742
- private RegattaAndRaceIdentifier getRaceIdentifier() {
1743
- return trackedRace.getRaceIdentifier();
1744
- }
1745
-
1746
- }
1747
-
1748
- /**
1749
- * Based on the <code>trackedRace</code>'s {@link TrackedRace#getRaceIdentifier() race identifier}, the tracked race
1750
- * is (re-)associated to all {@link RaceColumn race columns} that currently have no
1751
- * {@link RaceColumn#getTrackedRace(Fleet) tracked race assigned} and whose
1752
- * {@link RaceColumn#getRaceIdentifier(Fleet) race identifier} equals that of <code>trackedRace</code>.
1753
- */
1754
- private void linkRaceToConfiguredLeaderboardColumns(TrackedRace trackedRace) {
1755
- boolean leaderboardHasChanged = false;
1756
- RegattaAndRaceIdentifier trackedRaceIdentifier = trackedRace.getRaceIdentifier();
1757
- for (Leaderboard leaderboard : getLeaderboards().values()) {
1758
- for (RaceColumn column : leaderboard.getRaceColumns()) {
1759
- for (Fleet fleet : column.getFleets()) {
1760
- if (trackedRaceIdentifier.equals(column.getRaceIdentifier(fleet))
1761
- && column.getTrackedRace(fleet) == null) {
1762
- column.setTrackedRace(fleet, trackedRace);
1763
- leaderboardHasChanged = true;
1764
- replicate(new ConnectTrackedRaceToLeaderboardColumn(leaderboard.getName(), column.getName(),
1765
- fleet.getName(), trackedRaceIdentifier));
1766
- }
1767
- }
1768
- }
1769
- if (leaderboardHasChanged) {
1770
- // Update the corresponding groups, to keep them in sync
1771
- syncGroupsAfterLeaderboardChange(leaderboard, /* doDatabaseUpdate */false);
1772
- }
1773
- }
1774
- }
1775
-
1776
- @Override
1777
- public void stopTracking(Regatta regatta) throws MalformedURLException, IOException, InterruptedException {
1778
- final Set<RaceTracker> trackersForRegatta = raceTrackersByRegatta.get(regatta);
1779
- if (trackersForRegatta != null) {
1780
- for (RaceTracker raceTracker : trackersForRegatta) {
1781
- final Set<RaceDefinition> races = raceTracker.getRaces();
1782
- if (races != null) {
1783
- for (RaceDefinition race : races) {
1784
- stopTrackingWind(regatta, race);
1785
- }
1786
- }
1787
- raceTracker.stop(/* preemptive */false);
1788
- final Object trackerId = raceTracker.getID();
1789
- final NamedReentrantReadWriteLock lock = lockRaceTrackersById(trackerId);
1790
- try {
1791
- raceTrackersByID.remove(trackerId);
1792
- } finally {
1793
- unlockRaceTrackersById(trackerId, lock);
1794
- }
1795
- raceTrackersByID.remove(trackerId);
1796
- }
1797
- LockUtil.lockForWrite(raceTrackersByRegattaLock);
1798
- try {
1799
- raceTrackersByRegatta.remove(regatta);
1800
- } finally {
1801
- LockUtil.unlockAfterWrite(raceTrackersByRegattaLock);
1802
- }
1803
- }
1804
- }
1805
-
1806
- @Override
1807
- public void stopTrackingAndRemove(Regatta regatta) throws MalformedURLException, IOException, InterruptedException {
1808
- stopTracking(regatta);
1809
- if (regatta != null) {
1810
- if (regatta.getName() != null) {
1811
- logger.info("Removing regatta " + regatta.getName() + " (" + regatta.hashCode() + ") from " + this);
1812
- LockUtil.lockForWrite(regattasByNameLock);
1813
- try {
1814
- regattasByName.remove(regatta.getName());
1815
- } finally {
1816
- LockUtil.unlockAfterWrite(regattasByNameLock);
1817
- }
1818
- LockUtil.lockForWrite(regattaTrackingCacheLock);
1819
- try {
1820
- regattaTrackingCache.remove(regatta);
1821
- } finally {
1822
- LockUtil.unlockAfterWrite(regattaTrackingCacheLock);
1823
- }
1824
- regatta.removeRegattaListener(this);
1825
- regatta.removeRaceColumnListener(raceLogReplicator);
1826
- regatta.removeRaceColumnListener(raceLogScoringReplicator);
1827
- }
1828
- for (RaceDefinition race : regatta.getAllRaces()) {
1829
- stopTrackingWind(regatta, race);
1830
- }
1831
- }
1832
- }
1833
-
1834
- /**
1835
- * The tracker will initially try to connect to the tracking infrastructure to obtain basic race master data. If
1836
- * this fails after some timeout, to avoid garbage and lingering threads, the task scheduled by this method will
1837
- * check after the timeout expires if race master data was successfully received. If so, the tracker continues
1838
- * normally. Otherwise, the tracker is shut down orderly by calling {@link RaceTracker#stop(boolean) stopping}.
1839
- *
1840
- * @return the scheduled task, in case the caller wants to {@link ScheduledFuture#cancel(boolean) cancel} it, e.g.,
1841
- * when the tracker is stopped or has successfully received the race
1842
- */
1843
- private ScheduledFuture<?> scheduleAbortTrackerAfterInitialTimeout(final RaceTracker tracker,
1844
- final long timeoutInMilliseconds) {
1845
- ScheduledFuture<?> task = getScheduler().schedule(new Runnable() {
1846
- @Override
1847
- public void run() {
1848
- if (tracker.getRaces() == null || tracker.getRaces().isEmpty()) {
1849
- try {
1850
- Regatta regatta = tracker.getRegatta();
1851
- logger.log(Level.SEVERE, "RaceDefinition for a race in regatta " + regatta.getName()
1852
- + " not obtained within " + timeoutInMilliseconds
1853
- + "ms. Aborting tracker for this race.");
1854
- Set<RaceTracker> trackersForRegatta = raceTrackersByRegatta.get(regatta);
1855
- if (trackersForRegatta != null) {
1856
- trackersForRegatta.remove(tracker);
1857
- }
1858
- tracker.stop(/* preemptive */true);
1859
- final Object trackerId = tracker.getID();
1860
- final NamedReentrantReadWriteLock lock = lockRaceTrackersById(trackerId);
1861
- try {
1862
- raceTrackersByID.remove(trackerId);
1863
- } finally {
1864
- unlockRaceTrackersById(trackerId, lock);
1865
- }
1866
- if (trackersForRegatta == null || trackersForRegatta.isEmpty()) {
1867
- stopTracking(regatta);
1868
- }
1869
- } catch (Exception e) {
1870
- logger.log(Level.SEVERE, "scheduleAbortTrackerAfterInitialTimeout", e);
1871
- e.printStackTrace();
1872
- }
1873
- }
1874
- }
1875
- }, /* delay */timeoutInMilliseconds, /* unit */TimeUnit.MILLISECONDS);
1876
- return task;
1877
- }
1878
-
1879
- @Override
1880
- public void stopTracking(Regatta regatta, RaceDefinition race) throws MalformedURLException, IOException,
1881
- InterruptedException {
1882
- logger.info("Stopping tracking for " + race + "...");
1883
- final Set<RaceTracker> trackerSet = raceTrackersByRegatta.get(regatta);
1884
- if (trackerSet != null) {
1885
- Iterator<RaceTracker> trackerIter = trackerSet.iterator();
1886
- while (trackerIter.hasNext()) {
1887
- RaceTracker raceTracker = trackerIter.next();
1888
- if (raceTracker.getRaces() != null && raceTracker.getRaces().contains(race)) {
1889
- logger.info("Found tracker to stop for races " + raceTracker.getRaces());
1890
- raceTracker.stop(/* preemptive */false);
1891
- trackerIter.remove();
1892
- final Object trackerId = raceTracker.getID();
1893
- final NamedReentrantReadWriteLock lock = lockRaceTrackersById(trackerId);
1894
- try {
1895
- raceTrackersByID.remove(trackerId);
1896
- } finally {
1897
- unlockRaceTrackersById(trackerId, lock);
1898
- }
1899
- }
1900
- }
1901
- } else {
1902
- logger.warning("Didn't find any trackers for regatta " + regatta);
1903
- }
1904
- stopTrackingWind(regatta, race);
1905
- // if the last tracked race was removed, confirm that tracking for the entire regatta has stopped
1906
- if (trackerSet == null || trackerSet.isEmpty()) {
1907
- stopTracking(regatta);
1908
- }
1909
- }
1910
-
1911
- @Override
1912
- public void removeRegatta(Regatta regatta) throws MalformedURLException, IOException, InterruptedException {
1913
- Set<RegattaLeaderboard> leaderboardsToRemove = new HashSet<>();
1914
- for (Leaderboard leaderboard : getLeaderboards().values()) {
1915
- if (leaderboard instanceof RegattaLeaderboard) {
1916
- RegattaLeaderboard regattaLeaderboard = (RegattaLeaderboard) leaderboard;
1917
- if (regattaLeaderboard.getRegatta() == regatta) {
1918
- leaderboardsToRemove.add(regattaLeaderboard);
1919
- }
1920
- }
1921
- }
1922
- for (RegattaLeaderboard regattaLeaderboardToRemove : leaderboardsToRemove) {
1923
- removeLeaderboard(regattaLeaderboardToRemove.getName());
1924
- }
1925
- // avoid ConcurrentModificationException by copying the races to remove:
1926
- Set<RaceDefinition> racesToRemove = new HashSet<>();
1927
- Util.addAll(regatta.getAllRaces(), racesToRemove);
1928
- for (RaceDefinition race : racesToRemove) {
1929
- removeRace(regatta, race);
1930
- mongoObjectFactory.removeRegattaForRaceID(race.getName(), regatta);
1931
- persistentRegattasForRaceIDs.remove(race.getId().toString());
1932
- }
1933
- if (regatta.isPersistent()) {
1934
- mongoObjectFactory.removeRegatta(regatta);
1935
- }
1936
- LockUtil.lockForWrite(regattasByNameLock);
1937
- try {
1938
- regattasByName.remove(regatta.getName());
1939
- } finally {
1940
- LockUtil.unlockAfterWrite(regattasByNameLock);
1941
- }
1942
- regatta.removeRegattaListener(this);
1943
- regatta.removeRaceColumnListener(raceLogReplicator);
1944
- regatta.removeRaceColumnListener(raceLogScoringReplicator);
1945
- onRegattaLikeRemoved(regatta);
1946
- }
1947
-
1948
- @Override
1949
- public void removeSeries(Series series) throws MalformedURLException, IOException, InterruptedException {
1950
- Regatta regatta = series.getRegatta();
1951
- regatta.removeSeries(series);
1952
- if (regatta.isPersistent()) {
1953
- mongoObjectFactory.storeRegatta(regatta);
1954
- }
1955
- }
1956
-
1957
- @Override
1958
- public Regatta updateRegatta(RegattaIdentifier regattaIdentifier, TimePoint startDate, TimePoint endDate,
1959
- Serializable newDefaultCourseAreaId, RegattaConfiguration newRegattaConfiguration,
1960
- Iterable<? extends Series> series, boolean useStartTimeInference) {
1961
- // We're not doing any renaming of the regatta itself, therefore we don't have to sync on the maps.
1962
- Regatta regatta = getRegatta(regattaIdentifier);
1963
- CourseArea newCourseArea = getCourseArea(newDefaultCourseAreaId);
1964
- if (newCourseArea != regatta.getDefaultCourseArea()) {
1965
- regatta.setDefaultCourseArea(newCourseArea);
1966
- }
1967
- regatta.setStartDate(startDate);
1968
- regatta.setEndDate(endDate);
1969
- if (regatta.useStartTimeInference() != useStartTimeInference) {
1970
- regatta.setUseStartTimeInference(useStartTimeInference);
1971
- final DynamicTrackedRegatta trackedRegatta = getTrackedRegatta(regatta);
1972
- if (trackedRegatta != null) {
1973
- trackedRegatta.lockTrackedRacesForRead();
1974
- try {
1975
- for (DynamicTrackedRace trackedRace : trackedRegatta.getTrackedRaces()) {
1976
- // the start times of the regatta's tracked races now have to be re-evaluated the next time they
1977
- // are queried
1978
- trackedRace.invalidateStartTime();
1979
- }
1980
- } finally {
1981
- trackedRegatta.unlockTrackedRacesAfterRead();
1982
- }
1983
- }
1984
- }
1985
- regatta.setRegattaConfiguration(newRegattaConfiguration);
1986
- if (series != null) {
1987
- for (Series seriesObj : series) {
1988
- regatta.addSeries(seriesObj);
1989
- }
1990
- }
1991
- regatta.adjustEventToRegattaAssociation(this);
1992
- if (regatta.isPersistent()) {
1993
- mongoObjectFactory.storeRegatta(regatta);
1994
- }
1995
- return regatta;
1996
- }
1997
-
1998
- @Override
1999
- public void removeRace(Regatta regatta, RaceDefinition race) throws MalformedURLException, IOException,
2000
- InterruptedException {
2001
- logger.info("Removing the race " + race + "...");
2002
- stopAllTrackersForWhichRaceIsLastReachable(regatta, race);
2003
- stopTrackingWind(regatta, race);
2004
- TrackedRace trackedRace = getExistingTrackedRace(regatta, race);
2005
- if (trackedRace != null) {
2006
- TrackedRegatta trackedRegatta = getTrackedRegatta(regatta);
2007
- final boolean isTrackedRacesEmpty;
2008
- if (trackedRegatta != null) {
2009
- trackedRegatta.lockTrackedRacesForWrite();
2010
- try {
2011
- trackedRegatta.removeTrackedRace(trackedRace);
2012
- isTrackedRacesEmpty = Util.isEmpty(trackedRegatta.getTrackedRaces());
2013
- } finally {
2014
- trackedRegatta.unlockTrackedRacesAfterWrite();
2015
- }
2016
- } else {
2017
- isTrackedRacesEmpty = false;
2018
- }
2019
- if (isTrackedRacesEmpty) {
2020
- removeTrackedRegatta(regatta);
2021
- }
2022
- // remove tracked race from RaceColumns of regatta
2023
- for (Series series : regatta.getSeries()) {
2024
- for (RaceColumnInSeries raceColumn : series.getRaceColumns()) {
2025
- for (Fleet fleet : series.getFleets()) {
2026
- if (raceColumn.getTrackedRace(fleet) == trackedRace) {
2027
- raceColumn.releaseTrackedRace(fleet);
2028
- }
2029
- }
2030
- }
2031
- }
2032
- for (Leaderboard leaderboard : getLeaderboards().values()) {
2033
- if (leaderboard instanceof FlexibleLeaderboard) { // RegattaLeaderboards have implicitly been updated by
2034
- // the code above
2035
- for (RaceColumn raceColumn : leaderboard.getRaceColumns()) {
2036
- for (Fleet fleet : raceColumn.getFleets()) {
2037
- if (raceColumn.getTrackedRace(fleet) == trackedRace) {
2038
- raceColumn.releaseTrackedRace(fleet); // but leave the RaceIdentifier on the race column
2039
- // untouched, e.g., for later re-load
2040
- }
2041
- }
2042
- }
2043
- }
2044
- }
2045
- }
2046
- // remove the race from the (default) regatta if the regatta is not persistently stored
2047
- regatta.removeRace(race);
2048
- if (!regatta.isPersistent() && Util.isEmpty(regatta.getAllRaces())) {
2049
- logger.info("Removing regatta " + regatta.getName() + " (" + regatta.hashCode() + ") from service " + this);
2050
- LockUtil.lockForWrite(regattasByNameLock);
2051
- try {
2052
- regattasByName.remove(regatta.getName());
2053
- } finally {
2054
- LockUtil.unlockAfterWrite(regattasByNameLock);
2055
- }
2056
- regatta.removeRegattaListener(this);
2057
- regatta.removeRaceColumnListener(raceLogReplicator);
2058
- regatta.removeRaceColumnListener(raceLogScoringReplicator);
2059
- }
2060
- }
2061
-
2062
- /**
2063
- * Doesn't stop any wind trackers
2064
- */
2065
- private void stopAllTrackersForWhichRaceIsLastReachable(Regatta regatta, RaceDefinition race)
2066
- throws MalformedURLException, IOException, InterruptedException {
2067
- if (raceTrackersByRegatta.containsKey(regatta)) {
2068
- Iterator<RaceTracker> trackerIter = raceTrackersByRegatta.get(regatta).iterator();
2069
- while (trackerIter.hasNext()) {
2070
- RaceTracker raceTracker = trackerIter.next();
2071
- if (raceTracker.getRaces() != null && raceTracker.getRaces().contains(race)) {
2072
- boolean foundReachableRace = false;
2073
- for (RaceDefinition raceTrackedByTracker : raceTracker.getRaces()) {
2074
- if (raceTrackedByTracker != race && isReachable(regatta, raceTrackedByTracker)) {
2075
- foundReachableRace = true;
2076
- break;
2077
- }
2078
- }
2079
- if (!foundReachableRace) {
2080
- // firstly stop the tracker
2081
- raceTracker.stop(/* preemptive */true);
2082
- // remove it from the raceTrackers by Regatta
2083
- trackerIter.remove();
2084
- final Object trackerId = raceTracker.getID();
2085
- final NamedReentrantReadWriteLock lock = lockRaceTrackersById(trackerId);
2086
- try {
2087
- raceTrackersByID.remove(trackerId);
2088
- } finally {
2089
- unlockRaceTrackersById(trackerId, lock);
2090
- }
2091
- // if the last tracked race was removed, remove the entire regatta
2092
- if (raceTrackersByRegatta.get(regatta).isEmpty()) {
2093
- stopTracking(regatta);
2094
- }
2095
- }
2096
- }
2097
- }
2098
- }
2099
- }
2100
-
2101
- private boolean isReachable(Regatta regatta, RaceDefinition race) {
2102
- return Util.contains(regatta.getAllRaces(), race);
2103
- }
2104
-
2105
- @Override
2106
- public void startTrackingWind(Regatta regatta, RaceDefinition race, boolean correctByDeclination) throws Exception {
2107
- for (WindTrackerFactory windTrackerFactory : getWindTrackerFactories()) {
2108
- windTrackerFactory.createWindTracker(getOrCreateTrackedRegatta(regatta), race, correctByDeclination);
2109
- }
2110
- }
2111
-
2112
- @Override
2113
- public void stopTrackingWind(Regatta regatta, RaceDefinition race) throws SocketException, IOException {
2114
- for (WindTrackerFactory windTrackerFactory : getWindTrackerFactories()) {
2115
- WindTracker windTracker = windTrackerFactory.getExistingWindTracker(race);
2116
- if (windTracker != null) {
2117
- windTracker.stop();
2118
- }
2119
- }
2120
- }
2121
-
2122
- @Override
2123
- public Iterable<com.sap.sse.common.Util.Triple<Regatta, RaceDefinition, String>> getWindTrackedRaces() {
2124
- List<com.sap.sse.common.Util.Triple<Regatta, RaceDefinition, String>> result = new ArrayList<com.sap.sse.common.Util.Triple<Regatta, RaceDefinition, String>>();
2125
- for (Regatta regatta : getAllRegattas()) {
2126
- for (RaceDefinition race : regatta.getAllRaces()) {
2127
- for (WindTrackerFactory windTrackerFactory : getWindTrackerFactories()) {
2128
- WindTracker windTracker = windTrackerFactory.getExistingWindTracker(race);
2129
- if (windTracker != null) {
2130
- result.add(new com.sap.sse.common.Util.Triple<Regatta, RaceDefinition, String>(regatta, race,
2131
- windTracker.toString()));
2132
- }
2133
- }
2134
- }
2135
- }
2136
- return result;
2137
- }
2138
-
2139
- @Override
2140
- public DynamicTrackedRace getTrackedRace(Regatta regatta, RaceDefinition race) {
2141
- return getOrCreateTrackedRegatta(regatta).getTrackedRace(race);
2142
- }
2143
-
2144
- private DynamicTrackedRace getExistingTrackedRace(Regatta regatta, RaceDefinition race) {
2145
- return getOrCreateTrackedRegatta(regatta).getExistingTrackedRace(race);
2146
- }
2147
-
2148
- @Override
2149
- public DynamicTrackedRegatta getOrCreateTrackedRegatta(Regatta regatta) {
2150
- cacheAndReplicateDefaultRegatta(regatta);
2151
- LockUtil.lockForWrite(regattaTrackingCacheLock);
2152
- try {
2153
- DynamicTrackedRegatta result = regattaTrackingCache.get(regatta);
2154
- if (result == null) {
2155
- logger.info("Creating DynamicTrackedRegattaImpl for regatta " + regatta.getName() + " with hashCode "
2156
- + regatta.hashCode());
2157
- result = new DynamicTrackedRegattaImpl(regatta);
2158
- replicate(new TrackRegatta(regatta.getRegattaIdentifier()));
2159
- regattaTrackingCache.put(regatta, result);
2160
- ensureRegattaIsObservedForDefaultLeaderboardAndAutoLeaderboardLinking(result);
2161
- }
2162
- return result;
2163
- } finally {
2164
- LockUtil.unlockAfterWrite(regattaTrackingCacheLock);
2165
- }
2166
- }
2167
-
2168
- @Override
2169
- public DynamicTrackedRegatta getTrackedRegatta(com.sap.sailing.domain.base.Regatta regatta) {
2170
- return regattaTrackingCache.get(regatta);
2171
- }
2172
-
2173
- @Override
2174
- public void removeTrackedRegatta(Regatta regatta) {
2175
- logger.info("Removing regatta " + regatta.getName() + " from regattaTrackingCache");
2176
- final DynamicTrackedRegatta trackedRegatta;
2177
- LockUtil.lockForWrite(regattaTrackingCacheLock);
2178
- try {
2179
- trackedRegatta = regattaTrackingCache.remove(regatta);
2180
- } finally {
2181
- LockUtil.unlockAfterWrite(regattaTrackingCacheLock);
2182
- }
2183
- stopObservingRegattaForRedaultLeaderboardAndAutoLeaderboardLinking(trackedRegatta);
2184
- }
2185
-
2186
- @Override
2187
- public Regatta getRegatta(RegattaName regattaName) {
2188
- return (Regatta) regattasByName.get(regattaName.getRegattaName());
2189
- }
2190
-
2191
- @Override
2192
- public Regatta getRegatta(RegattaIdentifier regattaIdentifier) {
2193
- return (Regatta) regattaIdentifier.getRegatta(this);
2194
- }
2195
-
2196
- @Override
2197
- public DynamicTrackedRace getTrackedRace(RegattaAndRaceIdentifier raceIdentifier) {
2198
- DynamicTrackedRace result = null;
2199
- Regatta regatta = regattasByName.get(raceIdentifier.getRegattaName());
2200
- if (regatta != null) {
2201
- DynamicTrackedRegatta trackedRegatta = regattaTrackingCache.get(regatta);
2202
- if (trackedRegatta != null) {
2203
- RaceDefinition race = getRace(raceIdentifier);
2204
- if (race != null) {
2205
- result = trackedRegatta.getTrackedRace(race);
2206
- }
2207
- }
2208
- }
2209
- return result;
2210
- }
2211
-
2212
- @Override
2213
- public DynamicTrackedRace getExistingTrackedRace(RegattaAndRaceIdentifier raceIdentifier) {
2214
- Regatta regatta = getRegattaByName(raceIdentifier.getRegattaName());
2215
- DynamicTrackedRace trackedRace = null;
2216
- if (regatta != null) {
2217
- RaceDefinition race = regatta.getRaceByName(raceIdentifier.getRaceName());
2218
- trackedRace = getOrCreateTrackedRegatta(regatta).getExistingTrackedRace(race);
2219
- }
2220
- return trackedRace;
2221
- }
2222
-
2223
- @Override
2224
- public RaceDefinition getRace(RegattaAndRaceIdentifier regattaNameAndRaceName) {
2225
- RaceDefinition result = null;
2226
- Regatta regatta = getRegatta(regattaNameAndRaceName);
2227
- if (regatta != null) {
2228
- result = regatta.getRaceByName(regattaNameAndRaceName.getRaceName());
2229
- }
2230
- return result;
2231
- }
2232
-
2233
- @Override
2234
- public Map<String, LeaderboardGroup> getLeaderboardGroups() {
2235
- return Collections.unmodifiableMap(new HashMap<String, LeaderboardGroup>(leaderboardGroupsByName));
2236
- }
2237
-
2238
- @Override
2239
- public LeaderboardGroup getLeaderboardGroupByName(String groupName) {
2240
- return leaderboardGroupsByName.get(groupName);
2241
- }
2242
-
2243
- @Override
2244
- public LeaderboardGroup getLeaderboardGroupByID(UUID leaderboardGroupID) {
2245
- return leaderboardGroupsByID.get(leaderboardGroupID);
2246
- }
2247
-
2248
- @Override
2249
- public LeaderboardGroup addLeaderboardGroup(UUID id, String groupName, String description, String displayName,
2250
- boolean displayGroupsInReverseOrder, List<String> leaderboardNames,
2251
- int[] overallLeaderboardDiscardThresholds, ScoringSchemeType overallLeaderboardScoringSchemeType) {
2252
- ArrayList<Leaderboard> leaderboards = new ArrayList<>();
2253
- for (String leaderboardName : leaderboardNames) {
2254
- Leaderboard leaderboard = leaderboardsByName.get(leaderboardName);
2255
- if (leaderboard == null) {
2256
- throw new IllegalArgumentException("No leaderboard with name " + leaderboardName + " found");
2257
- } else {
2258
- leaderboards.add(leaderboard);
2259
- }
2260
- }
2261
- LeaderboardGroup result = new LeaderboardGroupImpl(id, groupName, description, displayName,
2262
- displayGroupsInReverseOrder, leaderboards);
2263
- if (overallLeaderboardScoringSchemeType != null) {
2264
- // create overall leaderboard and its discards settings
2265
- addOverallLeaderboardToLeaderboardGroup(result,
2266
- getBaseDomainFactory().createScoringScheme(overallLeaderboardScoringSchemeType),
2267
- overallLeaderboardDiscardThresholds);
2268
- }
2269
- LockUtil.lockForWrite(leaderboardGroupsByNameLock);
2270
- try {
2271
- if (leaderboardGroupsByName.containsKey(groupName)) {
2272
- throw new IllegalArgumentException("Leaderboard group with name " + groupName + " already exists");
2273
- }
2274
- leaderboardGroupsByName.put(groupName, result);
2275
- leaderboardGroupsByID.put(result.getId(), result);
2276
- } finally {
2277
- LockUtil.unlockAfterWrite(leaderboardGroupsByNameLock);
2278
- }
2279
- mongoObjectFactory.storeLeaderboardGroup(result);
2280
- return result;
2281
- }
2282
-
2283
- @Override
2284
- public void addLeaderboardGroupWithoutReplication(LeaderboardGroup leaderboardGroup) {
2285
- LockUtil.lockForWrite(leaderboardGroupsByNameLock);
2286
- try {
2287
- String groupName = leaderboardGroup.getName();
2288
- if (leaderboardGroupsByName.containsKey(groupName)) {
2289
- throw new IllegalArgumentException("Leaderboard group with name " + groupName + " already exists");
2290
- }
2291
- leaderboardGroupsByName.put(groupName, leaderboardGroup);
2292
- leaderboardGroupsByID.put(leaderboardGroup.getId(), leaderboardGroup);
2293
- } finally {
2294
- LockUtil.unlockAfterWrite(leaderboardGroupsByNameLock);
2295
- }
2296
- if (leaderboardGroup.hasOverallLeaderboard()) {
2297
- addLeaderboard(leaderboardGroup.getOverallLeaderboard());
2298
- }
2299
- mongoObjectFactory.storeLeaderboardGroup(leaderboardGroup);
2300
- }
2301
-
2302
- @Override
2303
- public void removeLeaderboardGroup(String groupName) {
2304
- final LeaderboardGroup leaderboardGroup;
2305
- LockUtil.lockForWrite(leaderboardGroupsByNameLock);
2306
- try {
2307
- leaderboardGroup = leaderboardGroupsByName.remove(groupName);
2308
- if (leaderboardGroup != null) {
2309
- leaderboardGroupsByID.remove(leaderboardGroup.getId());
2310
- }
2311
- } finally {
2312
- LockUtil.unlockAfterWrite(leaderboardGroupsByNameLock);
2313
- }
2314
- mongoObjectFactory.removeLeaderboardGroup(groupName);
2315
- if (leaderboardGroup != null && leaderboardGroup.getOverallLeaderboard() != null) {
2316
- removeLeaderboard(leaderboardGroup.getOverallLeaderboard().getName());
2317
- }
2318
- }
2319
-
2320
- @Override
2321
- public void renameLeaderboardGroup(String oldName, String newName) {
2322
- LockUtil.lockForWrite(leaderboardGroupsByNameLock);
2323
- try {
2324
- final LeaderboardGroup toRename = leaderboardGroupsByName.get(oldName);
2325
- if (toRename == null) {
2326
- throw new IllegalArgumentException("No leaderboard group with name " + oldName + " found");
2327
- }
2328
- if (leaderboardGroupsByName.containsKey(newName)) {
2329
- throw new IllegalArgumentException("Leaderboard group with name " + newName + " already exists");
2330
- }
2331
- leaderboardGroupsByName.remove(oldName);
2332
- toRename.setName(newName);
2333
- leaderboardGroupsByName.put(newName, toRename);
2334
- } finally {
2335
- LockUtil.unlockAfterWrite(leaderboardGroupsByNameLock);
2336
- }
2337
- mongoObjectFactory.renameLeaderboardGroup(oldName, newName);
2338
- }
2339
-
2340
- @Override
2341
- public void updateLeaderboardGroup(String oldName, String newName, String description, String displayName,
2342
- List<String> leaderboardNames, int[] overallLeaderboardDiscardThresholds,
2343
- ScoringSchemeType overallLeaderboardScoringSchemeType) {
2344
- if (!oldName.equals(newName)) {
2345
- renameLeaderboardGroup(oldName, newName);
2346
- }
2347
- LeaderboardGroup group = getLeaderboardGroupByName(newName);
2348
- if (!description.equals(group.getDescription())) {
2349
- group.setDescriptiom(description);
2350
- }
2351
- if (!Util.equalsWithNull(displayName, group.getDisplayName())) {
2352
- group.setDisplayName(displayName);
2353
- }
2354
- group.clearLeaderboards();
2355
- for (String leaderboardName : leaderboardNames) {
2356
- Leaderboard leaderboard = getLeaderboardByName(leaderboardName);
2357
- if (leaderboard != null) {
2358
- group.addLeaderboard(leaderboard);
2359
- }
2360
- }
2361
- Leaderboard overallLeaderboard = group.getOverallLeaderboard();
2362
- if (overallLeaderboard != null) {
2363
- if (overallLeaderboardScoringSchemeType == null) {
2364
- group.setOverallLeaderboard(null);
2365
- removeLeaderboard(overallLeaderboard.getName());
2366
- } else {
2367
- // update existing overall leaderboard's discards settings; scoring scheme cannot be updated in-place
2368
- overallLeaderboard.setCrossLeaderboardResultDiscardingRule(new ThresholdBasedResultDiscardingRuleImpl(
2369
- overallLeaderboardDiscardThresholds));
2370
- updateStoredLeaderboard(overallLeaderboard);
2371
- }
2372
- } else if (overallLeaderboard == null && overallLeaderboardScoringSchemeType != null) {
2373
- addOverallLeaderboardToLeaderboardGroup(group,
2374
- getBaseDomainFactory().createScoringScheme(overallLeaderboardScoringSchemeType),
2375
- overallLeaderboardDiscardThresholds);
2376
- }
2377
- updateStoredLeaderboardGroup(group);
2378
- }
2379
-
2380
- private void addOverallLeaderboardToLeaderboardGroup(LeaderboardGroup leaderboardGroup,
2381
- ScoringScheme scoringScheme, int[] discardThresholds) {
2382
- Leaderboard overallLeaderboard = new LeaderboardGroupMetaLeaderboard(leaderboardGroup, scoringScheme,
2383
- new ThresholdBasedResultDiscardingRuleImpl(discardThresholds));
2384
- leaderboardGroup.setOverallLeaderboard(overallLeaderboard);
2385
- addLeaderboard(overallLeaderboard);
2386
- updateStoredLeaderboard(overallLeaderboard);
2387
- }
2388
-
2389
- @Override
2390
- public void updateStoredLeaderboardGroup(LeaderboardGroup leaderboardGroup) {
2391
- mongoObjectFactory.storeLeaderboardGroup(leaderboardGroup);
2392
- }
2393
-
2394
- private ScheduledExecutorService getScheduler() {
2395
- return scheduler;
2396
- }
2397
-
2398
- @Override
2399
- public ObjectInputStream createObjectInputStreamResolvingAgainstCache(InputStream is) throws IOException {
2400
- return getBaseDomainFactory().createObjectInputStreamResolvingAgainstThisFactory(is);
2401
- }
2402
-
2403
- @Override
2404
- public ClassLoader getDeserializationClassLoader() {
2405
- return joinedClassLoader;
2406
- }
2407
-
2408
- @Override
2409
- public Serializable getId() {
2410
- return getClass().getName();
2411
- }
2412
-
2413
- @Override
2414
- public Iterable<OperationExecutionListener<RacingEventService>> getOperationExecutionListeners() {
2415
- return operationExecutionListeners.keySet();
2416
- }
2417
-
2418
- @Override
2419
- public void addOperationExecutionListener(OperationExecutionListener<RacingEventService> listener) {
2420
- operationExecutionListeners.put(listener, listener);
2421
- }
2422
-
2423
- @Override
2424
- public void removeOperationExecutionListener(OperationExecutionListener<RacingEventService> listener) {
2425
- operationExecutionListeners.remove(listener);
2426
- }
2427
-
2428
- @Override
2429
- public void serializeForInitialReplicationInternal(ObjectOutputStream oos) throws IOException {
2430
- StringBuffer logoutput = new StringBuffer();
2431
-
2432
- logger.info("Serializing regattas...");
2433
- oos.writeObject(regattasByName);
2434
- logoutput.append("Serialized " + regattasByName.size() + " regattas\n");
2435
- for (Regatta regatta : regattasByName.values()) {
2436
- logoutput.append(String.format("%3s\n", regatta.toString()));
2437
- }
2438
-
2439
- logger.info("Serializing events...");
2440
- oos.writeObject(eventsById);
2441
- logoutput.append("\nSerialized " + eventsById.size() + " events\n");
2442
- for (Event event : eventsById.values()) {
2443
- logoutput.append(String.format("%3s\n", event.toString()));
2444
- }
2445
-
2446
- logger.info("Serializing regattas observed...");
2447
- oos.writeObject(regattasObservedForDefaultLeaderboard);
2448
- logger.info("Serializing regatta tracking cache...");
2449
- oos.writeObject(regattaTrackingCache);
2450
- logger.info("Serializing leaderboard groups...");
2451
- oos.writeObject(leaderboardGroupsByName);
2452
- logoutput.append("Serialized " + leaderboardGroupsByName.size() + " leaderboard groups\n");
2453
- for (LeaderboardGroup lg : leaderboardGroupsByName.values()) {
2454
- logoutput.append(String.format("%3s\n", lg.toString()));
2455
- }
2456
- logger.info("Serializing leaderboards...");
2457
- oos.writeObject(leaderboardsByName);
2458
- logoutput.append("Serialized " + leaderboardsByName.size() + " leaderboards\n");
2459
- for (Leaderboard lg : leaderboardsByName.values()) {
2460
- logoutput.append(String.format("%3s\n", lg.toString()));
2461
- }
2462
- logger.info("Serializing media library...");
2463
- mediaLibrary.serialize(oos);
2464
- logoutput.append("Serialized " + mediaLibrary.allTracks().size() + " media tracks\n");
2465
- for (MediaTrack lg : mediaLibrary.allTracks()) {
2466
- logoutput.append(String.format("%3s\n", lg.toString()));
2467
- }
2468
- logger.info("Serializing persisted competitors...");
2469
- oos.writeObject(competitorStore);
2470
- logoutput.append("Serialized " + competitorStore.size() + " persisted competitors\n");
2471
-
2472
- logger.info("Serializing configuration map...");
2473
- oos.writeObject(configurationMap);
2474
- logoutput.append("Serialized " + configurationMap.size() + " configuration entries\n");
2475
- for (DeviceConfigurationMatcher matcher : configurationMap.keySet()) {
2476
- logoutput.append(String.format("%3s\n", matcher.toString()));
2477
- }
2478
-
2479
- logger.info("Serializing remote sailing server references...");
2480
- final ArrayList<RemoteSailingServerReference> remoteServerReferences = new ArrayList<>(remoteSailingServerSet
2481
- .getCachedEventsForRemoteSailingServers().keySet());
2482
- oos.writeObject(remoteServerReferences);
2483
- logoutput.append("Serialized " + remoteServerReferences.size() + " remote sailing server references\n");
2484
-
2485
- logger.info(logoutput.toString());
2486
- }
2487
-
2488
- @SuppressWarnings("unchecked")
2489
- // all the casts of ois.readObject()'s return value to Map<..., ...>
2490
- // the type-parameters in the casts of the de-serialized collection objects can't be checked
2491
- @Override
2492
- public void initiallyFillFromInternal(ObjectInputStream ois) throws IOException, ClassNotFoundException,
2493
- InterruptedException {
2494
- logger.info("Performing initial replication load on " + this);
2495
- // Use this object's class's class loader as the context class loader which will then be used for
2496
- // de-serialization; this will cause all classes to be visible that this bundle
2497
- // (com.sap.sailing.server) can see
2498
- StringBuffer logoutput = new StringBuffer();
2499
- logger.info("Reading all regattas...");
2500
- regattasByName.putAll((Map<String, Regatta>) ois.readObject());
2501
- logoutput.append("Received " + regattasByName.size() + " NEW regattas\n");
2502
- for (Regatta regatta : regattasByName.values()) {
2503
- logoutput.append(String.format("%3s\n", regatta.toString()));
2504
- }
2505
-
2506
- logger.info("Reading all events...");
2507
- eventsById.putAll((Map<Serializable, Event>) ois.readObject());
2508
- logoutput.append("\nReceived " + eventsById.size() + " NEW events\n");
2509
- for (Event event : eventsById.values()) {
2510
- logoutput.append(String.format("%3s\n", event.toString()));
2511
- }
2512
-
2513
- // it is important that the leaderboards and tracked regattas are cleared before auto-linking to
2514
- // old leaderboards takes place which then don't match the new ones
2515
- logger.info("Reading all dynamic tracked regattas...");
2516
- for (DynamicTrackedRegatta trackedRegattaToObserve : (Set<DynamicTrackedRegatta>) ois.readObject()) {
2517
- ensureRegattaIsObservedForDefaultLeaderboardAndAutoLeaderboardLinking(trackedRegattaToObserve);
2518
- }
2519
-
2520
- logger.info("Reading all of the regatta tracking cache...");
2521
- regattaTrackingCache.putAll((Map<Regatta, DynamicTrackedRegatta>) ois.readObject());
2522
- logoutput.append("Received " + regattaTrackingCache.size() + " NEW regatta tracking cache entries\n");
2523
-
2524
- logger.info("Reading leaderboard groups...");
2525
- leaderboardGroupsByName.putAll((Map<String, LeaderboardGroup>) ois.readObject());
2526
- logoutput.append("Received " + leaderboardGroupsByName.size() + " NEW leaderboard groups\n");
2527
- for (LeaderboardGroup lg : leaderboardGroupsByName.values()) {
2528
- leaderboardGroupsByID.put(lg.getId(), lg);
2529
- logoutput.append(String.format("%3s\n", lg.toString()));
2530
- }
2531
-
2532
- logger.info("Reading leaderboards by name...");
2533
- leaderboardsByName.putAll((Map<String, Leaderboard>) ois.readObject());
2534
- logoutput.append("Received " + leaderboardsByName.size() + " NEW leaderboards\n");
2535
- for (Leaderboard leaderboard : leaderboardsByName.values()) {
2536
- logoutput.append(String.format("%3s\n", leaderboard.toString()));
2537
- }
2538
-
2539
- // now fix ScoreCorrectionListener setup for LeaderboardGroupMetaLeaderboard instances:
2540
- for (Leaderboard leaderboard : leaderboardsByName.values()) {
2541
- if (leaderboard instanceof LeaderboardGroupMetaLeaderboard) {
2542
- ((LeaderboardGroupMetaLeaderboard) leaderboard)
2543
- .registerAsScoreCorrectionChangeForwarderAndRaceColumnListenerOnAllLeaderboards();
2544
- } else if (leaderboard instanceof FlexibleLeaderboard) {
2545
- // and re-establish the RaceLogReplicator as listener on FlexibleLeaderboard objects
2546
- leaderboard.addRaceColumnListener(raceLogReplicator);
2547
- }
2548
- }
2549
-
2550
- logger.info("Reading media library...");
2551
- mediaLibrary.deserialize(ois);
2552
- logoutput.append("Received " + mediaLibrary.allTracks().size() + " NEW media tracks\n");
2553
- for (MediaTrack mediatrack : mediaLibrary.allTracks()) {
2554
- logoutput.append(String.format("%3s\n", mediatrack.toString()));
2555
- }
2556
-
2557
- // only copy the competitors from the deserialized competitor store; don't use it because it will have set
2558
- // a default Mongo object factory
2559
- logger.info("Reading competitors...");
2560
- for (Competitor competitor : ((CompetitorStore) ois.readObject()).getCompetitors()) {
2561
- DynamicCompetitor dynamicCompetitor = (DynamicCompetitor) competitor;
2562
- // the following should actually be redundant because during de-serialization the Competitor objects,
2563
- // whose classes implement IsManagedByCache, should already have been got/created from/in the
2564
- // competitor store
2565
- competitorStore.getOrCreateCompetitor(dynamicCompetitor.getId(), dynamicCompetitor.getName(),
2566
- dynamicCompetitor.getColor(), dynamicCompetitor.getEmail(), dynamicCompetitor.getFlagImage(),
2567
- dynamicCompetitor.getTeam(), dynamicCompetitor.getBoat(), dynamicCompetitor.getTimeOnTimeFactor(),
2568
- dynamicCompetitor.getTimeOnDistanceAllowancePerNauticalMile());
2569
- }
2570
- logoutput.append("Received " + competitorStore.size() + " NEW competitors\n");
2571
-
2572
- logger.info("Reading device configurations...");
2573
- configurationMap.putAll((DeviceConfigurationMapImpl) ois.readObject());
2574
- logoutput.append("Received " + configurationMap.size() + " NEW configuration entries\n");
2575
- for (DeviceConfigurationMatcher matcher : configurationMap.keySet()) {
2576
- logoutput.append(String.format("%3s\n", matcher.toString()));
2577
- }
2578
-
2579
- logger.info("Reading remote sailing server references...");
2580
- for (RemoteSailingServerReference remoteSailingServerReference : (Iterable<RemoteSailingServerReference>) ois
2581
- .readObject()) {
2582
- remoteSailingServerSet.add(remoteSailingServerReference);
2583
- logoutput.append("Received remote sailing server reference " + remoteSailingServerReference);
2584
- }
2585
-
2586
- // make sure to initialize listeners correctly
2587
- for (Regatta regatta : regattasByName.values()) {
2588
- RegattaImpl regattaImpl = (RegattaImpl) regatta;
2589
- regattaImpl.initializeSeriesAfterDeserialize();
2590
- regattaImpl.addRaceColumnListener(raceLogReplicator);
2591
- }
2592
- // re-establish RaceLogResolver references to this RacingEventService in all TrackedRace instances
2593
- for (DynamicTrackedRegatta trackedRegatta : regattaTrackingCache.values()) {
2594
- trackedRegatta.lockTrackedRacesForRead();
2595
- try {
2596
- for (TrackedRace trackedRace : trackedRegatta.getTrackedRaces()) {
2597
- ((TrackedRaceImpl) trackedRace).setRaceLogResolver(this);
2598
- }
2599
- } finally {
2600
- trackedRegatta.unlockTrackedRacesAfterRead();
2601
- }
2602
- }
2603
- logger.info(logoutput.toString());
2604
- }
2605
-
2606
- @Override
2607
- public void clearReplicaState() throws MalformedURLException, IOException, InterruptedException {
2608
- logger.info("Clearing all data structures...");
2609
- LockUtil.lockForWrite(regattasByNameLock);
2610
- try {
2611
- regattasByName.clear();
2612
- } finally {
2613
- LockUtil.unlockAfterWrite(regattasByNameLock);
2614
- }
2615
- regattasObservedForDefaultLeaderboard.clear();
2616
-
2617
- if (raceTrackersByRegatta != null && !raceTrackersByRegatta.isEmpty()) {
2618
- for (DynamicTrackedRegatta regatta : regattaTrackingCache.values()) {
2619
- final Set<RaceTracker> trackers = raceTrackersByRegatta.get(regatta.getRegatta());
2620
- if (trackers != null) {
2621
- for (RaceTracker tracker : trackers) {
2622
- tracker.stop(/* preemptive */true);
2623
- }
2624
- }
2625
- }
2626
- }
2627
- LockUtil.lockForWrite(regattaTrackingCacheLock);
2628
- try {
2629
- regattaTrackingCache.clear();
2630
- } finally {
2631
- LockUtil.unlockAfterWrite(regattaTrackingCacheLock);
2632
- }
2633
- LockUtil.lockForWrite(raceTrackersByRegattaLock);
2634
- try {
2635
- raceTrackersByRegatta.clear();
2636
- } finally {
2637
- LockUtil.unlockAfterWrite(raceTrackersByRegattaLock);
2638
- }
2639
- LockUtil.lockForWrite(leaderboardGroupsByNameLock);
2640
- try {
2641
- leaderboardGroupsByName.clear();
2642
- leaderboardGroupsByID.clear();
2643
- } finally {
2644
- LockUtil.unlockAfterWrite(leaderboardGroupsByNameLock);
2645
- }
2646
- LockUtil.lockForWrite(leaderboardsByNameLock);
2647
- try {
2648
- leaderboardsByName.clear();
2649
- } finally {
2650
- LockUtil.unlockAfterWrite(leaderboardsByNameLock);
2651
- }
2652
- eventsById.clear();
2653
- mediaLibrary.clear();
2654
- competitorStore.clear();
2655
- remoteSailingServerSet.clear();
2656
- }
2657
-
2658
- // Used for TESTING only
2659
- @Override
2660
- public Event addEvent(String eventName, String eventDescription, TimePoint startDate, TimePoint endDate,
2661
- String venue, boolean isPublic, UUID id) {
2662
- Event result = createEventWithoutReplication(eventName, eventDescription, startDate, endDate, venue, isPublic,
2663
- id, /* officialWebsiteURL */null, /* sailorsInfoWebsiteURLAsString */null,
2664
- /* images */Collections.<ImageDescriptor> emptyList(), /* videos */Collections.<VideoDescriptor> emptyList());
2665
- replicate(new CreateEvent(eventName, eventDescription, startDate, endDate, venue, isPublic, id,
2666
- /* officialWebsiteURLAsString */null, /* sailorsInfoWebsiteURLAsString */null,
2667
- /* images */Collections.<ImageDescriptor> emptyList(), /* videos */Collections.<VideoDescriptor> emptyList()));
2668
- return result;
2669
- }
2670
-
2671
- @Override
2672
- public void addEventWithoutReplication(Event event) {
2673
- addEvent(event);
2674
- }
2675
-
2676
- @Override
2677
- public Event createEventWithoutReplication(String eventName, String eventDescription, TimePoint startDate,
2678
- TimePoint endDate, String venue, boolean isPublic, UUID id, URL officialWebsiteURL, URL sailorsInfoWebsiteURL,
2679
- Iterable<ImageDescriptor> images, Iterable<VideoDescriptor> videos) {
2680
- Event result = new EventImpl(eventName, startDate, endDate, venue, isPublic, id);
2681
- addEvent(result);
2682
- result.setDescription(eventDescription);
2683
- result.setOfficialWebsiteURL(officialWebsiteURL);
2684
- result.setImages(images);
2685
- result.setVideos(videos);
2686
- return result;
2687
- }
2688
-
2689
- private void addEvent(Event result) {
2690
- if (eventsById.containsKey(result.getId())) {
2691
- throw new IllegalArgumentException("Event with ID " + result.getId()
2692
- + " already exists which is pretty surprising...");
2693
- }
2694
- eventsById.put(result.getId(), result);
2695
- mongoObjectFactory.storeEvent(result);
2696
- }
2697
-
2698
- @Override
2699
- public void updateEvent(UUID id, String eventName, String eventDescription, TimePoint startDate, TimePoint endDate,
2700
- String venueName, boolean isPublic, Iterable<UUID> leaderboardGroupIds, URL officialWebsiteURL, URL sailorsInfoWebsiteURL,
2701
- Iterable<ImageDescriptor> images, Iterable<VideoDescriptor> videos) {
2702
- final Event event = eventsById.get(id);
2703
- if (event == null) {
2704
- throw new IllegalArgumentException("Sailing event with ID " + id + " does not exist.");
2705
- }
2706
- event.setName(eventName);
2707
- event.setDescription(eventDescription);
2708
- event.setStartDate(startDate);
2709
- event.setEndDate(endDate);
2710
- event.setPublic(isPublic);
2711
- event.getVenue().setName(venueName);
2712
- List<LeaderboardGroup> leaderboardGroups = new ArrayList<>();
2713
- for (UUID lgid : leaderboardGroupIds) {
2714
- LeaderboardGroup lg = getLeaderboardGroupByID(lgid);
2715
- if (lg != null) {
2716
- leaderboardGroups.add(lg);
2717
- } else {
2718
- logger.info("Couldn't find leaderboard group with ID " + lgid + " while updating event "
2719
- + event.getName());
2720
- }
2721
- }
2722
- event.setLeaderboardGroups(leaderboardGroups);
2723
- event.setOfficialWebsiteURL(officialWebsiteURL);
2724
- event.setSailorsInfoWebsiteURL(sailorsInfoWebsiteURL);
2725
- event.setImages(images);
2726
- event.setVideos(videos);
2727
- // TODO consider use diffutils to compute diff between old and new leaderboard groups list and apply the patch
2728
- // to keep changes minimial
2729
- mongoObjectFactory.storeEvent(event);
2730
- }
2731
-
2732
- @Override
2733
- public void renameEvent(UUID id, String newName) {
2734
- final Event toRename = eventsById.get(id);
2735
- if (toRename == null) {
2736
- throw new IllegalArgumentException("No sailing event with ID " + id + " found.");
2737
- }
2738
- toRename.setName(newName);
2739
- mongoObjectFactory.renameEvent(id, newName);
2740
- replicate(new RenameEvent(id, newName));
2741
- }
2742
-
2743
- @Override
2744
- public void removeEvent(UUID id) {
2745
- removeEventFromEventsById(id);
2746
- mongoObjectFactory.removeEvent(id);
2747
- replicate(new RemoveEvent(id));
2748
- }
2749
-
2750
- protected void removeEventFromEventsById(Serializable id) {
2751
- eventsById.remove(id);
2752
- }
2753
-
2754
- @Override
2755
- public Regatta getRememberedRegattaForRace(Serializable raceID) {
2756
- return persistentRegattasForRaceIDs.get(raceID.toString());
2757
- }
2758
-
2759
- /**
2760
- * Persistently remembers the association of the race with its {@link RaceDefinition#getId()} to the
2761
- * <code>regatta</code> with its {@link Regatta#getRegattaIdentifier() identifier} so that the next time
2762
- * {@link #getRememberedRegattaForRace(RaceDefinition)} is called with <code>race</code> as argument,
2763
- * <code>regatta</code> will be returned.
2764
- */
2765
- private void setRegattaForRace(Regatta regatta, RaceDefinition race) {
2766
- setRegattaForRace(regatta, race.getId().toString());
2767
- }
2768
-
2769
- @Override
2770
- public void setRegattaForRace(Regatta regatta, String raceIdAsString) {
2771
- persistentRegattasForRaceIDs.put(raceIdAsString, regatta);
2772
- mongoObjectFactory.storeRegattaForRaceID(raceIdAsString, regatta);
2773
- }
2774
-
2775
- @Override
2776
- public CourseArea[] addCourseAreas(UUID eventId, String[] courseAreaNames, UUID[] courseAreaIds) {
2777
- final CourseArea[] courseAreas = addCourseAreasWithoutReplication(eventId, courseAreaIds, courseAreaNames);
2778
- replicate(new AddCourseAreas(eventId, courseAreaNames, courseAreaIds));
2779
- return courseAreas;
2780
- }
2781
-
2782
- @Override
2783
- public CourseArea[] addCourseAreasWithoutReplication(UUID eventId, UUID[] courseAreaIds, String[] courseAreaNames) {
2784
- final CourseArea[] result = new CourseArea[courseAreaNames.length];
2785
- for (int i=0; i<courseAreaIds.length; i++) {
2786
- final CourseArea courseArea = getBaseDomainFactory().getOrCreateCourseArea(courseAreaIds[i], courseAreaNames[i]);
2787
- final Event event = eventsById.get(eventId);
2788
- if (event == null) {
2789
- throw new IllegalArgumentException("No sailing event with ID " + eventId + " found.");
2790
- }
2791
- event.getVenue().addCourseArea(courseArea);
2792
- mongoObjectFactory.storeEvent(event);
2793
- result[i] = courseArea;
2794
- }
2795
- return result;
2796
- }
2797
-
2798
- @Override
2799
- public CourseArea[] removeCourseAreaWithoutReplication(UUID eventId, UUID[] courseAreaIds) {
2800
- final Event event = eventsById.get(eventId);
2801
- if (event == null) {
2802
- throw new IllegalArgumentException("No sailing event with ID " + eventId + " found.");
2803
- }
2804
- final CourseArea[] courseAreasRemoved = new CourseArea[courseAreaIds.length];
2805
- int i=0;
2806
- for (final UUID courseAreaId : courseAreaIds) {
2807
- final CourseArea courseArea = getBaseDomainFactory().getExistingCourseAreaById(courseAreaId);
2808
- if (courseArea == null) {
2809
- throw new IllegalArgumentException("No course area with ID " + courseAreaId + " found.");
2810
- }
2811
- courseAreasRemoved[i++] = courseArea;
2812
- event.getVenue().removeCourseArea(courseArea);
2813
- mongoObjectFactory.storeEvent(event);
2814
- }
2815
- return courseAreasRemoved;
2816
- }
2817
-
2818
- @Override
2819
- public void mediaTrackAdded(MediaTrack mediaTrack) {
2820
- if (mediaTrack.dbId == null) {
2821
- mediaTrack.dbId = mediaDB.insertMediaTrack(mediaTrack.title, mediaTrack.url, mediaTrack.startTime,
2822
- mediaTrack.duration, mediaTrack.mimeType, mediaTrack.assignedRaces);
2823
- }
2824
- mediaLibrary.addMediaTrack(mediaTrack);
2825
- replicate(new AddMediaTrackOperation(mediaTrack));
2826
- }
2827
-
2828
- @Override
2829
- public void mediaTracksAdded(Collection<MediaTrack> mediaTracks) {
2830
- mediaLibrary.addMediaTracks(mediaTracks);
2831
- }
2832
-
2833
- @Override
2834
- public void mediaTrackTitleChanged(MediaTrack mediaTrack) {
2835
- mediaDB.updateTitle(mediaTrack.dbId, mediaTrack.title);
2836
- mediaLibrary.titleChanged(mediaTrack);
2837
- replicate(new UpdateMediaTrackTitleOperation(mediaTrack));
2838
- }
2839
-
2840
- @Override
2841
- public void mediaTrackUrlChanged(MediaTrack mediaTrack) {
2842
- mediaDB.updateUrl(mediaTrack.dbId, mediaTrack.url);
2843
- mediaLibrary.urlChanged(mediaTrack);
2844
- replicate(new UpdateMediaTrackUrlOperation(mediaTrack));
2845
- }
2846
-
2847
- @Override
2848
- public void mediaTrackStartTimeChanged(MediaTrack mediaTrack) {
2849
- mediaDB.updateStartTime(mediaTrack.dbId, mediaTrack.startTime);
2850
- mediaLibrary.startTimeChanged(mediaTrack);
2851
- replicate(new UpdateMediaTrackStartTimeOperation(mediaTrack));
2852
- }
2853
-
2854
- @Override
2855
- public void mediaTrackDurationChanged(MediaTrack mediaTrack) {
2856
- mediaDB.updateDuration(mediaTrack.dbId, mediaTrack.duration);
2857
- mediaLibrary.durationChanged(mediaTrack);
2858
- replicate(new UpdateMediaTrackDurationOperation(mediaTrack));
2859
- }
2860
-
2861
- @Override
2862
- public void mediaTrackAssignedRacesChanged(MediaTrack mediaTrack) {
2863
- mediaDB.updateRace(mediaTrack.dbId, mediaTrack.assignedRaces);
2864
- mediaLibrary.assignedRacesChanged(mediaTrack);
2865
- replicate(new UpdateMediaTrackRacesOperation(mediaTrack));
2866
-
2867
- }
2868
-
2869
- @Override
2870
- public void mediaTrackDeleted(MediaTrack mediaTrack) {
2871
- mediaDB.deleteMediaTrack(mediaTrack.dbId);
2872
- mediaLibrary.deleteMediaTrack(mediaTrack);
2873
- replicate(new RemoveMediaTrackOperation(mediaTrack));
2874
- }
2875
-
2876
- @Override
2877
- public void mediaTracksImported(Collection<MediaTrack> mediaTracksToImport, boolean override) {
2878
- for (MediaTrack trackToImport : mediaTracksToImport) {
2879
- MediaTrack existingTrack = mediaLibrary.lookupMediaTrack(trackToImport);
2880
- if (existingTrack == null) {
2881
- mediaDB.insertMediaTrackWithId(trackToImport.dbId, trackToImport.title, trackToImport.url,
2882
- trackToImport.startTime, trackToImport.duration, trackToImport.mimeType,
2883
- trackToImport.assignedRaces);
2884
- mediaTrackAdded(trackToImport);
2885
- } else if (override) {
2886
-
2887
- // Using fine-grained update methods.
2888
- // Rationale: Changes on more than one track property are rare
2889
- // and don't justify the introduction of a new set
2890
- // of methods (including replication).
2891
- if (!Util.equalsWithNull(existingTrack.title, trackToImport.title)) {
2892
- mediaTrackTitleChanged(trackToImport);
2893
- }
2894
- if (!Util.equalsWithNull(existingTrack.url, trackToImport.url)) {
2895
- mediaTrackUrlChanged(trackToImport);
2896
- }
2897
- if (!Util.equalsWithNull(existingTrack.startTime, trackToImport.startTime)) {
2898
- mediaTrackStartTimeChanged(trackToImport);
2899
- }
2900
- if (!Util.equalsWithNull(existingTrack.duration, trackToImport.duration)) {
2901
- mediaTrackDurationChanged(trackToImport);
2902
- }
2903
- if (!Util.equalsWithNull(existingTrack.assignedRaces, trackToImport.assignedRaces)) {
2904
- mediaTrackAssignedRacesChanged(trackToImport);
2905
- }
2906
- }
2907
- }
2908
- }
2909
-
2910
- @Override
2911
- public Collection<MediaTrack> getMediaTracksForRace(RegattaAndRaceIdentifier regattaAndRaceIdentifier) {
2912
- return mediaLibrary.findMediaTracksForRace(regattaAndRaceIdentifier);
2913
- }
2914
-
2915
- @Override
2916
- public Collection<MediaTrack> getMediaTracksInTimeRange(RegattaAndRaceIdentifier regattaAndRaceIdentifier) {
2917
- TrackedRace trackedRace = getExistingTrackedRace(regattaAndRaceIdentifier);
2918
- if (trackedRace != null) {
2919
- if (trackedRace.isLive(MillisecondsTimePoint.now())) {
2920
- return mediaLibrary.findLiveMediaTracks();
2921
- } else {
2922
- TimePoint raceStart = trackedRace.getStartOfRace() == null ? trackedRace.getStartOfTracking()
2923
- : trackedRace.getStartOfRace();
2924
- TimePoint raceEnd = trackedRace.getEndOfRace() == null ? trackedRace.getEndOfTracking() : trackedRace
2925
- .getEndOfRace();
2926
- return mediaLibrary.findMediaTracksInTimeRange(raceStart, raceEnd);
2927
- }
2928
- } else {
2929
- return Collections.emptyList();
2930
- }
2931
- }
2932
-
2933
- @Override
2934
- public Collection<MediaTrack> getAllMediaTracks() {
2935
- return mediaLibrary.allTracks();
2936
- }
2937
-
2938
- public String toString() {
2939
- return "RacingEventService: " + this.hashCode() + " Build: " + ServerInfo.getBuildVersion();
2940
- }
2941
-
2942
- @Override
2943
- public void reloadRaceLog(String leaderboardName, String raceColumnName, String fleetName) {
2944
- Leaderboard leaderboard = getLeaderboardByName(leaderboardName);
2945
- if (leaderboard != null) {
2946
- RaceColumn raceColumn = leaderboard.getRaceColumnByName(raceColumnName);
2947
- if (raceColumn != null) {
2948
- Fleet fleetImpl = raceColumn.getFleetByName(fleetName);
2949
- RaceLog racelog = raceColumn.getRaceLog(fleetImpl);
2950
- if (racelog != null) {
2951
- raceColumn.reloadRaceLog(fleetImpl);
2952
- logger.info("Reloaded race log for fleet " + fleetImpl + " for race column " + raceColumn.getName()
2953
- + " for leaderboard " + leaderboard.getName());
2954
- }
2955
- }
2956
- }
2957
- }
2958
-
2959
- @Override
2960
- public ConcurrentHashMap<String, Regatta> getPersistentRegattasForRaceIDs() {
2961
- return persistentRegattasForRaceIDs;
2962
- }
2963
-
2964
- @Override
2965
- public WindStore getWindStore() {
2966
- return windStore;
2967
- }
2968
-
2969
- @Override
2970
- public DeviceConfiguration getDeviceConfiguration(DeviceConfigurationIdentifier identifier) {
2971
- return configurationMap.getByMatch(identifier);
2972
- }
2973
-
2974
- @Override
2975
- public void createOrUpdateDeviceConfiguration(DeviceConfigurationMatcher matcher, DeviceConfiguration configuration) {
2976
- configurationMap.put(matcher, configuration);
2977
- mongoObjectFactory.storeDeviceConfiguration(matcher, configuration);
2978
- replicate(new CreateOrUpdateDeviceConfiguration(matcher, configuration));
2979
- }
2980
-
2981
- @Override
2982
- public void removeDeviceConfiguration(DeviceConfigurationMatcher matcher) {
2983
- configurationMap.remove(matcher);
2984
- mongoObjectFactory.removeDeviceConfiguration(matcher);
2985
- replicate(new RemoveDeviceConfiguration(matcher));
2986
- }
2987
-
2988
- @Override
2989
- public Map<DeviceConfigurationMatcher, DeviceConfiguration> getAllDeviceConfigurations() {
2990
- return new HashMap<DeviceConfigurationMatcher, DeviceConfiguration>(configurationMap);
2991
- }
2992
-
2993
- @Override
2994
- public TimePoint setStartTimeAndProcedure(String leaderboardName, String raceColumnName, String fleetName,
2995
- String authorName, int authorPriority, int passId, TimePoint logicalTimePoint, TimePoint startTime,
2996
- RacingProcedureType racingProcedure) {
2997
- RaceLog raceLog = getRaceLog(leaderboardName, raceColumnName, fleetName);
2998
- Leaderboard leaderboard = getLeaderboardByName(leaderboardName);
2999
- final TimePoint result;
3000
- if (leaderboard instanceof HasRegattaLike && raceLog != null) {
3001
- RaceState state = RaceStateImpl.create(/* race log resolver */ this, raceLog, new LogEventAuthorImpl(authorName, authorPriority));
3002
- if (passId > raceLog.getCurrentPassId()) {
3003
- state.setAdvancePass(logicalTimePoint);
3004
- }
3005
- state.setRacingProcedure(logicalTimePoint, racingProcedure);
3006
- state.forceNewStartTime(logicalTimePoint, startTime);
3007
- result = state.getStartTime();
3008
- } else {
3009
- result = null;
3010
- }
3011
- return result;
3012
- }
3013
-
3014
- public RaceLog getRaceLog(String leaderboardName, String raceColumnName, String fleetName) {
3015
- Leaderboard leaderboard = getLeaderboardByName(leaderboardName);
3016
- if (leaderboard != null) {
3017
- RaceColumn raceColumn = leaderboard.getRaceColumnByName(raceColumnName);
3018
- if (raceColumn != null) {
3019
- Fleet fleetImpl = raceColumn.getFleetByName(fleetName);
3020
- return raceColumn.getRaceLog(fleetImpl);
3021
- }
3022
- }
3023
- return null;
3024
- }
3025
-
3026
- @Override
3027
- public com.sap.sse.common.Util.Triple<TimePoint, Integer, RacingProcedureType> getStartTimeAndProcedure(
3028
- String leaderboardName, String raceColumnName, String fleetName) {
3029
- RaceLog raceLog = getRaceLog(leaderboardName, raceColumnName, fleetName);
3030
- Leaderboard leaderboard = getLeaderboardByName(leaderboardName);
3031
- final Triple<TimePoint, Integer, RacingProcedureType> result;
3032
- if (leaderboard instanceof HasRegattaLike && raceLog != null) {
3033
- ReadonlyRaceState state = ReadonlyRaceStateImpl.create(/* race log resolver */ this, raceLog);
3034
- result = new com.sap.sse.common.Util.Triple<TimePoint, Integer, RacingProcedureType>(state.getStartTime(),
3035
- raceLog.getCurrentPassId(), state.getRacingProcedure().getType());
3036
- } else {
3037
- result = null;
3038
- }
3039
- return result;
3040
- }
3041
-
3042
- private Iterable<WindTrackerFactory> getWindTrackerFactories() {
3043
- final Set<WindTrackerFactory> result;
3044
- if (bundleContext == null) {
3045
- result = Collections.singleton((WindTrackerFactory) ExpeditionWindTrackerFactory.getInstance());
3046
- } else {
3047
- ServiceTracker<WindTrackerFactory, WindTrackerFactory> tracker = new ServiceTracker<WindTrackerFactory, WindTrackerFactory>(
3048
- bundleContext, WindTrackerFactory.class.getName(), null);
3049
- tracker.open();
3050
- result = new HashSet<>();
3051
- for (WindTrackerFactory factory : tracker.getServices(new WindTrackerFactory[0])) {
3052
- result.add(factory);
3053
- }
3054
- }
3055
- return result;
3056
- }
3057
-
3058
- @Override
3059
- public GPSFixStore getGPSFixStore() {
3060
- return gpsFixStore;
3061
- }
3062
-
3063
- @Override
3064
- public RaceTracker getRaceTrackerById(Object id) {
3065
- return raceTrackersByID.get(id);
3066
- }
3067
-
3068
- @Override
3069
- public AbstractLogEventAuthor getServerAuthor() {
3070
- return raceLogEventAuthorForServer;
3071
- }
3072
-
3073
- @Override
3074
- public CompetitorStore getCompetitorStore() {
3075
- return competitorStore;
3076
- }
3077
-
3078
- @Override
3079
- public TypeBasedServiceFinderFactory getTypeBasedServiceFinderFactory() {
3080
- return serviceFinderFactory;
3081
- }
3082
-
3083
- @Override
3084
- public DataImportLockWithProgress getDataImportLock() {
3085
- return dataImportLock;
3086
- }
3087
-
3088
- @Override
3089
- public DataImportProgress createOrUpdateDataImportProgressWithReplication(UUID importOperationId,
3090
- double overallProgressPct, String subProgressName, double subProgressPct) {
3091
- // Create/Update locally
3092
- DataImportProgress progress = createOrUpdateDataImportProgressWithoutReplication(importOperationId,
3093
- overallProgressPct, subProgressName, subProgressPct);
3094
- // Create/Update on replicas
3095
- replicate(new CreateOrUpdateDataImportProgress(importOperationId, overallProgressPct, subProgressName,
3096
- subProgressPct));
3097
- return progress;
3098
- }
3099
-
3100
- @Override
3101
- public DataImportProgress createOrUpdateDataImportProgressWithoutReplication(UUID importOperationId,
3102
- double overallProgressPct, String subProgressName, double subProgressPct) {
3103
- DataImportProgress progress = dataImportLock.getProgress(importOperationId);
3104
- boolean newObject = false;
3105
- if (progress == null) {
3106
- progress = new DataImportProgressImpl(importOperationId);
3107
- newObject = true;
3108
- }
3109
- progress.setOverAllProgressPct(overallProgressPct);
3110
- progress.setNameOfCurrentSubProgress(subProgressName);
3111
- progress.setCurrentSubProgressPct(subProgressPct);
3112
- if (newObject) {
3113
- dataImportLock.addProgress(importOperationId, progress);
3114
- }
3115
- return progress;
3116
- }
3117
-
3118
- @Override
3119
- public void setDataImportFailedWithoutReplication(UUID importOperationId, String errorMessage) {
3120
- DataImportProgress progress = dataImportLock.getProgress(importOperationId);
3121
- if (progress != null) {
3122
- progress.setFailed();
3123
- progress.setErrorMessage(errorMessage);
3124
- }
3125
- }
3126
-
3127
- @Override
3128
- public void setDataImportFailedWithReplication(UUID importOperationId, String errorMessage) {
3129
- setDataImportFailedWithoutReplication(importOperationId, errorMessage);
3130
- replicate(new DataImportFailed(importOperationId, errorMessage));
3131
- }
3132
-
3133
- @Override
3134
- public void setDataImportDeleteProgressFromMapTimerWithReplication(UUID importOperationId) {
3135
- setDataImportDeleteProgressFromMapTimerWithoutReplication(importOperationId);
3136
- replicate(new SetDataImportDeleteProgressFromMapTimer(importOperationId));
3137
- }
3138
-
3139
- @Override
3140
- public void setDataImportDeleteProgressFromMapTimerWithoutReplication(UUID importOperationId) {
3141
- dataImportLock.setDeleteFromMapTimer(importOperationId);
3142
- }
3143
-
3144
- @Override
3145
- public Result<LeaderboardSearchResult> search(KeywordQuery query) {
3146
- long start = System.currentTimeMillis();
3147
- logger.info("Searching local server for " + query);
3148
- Result<LeaderboardSearchResult> result = new RegattaByKeywordSearchService().search(this, query);
3149
- logger.fine("Search for " + query + " took " + (System.currentTimeMillis() - start) + "ms");
3150
- return result;
3151
- }
3152
-
3153
- @Override
3154
- public Result<LeaderboardSearchResultBase> searchRemotely(String remoteServerReferenceName, KeywordQuery query) {
3155
- long start = System.currentTimeMillis();
3156
- ResultImpl<LeaderboardSearchResultBase> result = null;
3157
- RemoteSailingServerReference remoteRef = remoteSailingServerSet
3158
- .getServerReferenceByName(remoteServerReferenceName);
3159
- if (remoteRef == null) {
3160
- result = null;
3161
- } else {
3162
- BufferedReader bufferedReader = null;
3163
- try {
3164
- try {
3165
- final URL eventsURL = new URL(remoteRef.getURL(), "sailingserver/api/v1/search?q="
3166
- + URLEncoder.encode(query.toString(), "UTF-8"));
3167
- logger.info("Searching remote server " + remoteRef + " for " + query);
3168
- URLConnection urlConnection = eventsURL.openConnection();
3169
- urlConnection.connect();
3170
- bufferedReader = new BufferedReader(new InputStreamReader(urlConnection.getInputStream(), "UTF-8"));
3171
- JSONParser parser = new JSONParser();
3172
- Object eventsAsObject = parser.parse(bufferedReader);
3173
- final LeaderboardGroupBaseJsonDeserializer leaderboardGroupBaseJsonDeserializer = new LeaderboardGroupBaseJsonDeserializer();
3174
- LeaderboardSearchResultBaseJsonDeserializer deserializer = new LeaderboardSearchResultBaseJsonDeserializer(
3175
- new EventBaseJsonDeserializer(new VenueJsonDeserializer(new CourseAreaJsonDeserializer(
3176
- DomainFactory.INSTANCE)), leaderboardGroupBaseJsonDeserializer),
3177
- leaderboardGroupBaseJsonDeserializer);
3178
- result = new ResultImpl<LeaderboardSearchResultBase>(query,
3179
- new LeaderboardSearchResultBaseRanker<LeaderboardSearchResultBase>());
3180
- JSONArray hitsAsJsonArray = (JSONArray) eventsAsObject;
3181
- for (Object hitAsObject : hitsAsJsonArray) {
3182
- JSONObject hitAsJson = (JSONObject) hitAsObject;
3183
- LeaderboardSearchResultBase hit = deserializer.deserialize(hitAsJson);
3184
- result.addHit(hit);
3185
- }
3186
- } finally {
3187
- if (bufferedReader != null) {
3188
- bufferedReader.close();
3189
- }
3190
- }
3191
- } catch (IOException | ParseException e) {
3192
- logger.log(Level.INFO,
3193
- "Exception trying to fetch events from remote server " + remoteRef + ": " + e.getMessage(), e);
3194
- }
3195
- }
3196
- logger.fine("Remote search on " + remoteRef + " for " + query + " took " + (System.currentTimeMillis() - start)
3197
- + "ms");
3198
- return result;
3199
- }
3200
-
3201
- @Override
3202
- public ReplicationMasterDescriptor getMasterDescriptor() {
3203
- return replicatingFromMaster;
3204
- }
3205
-
3206
- @Override
3207
- public void startedReplicatingFrom(ReplicationMasterDescriptor master) {
3208
- this.replicatingFromMaster = master;
3209
- }
3210
-
3211
- @Override
3212
- public void stoppedReplicatingFrom(ReplicationMasterDescriptor master) {
3213
- this.replicatingFromMaster = null;
3214
- }
3215
-
3216
- @Override
3217
- public void addOperationSentToMasterForReplication(
3218
- OperationWithResultWithIdWrapper<RacingEventService, ?> operationWithResultWithIdWrapper) {
3219
- this.operationsSentToMasterForReplication.add(operationWithResultWithIdWrapper);
3220
- }
3221
-
3222
- @Override
3223
- public boolean hasSentOperationToMaster(OperationWithResult<RacingEventService, ?> operation) {
3224
- return this.operationsSentToMasterForReplication.remove(operation);
3225
- }
3226
-
3227
- @Override
3228
- public FileStorageManagementService getFileStorageManagementService() {
3229
- ServiceReference<FileStorageManagementService> ref = bundleContext
3230
- .getServiceReference(FileStorageManagementService.class);
3231
- if (ref == null) {
3232
- logger.warning("No file storage management service registered");
3233
- return null;
3234
- }
3235
- return bundleContext.getService(ref);
3236
- }
3237
-
3238
- public void addMasterDataClassLoader(ClassLoader classLoader) {
3239
- masterDataClassLoaders.add(classLoader);
3240
- }
3241
-
3242
- public void removeMasterDataClassLoader(ClassLoader classLoader) {
3243
- masterDataClassLoaders.remove(classLoader);
3244
- }
3245
-
3246
- @Override
3247
- public ClassLoader getCombinedMasterDataClassLoader() {
3248
- JoinedClassLoader joinedClassLoader = new JoinedClassLoader(masterDataClassLoaders);
3249
- return joinedClassLoader;
3250
- }
3251
-
3252
- public void setPolarDataService(PolarDataService service) {
3253
- if (this.polarDataService == null && service != null) {
3254
- polarDataService = service;
3255
- polarDataService.registerDomainFactory(baseDomainFactory);
3256
- setPolarDataServiceOnAllTrackedRaces(service);
3257
- }
3258
- }
3259
-
3260
- private void setPolarDataServiceOnAllTrackedRaces(PolarDataService service) {
3261
- Iterable<Regatta> allRegattas = getAllRegattas();
3262
- for (Regatta regatta : allRegattas) {
3263
- DynamicTrackedRegatta trackedRegatta = getTrackedRegatta(regatta);
3264
- if (trackedRegatta != null) {
3265
- trackedRegatta.lockTrackedRacesForRead();
3266
- try {
3267
- Iterable<DynamicTrackedRace> trackedRaces = trackedRegatta.getTrackedRaces();
3268
- for (TrackedRace trackedRace : trackedRaces) {
3269
- trackedRace.setPolarDataService(service);
3270
- service.insertExistingFixes(trackedRace);
3271
- }
3272
- } finally {
3273
- trackedRegatta.unlockTrackedRacesAfterRead();
3274
- }
3275
- }
3276
- }
3277
- }
3278
-
3279
- public void unsetPolarDataService(PolarDataService service) {
3280
- if (polarDataService == service) {
3281
- polarDataService.unregisterDomainFactory(baseDomainFactory);
3282
- polarDataService = null;
3283
- setPolarDataService(null);
3284
- }
3285
- }
3286
-
3287
- @Override
3288
- public Iterable<Competitor> getCompetitorInOrderOfWindwardDistanceTraveledFarthestFirst(TrackedRace trackedRace, TimePoint timePoint) {
3289
- final RankingInfo rankingInfo = trackedRace.getRankingMetric().getRankingInfo(timePoint);
3290
- final List<Competitor> result = new ArrayList<>();
3291
- final Map<Competitor, Distance> windwardDistanceSailedPerCompetitor = new HashMap<>();
3292
- for (final Competitor competitor : trackedRace.getRace().getCompetitors()) {
3293
- result.add(competitor);
3294
- final CompetitorRankingInfo competitorRankingInfo = rankingInfo.getCompetitorRankingInfo().apply(competitor);
3295
- windwardDistanceSailedPerCompetitor.put(competitor, competitorRankingInfo == null ? null : competitorRankingInfo.getWindwardDistanceSailed());
3296
- }
3297
- final Comparator<Distance> durationComparatorNullsLast = Comparator.nullsLast(Comparator.naturalOrder());
3298
- result.sort((c1, c2) -> durationComparatorNullsLast.compare(windwardDistanceSailedPerCompetitor.get(c2),
3299
- windwardDistanceSailedPerCompetitor.get(c1)));
3300
- return result;
3301
- }
3302
-
3303
- /**
3304
- * A {@link SimpleRaceLogIdentifier} in particular has a {@link SimpleRaceLogIdentifier#getRegattaLikeParentName()}
3305
- * which identifies either a regatta by name or a flexible leaderboard by name. Here is why this can luckily be
3306
- * resolved unanimously: A regatta leaderboard always uses as its name the regatta name (see
3307
- * {@link RegattaImpl#getName()}). Trying to {@link RegattaLeaderboardImpl#setName(String) set} the regatta leaderboard's
3308
- * name can only update its {@link Leaderboard#getDisplayName() display name}. Therefore, regatta leaderboards are always
3309
- * keyed in {@link #leaderboardsByName} by their regatta's name. Thus, no flexible leaderboard can have a regatta's name
3310
- * as its name, and therefore leaderboard names <em>and</em> regatta names are unitedly unique.
3311
- */
3312
- @Override
3313
- public RaceLog resolve(SimpleRaceLogIdentifier identifier) {
3314
- final RaceLog result;
3315
- final IsRegattaLike regattaLike;
3316
- final Regatta regatta = regattasByName.get(identifier.getRegattaLikeParentName());
3317
- if (regatta != null) {
3318
- regattaLike = regatta;
3319
- } else {
3320
- final Leaderboard leaderboard = leaderboardsByName.get(identifier.getRegattaLikeParentName());
3321
- if (leaderboard != null && leaderboard instanceof FlexibleLeaderboard) {
3322
- regattaLike = (FlexibleLeaderboard) leaderboard;
3323
- } else {
3324
- regattaLike = null;
3325
- }
3326
- }
3327
- if (regattaLike != null) {
3328
- final RaceColumn raceColumn = regattaLike.getRaceColumnByName(identifier.getRaceColumnName());
3329
- if (raceColumn != null) {
3330
- final Fleet fleet = raceColumn.getFleetByName(identifier.getFleetName());
3331
- if (fleet != null) {
3332
- result = raceColumn.getRaceLog(fleet);
3333
- } else {
3334
- result = null;
3335
- }
3336
- } else {
3337
- result = null;
3338
- }
3339
- } else {
3340
- result = null;
3341
- }
3342
- return result;
3343
- }
3344
-}
1
+package com.sap.sailing.server.impl;
2
+
3
+import java.io.BufferedReader;
4
+import java.io.IOException;
5
+import java.io.InputStream;
6
+import java.io.InputStreamReader;
7
+import java.io.ObjectInputStream;
8
+import java.io.ObjectOutputStream;
9
+import java.io.Serializable;
10
+import java.net.MalformedURLException;
11
+import java.net.SocketException;
12
+import java.net.URL;
13
+import java.net.URLConnection;
14
+import java.net.URLEncoder;
15
+import java.util.ArrayList;
16
+import java.util.Arrays;
17
+import java.util.Collection;
18
+import java.util.Collections;
19
+import java.util.Comparator;
20
+import java.util.HashMap;
21
+import java.util.HashSet;
22
+import java.util.Iterator;
23
+import java.util.LinkedHashMap;
24
+import java.util.List;
25
+import java.util.Map;
26
+import java.util.Map.Entry;
27
+import java.util.Set;
28
+import java.util.UUID;
29
+import java.util.concurrent.ConcurrentHashMap;
30
+import java.util.concurrent.Executor;
31
+import java.util.concurrent.Executors;
32
+import java.util.concurrent.LinkedBlockingQueue;
33
+import java.util.concurrent.ScheduledExecutorService;
34
+import java.util.concurrent.ScheduledFuture;
35
+import java.util.concurrent.ThreadPoolExecutor;
36
+import java.util.concurrent.TimeUnit;
37
+import java.util.function.Function;
38
+import java.util.logging.Level;
39
+import java.util.logging.Logger;
40
+
41
+import org.json.simple.JSONArray;
42
+import org.json.simple.JSONObject;
43
+import org.json.simple.parser.JSONParser;
44
+import org.json.simple.parser.ParseException;
45
+import org.osgi.framework.BundleContext;
46
+import org.osgi.framework.ServiceReference;
47
+import org.osgi.util.tracker.ServiceTracker;
48
+
49
+import com.sap.sailing.domain.abstractlog.AbstractLog;
50
+import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor;
51
+import com.sap.sailing.domain.abstractlog.impl.LogEventAuthorImpl;
52
+import com.sap.sailing.domain.abstractlog.race.RaceLog;
53
+import com.sap.sailing.domain.abstractlog.race.RaceLogEvent;
54
+import com.sap.sailing.domain.abstractlog.race.RaceLogEventVisitor;
55
+import com.sap.sailing.domain.abstractlog.race.SimpleRaceLogIdentifier;
56
+import com.sap.sailing.domain.abstractlog.race.analyzing.impl.RaceLogResolver;
57
+import com.sap.sailing.domain.abstractlog.race.state.RaceState;
58
+import com.sap.sailing.domain.abstractlog.race.state.ReadonlyRaceState;
59
+import com.sap.sailing.domain.abstractlog.race.state.impl.RaceStateImpl;
60
+import com.sap.sailing.domain.abstractlog.race.state.impl.ReadonlyRaceStateImpl;
61
+import com.sap.sailing.domain.base.Competitor;
62
+import com.sap.sailing.domain.base.CompetitorStore;
63
+import com.sap.sailing.domain.base.CompetitorStore.CompetitorUpdateListener;
64
+import com.sap.sailing.domain.base.ControlPoint;
65
+import com.sap.sailing.domain.base.CourseArea;
66
+import com.sap.sailing.domain.base.DomainFactory;
67
+import com.sap.sailing.domain.base.Event;
68
+import com.sap.sailing.domain.base.EventBase;
69
+import com.sap.sailing.domain.base.EventFetcher;
70
+import com.sap.sailing.domain.base.Fleet;
71
+import com.sap.sailing.domain.base.LeaderboardSearchResult;
72
+import com.sap.sailing.domain.base.LeaderboardSearchResultBase;
73
+import com.sap.sailing.domain.base.Mark;
74
+import com.sap.sailing.domain.base.RaceColumn;
75
+import com.sap.sailing.domain.base.RaceColumnInSeries;
76
+import com.sap.sailing.domain.base.RaceDefinition;
77
+import com.sap.sailing.domain.base.Regatta;
78
+import com.sap.sailing.domain.base.RegattaListener;
79
+import com.sap.sailing.domain.base.RemoteSailingServerReference;
80
+import com.sap.sailing.domain.base.SailingServerConfiguration;
81
+import com.sap.sailing.domain.base.Series;
82
+import com.sap.sailing.domain.base.Sideline;
83
+import com.sap.sailing.domain.base.Waypoint;
84
+import com.sap.sailing.domain.base.configuration.DeviceConfiguration;
85
+import com.sap.sailing.domain.base.configuration.DeviceConfigurationIdentifier;
86
+import com.sap.sailing.domain.base.configuration.DeviceConfigurationMatcher;
87
+import com.sap.sailing.domain.base.configuration.RegattaConfiguration;
88
+import com.sap.sailing.domain.base.configuration.impl.DeviceConfigurationMapImpl;
89
+import com.sap.sailing.domain.base.impl.DynamicCompetitor;
90
+import com.sap.sailing.domain.base.impl.EventImpl;
91
+import com.sap.sailing.domain.base.impl.RegattaImpl;
92
+import com.sap.sailing.domain.base.impl.RemoteSailingServerReferenceImpl;
93
+import com.sap.sailing.domain.common.DataImportProgress;
94
+import com.sap.sailing.domain.common.Distance;
95
+import com.sap.sailing.domain.common.Position;
96
+import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
97
+import com.sap.sailing.domain.common.RegattaIdentifier;
98
+import com.sap.sailing.domain.common.RegattaName;
99
+import com.sap.sailing.domain.common.Renamable;
100
+import com.sap.sailing.domain.common.ScoringSchemeType;
101
+import com.sap.sailing.domain.common.TrackedRaceStatusEnum;
102
+import com.sap.sailing.domain.common.Wind;
103
+import com.sap.sailing.domain.common.WindSource;
104
+import com.sap.sailing.domain.common.dto.FleetDTO;
105
+import com.sap.sailing.domain.common.dto.RegattaCreationParametersDTO;
106
+import com.sap.sailing.domain.common.dto.SeriesCreationParametersDTO;
107
+import com.sap.sailing.domain.common.impl.DataImportProgressImpl;
108
+import com.sap.sailing.domain.common.media.MediaTrack;
109
+import com.sap.sailing.domain.common.racelog.RacingProcedureType;
110
+import com.sap.sailing.domain.common.tracking.GPSFix;
111
+import com.sap.sailing.domain.common.tracking.GPSFixMoving;
112
+import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard;
113
+import com.sap.sailing.domain.leaderboard.FlexibleRaceColumn;
114
+import com.sap.sailing.domain.leaderboard.Leaderboard;
115
+import com.sap.sailing.domain.leaderboard.LeaderboardGroup;
116
+import com.sap.sailing.domain.leaderboard.LeaderboardRegistry;
117
+import com.sap.sailing.domain.leaderboard.RegattaLeaderboard;
118
+import com.sap.sailing.domain.leaderboard.ScoringScheme;
119
+import com.sap.sailing.domain.leaderboard.impl.FlexibleLeaderboardImpl;
120
+import com.sap.sailing.domain.leaderboard.impl.LeaderboardGroupImpl;
121
+import com.sap.sailing.domain.leaderboard.impl.RegattaLeaderboardImpl;
122
+import com.sap.sailing.domain.leaderboard.impl.ThresholdBasedResultDiscardingRuleImpl;
123
+import com.sap.sailing.domain.leaderboard.meta.LeaderboardGroupMetaLeaderboard;
124
+import com.sap.sailing.domain.persistence.DomainObjectFactory;
125
+import com.sap.sailing.domain.persistence.MongoObjectFactory;
126
+import com.sap.sailing.domain.persistence.MongoRaceLogStoreFactory;
127
+import com.sap.sailing.domain.persistence.MongoRegattaLogStoreFactory;
128
+import com.sap.sailing.domain.persistence.MongoWindStore;
129
+import com.sap.sailing.domain.persistence.MongoWindStoreFactory;
130
+import com.sap.sailing.domain.persistence.PersistenceFactory;
131
+import com.sap.sailing.domain.persistence.media.MediaDB;
132
+import com.sap.sailing.domain.persistence.media.MediaDBFactory;
133
+import com.sap.sailing.domain.persistence.racelog.tracking.MongoGPSFixStoreFactory;
134
+import com.sap.sailing.domain.polars.PolarDataService;
135
+import com.sap.sailing.domain.racelog.RaceLogIdentifier;
136
+import com.sap.sailing.domain.racelog.RaceLogStore;
137
+import com.sap.sailing.domain.racelog.tracking.GPSFixStore;
138
+import com.sap.sailing.domain.racelogtracking.DeviceIdentifier;
139
+import com.sap.sailing.domain.ranking.RankingMetric.CompetitorRankingInfo;
140
+import com.sap.sailing.domain.ranking.RankingMetric.RankingInfo;
141
+import com.sap.sailing.domain.ranking.RankingMetricConstructor;
142
+import com.sap.sailing.domain.regattalike.HasRegattaLike;
143
+import com.sap.sailing.domain.regattalike.IsRegattaLike;
144
+import com.sap.sailing.domain.regattalike.LeaderboardThatHasRegattaLike;
145
+import com.sap.sailing.domain.regattalog.RegattaLogStore;
146
+import com.sap.sailing.domain.tracking.DynamicTrackedRace;
147
+import com.sap.sailing.domain.tracking.DynamicTrackedRegatta;
148
+import com.sap.sailing.domain.tracking.GPSFixTrack;
149
+import com.sap.sailing.domain.tracking.MarkPassing;
150
+import com.sap.sailing.domain.tracking.RaceChangeListener;
151
+import com.sap.sailing.domain.tracking.RaceHandle;
152
+import com.sap.sailing.domain.tracking.RaceListener;
153
+import com.sap.sailing.domain.tracking.RaceTracker;
154
+import com.sap.sailing.domain.tracking.RaceTrackingConnectivityParameters;
155
+import com.sap.sailing.domain.tracking.TrackedRace;
156
+import com.sap.sailing.domain.tracking.TrackedRaceStatus;
157
+import com.sap.sailing.domain.tracking.TrackedRegatta;
158
+import com.sap.sailing.domain.tracking.WindStore;
159
+import com.sap.sailing.domain.tracking.WindTracker;
160
+import com.sap.sailing.domain.tracking.WindTrackerFactory;
161
+import com.sap.sailing.domain.tracking.impl.AbstractRaceChangeListener;
162
+import com.sap.sailing.domain.tracking.impl.DynamicGPSFixTrackImpl;
163
+import com.sap.sailing.domain.tracking.impl.DynamicTrackedRegattaImpl;
164
+import com.sap.sailing.domain.tracking.impl.TrackedRaceImpl;
165
+import com.sap.sailing.expeditionconnector.ExpeditionWindTrackerFactory;
166
+import com.sap.sailing.server.RacingEventService;
167
+import com.sap.sailing.server.Replicator;
168
+import com.sap.sailing.server.gateway.deserialization.impl.CourseAreaJsonDeserializer;
169
+import com.sap.sailing.server.gateway.deserialization.impl.EventBaseJsonDeserializer;
170
+import com.sap.sailing.server.gateway.deserialization.impl.LeaderboardGroupBaseJsonDeserializer;
171
+import com.sap.sailing.server.gateway.deserialization.impl.LeaderboardSearchResultBaseJsonDeserializer;
172
+import com.sap.sailing.server.gateway.deserialization.impl.VenueJsonDeserializer;
173
+import com.sap.sailing.server.masterdata.DataImportLockWithProgress;
174
+import com.sap.sailing.server.operationaltransformation.AddCourseAreas;
175
+import com.sap.sailing.server.operationaltransformation.AddDefaultRegatta;
176
+import com.sap.sailing.server.operationaltransformation.AddMediaTrackOperation;
177
+import com.sap.sailing.server.operationaltransformation.AddRaceDefinition;
178
+import com.sap.sailing.server.operationaltransformation.AddSpecificRegatta;
179
+import com.sap.sailing.server.operationaltransformation.ConnectTrackedRaceToLeaderboardColumn;
180
+import com.sap.sailing.server.operationaltransformation.CreateEvent;
181
+import com.sap.sailing.server.operationaltransformation.CreateOrUpdateDataImportProgress;
182
+import com.sap.sailing.server.operationaltransformation.CreateOrUpdateDeviceConfiguration;
183
+import com.sap.sailing.server.operationaltransformation.CreateTrackedRace;
184
+import com.sap.sailing.server.operationaltransformation.DataImportFailed;
185
+import com.sap.sailing.server.operationaltransformation.RecordCompetitorGPSFix;
186
+import com.sap.sailing.server.operationaltransformation.RecordMarkGPSFix;
187
+import com.sap.sailing.server.operationaltransformation.RecordMarkGPSFixForExistingTrack;
188
+import com.sap.sailing.server.operationaltransformation.RecordMarkGPSFixForNewMarkTrack;
189
+import com.sap.sailing.server.operationaltransformation.RecordWindFix;
190
+import com.sap.sailing.server.operationaltransformation.RemoveDeviceConfiguration;
191
+import com.sap.sailing.server.operationaltransformation.RemoveEvent;
192
+import com.sap.sailing.server.operationaltransformation.RemoveMediaTrackOperation;
193
+import com.sap.sailing.server.operationaltransformation.RemoveWindFix;
194
+import com.sap.sailing.server.operationaltransformation.RenameEvent;
195
+import com.sap.sailing.server.operationaltransformation.SetDataImportDeleteProgressFromMapTimer;
196
+import com.sap.sailing.server.operationaltransformation.TrackRegatta;
197
+import com.sap.sailing.server.operationaltransformation.UpdateCompetitor;
198
+import com.sap.sailing.server.operationaltransformation.UpdateEndOfTracking;
199
+import com.sap.sailing.server.operationaltransformation.UpdateMarkPassings;
200
+import com.sap.sailing.server.operationaltransformation.UpdateMediaTrackDurationOperation;
201
+import com.sap.sailing.server.operationaltransformation.UpdateMediaTrackRacesOperation;
202
+import com.sap.sailing.server.operationaltransformation.UpdateMediaTrackStartTimeOperation;
203
+import com.sap.sailing.server.operationaltransformation.UpdateMediaTrackTitleOperation;
204
+import com.sap.sailing.server.operationaltransformation.UpdateMediaTrackUrlOperation;
205
+import com.sap.sailing.server.operationaltransformation.UpdateRaceDelayToLive;
206
+import com.sap.sailing.server.operationaltransformation.UpdateStartOfTracking;
207
+import com.sap.sailing.server.operationaltransformation.UpdateStartTimeReceived;
208
+import com.sap.sailing.server.operationaltransformation.UpdateTrackedRaceStatus;
209
+import com.sap.sailing.server.operationaltransformation.UpdateWindAveragingTime;
210
+import com.sap.sailing.server.operationaltransformation.UpdateWindSourcesToExclude;
211
+import com.sap.sailing.server.simulation.SimulationService;
212
+import com.sap.sailing.server.simulation.SimulationServiceFactory;
213
+import com.sap.sse.ServerInfo;
214
+import com.sap.sse.common.TimePoint;
215
+import com.sap.sse.common.TypeBasedServiceFinderFactory;
216
+import com.sap.sse.common.Util;
217
+import com.sap.sse.common.Util.Pair;
218
+import com.sap.sse.common.Util.Triple;
219
+import com.sap.sse.common.impl.MillisecondsTimePoint;
220
+import com.sap.sse.common.search.KeywordQuery;
221
+import com.sap.sse.common.search.Result;
222
+import com.sap.sse.common.search.ResultImpl;
223
+import com.sap.sse.concurrent.LockUtil;
224
+import com.sap.sse.concurrent.NamedReentrantReadWriteLock;
225
+import com.sap.sse.filestorage.FileStorageManagementService;
226
+import com.sap.sse.replication.OperationExecutionListener;
227
+import com.sap.sse.replication.OperationWithResult;
228
+import com.sap.sse.replication.ReplicationMasterDescriptor;
229
+import com.sap.sse.replication.impl.OperationWithResultWithIdWrapper;
230
+import com.sap.sse.shared.media.ImageDescriptor;
231
+import com.sap.sse.shared.media.VideoDescriptor;
232
+import com.sap.sse.util.ClearStateTestSupport;
233
+import com.sap.sse.util.JoinedClassLoader;
234
+import com.sap.sse.util.impl.ThreadFactoryWithPriority;
235
+
236
+public class RacingEventServiceImpl implements RacingEventService, ClearStateTestSupport, RegattaListener,
237
+ LeaderboardRegistry, Replicator, EventFetcher {
238
+ private static final Logger logger = Logger.getLogger(RacingEventServiceImpl.class.getName());
239
+
240
+ /**
241
+ * A scheduler for the periodic checks of the paramURL documents for the advent of {@link ControlPoint}s with static
242
+ * position information otherwise not available through <code>MarkPassingReceiver</code>'s events.
243
+ */
244
+ private static final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1, new ThreadFactoryWithPriority(Thread.NORM_PRIORITY, /* daemon */ true));
245
+
246
+ private final com.sap.sailing.domain.base.DomainFactory baseDomainFactory;
247
+
248
+ /**
249
+ * Holds the {@link Event} objects for those event registered with this service. Note that there may be
250
+ * {@link Event} objects that exist outside this service for events not (yet) registered here.
251
+ */
252
+ private final ConcurrentHashMap<Serializable, Event> eventsById;
253
+
254
+ private final RemoteSailingServerSet remoteSailingServerSet;
255
+
256
+ /**
257
+ * Holds the {@link Regatta} objects for those races registered with this service. Note that there may be
258
+ * {@link Regatta} objects that exist outside this service for regattas not (yet) registered here.
259
+ */
260
+ protected final ConcurrentHashMap<String, Regatta> regattasByName;
261
+
262
+ private final NamedReentrantReadWriteLock regattasByNameLock;
263
+
264
+ private final ConcurrentHashMap<RaceDefinition, CourseChangeReplicator> courseListeners;
265
+
266
+ protected final ConcurrentHashMap<Regatta, Set<RaceTracker>> raceTrackersByRegatta;
267
+
268
+ /**
269
+ * Although {@link #raceTrackersByRegatta} is a concurrent hash map, entering sets as values needs to be
270
+ * synchronized using this lock's write lock to avoid two value sets overwriting each other.
271
+ */
272
+ private final NamedReentrantReadWriteLock raceTrackersByRegattaLock;
273
+
274
+ /**
275
+ * Remembers the trackers by paramURL/liveURI/storedURI to avoid duplication
276
+ */
277
+ protected final ConcurrentHashMap<Object, RaceTracker> raceTrackersByID;
278
+
279
+ /**
280
+ * {@link #addRace(RegattaIdentifier, RaceTrackingConnectivityParameters, long)} will check
281
+ * {@link #raceTrackersByID} for the presence of a tracker and won't create a new tracker if one for the
282
+ * connectivity parameters' ID already exists. This check and creation and addition to {@link #raceTrackersByID}
283
+ * requires locking in the face of concurrent calls to
284
+ * {@link #addRace(RegattaIdentifier, RaceTrackingConnectivityParameters, long)}. Using <code>synchronized</code> is
285
+ * not ideal due to its coarse-grained locking style which allows for little concurrency. Instead, this map is used
286
+ * to keep locks for any ID that any invocation of the
287
+ * {@link #addRace(RegattaIdentifier, RaceTrackingConnectivityParameters, long)} method is currently working on.
288
+ * Fetching or creating and putting a lock to this map happens in ({@link #getOrCreateRaceTrackersByIdLock}) which
289
+ * takes care of managing concurrent access to this concurrent map. When done, the
290
+ * {@link #addRace(RegattaIdentifier, RaceTrackingConnectivityParameters, long)} method cleans up by removing the
291
+ * lock again from this map, again using a synchronized method (
292
+ * {@link #unlockRaceTrackersById(Object, NamedReentrantReadWriteLock)}).
293
+ */
294
+ private final ConcurrentHashMap<Object, NamedReentrantReadWriteLock> raceTrackersByIDLocks;
295
+
296
+ /**
297
+ * Leaderboards managed by this racing event service
298
+ */
299
+ private final ConcurrentHashMap<String, Leaderboard> leaderboardsByName;
300
+
301
+ /**
302
+ * {@link #leaderboardsByName} is already a concurrent hash map; however, when renaming a leaderboard, this shall
303
+ * happen as an atomic transaction, not interruptible by other write accesses on the same map because otherwise
304
+ * assumptions made during the rename process wouldn't hold. See, in particular,
305
+ * {@link #renameLeaderboard(String, String)}.
306
+ */
307
+ private final NamedReentrantReadWriteLock leaderboardsByNameLock;
308
+
309
+ private final ConcurrentHashMap<String, LeaderboardGroup> leaderboardGroupsByName;
310
+
311
+ private final ConcurrentHashMap<UUID, LeaderboardGroup> leaderboardGroupsByID;
312
+
313
+ /**
314
+ * See {@link #leaderboardsByNameLock}
315
+ */
316
+ private final NamedReentrantReadWriteLock leaderboardGroupsByNameLock;
317
+
318
+ private final CompetitorStore competitorStore;
319
+
320
+ /**
321
+ * A set based on a concurrent hash map, therefore being thread safe
322
+ */
323
+ private Set<DynamicTrackedRegatta> regattasObservedForDefaultLeaderboard = Collections
324
+ .newSetFromMap(new ConcurrentHashMap<DynamicTrackedRegatta, Boolean>());
325
+
326
+ private final MongoObjectFactory mongoObjectFactory;
327
+
328
+ private final DomainObjectFactory domainObjectFactory;
329
+
330
+ private final ConcurrentHashMap<Regatta, DynamicTrackedRegatta> regattaTrackingCache;
331
+
332
+ /**
333
+ * Protects write access transactions that do a previous read to {@link #regattaTrackingCache}; read-only access is
334
+ * already synchronized by using a concurrent hash map for {@link #regattaTrackingCache}.
335
+ */
336
+ private final NamedReentrantReadWriteLock regattaTrackingCacheLock;
337
+
338
+ private final ConcurrentHashMap<OperationExecutionListener<RacingEventService>, OperationExecutionListener<RacingEventService>> operationExecutionListeners;
339
+
340
+ /**
341
+ * Keys are the toString() representation of the {@link RaceDefinition#getId() IDs} of races passed to
342
+ * {@link #setRegattaForRace(Regatta, RaceDefinition)}.
343
+ */
344
+ private final ConcurrentHashMap<String, Regatta> persistentRegattasForRaceIDs;
345
+
346
+ private final RaceLogReplicator raceLogReplicator;
347
+ private final RegattaLogReplicator regattaLogReplicator;
348
+
349
+ private final RaceLogScoringReplicator raceLogScoringReplicator;
350
+
351
+ private final MediaDB mediaDB;
352
+
353
+ private final MediaLibrary mediaLibrary;
354
+
355
+ /**
356
+ * Currently valid pairs of {@link DeviceConfigurationMatcher}s and {@link DeviceConfiguration}s. The contents of
357
+ * this map is persisted and replicated. See {@link DeviceConfigurationMapImpl}.
358
+ */
359
+ protected final DeviceConfigurationMapImpl configurationMap;
360
+
361
+ private final WindStore windStore;
362
+ private final GPSFixStore gpsFixStore;
363
+
364
+ /**
365
+ * This author should be used for server generated race log events
366
+ */
367
+ private final AbstractLogEventAuthor raceLogEventAuthorForServer = new LogEventAuthorImpl(
368
+ RacingEventService.class.getName(), 0);
369
+
370
+ private PolarDataService polarDataService;
371
+
372
+ private final SimulationService simulationService;
373
+
374
+ /**
375
+ * Allow only one master data import at a time to avoid situation where multiple Imports override each other in
376
+ * unpredictable fashion
377
+ */
378
+ private final DataImportLockWithProgress dataImportLock;
379
+
380
+ /**
381
+ * If this service runs in the context of an OSGi environment, the activator should {@link #setBundleContext set the
382
+ * bundle context} on this object so that service lookups become possible.
383
+ */
384
+ private BundleContext bundleContext;
385
+
386
+ private TypeBasedServiceFinderFactory serviceFinderFactory;
387
+
388
+ /**
389
+ * The master from which this replicable is currently replicating, or <code>null</code> if this replicable is not
390
+ * currently replicated from any master.
391
+ */
392
+ private ReplicationMasterDescriptor replicatingFromMaster;
393
+
394
+ private Set<OperationWithResultWithIdWrapper<?, ?>> operationsSentToMasterForReplication;
395
+
396
+ private ThreadLocal<Boolean> currentlyFillingFromInitialLoadOrApplyingOperationReceivedFromMaster = ThreadLocal
397
+ .withInitial(() -> false);
398
+
399
+ private final Set<ClassLoader> masterDataClassLoaders = new HashSet<ClassLoader>();
400
+
401
+ private final JoinedClassLoader joinedClassLoader;
402
+
403
+ private SailingServerConfiguration sailingServerConfiguration;
404
+
405
+ /**
406
+ * Providing the constructor parameters for a new {@link RacingEventServiceImpl} instance is a bit tricky
407
+ * in some cases because containment and initialization order of some types is fairly tightly coupled.
408
+ * There is a dependency of many such objects on an instance of {@link RaceLogResolver} which is implemented
409
+ * by {@link RacingEventServiceImpl}. However, therefore, this instance only becomes available in the
410
+ * innermost constructor.
411
+ *
412
+ * @author Axel Uhl (d043530)
413
+ *
414
+ */
415
+ public static interface ConstructorParameters {
416
+ DomainObjectFactory getDomainObjectFactory();
417
+ MongoObjectFactory getMongoObjectFactory();
418
+ com.sap.sailing.domain.base.DomainFactory getBaseDomainFactory();
419
+ CompetitorStore getCompetitorStore();
420
+ }
421
+
422
+ /**
423
+ * Constructs a {@link DomainFactory base domain factory} that uses this object's {@link #competitorStore competitor
424
+ * store} for competitor management. This base domain factory is then also used for the construction of the
425
+ * {@link DomainObjectFactory}. This constructor variant initially clears the persistent competitor collection,
426
+ * hence removes all previously persistent competitors. This is the default for testing and for backward
427
+ * compatibility with prior releases that did not support a persistent competitor collection.
428
+ */
429
+ public RacingEventServiceImpl() {
430
+ this(true, null);
431
+ }
432
+
433
+ public RacingEventServiceImpl(WindStore windStore, GPSFixStore gpsFixStore,
434
+ TypeBasedServiceFinderFactory serviceFinderFactory) {
435
+ this(true, windStore, gpsFixStore, serviceFinderFactory);
436
+ }
437
+
438
+ void setBundleContext(BundleContext bundleContext) {
439
+ this.bundleContext = bundleContext;
440
+ }
441
+
442
+ /**
443
+ * Like {@link #RacingEventServiceImpl()}, but allows callers to specify that the persistent competitor collection
444
+ * be cleared before the service starts.
445
+ *
446
+ * @param clearPersistentCompetitorStore
447
+ * if <code>true</code>, the {@link PersistentCompetitorStore} is created empty, with the corresponding
448
+ * database collection cleared as well. Use with caution! When used with <code>false</code>, competitors
449
+ * created and stored during previous service executions will initially be loaded.
450
+ */
451
+ public RacingEventServiceImpl(boolean clearPersistentCompetitorStore, final TypeBasedServiceFinderFactory serviceFinderFactory) {
452
+ this((final RaceLogResolver raceLogResolver)-> {
453
+ return new ConstructorParameters() {
454
+ private final MongoObjectFactory mongoObjectFactory = PersistenceFactory.INSTANCE.getDefaultMongoObjectFactory(serviceFinderFactory);
455
+ private final PersistentCompetitorStore competitorStore = new PersistentCompetitorStore(
456
+ PersistenceFactory.INSTANCE.getDefaultMongoObjectFactory(serviceFinderFactory),
457
+ clearPersistentCompetitorStore, serviceFinderFactory, raceLogResolver);
458
+
459
+ @Override public DomainObjectFactory getDomainObjectFactory() { return competitorStore.getDomainObjectFactory(); }
460
+ @Override public MongoObjectFactory getMongoObjectFactory() { return mongoObjectFactory; }
461
+ @Override public DomainFactory getBaseDomainFactory() { return competitorStore.getBaseDomainFactory(); }
462
+ @Override public CompetitorStore getCompetitorStore() { return competitorStore; }
463
+ };
464
+ }, MediaDBFactory.INSTANCE.getDefaultMediaDB(), null, null, serviceFinderFactory);
465
+ }
466
+
467
+ private RacingEventServiceImpl(final boolean clearPersistentCompetitorStore, WindStore windStore,
468
+ GPSFixStore gpsFixStore, final TypeBasedServiceFinderFactory serviceFinderFactory) {
469
+ this((final RaceLogResolver raceLogResolver)-> {
470
+ return new ConstructorParameters() {
471
+ private final MongoObjectFactory mongoObjectFactory = PersistenceFactory.INSTANCE.getDefaultMongoObjectFactory(serviceFinderFactory);
472
+ private final PersistentCompetitorStore competitorStore = new PersistentCompetitorStore(
473
+ mongoObjectFactory,
474
+ clearPersistentCompetitorStore, serviceFinderFactory, raceLogResolver);
475
+
476
+ @Override public DomainObjectFactory getDomainObjectFactory() { return competitorStore.getDomainObjectFactory(); }
477
+ @Override public MongoObjectFactory getMongoObjectFactory() { return mongoObjectFactory; }
478
+ @Override public DomainFactory getBaseDomainFactory() { return competitorStore.getBaseDomainFactory(); }
479
+ @Override public CompetitorStore getCompetitorStore() { return competitorStore; }
480
+ };
481
+ }, MediaDBFactory.INSTANCE.getDefaultMediaDB(), windStore, gpsFixStore, serviceFinderFactory);
482
+ }
483
+
484
+ /**
485
+ * Uses the default factories for the tracking adapters and the {@link DomainFactory base domain factory} of the
486
+ * {@link PersistenceFactory#getDefaultDomainObjectFactory() default domain object factory}. This constructor should
487
+ * be used for testing because it provides a transient {@link CompetitorStore} as required for competitor
488
+ * persistence.
489
+ */
490
+ public RacingEventServiceImpl(Function<RaceLogResolver, DomainObjectFactory> domainObjectFactoryProvider,
491
+ final MongoObjectFactory mongoObjectFactory, MediaDB mediaDB, WindStore windStore, GPSFixStore gpsFixStore) {
492
+ this((final RaceLogResolver raceLogResolver)-> {
493
+ return new ConstructorParameters() {
494
+ private final DomainObjectFactory domainObjectFactory = domainObjectFactoryProvider.apply(raceLogResolver);
495
+
496
+ @Override public DomainObjectFactory getDomainObjectFactory() { return domainObjectFactory; }
497
+ @Override public MongoObjectFactory getMongoObjectFactory() { return mongoObjectFactory; }
498
+ @Override public DomainFactory getBaseDomainFactory() { return domainObjectFactory.getBaseDomainFactory(); }
499
+ @Override public CompetitorStore getCompetitorStore() { return getBaseDomainFactory().getCompetitorStore(); }
500
+ };
501
+ }, mediaDB, windStore, gpsFixStore, null);
502
+ }
503
+
504
+ public RacingEventServiceImpl(final DomainObjectFactory domainObjectFactory, MongoObjectFactory mongoObjectFactory,
505
+ MediaDB mediaDB, WindStore windStore, GPSFixStore gpsFixStore) {
506
+ this((final RaceLogResolver raceLogResolver)-> {
507
+ return new ConstructorParameters() {
508
+ @Override public DomainObjectFactory getDomainObjectFactory() { return domainObjectFactory; }
509
+ @Override public MongoObjectFactory getMongoObjectFactory() { return mongoObjectFactory; }
510
+ @Override public DomainFactory getBaseDomainFactory() { return domainObjectFactory.getBaseDomainFactory(); }
511
+ @Override public CompetitorStore getCompetitorStore() { return getBaseDomainFactory().getCompetitorStore(); }
512
+ };
513
+ }, mediaDB, windStore, gpsFixStore, null);
514
+ }
515
+
516
+ /**
517
+ * @param windStore
518
+ * if <code>null</code>, a default {@link MongoWindStore} will be used, based on the persistence set-up
519
+ * of this service
520
+ * @param serviceFinderFactory
521
+ * used to find the services handling specific types of tracking devices, such as the persistent storage
522
+ * of {@link DeviceIdentifier}s of specific device types or the managing of the device-to-competitor
523
+ * associations per race tracked.
524
+ */
525
+ public RacingEventServiceImpl(Function<RaceLogResolver, ConstructorParameters> constructorParametersProvider, MediaDB mediaDb,
526
+ WindStore windStore, GPSFixStore gpsFixStore, TypeBasedServiceFinderFactory serviceFinderFactory) {
527
+ logger.info("Created " + this);
528
+ final ConstructorParameters constructorParameters = constructorParametersProvider.apply(this);
529
+ this.domainObjectFactory = constructorParameters.getDomainObjectFactory();
530
+ this.masterDataClassLoaders.add(this.getClass().getClassLoader());
531
+ joinedClassLoader = new JoinedClassLoader(masterDataClassLoaders);
532
+ this.operationsSentToMasterForReplication = new HashSet<>();
533
+ this.baseDomainFactory = constructorParameters.getBaseDomainFactory();
534
+ this.mongoObjectFactory = constructorParameters.getMongoObjectFactory();
535
+ this.mediaDB = mediaDb;
536
+ this.competitorStore = constructorParameters.getCompetitorStore();
537
+ if (windStore == null) {
538
+ try {
539
+ windStore = MongoWindStoreFactory.INSTANCE.getMongoWindStore(mongoObjectFactory, domainObjectFactory);
540
+ } catch (Exception e) {
541
+ throw new RuntimeException(e);
542
+ }
543
+ }
544
+ this.competitorStore.addCompetitorUpdateListener(new CompetitorUpdateListener() {
545
+ @Override
546
+ public void competitorUpdated(Competitor competitor) {
547
+ replicate(new UpdateCompetitor(competitor.getId().toString(), competitor.getName(), competitor
548
+ .getColor(), competitor.getEmail(), competitor.getBoat().getSailID(), competitor.getTeam().getNationality(),
549
+ competitor.getTeam().getImage(), competitor.getFlagImage(),
550
+ competitor.getTimeOnTimeFactor(), competitor.getTimeOnDistanceAllowancePerNauticalMile()));
551
+ }
552
+ });
553
+ this.windStore = windStore;
554
+ this.dataImportLock = new DataImportLockWithProgress();
555
+
556
+ remoteSailingServerSet = new RemoteSailingServerSet(scheduler);
557
+ regattasByName = new ConcurrentHashMap<String, Regatta>();
558
+ regattasByNameLock = new NamedReentrantReadWriteLock("regattasByName for " + this, /* fair */false);
559
+ eventsById = new ConcurrentHashMap<Serializable, Event>();
560
+ regattaTrackingCache = new ConcurrentHashMap<>();
561
+ regattaTrackingCacheLock = new NamedReentrantReadWriteLock("regattaTrackingCache for " + this, /* fair */false);
562
+ raceTrackersByRegatta = new ConcurrentHashMap<>();
563
+ raceTrackersByRegattaLock = new NamedReentrantReadWriteLock("raceTrackersByRegatta for " + this, /* fair */
564
+ false);
565
+ raceTrackersByID = new ConcurrentHashMap<>();
566
+ raceTrackersByIDLocks = new ConcurrentHashMap<>();
567
+ leaderboardGroupsByName = new ConcurrentHashMap<>();
568
+ leaderboardGroupsByID = new ConcurrentHashMap<>();
569
+ leaderboardGroupsByNameLock = new NamedReentrantReadWriteLock("leaderboardGroupsByName for " + this, /* fair */
570
+ false);
571
+ leaderboardsByName = new ConcurrentHashMap<String, Leaderboard>();
572
+ leaderboardsByNameLock = new NamedReentrantReadWriteLock("leaderboardsByName for " + this, /* fair */false);
573
+ operationExecutionListeners = new ConcurrentHashMap<>();
574
+ courseListeners = new ConcurrentHashMap<>();
575
+ persistentRegattasForRaceIDs = new ConcurrentHashMap<>();
576
+ final int SIMULATION_THREAD_POOL_SIZE = Math.max(Runtime.getRuntime().availableProcessors()/3, 1);
577
+ Executor simulatorExecutor = new ThreadPoolExecutor(/* corePoolSize */SIMULATION_THREAD_POOL_SIZE,
578
+ /* maximumPoolSize */SIMULATION_THREAD_POOL_SIZE,
579
+ /* keepAliveTime */60, TimeUnit.SECONDS,
580
+ /* workQueue */new LinkedBlockingQueue<Runnable>());
581
+ // TODO: initialize smart-future-cache for simulation-results and add to simulation-service
582
+ simulationService = SimulationServiceFactory.INSTANCE.getService(simulatorExecutor, this);
583
+ this.raceLogReplicator = new RaceLogReplicator(this);
584
+ this.regattaLogReplicator = new RegattaLogReplicator(this);
585
+ this.raceLogScoringReplicator = new RaceLogScoringReplicator(this);
586
+ this.mediaLibrary = new MediaLibrary();
587
+ if (gpsFixStore == null) {
588
+ try {
589
+ gpsFixStore = MongoGPSFixStoreFactory.INSTANCE.getMongoGPSFixStore(mongoObjectFactory,
590
+ domainObjectFactory, serviceFinderFactory);
591
+ } catch (Exception e) {
592
+ e.printStackTrace();
593
+ throw new RuntimeException(e);
594
+ }
595
+ }
596
+ this.gpsFixStore = gpsFixStore;
597
+ this.configurationMap = new DeviceConfigurationMapImpl();
598
+ this.serviceFinderFactory = serviceFinderFactory;
599
+
600
+ sailingServerConfiguration = domainObjectFactory.loadServerConfiguration();
601
+ final Iterable<Pair<Event, Boolean>> loadedEventsWithRequireStoreFlag = loadStoredEvents();
602
+ loadStoredRegattas();
603
+ loadRaceIDToRegattaAssociations();
604
+ loadStoredLeaderboardsAndGroups();
605
+ loadLinksFromEventsToLeaderboardGroups();
606
+ loadMediaLibary();
607
+ loadStoredDeviceConfigurations();
608
+ loadAllRemoteSailingServersAndSchedulePeriodicEventCacheRefresh();
609
+
610
+ // Stores all events which run through a data migration
611
+ // Remark: must be called after loadLinksFromEventsToLeaderboardGroups(), otherwise would loose the Event -> LeaderboardGroup relation
612
+ for (Pair<Event, Boolean> eventAndRequireStoreFlag : loadedEventsWithRequireStoreFlag) {
613
+ if (eventAndRequireStoreFlag.getB()) {
614
+ mongoObjectFactory.storeEvent(eventAndRequireStoreFlag.getA());
615
+ }
616
+ }
617
+ }
618
+
619
+ @Override
620
+ public boolean isCurrentlyFillingFromInitialLoadOrApplyingOperationReceivedFromMaster() {
621
+ return currentlyFillingFromInitialLoadOrApplyingOperationReceivedFromMaster.get();
622
+ }
623
+
624
+ @Override
625
+ public void setCurrentlyFillingFromInitialLoadOrApplyingOperationReceivedFromMaster(
626
+ boolean currentlyFillingFromInitialLoadOrApplyingOperationReceivedFromMaster) {
627
+ this.currentlyFillingFromInitialLoadOrApplyingOperationReceivedFromMaster
628
+ .set(currentlyFillingFromInitialLoadOrApplyingOperationReceivedFromMaster);
629
+ }
630
+
631
+ @Override
632
+ public PolarDataService getPolarDataService() {
633
+ return polarDataService;
634
+ }
635
+
636
+ @Override
637
+ public SimulationService getSimulationService() {
638
+ return simulationService;
639
+ }
640
+
641
+ @Override
642
+ public void clearState() throws Exception {
643
+ for (String leaderboardGroupName : new ArrayList<>(this.leaderboardGroupsByName.keySet())) {
644
+ removeLeaderboardGroup(leaderboardGroupName);
645
+ }
646
+ for (String leaderboardName : new ArrayList<>(this.leaderboardsByName.keySet())) {
647
+ removeLeaderboard(leaderboardName);
648
+ }
649
+ for (Regatta regatta : new ArrayList<>(this.regattasByName.values())) {
650
+ stopTracking(regatta);
651
+ removeRegatta(regatta);
652
+ }
653
+ for (Event event : new ArrayList<>(this.eventsById.values())) {
654
+ removeEvent(event.getId());
655
+ }
656
+ for (MediaTrack mediaTrack : this.mediaLibrary.allTracks()) {
657
+ mediaTrackDeleted(mediaTrack);
658
+ }
659
+ // TODO clear user store? See bug 2430.
660
+ this.competitorStore.clear();
661
+ }
662
+
663
+ @Override
664
+ public com.sap.sailing.domain.base.DomainFactory getBaseDomainFactory() {
665
+ return baseDomainFactory;
666
+ }
667
+
668
+ @Override
669
+ public MongoObjectFactory getMongoObjectFactory() {
670
+ return mongoObjectFactory;
671
+ }
672
+
673
+ @Override
674
+ public DomainObjectFactory getDomainObjectFactory() {
675
+ return domainObjectFactory;
676
+ }
677
+
678
+ private void loadRaceIDToRegattaAssociations() {
679
+ persistentRegattasForRaceIDs.putAll(domainObjectFactory.loadRaceIDToRegattaAssociations(this));
680
+ }
681
+
682
+ private void loadStoredRegattas() {
683
+ LockUtil.lockForWrite(regattasByNameLock);
684
+ try {
685
+ for (Regatta regatta : domainObjectFactory.loadAllRegattas(this)) {
686
+ logger.info("putting regatta " + regatta.getName() + " (" + regatta.hashCode()
687
+ + ") into regattasByName");
688
+ regattasByName.put(regatta.getName(), regatta);
689
+ regatta.addRegattaListener(this);
690
+ onRegattaLikeAdded(regatta);
691
+ regatta.addRaceColumnListener(raceLogReplicator);
692
+ regatta.addRaceColumnListener(raceLogScoringReplicator);
693
+ }
694
+ } finally {
695
+ LockUtil.unlockAfterWrite(regattasByNameLock);
696
+ }
697
+ }
698
+
699
+ private Iterable<Pair<Event, Boolean>> loadStoredEvents() {
700
+ Iterable<Pair<Event, Boolean>> loadedEventsWithRequireStoreFlag = domainObjectFactory.loadAllEvents();
701
+ for (Pair<Event, Boolean> eventAndFlag : loadedEventsWithRequireStoreFlag) {
702
+ Event event = eventAndFlag.getA();
703
+ if (event.getId() != null)
704
+ eventsById.put(event.getId(), event);
705
+ }
706
+ return loadedEventsWithRequireStoreFlag;
707
+ }
708
+
709
+ private void loadLinksFromEventsToLeaderboardGroups() {
710
+ domainObjectFactory.loadLeaderboardGroupLinksForEvents(/* eventResolver */this, /* leaderboardGroupResolver */
711
+ this);
712
+ }
713
+
714
+ private void loadAllRemoteSailingServersAndSchedulePeriodicEventCacheRefresh() {
715
+ for (RemoteSailingServerReference sailingServer : domainObjectFactory.loadAllRemoteSailingServerReferences()) {
716
+ remoteSailingServerSet.add(sailingServer);
717
+ }
718
+ }
719
+
720
+ /**
721
+ * Collects media track references from the configured sources (mongo DB by default, ftp folder yet to be
722
+ * implemented). The method is expected to be called initially blocking the API until finished.
723
+ *
724
+ * Subsequent calls (assumed to be triggered from the admin console or in scheduled intervals) don't need to block.
725
+ * In that case, the API will simply serve the current state.
726
+ *
727
+ */
728
+ private void loadMediaLibary() {
729
+ Collection<MediaTrack> allDbMediaTracks = mediaDB.loadAllMediaTracks();
730
+ mediaTracksAdded(allDbMediaTracks);
731
+ }
732
+
733
+ private void loadStoredDeviceConfigurations() {
734
+ for (Entry<DeviceConfigurationMatcher, DeviceConfiguration> entry : domainObjectFactory
735
+ .loadAllDeviceConfigurations()) {
736
+ configurationMap.put(entry.getKey(), entry.getValue());
737
+ }
738
+ }
739
+
740
+ @Override
741
+ public void addLeaderboard(Leaderboard leaderboard) {
742
+ LockUtil.lockForWrite(leaderboardsByNameLock);
743
+ try {
744
+ leaderboardsByName.put(leaderboard.getName(), leaderboard);
745
+ } finally {
746
+ LockUtil.unlockAfterWrite(leaderboardsByNameLock);
747
+ }
748
+ // RaceColumns of RegattaLeaderboards are tracked via its Regatta!
749
+ if (leaderboard instanceof FlexibleLeaderboard) {
750
+ onRegattaLikeAdded(((FlexibleLeaderboard) leaderboard).getRegattaLike());
751
+ leaderboard.addRaceColumnListener(raceLogReplicator);
752
+ leaderboard.addRaceColumnListener(raceLogScoringReplicator);
753
+ }
754
+ }
755
+
756
+ private void loadStoredLeaderboardsAndGroups() {
757
+ logger.info("loading stored leaderboards and groups");
758
+ // Loading all leaderboard groups and the contained leaderboards
759
+ for (LeaderboardGroup leaderboardGroup : domainObjectFactory.getAllLeaderboardGroups(this, this)) {
760
+ logger.info("loaded leaderboard group " + leaderboardGroup.getName() + " into " + this);
761
+ LockUtil.lockForWrite(leaderboardGroupsByNameLock);
762
+ try {
763
+ leaderboardGroupsByName.put(leaderboardGroup.getName(), leaderboardGroup);
764
+ leaderboardGroupsByID.put(leaderboardGroup.getId(), leaderboardGroup);
765
+ } finally {
766
+ LockUtil.unlockAfterWrite(leaderboardGroupsByNameLock);
767
+ }
768
+ }
769
+ // Loading the remaining leaderboards
770
+ domainObjectFactory.getLeaderboardsNotInGroup(this, this);
771
+ logger.info("done with loading stored leaderboards and groups");
772
+ }
773
+
774
+ @Override
775
+ public FlexibleLeaderboard addFlexibleLeaderboard(String leaderboardName, String leaderboardDisplayName,
776
+ int[] discardThresholds, ScoringScheme scoringScheme, Serializable courseAreaId) {
777
+ logger.info("adding flexible leaderboard " + leaderboardName);
778
+ CourseArea courseArea = getCourseArea(courseAreaId);
779
+ FlexibleLeaderboard result = new FlexibleLeaderboardImpl(getRaceLogStore(), getRegattaLogStore(),
780
+ leaderboardName, new ThresholdBasedResultDiscardingRuleImpl(discardThresholds), scoringScheme,
781
+ courseArea);
782
+ result.setDisplayName(leaderboardDisplayName);
783
+ if (getLeaderboardByName(leaderboardName) != null) {
784
+ throw new IllegalArgumentException("Leaderboard with name " + leaderboardName + " already exists");
785
+ }
786
+ addLeaderboard(result);
787
+ mongoObjectFactory.storeLeaderboard(result);
788
+ return result;
789
+ }
790
+
791
+ @Override
792
+ public CourseArea getCourseArea(Serializable courseAreaId) {
793
+ for (Event event : getAllEvents()) {
794
+ for (CourseArea courseArea : event.getVenue().getCourseAreas()) {
795
+ if (courseArea.getId().equals(courseAreaId)) {
796
+ return courseArea;
797
+ }
798
+ }
799
+ }
800
+ return null;
801
+ }
802
+
803
+ @Override
804
+ public RegattaLeaderboard addRegattaLeaderboard(RegattaIdentifier regattaIdentifier, String leaderboardDisplayName,
805
+ int[] discardThresholds) {
806
+ Regatta regatta = getRegatta(regattaIdentifier);
807
+ logger.info("adding regatta leaderboard for regatta "
808
+ + (regatta == null ? "null" : (regatta.getName() + " (" + regatta.hashCode() + ")")) + " to " + this);
809
+ RegattaLeaderboard result = null;
810
+ if (regatta != null) {
811
+ result = new RegattaLeaderboardImpl(regatta, new ThresholdBasedResultDiscardingRuleImpl(discardThresholds));
812
+ result.setDisplayName(leaderboardDisplayName);
813
+ if (getLeaderboardByName(result.getName()) != null) {
814
+ throw new IllegalArgumentException("Leaderboard with name " + result.getName() + " already exists in "
815
+ + this);
816
+ }
817
+ addLeaderboard(result);
818
+ mongoObjectFactory.storeLeaderboard(result);
819
+ } else {
820
+ logger.warning("Cannot find regatta " + regattaIdentifier
821
+ + ". Hence, cannot create regatta leaderboard for it.");
822
+ }
823
+ return result;
824
+ }
825
+
826
+ @Override
827
+ public RaceColumn addColumnToLeaderboard(String columnName, String leaderboardName, boolean medalRace) {
828
+ Leaderboard leaderboard = getLeaderboardByName(leaderboardName);
829
+ if (leaderboard != null) {
830
+ if (leaderboard instanceof FlexibleLeaderboard) {
831
+ // uses the default fleet as the single fleet for the new column
832
+ RaceColumn result = ((FlexibleLeaderboard) leaderboard).addRaceColumn(columnName, medalRace);
833
+ updateStoredLeaderboard((FlexibleLeaderboard) leaderboard);
834
+ return result;
835
+ } else {
836
+ throw new IllegalArgumentException("Leaderboard named " + leaderboardName
837
+ + " is not a FlexibleLeaderboard");
838
+ }
839
+ } else {
840
+ throw new IllegalArgumentException("Leaderboard named " + leaderboardName + " not found");
841
+ }
842
+ }
843
+
844
+ @Override
845
+ public void moveLeaderboardColumnUp(String leaderboardName, String columnName) {
846
+ Leaderboard leaderboard = getLeaderboardByName(leaderboardName);
847
+ if (leaderboard != null && leaderboard instanceof FlexibleLeaderboard) {
848
+ ((FlexibleLeaderboard) leaderboard).moveRaceColumnUp(columnName);
849
+ updateStoredLeaderboard((FlexibleLeaderboard) leaderboard);
850
+ } else {
851
+ throw new IllegalArgumentException("Leaderboard named " + leaderboardName + " not found");
852
+ }
853
+ }
854
+
855
+ @Override
856
+ public void moveLeaderboardColumnDown(String leaderboardName, String columnName) {
857
+ Leaderboard leaderboard = getLeaderboardByName(leaderboardName);
858
+ if (leaderboard != null && leaderboard instanceof FlexibleLeaderboard) {
859
+ ((FlexibleLeaderboard) leaderboard).moveRaceColumnDown(columnName);
860
+ updateStoredLeaderboard((FlexibleLeaderboard) leaderboard);
861
+ } else {
862
+ throw new IllegalArgumentException("Leaderboard named " + leaderboardName + " not found");
863
+ }
864
+ }
865
+
866
+ @Override
867
+ public void removeLeaderboardColumn(String leaderboardName, String columnName) {
868
+ Leaderboard leaderboard = getLeaderboardByName(leaderboardName);
869
+ if (leaderboard == null) {
870
+ throw new IllegalArgumentException("Leaderboard named " + leaderboardName + " not found");
871
+ } else if (!(leaderboard instanceof FlexibleLeaderboard)) {
872
+ throw new IllegalArgumentException("Columns cannot be removed from Leaderboard named " + leaderboardName);
873
+ } else {
874
+ ((FlexibleLeaderboard) leaderboard).removeRaceColumn(columnName);
875
+ updateStoredLeaderboard((FlexibleLeaderboard) leaderboard);
876
+ }
877
+ }
878
+
879
+ @Override
880
+ public void renameLeaderboardColumn(String leaderboardName, String oldColumnName, String newColumnName) {
881
+ Leaderboard leaderboard = getLeaderboardByName(leaderboardName);
882
+ if (leaderboard != null) {
883
+ final RaceColumn raceColumn = leaderboard.getRaceColumnByName(oldColumnName);
884
+ if (raceColumn instanceof FlexibleRaceColumn) {
885
+ // remove race log under old identifier; the race log identifier changes
886
+ for (Fleet fleet : raceColumn.getFleets()) {
887
+ getMongoObjectFactory().removeRaceLog(raceColumn.getRaceLogIdentifier(fleet));
888
+ }
889
+ ((FlexibleRaceColumn) raceColumn).setName(newColumnName);
890
+ // store the race logs again under the new identifiers
891
+ storeRaceLogs(raceColumn);
892
+ updateStoredLeaderboard(leaderboard);
893
+ } else {
894
+ throw new IllegalArgumentException("Race column " + oldColumnName + " cannot be renamed");
895
+ }
896
+ } else {
897
+ throw new IllegalArgumentException("Leaderboard named " + leaderboardName + " not found");
898
+ }
899
+ }
900
+
901
+ /**
902
+ * When a race column is renamed, its race log identifiers change. Therefore, the race logs need to be stored under
903
+ * the new identifier again to be consistent with the in-memory image again.
904
+ */
905
+ private void storeRaceLogs(RaceColumn raceColumn) {
906
+ for (Fleet fleet : raceColumn.getFleets()) {
907
+ RaceLogIdentifier identifier = raceColumn.getRaceLogIdentifier(fleet);
908
+ RaceLogEventVisitor storeVisitor = MongoRaceLogStoreFactory.INSTANCE.getMongoRaceLogStoreVisitor(
909
+ identifier, getMongoObjectFactory());
910
+ RaceLog raceLog = raceColumn.getRaceLog(fleet);
911
+ raceLog.lockForRead();
912
+ try {
913
+ for (RaceLogEvent e : raceLog.getRawFixes()) {
914
+ e.accept(storeVisitor);
915
+ }
916
+ } finally {
917
+ raceLog.unlockAfterRead();
918
+ }
919
+ }
920
+ }
921
+
922
+ @Override
923
+ public void updateLeaderboardColumnFactor(String leaderboardName, String columnName, Double factor) {
924
+ Leaderboard leaderboard = getLeaderboardByName(leaderboardName);
925
+ if (leaderboard != null) {
926
+ final RaceColumn raceColumn = leaderboard.getRaceColumnByName(columnName);
927
+ if (raceColumn != null) {
928
+ raceColumn.setFactor(factor);
929
+ updateStoredLeaderboard(leaderboard);
930
+ } else {
931
+ throw new IllegalArgumentException("Race column " + columnName + " not found in leaderboard "
932
+ + leaderboardName);
933
+ }
934
+ } else {
935
+ throw new IllegalArgumentException("Leaderboard named " + leaderboardName + " not found");
936
+ }
937
+ }
938
+
939
+ @Override
940
+ public void renameLeaderboard(String oldName, String newName) {
941
+ final Leaderboard toRename = leaderboardsByName.get(oldName);
942
+ LockUtil.lockForWrite(leaderboardsByNameLock);
943
+ try {
944
+ if (toRename == null) {
945
+ throw new IllegalArgumentException("No leaderboard with name " + oldName + " found");
946
+ }
947
+ if (leaderboardsByName.containsKey(newName)) {
948
+ throw new IllegalArgumentException("Leaderboard with name " + newName + " already exists");
949
+ }
950
+ if (toRename instanceof Renamable) {
951
+ ((Renamable) toRename).setName(newName);
952
+ leaderboardsByName.remove(oldName);
953
+ leaderboardsByName.put(newName, toRename);
954
+ } else {
955
+ throw new IllegalArgumentException("Leaderboard with name " + newName + " is of type "
956
+ + toRename.getClass().getSimpleName() + " and therefore cannot be renamed");
957
+ }
958
+ } finally {
959
+ LockUtil.unlockAfterWrite(leaderboardsByNameLock);
960
+ }
961
+ // don't need the lock anymore to update DB
962
+ if (toRename instanceof Renamable) {
963
+ mongoObjectFactory.renameLeaderboard(oldName, newName);
964
+ syncGroupsAfterLeaderboardChange(toRename, true);
965
+ }
966
+ }
967
+
968
+ @Override
969
+ public void updateStoredLeaderboard(Leaderboard leaderboard) {
970
+ getMongoObjectFactory().storeLeaderboard(leaderboard);
971
+ syncGroupsAfterLeaderboardChange(leaderboard, true);
972
+ }
973
+
974
+ @Override
975
+ public void updateStoredRegatta(Regatta regatta) {
976
+ if (regatta.isPersistent()) {
977
+ mongoObjectFactory.storeRegatta(regatta);
978
+ }
979
+ }
980
+
981
+ /**
982
+ * Checks all groups, if they contain a leaderboard with the name of the <code>updatedLeaderboard</code> and
983
+ * replaces the one in the group with the updated one.<br />
984
+ * This synchronizes things like the RaceIdentifier in the leaderboard columns.
985
+ */
986
+ private void syncGroupsAfterLeaderboardChange(Leaderboard updatedLeaderboard, boolean doDatabaseUpdate) {
987
+ boolean groupNeedsUpdate = false;
988
+ for (LeaderboardGroup leaderboardGroup : leaderboardGroupsByName.values()) {
989
+ for (Leaderboard leaderboard : leaderboardGroup.getLeaderboards()) {
990
+ if (leaderboard == updatedLeaderboard) {
991
+ int index = leaderboardGroup.getIndexOf(leaderboard);
992
+ leaderboardGroup.removeLeaderboard(leaderboard);
993
+ leaderboardGroup.addLeaderboardAt(updatedLeaderboard, index);
994
+ groupNeedsUpdate = true;
995
+ // TODO we assume that the leaderboard names are unique, so we can break the inner loop here
996
+ break;
997
+ }
998
+ }
999
+
1000
+ if (doDatabaseUpdate && groupNeedsUpdate) {
1001
+ mongoObjectFactory.storeLeaderboardGroup(leaderboardGroup);
1002
+ }
1003
+ groupNeedsUpdate = false;
1004
+ }
1005
+ }
1006
+
1007
+ @Override
1008
+ public void removeLeaderboard(String leaderboardName) {
1009
+ Leaderboard leaderboard = removeLeaderboardFromLeaderboardsByName(leaderboardName);
1010
+ if (leaderboard != null) {
1011
+ leaderboard.removeRaceColumnListener(raceLogReplicator);
1012
+ leaderboard.removeRaceColumnListener(raceLogScoringReplicator);
1013
+ mongoObjectFactory.removeLeaderboard(leaderboardName);
1014
+ syncGroupsAfterLeaderboardRemove(leaderboardName, true);
1015
+ if (leaderboard instanceof FlexibleLeaderboard) {
1016
+ onRegattaLikeRemoved(((FlexibleLeaderboard) leaderboard).getRegattaLike());
1017
+ }
1018
+ leaderboard.destroy();
1019
+ }
1020
+ }
1021
+
1022
+ private Leaderboard removeLeaderboardFromLeaderboardsByName(String leaderboardName) {
1023
+ LockUtil.lockForWrite(leaderboardsByNameLock);
1024
+ try {
1025
+ return leaderboardsByName.remove(leaderboardName);
1026
+ } finally {
1027
+ LockUtil.unlockAfterWrite(leaderboardsByNameLock);
1028
+ }
1029
+ }
1030
+
1031
+ /**
1032
+ * Checks all groups, if they contain a leaderboard with the <code>removedLeaderboardName</code> and removes it from
1033
+ * the group.
1034
+ *
1035
+ * @param removedLeaderboardName
1036
+ */
1037
+ private void syncGroupsAfterLeaderboardRemove(String removedLeaderboardName, boolean doDatabaseUpdate) {
1038
+ boolean groupNeedsUpdate = false;
1039
+ for (LeaderboardGroup leaderboardGroup : leaderboardGroupsByName.values()) {
1040
+ for (Leaderboard leaderboard : leaderboardGroup.getLeaderboards()) {
1041
+ if (leaderboard.getName().equals(removedLeaderboardName)) {
1042
+ leaderboardGroup.removeLeaderboard(leaderboard);
1043
+ groupNeedsUpdate = true;
1044
+ // TODO we assume that the leaderboard names are unique, so we can break the inner loop here
1045
+ break;
1046
+ }
1047
+ }
1048
+
1049
+ if (doDatabaseUpdate && groupNeedsUpdate) {
1050
+ mongoObjectFactory.storeLeaderboardGroup(leaderboardGroup);
1051
+ }
1052
+ groupNeedsUpdate = false;
1053
+ }
1054
+ }
1055
+
1056
+ @Override
1057
+ public Leaderboard getLeaderboardByName(String name) {
1058
+ return leaderboardsByName.get(name);
1059
+ }
1060
+
1061
+ @Override
1062
+ public Position getMarkPosition(Mark mark, LeaderboardThatHasRegattaLike leaderboard, TimePoint timePoint, RaceLog raceLog) {
1063
+ GPSFixTrack<Mark, GPSFix> track = null;
1064
+ // If no spanning track is found, the fix closest to the time point requested is used instead
1065
+ GPSFix nonSpanningFallback = null;
1066
+ for (TrackedRace trackedRace : leaderboard.getTrackedRaces()) {
1067
+ GPSFixTrack<Mark, GPSFix> trackCandidate = trackedRace.getTrack(mark);
1068
+ if (trackCandidate != null) {
1069
+ if (spansTimePoint(trackCandidate, timePoint)) {
1070
+ track = trackCandidate;
1071
+ break;
1072
+ } else {
1073
+ nonSpanningFallback = improveTimewiseClosestFix(nonSpanningFallback, trackCandidate, timePoint);
1074
+ }
1075
+ }
1076
+ }
1077
+ if (track == null) { // no spanning track found in any tracked race, or no tracked races found
1078
+ // try to load from store
1079
+ DynamicGPSFixTrackImpl<Mark> loadedTrack = new DynamicGPSFixTrackImpl<Mark>(mark, 0);
1080
+ track = loadedTrack;
1081
+ Set<AbstractLog<?, ?>> logs = new HashSet<>();
1082
+ logs.add(leaderboard.getRegattaLike().getRegattaLog());
1083
+ if (raceLog == null) { // no race log explicitly provided --> use all race logs
1084
+ for (RaceColumn raceColumn : leaderboard.getRaceColumns()) {
1085
+ for (Fleet fleet : raceColumn.getFleets()) {
1086
+ logs.add(raceColumn.getRaceLog(fleet));
1087
+ }
1088
+ }
1089
+ } else {
1090
+ logs.add(raceLog);
1091
+ }
1092
+ for (AbstractLog<?, ?> log : logs) {
1093
+ try {
1094
+ getGPSFixStore().loadMarkTrack(loadedTrack, log, mark);
1095
+ } catch (Exception e) {
1096
+ logger.log(Level.WARNING, "Couldn't load mark track for mark " + mark + " from log " + log, e);
1097
+ }
1098
+ }
1099
+ }
1100
+ Position result = track.getEstimatedPosition(timePoint, /* extrapolate */ false);
1101
+ if (result == null) {
1102
+ result = nonSpanningFallback == null ? null : nonSpanningFallback.getPosition();
1103
+ }
1104
+ return result;
1105
+ }
1106
+
1107
+ private GPSFix improveTimewiseClosestFix(GPSFix nonSpanningFallback, GPSFixTrack<Mark, GPSFix> track, final TimePoint timePoint) {
1108
+ GPSFix lastAtOrBefore = track.getLastFixAtOrBefore(timePoint);
1109
+ GPSFix firstAtOrAfter = track.getFirstFixAtOrAfter(timePoint);
1110
+ // find the fix closes to timePoint, sorting null values to the end and fixes near timePoint to the beginning
1111
+ final List<GPSFix> list = Arrays.asList(nonSpanningFallback, lastAtOrBefore, firstAtOrAfter);
1112
+ list.sort(new Comparator<GPSFix>() {
1113
+ @Override
1114
+ public int compare(GPSFix o1, GPSFix o2) {
1115
+ final int result;
1116
+ if (o1 == null) {
1117
+ if (o2 == null) {
1118
+ result = 0;
1119
+ } else {
1120
+ result = 1;
1121
+ }
1122
+ } else if (o2 == null) {
1123
+ result = -1;
1124
+ } else {
1125
+ result = new Long(Math.abs(o1.getTimePoint().until(timePoint).asMillis())).compareTo(
1126
+ Math.abs(o2.getTimePoint().until(timePoint).asMillis()));
1127
+ }
1128
+ return result;
1129
+ }
1130
+ });
1131
+ return list.get(0);
1132
+ }
1133
+
1134
+ private boolean spansTimePoint(GPSFixTrack<Mark, GPSFix> track, TimePoint timePoint) {
1135
+ return track.getLastFixAtOrBefore(timePoint) != null && track.getFirstFixAtOrAfter(timePoint) != null;
1136
+ }
1137
+
1138
+ @Override
1139
+ public Map<String, Leaderboard> getLeaderboards() {
1140
+ return Collections.unmodifiableMap(new HashMap<String, Leaderboard>(leaderboardsByName));
1141
+ }
1142
+
1143
+ @Override
1144
+ public SailingServerConfiguration getSailingServerConfiguration() {
1145
+ return sailingServerConfiguration;
1146
+ }
1147
+
1148
+ @Override
1149
+ public void updateServerConfiguration(SailingServerConfiguration serverConfiguration) {
1150
+ this.sailingServerConfiguration = serverConfiguration;
1151
+ mongoObjectFactory.storeServerConfiguration(serverConfiguration);
1152
+ }
1153
+
1154
+ @Override
1155
+ public Map<RemoteSailingServerReference, com.sap.sse.common.Util.Pair<Iterable<EventBase>, Exception>> getPublicEventsOfAllSailingServers() {
1156
+ return remoteSailingServerSet.getCachedEventsForRemoteSailingServers(); // FIXME should probably add our own
1157
+ // stuff here... Is it enough to pass on
1158
+ // the remote reference URL to the
1159
+ // client for leaderboard group URL
1160
+ // construction?
1161
+ }
1162
+
1163
+ @Override
1164
+ public RemoteSailingServerReference addRemoteSailingServerReference(String name, URL url) {
1165
+ RemoteSailingServerReference result = new RemoteSailingServerReferenceImpl(name, url);
1166
+ remoteSailingServerSet.add(result);
1167
+ mongoObjectFactory.storeSailingServer(result);
1168
+ return result;
1169
+ }
1170
+
1171
+ @Override
1172
+ public Iterable<RemoteSailingServerReference> getLiveRemoteServerReferences() {
1173
+ return remoteSailingServerSet.getLiveRemoteServerReferences();
1174
+ }
1175
+
1176
+ @Override
1177
+ public RemoteSailingServerReference getRemoteServerReferenceByName(String remoteServerReferenceName) {
1178
+ return remoteSailingServerSet.getServerReferenceByName(remoteServerReferenceName);
1179
+ }
1180
+
1181
+ @Override
1182
+ public com.sap.sse.common.Util.Pair<Iterable<EventBase>, Exception> updateRemoteServerEventCacheSynchronously(
1183
+ RemoteSailingServerReference ref) {
1184
+ return remoteSailingServerSet.getEventsOrException(ref);
1185
+ }
1186
+
1187
+ @Override
1188
+ public void removeRemoteSailingServerReference(String name) {
1189
+ remoteSailingServerSet.remove(name);
1190
+ mongoObjectFactory.removeSailingServer(name);
1191
+ }
1192
+
1193
+ @Override
1194
+ public Iterable<Event> getAllEvents() {
1195
+ return Collections.unmodifiableCollection(new ArrayList<Event>(eventsById.values()));
1196
+ }
1197
+
1198
+ @Override
1199
+ public Event getEvent(Serializable id) {
1200
+ return id == null ? null : eventsById.get(id);
1201
+ }
1202
+
1203
+ @Override
1204
+ public Iterable<Regatta> getAllRegattas() {
1205
+ return Collections.unmodifiableCollection(new ArrayList<Regatta>(regattasByName.values()));
1206
+ }
1207
+
1208
+ @Override
1209
+ public boolean isRaceBeingTracked(Regatta regattaContext, RaceDefinition r) {
1210
+ Set<RaceTracker> trackers = raceTrackersByRegatta.get(regattaContext);
1211
+ if (trackers != null) {
1212
+ for (RaceTracker tracker : trackers) {
1213
+ final Set<RaceDefinition> races = tracker.getRaces();
1214
+ if (races != null && races.contains(r)) {
1215
+ return true;
1216
+ }
1217
+ }
1218
+ }
1219
+ return false;
1220
+ }
1221
+
1222
+ @Override
1223
+ public Regatta getRegattaByName(String name) {
1224
+ return name == null ? null : regattasByName.get(name);
1225
+ }
1226
+
1227
+ @Override
1228
+ public Regatta getOrCreateDefaultRegatta(String name, String boatClassName, Serializable id) {
1229
+ Regatta result = regattasByName.get(name);
1230
+ if (result == null) {
1231
+ result = new RegattaImpl(getRaceLogStore(), getRegattaLogStore(), name, getBaseDomainFactory()
1232
+ .getOrCreateBoatClass(boatClassName), /* startDate */null, /* endDate */null, this,
1233
+ getBaseDomainFactory().createScoringScheme(ScoringSchemeType.LOW_POINT), id, null);
1234
+ logger.info("Created default regatta " + result.getName() + " (" + hashCode() + ") on " + this);
1235
+ onRegattaLikeAdded(result);
1236
+ cacheAndReplicateDefaultRegatta(result);
1237
+ }
1238
+ return result;
1239
+ }
1240
+
1241
+ private void onRegattaLikeAdded(IsRegattaLike isRegattaLike) {
1242
+ isRegattaLike.addListener(regattaLogReplicator);
1243
+ }
1244
+
1245
+ private void onRegattaLikeRemoved(IsRegattaLike isRegattaLike) {
1246
+ isRegattaLike.removeListener(regattaLogReplicator);
1247
+ getRegattaLogStore().removeRegattaLog(isRegattaLike.getRegattaLikeIdentifier());
1248
+ }
1249
+
1250
+ @Override
1251
+ public Regatta createRegatta(String fullRegattaName, String boatClassName, TimePoint startDate, TimePoint endDate,
1252
+ Serializable id, Iterable<? extends Series> series, boolean persistent, ScoringScheme scoringScheme,
1253
+ Serializable defaultCourseAreaId, boolean useStartTimeInference, RankingMetricConstructor rankingMetricConstructor) {
1254
+ com.sap.sse.common.Util.Pair<Regatta, Boolean> regattaWithCreatedFlag = getOrCreateRegattaWithoutReplication(
1255
+ fullRegattaName, boatClassName, startDate, endDate, id, series, persistent, scoringScheme,
1256
+ defaultCourseAreaId, useStartTimeInference, rankingMetricConstructor);
1257
+ Regatta regatta = regattaWithCreatedFlag.getA();
1258
+ if (regattaWithCreatedFlag.getB()) {
1259
+ onRegattaLikeAdded(regatta);
1260
+ replicateSpecificRegattaWithoutRaceColumns(regatta);
1261
+ }
1262
+ return regatta;
1263
+ }
1264
+
1265
+ @Override
1266
+ public void addRegattaWithoutReplication(Regatta regatta) {
1267
+ UUID defaultCourseAreaId = null;
1268
+ if (regatta.getDefaultCourseArea() != null) {
1269
+ defaultCourseAreaId = regatta.getDefaultCourseArea().getId();
1270
+ }
1271
+ boolean wasAdded = addAndConnectRegatta(regatta.isPersistent(), defaultCourseAreaId, regatta);
1272
+ if (!wasAdded) {
1273
+ logger.info("Regatta with name " + regatta.getName() + " already existed, so it hasn't been added.");
1274
+ }
1275
+ }
1276
+
1277
+ private RaceLogStore getRaceLogStore() {
1278
+ return MongoRaceLogStoreFactory.INSTANCE.getMongoRaceLogStore(mongoObjectFactory, domainObjectFactory);
1279
+ }
1280
+
1281
+ private RegattaLogStore getRegattaLogStore() {
1282
+ return MongoRegattaLogStoreFactory.INSTANCE.getMongoRegattaLogStore(mongoObjectFactory, domainObjectFactory);
1283
+ }
1284
+
1285
+ @Override
1286
+ public com.sap.sse.common.Util.Pair<Regatta, Boolean> getOrCreateRegattaWithoutReplication(String fullRegattaName,
1287
+ String boatClassName, TimePoint startDate, TimePoint endDate, Serializable id,
1288
+ Iterable<? extends Series> series, boolean persistent, ScoringScheme scoringScheme,
1289
+ Serializable defaultCourseAreaId, boolean useStartTimeInference, RankingMetricConstructor rankingMetricConstructor) {
1290
+ CourseArea courseArea = getCourseArea(defaultCourseAreaId);
1291
+ Regatta regatta = new RegattaImpl(getRaceLogStore(), getRegattaLogStore(), fullRegattaName,
1292
+ getBaseDomainFactory().getOrCreateBoatClass(boatClassName), startDate, endDate, series, persistent,
1293
+ scoringScheme, id, courseArea, useStartTimeInference, rankingMetricConstructor);
1294
+ boolean wasCreated = addAndConnectRegatta(persistent, defaultCourseAreaId, regatta);
1295
+ if (wasCreated) {
1296
+ logger.info("Created regatta " + regatta.getName() + " (" + hashCode() + ") on " + this);
1297
+ }
1298
+ return new com.sap.sse.common.Util.Pair<Regatta, Boolean>(regatta, wasCreated);
1299
+ }
1300
+
1301
+ private boolean addAndConnectRegatta(boolean persistent, Serializable defaultCourseAreaId, Regatta regatta) {
1302
+ boolean wasCreated = false;
1303
+ // try a quick read protected by the concurrent hash map implementation
1304
+ if (!regattasByName.containsKey(regatta.getName())) {
1305
+ LockUtil.lockForWrite(regattasByNameLock);
1306
+ try {
1307
+ // check again, now that we hold the exclusive write lock
1308
+ if (!regattasByName.containsKey(regatta.getName())) {
1309
+ wasCreated = true;
1310
+ logger.info("putting regatta " + regatta.getName() + " (" + regatta.hashCode()
1311
+ + ") into regattasByName of " + this);
1312
+ regattasByName.put(regatta.getName(), regatta);
1313
+ regatta.addRegattaListener(this);
1314
+ regatta.addRaceColumnListener(raceLogReplicator);
1315
+ regatta.addRaceColumnListener(raceLogScoringReplicator);
1316
+ }
1317
+ } finally {
1318
+ LockUtil.unlockAfterWrite(regattasByNameLock);
1319
+ }
1320
+ }
1321
+ if (persistent) {
1322
+ updateStoredRegatta(regatta);
1323
+ }
1324
+ for (Event event : getAllEvents()) {
1325
+ for (CourseArea eventCourseArea : event.getVenue().getCourseAreas()) {
1326
+ if (defaultCourseAreaId != null && eventCourseArea.getId().equals(defaultCourseAreaId)) {
1327
+ event.addRegatta(regatta);
1328
+ }
1329
+ }
1330
+ }
1331
+ return wasCreated;
1332
+ }
1333
+
1334
+ @Override
1335
+ public void addRace(RegattaIdentifier addToRegatta, RaceDefinition raceDefinition) {
1336
+ Regatta regatta = getRegatta(addToRegatta);
1337
+ regatta.addRace(raceDefinition); // will trigger the raceAdded operation because this service is listening on
1338
+ // all its regattas
1339
+ }
1340
+
1341
+ /**
1342
+ * If the <code>regatta</code> {@link Regatta#isPersistent() is a persistent one}, the association of the race with
1343
+ * the regatta is remembered persistently so that {@link #getRememberedRegattaForRace(Serializable)} will provide
1344
+ * it.
1345
+ */
1346
+ @Override
1347
+ public void raceAdded(Regatta regatta, RaceDefinition raceDefinition) {
1348
+ if (regatta.isPersistent()) {
1349
+ setRegattaForRace(regatta, raceDefinition);
1350
+ }
1351
+ final CourseChangeReplicator listener = new CourseChangeReplicator(this, regatta, raceDefinition);
1352
+ courseListeners.put(raceDefinition, listener);
1353
+ raceDefinition.getCourse().addCourseListener(listener);
1354
+ replicate(new AddRaceDefinition(regatta.getRegattaIdentifier(), raceDefinition));
1355
+ }
1356
+
1357
+ @Override
1358
+ public void raceRemoved(Regatta regatta, RaceDefinition raceDefinition) {
1359
+ raceDefinition.getCourse().removeCourseListener(courseListeners.remove(raceDefinition));
1360
+ }
1361
+
1362
+ private NamedReentrantReadWriteLock lockRaceTrackersById(Object trackerId) {
1363
+ NamedReentrantReadWriteLock lock;
1364
+ synchronized (raceTrackersByIDLocks) {
1365
+ lock = raceTrackersByIDLocks.get(trackerId);
1366
+ if (lock == null) {
1367
+ lock = new NamedReentrantReadWriteLock("raceTrackersByIDLock for " + trackerId, /* fair */false);
1368
+ raceTrackersByIDLocks.put(trackerId, lock);
1369
+ }
1370
+ }
1371
+ LockUtil.lockForWrite(lock);
1372
+ return lock;
1373
+ }
1374
+
1375
+ /**
1376
+ * @param lock
1377
+ * need to pass the lock obtained from {@link #lockRaceTrackersById(Object)} because a competing thread
1378
+ * may already have removed the lock from the {@link #raceTrackersByIDLocks} map
1379
+ */
1380
+ private void unlockRaceTrackersById(Object trackerId, NamedReentrantReadWriteLock lock) {
1381
+ LockUtil.unlockAfterWrite(lock);
1382
+ synchronized (raceTrackersByIDLocks) {
1383
+ raceTrackersByIDLocks.remove(trackerId);
1384
+ }
1385
+ }
1386
+
1387
+ @Override
1388
+ public RaceHandle addRace(RegattaIdentifier regattaToAddTo, RaceTrackingConnectivityParameters params,
1389
+ long timeoutInMilliseconds) throws Exception {
1390
+ final Object trackerID = params.getTrackerID();
1391
+ NamedReentrantReadWriteLock raceTrackersByIdLock = lockRaceTrackersById(trackerID);
1392
+ try {
1393
+ RaceTracker tracker = raceTrackersByID.get(trackerID);
1394
+ if (tracker == null) {
1395
+ Regatta regatta = regattaToAddTo == null ? null : getRegatta(regattaToAddTo);
1396
+ if (regatta == null) {
1397
+ // create tracker and use an existing or create a default regatta
1398
+ tracker = params.createRaceTracker(this, windStore, gpsFixStore, /* raceLogResolver */ this);
1399
+ } else {
1400
+ // use the regatta selected by the RaceIdentifier regattaToAddTo
1401
+ tracker = params.createRaceTracker(regatta, this, windStore, gpsFixStore, /* raceLogResolver */ this);
1402
+ assert tracker.getRegatta() == regatta;
1403
+ }
1404
+ LockUtil.lockForWrite(raceTrackersByRegattaLock);
1405
+ try {
1406
+ raceTrackersByID.put(trackerID, tracker);
1407
+ Set<RaceTracker> trackers = raceTrackersByRegatta.get(tracker.getRegatta());
1408
+ if (trackers == null) {
1409
+ trackers = Collections.newSetFromMap(new ConcurrentHashMap<RaceTracker, Boolean>());
1410
+ raceTrackersByRegatta.put(tracker.getRegatta(), trackers);
1411
+ }
1412
+ trackers.add(tracker);
1413
+ } finally {
1414
+ LockUtil.unlockAfterWrite(raceTrackersByRegattaLock);
1415
+ }
1416
+ // TODO we assume here that the regatta name is unique which necessitates adding the boat class name to
1417
+ // it in RegattaImpl constructor
1418
+ String regattaName = tracker.getRegatta().getName();
1419
+ Regatta regattaWithName = regattasByName.get(regattaName);
1420
+ // TODO we assume here that the regatta name is unique which necessitates adding the boat class name to
1421
+ // it in RegattaImpl constructor
1422
+ if (regattaWithName != null) {
1423
+ if (regattaWithName != tracker.getRegatta()) {
1424
+ if (Util.isEmpty(regattaWithName.getAllRaces())) {
1425
+ // probably, tracker removed the last races from the old regatta and created a new one
1426
+ LockUtil.lockForWrite(regattasByNameLock);
1427
+ try {
1428
+ regattasByName.remove(regattaName);
1429
+ cacheAndReplicateDefaultRegatta(tracker.getRegatta());
1430
+ } finally {
1431
+ LockUtil.unlockAfterWrite(regattasByNameLock);
1432
+ }
1433
+ } else {
1434
+ throw new RuntimeException("Internal error. Two regatta objects with equal name "
1435
+ + regattaName);
1436
+ }
1437
+ }
1438
+ } else {
1439
+ cacheAndReplicateDefaultRegatta(tracker.getRegatta());
1440
+ }
1441
+ } else {
1442
+ logger.warning("Race tracker with ID "+trackerID+" already found; not tracking twice to avoid race duplication");
1443
+ WindStore existingTrackersWindStore = tracker.getWindStore();
1444
+ if (!existingTrackersWindStore.equals(windStore)) {
1445
+ logger.warning("Wind store mismatch. Requested wind store: " + windStore
1446
+ + ". Wind store in use by existing tracker: " + existingTrackersWindStore);
1447
+ }
1448
+ GPSFixStore existingTrackersGPSFixStore = tracker.getGPSFixStore();
1449
+ if (!existingTrackersGPSFixStore.equals(gpsFixStore)) {
1450
+ logger.warning("GPSFix store mismatch. Requested GPSFix store: " + gpsFixStore
1451
+ + ". GPSFix store in use by existing tracker: " + existingTrackersGPSFixStore);
1452
+ }
1453
+ }
1454
+ if (timeoutInMilliseconds != -1) {
1455
+ scheduleAbortTrackerAfterInitialTimeout(tracker, timeoutInMilliseconds);
1456
+ }
1457
+ return tracker.getRacesHandle();
1458
+ } finally {
1459
+ unlockRaceTrackersById(trackerID, raceTrackersByIdLock);
1460
+ }
1461
+ }
1462
+
1463
+ /**
1464
+ * The regatta and all its contained {@link Regatta#getAllRaces() races} are replicated to all replicas.
1465
+ *
1466
+ * @param regatta
1467
+ * the series of this regatta must not have any {@link Series#getRaceColumns() race columns associated
1468
+ * (yet)}.
1469
+ */
1470
+ private void replicateSpecificRegattaWithoutRaceColumns(Regatta regatta) {
1471
+ Serializable courseAreaId = null;
1472
+ if (regatta.getDefaultCourseArea() != null) {
1473
+ courseAreaId = regatta.getDefaultCourseArea().getId();
1474
+ }
1475
+ replicate(new AddSpecificRegatta(regatta.getName(), regatta.getBoatClass() == null ? null : regatta
1476
+ .getBoatClass().getName(), regatta.getStartDate(), regatta.getEndDate(), regatta.getId(),
1477
+ getSeriesWithoutRaceColumnsConstructionParametersAsMap(regatta), regatta.isPersistent(),
1478
+ regatta.getScoringScheme(), courseAreaId, regatta.useStartTimeInference(), regatta.getRankingMetricType()));
1479
+ RegattaIdentifier regattaIdentifier = regatta.getRegattaIdentifier();
1480
+ for (RaceDefinition race : regatta.getAllRaces()) {
1481
+ replicate(new AddRaceDefinition(regattaIdentifier, race));
1482
+ }
1483
+ }
1484
+
1485
+ private RegattaCreationParametersDTO getSeriesWithoutRaceColumnsConstructionParametersAsMap(Regatta regatta) {
1486
+ LinkedHashMap<String, SeriesCreationParametersDTO> result = new LinkedHashMap<String, SeriesCreationParametersDTO>();
1487
+ for (Series s : regatta.getSeries()) {
1488
+ assert Util.isEmpty(s.getRaceColumns());
1489
+ List<FleetDTO> fleetNamesAndOrdering = new ArrayList<FleetDTO>();
1490
+ for (Fleet f : s.getFleets()) {
1491
+ fleetNamesAndOrdering.add(getBaseDomainFactory().convertToFleetDTO(f));
1492
+ }
1493
+ result.put(
1494
+ s.getName(),
1495
+ new SeriesCreationParametersDTO(fleetNamesAndOrdering, s.isMedal(), s.isStartsWithZeroScore(), s
1496
+ .isFirstColumnIsNonDiscardableCarryForward(), s.getResultDiscardingRule() == null ? null
1497
+ : s.getResultDiscardingRule().getDiscardIndexResultsStartingWithHowManyRaces(), s
1498
+ .hasSplitFleetContiguousScoring()));
1499
+ }
1500
+ return new RegattaCreationParametersDTO(result);
1501
+ }
1502
+
1503
+ /**
1504
+ * If <code>regatta</code> is not yet in {@link #regattasByName}, it is added, this service is
1505
+ * {@link Regatta#addRegattaListener(RegattaListener) added} as regatta listener, and the regatta and all its
1506
+ * contained {@link Regatta#getAllRaces() races} are replicated to all replica.
1507
+ */
1508
+ private void cacheAndReplicateDefaultRegatta(Regatta regatta) {
1509
+ // try a quick read first, protected by regattasByName being a concurrent hash set
1510
+ if (!regattasByName.containsKey(regatta.getName())) {
1511
+ // now we need to obtain exclusive write access; in between, some other thread may have added a regatta by
1512
+ // that
1513
+ // name, so we need to check again:
1514
+ LockUtil.lockForWrite(regattasByNameLock);
1515
+ try {
1516
+ if (!regattasByName.containsKey(regatta.getName())) {
1517
+ logger.info("putting regatta " + regatta.getName() + " (" + regatta.hashCode()
1518
+ + ") into regattasByName of " + this);
1519
+ regattasByName.put(regatta.getName(), regatta);
1520
+ regatta.addRegattaListener(this);
1521
+ regatta.addRaceColumnListener(raceLogReplicator);
1522
+ regatta.addRaceColumnListener(raceLogScoringReplicator);
1523
+
1524
+ replicate(new AddDefaultRegatta(regatta.getName(), regatta.getBoatClass() == null ? null : regatta
1525
+ .getBoatClass().getName(), regatta.getStartDate(), regatta.getEndDate(), regatta.getId()));
1526
+ RegattaIdentifier regattaIdentifier = regatta.getRegattaIdentifier();
1527
+ for (RaceDefinition race : regatta.getAllRaces()) {
1528
+ replicate(new AddRaceDefinition(regattaIdentifier, race));
1529
+ }
1530
+ }
1531
+ } finally {
1532
+ LockUtil.unlockAfterWrite(regattasByNameLock);
1533
+ }
1534
+ }
1535
+ }
1536
+
1537
+ @Override
1538
+ public DynamicTrackedRace createTrackedRace(RegattaAndRaceIdentifier raceIdentifier, WindStore windStore,
1539
+ GPSFixStore gpsFixStore, long delayToLiveInMillis, long millisecondsOverWhichToAverageWind,
1540
+ long millisecondsOverWhichToAverageSpeed, boolean useMarkPassingCalculator) {
1541
+ DynamicTrackedRegatta trackedRegatta = getOrCreateTrackedRegatta(getRegatta(raceIdentifier));
1542
+ RaceDefinition race = getRace(raceIdentifier);
1543
+ return trackedRegatta.createTrackedRace(race, Collections.<Sideline> emptyList(), windStore, gpsFixStore,
1544
+ delayToLiveInMillis, millisecondsOverWhichToAverageWind, millisecondsOverWhichToAverageSpeed,
1545
+ /* raceDefinitionSetToUpdate */null, useMarkPassingCalculator, /* raceLogResolver */ this);
1546
+ }
1547
+
1548
+ private void ensureRegattaIsObservedForDefaultLeaderboardAndAutoLeaderboardLinking(
1549
+ DynamicTrackedRegatta trackedRegatta) {
1550
+ if (regattasObservedForDefaultLeaderboard.add(trackedRegatta)) {
1551
+ trackedRegatta.addRaceListener(new RaceAdditionListener());
1552
+ }
1553
+ }
1554
+
1555
+ private void stopObservingRegattaForRedaultLeaderboardAndAutoLeaderboardLinking(DynamicTrackedRegatta trackedRegatta) {
1556
+ regattasObservedForDefaultLeaderboard.remove(trackedRegatta);
1557
+ }
1558
+
1559
+ /**
1560
+ * A listener class used to ensure that when a tracked race is added to any {@link TrackedRegatta} managed by this
1561
+ * service, the service adds the tracked race to the default leaderboard and links it to the leaderboard columns
1562
+ * that were previously connected to it. Additionally, a {@link RaceChangeListener} is added to the
1563
+ * {@link TrackedRace} which is responsible for triggering the replication of all relevant changes to the tracked
1564
+ * race. When a tracked race is removed, the {@link TrackedRaceReplicator} that was added as listener to that
1565
+ * tracked race is removed again.
1566
+ *
1567
+ * A {@link PolarFixCacheUpdater} is added to every race so that polar fixes are aggregated when new GPS fixes
1568
+ * arrive.
1569
+ *
1570
+ * @author Axel Uhl (d043530)
1571
+ *
1572
+ */
1573
+ private class RaceAdditionListener implements RaceListener, Serializable {
1574
+ private static final long serialVersionUID = 1036955460477000265L;
1575
+
1576
+ private final Map<TrackedRace, TrackedRaceReplicator> trackedRaceReplicators;
1577
+
1578
+ private final Map<TrackedRace, PolarFixCacheUpdater> polarFixCacheUpdaters;
1579
+
1580
+ public RaceAdditionListener() {
1581
+ this.trackedRaceReplicators = new HashMap<TrackedRace, TrackedRaceReplicator>();
1582
+ this.polarFixCacheUpdaters = new HashMap<TrackedRace, PolarFixCacheUpdater>();
1583
+ }
1584
+
1585
+ @Override
1586
+ public void raceRemoved(TrackedRace trackedRace) {
1587
+ TrackedRaceReplicator trackedRaceReplicator = trackedRaceReplicators.remove(trackedRace);
1588
+ if (trackedRaceReplicator != null) {
1589
+ trackedRace.removeListener(trackedRaceReplicator);
1590
+ }
1591
+ PolarFixCacheUpdater polarFixCacheUpdater = polarFixCacheUpdaters.remove(trackedRace);
1592
+ if (polarFixCacheUpdater != null) {
1593
+ trackedRace.removeListener(polarFixCacheUpdater);
1594
+ }
1595
+ }
1596
+
1597
+ @Override
1598
+ public void raceAdded(TrackedRace trackedRace) {
1599
+ // replicate the addition of the tracked race:
1600
+ CreateTrackedRace op = new CreateTrackedRace(trackedRace.getRaceIdentifier(), trackedRace.getWindStore(),
1601
+ trackedRace.getGPSFixStore(), trackedRace.getDelayToLiveInMillis(),
1602
+ trackedRace.getMillisecondsOverWhichToAverageWind(),
1603
+ trackedRace.getMillisecondsOverWhichToAverageSpeed());
1604
+ replicate(op);
1605
+ linkRaceToConfiguredLeaderboardColumns(trackedRace);
1606
+ TrackedRaceReplicator trackedRaceReplicator = new TrackedRaceReplicator(trackedRace);
1607
+ trackedRaceReplicators.put(trackedRace, trackedRaceReplicator);
1608
+ trackedRace.addListener(trackedRaceReplicator, /* fire wind already loaded */true, true);
1609
+
1610
+ PolarFixCacheUpdater polarFixCacheUpdater = new PolarFixCacheUpdater(trackedRace);
1611
+ polarFixCacheUpdaters.put(trackedRace, polarFixCacheUpdater);
1612
+ trackedRace.addListener(polarFixCacheUpdater);
1613
+
1614
+ if (polarDataService != null) {
1615
+ trackedRace.setPolarDataService(polarDataService);
1616
+ }
1617
+ }
1618
+ }
1619
+
1620
+ private class PolarFixCacheUpdater extends AbstractRaceChangeListener {
1621
+
1622
+ private final TrackedRace race;
1623
+
1624
+ public PolarFixCacheUpdater(TrackedRace race) {
1625
+ this.race = race;
1626
+ }
1627
+
1628
+ @Override
1629
+ public void competitorPositionChanged(GPSFixMoving fix, Competitor item) {
1630
+ if (polarDataService != null) {
1631
+ polarDataService.competitorPositionChanged(fix, item, race);
1632
+ }
1633
+ }
1634
+
1635
+ @Override
1636
+ public void statusChanged(TrackedRaceStatus newStatus, TrackedRaceStatus oldStatus) {
1637
+ if (oldStatus.getStatus() == TrackedRaceStatusEnum.LOADING
1638
+ && newStatus.getStatus() != TrackedRaceStatusEnum.LOADING) {
1639
+ if (polarDataService != null) {
1640
+ polarDataService.raceFinishedLoading(race);
1641
+ }
1642
+ }
1643
+ }
1644
+
1645
+ }
1646
+
1647
+ private class TrackedRaceReplicator implements RaceChangeListener {
1648
+ private final TrackedRace trackedRace;
1649
+
1650
+ public TrackedRaceReplicator(TrackedRace trackedRace) {
1651
+ this.trackedRace = trackedRace;
1652
+ }
1653
+
1654
+ @Override
1655
+ public void windSourcesToExcludeChanged(Iterable<? extends WindSource> windSourcesToExclude) {
1656
+ replicate(new UpdateWindSourcesToExclude(getRaceIdentifier(), windSourcesToExclude));
1657
+ }
1658
+
1659
+ @Override
1660
+ public void startOfTrackingChanged(TimePoint startOfTracking) {
1661
+ replicate(new UpdateStartOfTracking(getRaceIdentifier(), startOfTracking));
1662
+ }
1663
+
1664
+ @Override
1665
+ public void endOfTrackingChanged(TimePoint endOfTracking) {
1666
+ replicate(new UpdateEndOfTracking(getRaceIdentifier(), endOfTracking));
1667
+ }
1668
+
1669
+ @Override
1670
+ public void startTimeReceivedChanged(TimePoint startTimeReceived) {
1671
+ replicate(new UpdateStartTimeReceived(getRaceIdentifier(), startTimeReceived));
1672
+ }
1673
+
1674
+ @Override
1675
+ public void startOfRaceChanged(TimePoint oldStartOfRace, TimePoint newStartOfRace) {
1676
+ // no action required; the update signaled by this call is implicit; for explicit updates
1677
+ // see raceTimesChanged(TimePoint, TimePoint, TimePoint).
1678
+ }
1679
+
1680
+ @Override
1681
+ public void waypointAdded(int zeroBasedIndex, Waypoint waypointThatGotAdded) {
1682
+ // no-op; the course change is replicated by the separate CourseChangeReplicator
1683
+ }
1684
+
1685
+ @Override
1686
+ public void waypointRemoved(int zeroBasedIndex, Waypoint waypointThatGotRemoved) {
1687
+ // no-op; the course change is replicated by the separate CourseChangeReplicator
1688
+ }
1689
+
1690
+ @Override
1691
+ public void delayToLiveChanged(long delayToLiveInMillis) {
1692
+ replicate(new UpdateRaceDelayToLive(getRaceIdentifier(), delayToLiveInMillis));
1693
+ }
1694
+
1695
+ @Override
1696
+ public void windDataReceived(Wind wind, WindSource windSource) {
1697
+ replicate(new RecordWindFix(getRaceIdentifier(), windSource, wind));
1698
+ }
1699
+
1700
+ @Override
1701
+ public void windDataRemoved(Wind wind, WindSource windSource) {
1702
+ replicate(new RemoveWindFix(getRaceIdentifier(), windSource, wind));
1703
+ }
1704
+
1705
+ @Override
1706
+ public void windAveragingChanged(long oldMillisecondsOverWhichToAverage, long newMillisecondsOverWhichToAverage) {
1707
+ replicate(new UpdateWindAveragingTime(getRaceIdentifier(), newMillisecondsOverWhichToAverage));
1708
+ }
1709
+
1710
+ @Override
1711
+ public void competitorPositionChanged(GPSFixMoving fix, Competitor competitor) {
1712
+ replicate(new RecordCompetitorGPSFix(getRaceIdentifier(), competitor, fix));
1713
+ }
1714
+
1715
+ @Override
1716
+ public void statusChanged(TrackedRaceStatus newStatus, TrackedRaceStatus oldStatus) {
1717
+ replicate(new UpdateTrackedRaceStatus(getRaceIdentifier(), newStatus));
1718
+ }
1719
+
1720
+ @Override
1721
+ public void markPositionChanged(GPSFix fix, Mark mark, boolean firstInTrack) {
1722
+ final RecordMarkGPSFix operation;
1723
+ if (firstInTrack) {
1724
+ operation = new RecordMarkGPSFixForNewMarkTrack(getRaceIdentifier(), mark, fix);
1725
+ } else {
1726
+ operation = new RecordMarkGPSFixForExistingTrack(getRaceIdentifier(), mark, fix);
1727
+ }
1728
+ replicate(operation);
1729
+ }
1730
+
1731
+ @Override
1732
+ public void markPassingReceived(Competitor competitor, Map<Waypoint, MarkPassing> oldMarkPassings,
1733
+ Iterable<MarkPassing> markPassings) {
1734
+ replicate(new UpdateMarkPassings(getRaceIdentifier(), competitor, markPassings));
1735
+ }
1736
+
1737
+ @Override
1738
+ public void speedAveragingChanged(long oldMillisecondsOverWhichToAverage, long newMillisecondsOverWhichToAverage) {
1739
+ replicate(new UpdateWindAveragingTime(getRaceIdentifier(), newMillisecondsOverWhichToAverage));
1740
+ }
1741
+
1742
+ private RegattaAndRaceIdentifier getRaceIdentifier() {
1743
+ return trackedRace.getRaceIdentifier();
1744
+ }
1745
+
1746
+ }
1747
+
1748
+ /**
1749
+ * Based on the <code>trackedRace</code>'s {@link TrackedRace#getRaceIdentifier() race identifier}, the tracked race
1750
+ * is (re-)associated to all {@link RaceColumn race columns} that currently have no
1751
+ * {@link RaceColumn#getTrackedRace(Fleet) tracked race assigned} and whose
1752
+ * {@link RaceColumn#getRaceIdentifier(Fleet) race identifier} equals that of <code>trackedRace</code>.
1753
+ */
1754
+ private void linkRaceToConfiguredLeaderboardColumns(TrackedRace trackedRace) {
1755
+ boolean leaderboardHasChanged = false;
1756
+ RegattaAndRaceIdentifier trackedRaceIdentifier = trackedRace.getRaceIdentifier();
1757
+ for (Leaderboard leaderboard : getLeaderboards().values()) {
1758
+ for (RaceColumn column : leaderboard.getRaceColumns()) {
1759
+ for (Fleet fleet : column.getFleets()) {
1760
+ if (trackedRaceIdentifier.equals(column.getRaceIdentifier(fleet))
1761
+ && column.getTrackedRace(fleet) == null) {
1762
+ column.setTrackedRace(fleet, trackedRace);
1763
+ leaderboardHasChanged = true;
1764
+ replicate(new ConnectTrackedRaceToLeaderboardColumn(leaderboard.getName(), column.getName(),
1765
+ fleet.getName(), trackedRaceIdentifier));
1766
+ }
1767
+ }
1768
+ }
1769
+ if (leaderboardHasChanged) {
1770
+ // Update the corresponding groups, to keep them in sync
1771
+ syncGroupsAfterLeaderboardChange(leaderboard, /* doDatabaseUpdate */false);
1772
+ }
1773
+ }
1774
+ }
1775
+
1776
+ @Override
1777
+ public void stopTracking(Regatta regatta) throws MalformedURLException, IOException, InterruptedException {
1778
+ final Set<RaceTracker> trackersForRegatta = raceTrackersByRegatta.get(regatta);
1779
+ if (trackersForRegatta != null) {
1780
+ for (RaceTracker raceTracker : trackersForRegatta) {
1781
+ final Set<RaceDefinition> races = raceTracker.getRaces();
1782
+ if (races != null) {
1783
+ for (RaceDefinition race : races) {
1784
+ stopTrackingWind(regatta, race);
1785
+ }
1786
+ }
1787
+ raceTracker.stop(/* preemptive */false);
1788
+ final Object trackerId = raceTracker.getID();
1789
+ final NamedReentrantReadWriteLock lock = lockRaceTrackersById(trackerId);
1790
+ try {
1791
+ raceTrackersByID.remove(trackerId);
1792
+ } finally {
1793
+ unlockRaceTrackersById(trackerId, lock);
1794
+ }
1795
+ raceTrackersByID.remove(trackerId);
1796
+ }
1797
+ LockUtil.lockForWrite(raceTrackersByRegattaLock);
1798
+ try {
1799
+ raceTrackersByRegatta.remove(regatta);
1800
+ } finally {
1801
+ LockUtil.unlockAfterWrite(raceTrackersByRegattaLock);
1802
+ }
1803
+ }
1804
+ }
1805
+
1806
+ @Override
1807
+ public void stopTrackingAndRemove(Regatta regatta) throws MalformedURLException, IOException, InterruptedException {
1808
+ stopTracking(regatta);
1809
+ if (regatta != null) {
1810
+ if (regatta.getName() != null) {
1811
+ logger.info("Removing regatta " + regatta.getName() + " (" + regatta.hashCode() + ") from " + this);
1812
+ LockUtil.lockForWrite(regattasByNameLock);
1813
+ try {
1814
+ regattasByName.remove(regatta.getName());
1815
+ } finally {
1816
+ LockUtil.unlockAfterWrite(regattasByNameLock);
1817
+ }
1818
+ LockUtil.lockForWrite(regattaTrackingCacheLock);
1819
+ try {
1820
+ regattaTrackingCache.remove(regatta);
1821
+ } finally {
1822
+ LockUtil.unlockAfterWrite(regattaTrackingCacheLock);
1823
+ }
1824
+ regatta.removeRegattaListener(this);
1825
+ regatta.removeRaceColumnListener(raceLogReplicator);
1826
+ regatta.removeRaceColumnListener(raceLogScoringReplicator);
1827
+ }
1828
+ for (RaceDefinition race : regatta.getAllRaces()) {
1829
+ stopTrackingWind(regatta, race);
1830
+ }
1831
+ }
1832
+ }
1833
+
1834
+ /**
1835
+ * The tracker will initially try to connect to the tracking infrastructure to obtain basic race master data. If
1836
+ * this fails after some timeout, to avoid garbage and lingering threads, the task scheduled by this method will
1837
+ * check after the timeout expires if race master data was successfully received. If so, the tracker continues
1838
+ * normally. Otherwise, the tracker is shut down orderly by calling {@link RaceTracker#stop(boolean) stopping}.
1839
+ *
1840
+ * @return the scheduled task, in case the caller wants to {@link ScheduledFuture#cancel(boolean) cancel} it, e.g.,
1841
+ * when the tracker is stopped or has successfully received the race
1842
+ */
1843
+ private ScheduledFuture<?> scheduleAbortTrackerAfterInitialTimeout(final RaceTracker tracker,
1844
+ final long timeoutInMilliseconds) {
1845
+ ScheduledFuture<?> task = getScheduler().schedule(new Runnable() {
1846
+ @Override
1847
+ public void run() {
1848
+ if (tracker.getRaces() == null || tracker.getRaces().isEmpty()) {
1849
+ try {
1850
+ Regatta regatta = tracker.getRegatta();
1851
+ logger.log(Level.SEVERE, "RaceDefinition for a race in regatta " + regatta.getName()
1852
+ + " not obtained within " + timeoutInMilliseconds
1853
+ + "ms. Aborting tracker for this race.");
1854
+ Set<RaceTracker> trackersForRegatta = raceTrackersByRegatta.get(regatta);
1855
+ if (trackersForRegatta != null) {
1856
+ trackersForRegatta.remove(tracker);
1857
+ }
1858
+ tracker.stop(/* preemptive */true);
1859
+ final Object trackerId = tracker.getID();
1860
+ final NamedReentrantReadWriteLock lock = lockRaceTrackersById(trackerId);
1861
+ try {
1862
+ raceTrackersByID.remove(trackerId);
1863
+ } finally {
1864
+ unlockRaceTrackersById(trackerId, lock);
1865
+ }
1866
+ if (trackersForRegatta == null || trackersForRegatta.isEmpty()) {
1867
+ stopTracking(regatta);
1868
+ }
1869
+ } catch (Exception e) {
1870
+ logger.log(Level.SEVERE, "scheduleAbortTrackerAfterInitialTimeout", e);
1871
+ e.printStackTrace();
1872
+ }
1873
+ }
1874
+ }
1875
+ }, /* delay */timeoutInMilliseconds, /* unit */TimeUnit.MILLISECONDS);
1876
+ return task;
1877
+ }
1878
+
1879
+ @Override
1880
+ public void stopTracking(Regatta regatta, RaceDefinition race) throws MalformedURLException, IOException,
1881
+ InterruptedException {
1882
+ logger.info("Stopping tracking for " + race + "...");
1883
+ final Set<RaceTracker> trackerSet = raceTrackersByRegatta.get(regatta);
1884
+ if (trackerSet != null) {
1885
+ Iterator<RaceTracker> trackerIter = trackerSet.iterator();
1886
+ while (trackerIter.hasNext()) {
1887
+ RaceTracker raceTracker = trackerIter.next();
1888
+ if (raceTracker.getRaces() != null && raceTracker.getRaces().contains(race)) {
1889
+ logger.info("Found tracker to stop for races " + raceTracker.getRaces());
1890
+ raceTracker.stop(/* preemptive */false);
1891
+ trackerIter.remove();
1892
+ final Object trackerId = raceTracker.getID();
1893
+ final NamedReentrantReadWriteLock lock = lockRaceTrackersById(trackerId);
1894
+ try {
1895
+ raceTrackersByID.remove(trackerId);
1896
+ } finally {
1897
+ unlockRaceTrackersById(trackerId, lock);
1898
+ }
1899
+ }
1900
+ }
1901
+ } else {
1902
+ logger.warning("Didn't find any trackers for regatta " + regatta);
1903
+ }
1904
+ stopTrackingWind(regatta, race);
1905
+ // if the last tracked race was removed, confirm that tracking for the entire regatta has stopped
1906
+ if (trackerSet == null || trackerSet.isEmpty()) {
1907
+ stopTracking(regatta);
1908
+ }
1909
+ }
1910
+
1911
+ @Override
1912
+ public void removeRegatta(Regatta regatta) throws MalformedURLException, IOException, InterruptedException {
1913
+ Set<RegattaLeaderboard> leaderboardsToRemove = new HashSet<>();
1914
+ for (Leaderboard leaderboard : getLeaderboards().values()) {
1915
+ if (leaderboard instanceof RegattaLeaderboard) {
1916
+ RegattaLeaderboard regattaLeaderboard = (RegattaLeaderboard) leaderboard;
1917
+ if (regattaLeaderboard.getRegatta() == regatta) {
1918
+ leaderboardsToRemove.add(regattaLeaderboard);
1919
+ }
1920
+ }
1921
+ }
1922
+ for (RegattaLeaderboard regattaLeaderboardToRemove : leaderboardsToRemove) {
1923
+ removeLeaderboard(regattaLeaderboardToRemove.getName());
1924
+ }
1925
+ // avoid ConcurrentModificationException by copying the races to remove:
1926
+ Set<RaceDefinition> racesToRemove = new HashSet<>();
1927
+ Util.addAll(regatta.getAllRaces(), racesToRemove);
1928
+ for (RaceDefinition race : racesToRemove) {
1929
+ removeRace(regatta, race);
1930
+ mongoObjectFactory.removeRegattaForRaceID(race.getName(), regatta);
1931
+ persistentRegattasForRaceIDs.remove(race.getId().toString());
1932
+ }
1933
+ if (regatta.isPersistent()) {
1934
+ mongoObjectFactory.removeRegatta(regatta);
1935
+ }
1936
+ LockUtil.lockForWrite(regattasByNameLock);
1937
+ try {
1938
+ regattasByName.remove(regatta.getName());
1939
+ } finally {
1940
+ LockUtil.unlockAfterWrite(regattasByNameLock);
1941
+ }
1942
+ regatta.removeRegattaListener(this);
1943
+ regatta.removeRaceColumnListener(raceLogReplicator);
1944
+ regatta.removeRaceColumnListener(raceLogScoringReplicator);
1945
+ onRegattaLikeRemoved(regatta);
1946
+ }
1947
+
1948
+ @Override
1949
+ public void removeSeries(Series series) throws MalformedURLException, IOException, InterruptedException {
1950
+ Regatta regatta = series.getRegatta();
1951
+ regatta.removeSeries(series);
1952
+ if (regatta.isPersistent()) {
1953
+ mongoObjectFactory.storeRegatta(regatta);
1954
+ }
1955
+ }
1956
+
1957
+ @Override
1958
+ public Regatta updateRegatta(RegattaIdentifier regattaIdentifier, TimePoint startDate, TimePoint endDate,
1959
+ Serializable newDefaultCourseAreaId, RegattaConfiguration newRegattaConfiguration,
1960
+ Iterable<? extends Series> series, boolean useStartTimeInference) {
1961
+ // We're not doing any renaming of the regatta itself, therefore we don't have to sync on the maps.
1962
+ Regatta regatta = getRegatta(regattaIdentifier);
1963
+ CourseArea newCourseArea = getCourseArea(newDefaultCourseAreaId);
1964
+ if (newCourseArea != regatta.getDefaultCourseArea()) {
1965
+ regatta.setDefaultCourseArea(newCourseArea);
1966
+ }
1967
+ regatta.setStartDate(startDate);
1968
+ regatta.setEndDate(endDate);
1969
+ if (regatta.useStartTimeInference() != useStartTimeInference) {
1970
+ regatta.setUseStartTimeInference(useStartTimeInference);
1971
+ final DynamicTrackedRegatta trackedRegatta = getTrackedRegatta(regatta);
1972
+ if (trackedRegatta != null) {
1973
+ trackedRegatta.lockTrackedRacesForRead();
1974
+ try {
1975
+ for (DynamicTrackedRace trackedRace : trackedRegatta.getTrackedRaces()) {
1976
+ // the start times of the regatta's tracked races now have to be re-evaluated the next time they
1977
+ // are queried
1978
+ trackedRace.invalidateStartTime();
1979
+ }
1980
+ } finally {
1981
+ trackedRegatta.unlockTrackedRacesAfterRead();
1982
+ }
1983
+ }
1984
+ }
1985
+ regatta.setRegattaConfiguration(newRegattaConfiguration);
1986
+ if (series != null) {
1987
+ for (Series seriesObj : series) {
1988
+ regatta.addSeries(seriesObj);
1989
+ }
1990
+ }
1991
+ regatta.adjustEventToRegattaAssociation(this);
1992
+ if (regatta.isPersistent()) {
1993
+ mongoObjectFactory.storeRegatta(regatta);
1994
+ }
1995
+ return regatta;
1996
+ }
1997
+
1998
+ @Override
1999
+ public void removeRace(Regatta regatta, RaceDefinition race) throws MalformedURLException, IOException,
2000
+ InterruptedException {
2001
+ logger.info("Removing the race " + race + "...");
2002
+ stopAllTrackersForWhichRaceIsLastReachable(regatta, race);
2003
+ stopTrackingWind(regatta, race);
2004
+ TrackedRace trackedRace = getExistingTrackedRace(regatta, race);
2005
+ if (trackedRace != null) {
2006
+ TrackedRegatta trackedRegatta = getTrackedRegatta(regatta);
2007
+ final boolean isTrackedRacesEmpty;
2008
+ if (trackedRegatta != null) {
2009
+ trackedRegatta.lockTrackedRacesForWrite();
2010
+ try {
2011
+ trackedRegatta.removeTrackedRace(trackedRace);
2012
+ isTrackedRacesEmpty = Util.isEmpty(trackedRegatta.getTrackedRaces());
2013
+ } finally {
2014
+ trackedRegatta.unlockTrackedRacesAfterWrite();
2015
+ }
2016
+ } else {
2017
+ isTrackedRacesEmpty = false;
2018
+ }
2019
+ if (isTrackedRacesEmpty) {
2020
+ removeTrackedRegatta(regatta);
2021
+ }
2022
+ // remove tracked race from RaceColumns of regatta
2023
+ for (Series series : regatta.getSeries()) {
2024
+ for (RaceColumnInSeries raceColumn : series.getRaceColumns()) {
2025
+ for (Fleet fleet : series.getFleets()) {
2026
+ if (raceColumn.getTrackedRace(fleet) == trackedRace) {
2027
+ raceColumn.releaseTrackedRace(fleet);
2028
+ }
2029
+ }
2030
+ }
2031
+ }
2032
+ for (Leaderboard leaderboard : getLeaderboards().values()) {
2033
+ if (leaderboard instanceof FlexibleLeaderboard) { // RegattaLeaderboards have implicitly been updated by
2034
+ // the code above
2035
+ for (RaceColumn raceColumn : leaderboard.getRaceColumns()) {
2036
+ for (Fleet fleet : raceColumn.getFleets()) {
2037
+ if (raceColumn.getTrackedRace(fleet) == trackedRace) {
2038
+ raceColumn.releaseTrackedRace(fleet); // but leave the RaceIdentifier on the race column
2039
+ // untouched, e.g., for later re-load
2040
+ }
2041
+ }
2042
+ }
2043
+ }
2044
+ }
2045
+ }
2046
+ // remove the race from the (default) regatta if the regatta is not persistently stored
2047
+ regatta.removeRace(race);
2048
+ if (!regatta.isPersistent() && Util.isEmpty(regatta.getAllRaces())) {
2049
+ logger.info("Removing regatta " + regatta.getName() + " (" + regatta.hashCode() + ") from service " + this);
2050
+ LockUtil.lockForWrite(regattasByNameLock);
2051
+ try {
2052
+ regattasByName.remove(regatta.getName());
2053
+ } finally {
2054
+ LockUtil.unlockAfterWrite(regattasByNameLock);
2055
+ }
2056
+ regatta.removeRegattaListener(this);
2057
+ regatta.removeRaceColumnListener(raceLogReplicator);
2058
+ regatta.removeRaceColumnListener(raceLogScoringReplicator);
2059
+ }
2060
+ }
2061
+
2062
+ /**
2063
+ * Doesn't stop any wind trackers
2064
+ */
2065
+ private void stopAllTrackersForWhichRaceIsLastReachable(Regatta regatta, RaceDefinition race)
2066
+ throws MalformedURLException, IOException, InterruptedException {
2067
+ if (raceTrackersByRegatta.containsKey(regatta)) {
2068
+ Iterator<RaceTracker> trackerIter = raceTrackersByRegatta.get(regatta).iterator();
2069
+ while (trackerIter.hasNext()) {
2070
+ RaceTracker raceTracker = trackerIter.next();
2071
+ if (raceTracker.getRaces() != null && raceTracker.getRaces().contains(race)) {
2072
+ boolean foundReachableRace = false;
2073
+ for (RaceDefinition raceTrackedByTracker : raceTracker.getRaces()) {
2074
+ if (raceTrackedByTracker != race && isReachable(regatta, raceTrackedByTracker)) {
2075
+ foundReachableRace = true;
2076
+ break;
2077
+ }
2078
+ }
2079
+ if (!foundReachableRace) {
2080
+ // firstly stop the tracker
2081
+ raceTracker.stop(/* preemptive */true);
2082
+ // remove it from the raceTrackers by Regatta
2083
+ trackerIter.remove();
2084
+ final Object trackerId = raceTracker.getID();
2085
+ final NamedReentrantReadWriteLock lock = lockRaceTrackersById(trackerId);
2086
+ try {
2087
+ raceTrackersByID.remove(trackerId);
2088
+ } finally {
2089
+ unlockRaceTrackersById(trackerId, lock);
2090
+ }
2091
+ // if the last tracked race was removed, remove the entire regatta
2092
+ if (raceTrackersByRegatta.get(regatta).isEmpty()) {
2093
+ stopTracking(regatta);
2094
+ }
2095
+ }
2096
+ }
2097
+ }
2098
+ }
2099
+ }
2100
+
2101
+ private boolean isReachable(Regatta regatta, RaceDefinition race) {
2102
+ return Util.contains(regatta.getAllRaces(), race);
2103
+ }
2104
+
2105
+ @Override
2106
+ public void startTrackingWind(Regatta regatta, RaceDefinition race, boolean correctByDeclination) throws Exception {
2107
+ for (WindTrackerFactory windTrackerFactory : getWindTrackerFactories()) {
2108
+ windTrackerFactory.createWindTracker(getOrCreateTrackedRegatta(regatta), race, correctByDeclination);
2109
+ }
2110
+ }
2111
+
2112
+ @Override
2113
+ public void stopTrackingWind(Regatta regatta, RaceDefinition race) throws SocketException, IOException {
2114
+ for (WindTrackerFactory windTrackerFactory : getWindTrackerFactories()) {
2115
+ WindTracker windTracker = windTrackerFactory.getExistingWindTracker(race);
2116
+ if (windTracker != null) {
2117
+ windTracker.stop();
2118
+ }
2119
+ }
2120
+ }
2121
+
2122
+ @Override
2123
+ public Iterable<com.sap.sse.common.Util.Triple<Regatta, RaceDefinition, String>> getWindTrackedRaces() {
2124
+ List<com.sap.sse.common.Util.Triple<Regatta, RaceDefinition, String>> result = new ArrayList<com.sap.sse.common.Util.Triple<Regatta, RaceDefinition, String>>();
2125
+ for (Regatta regatta : getAllRegattas()) {
2126
+ for (RaceDefinition race : regatta.getAllRaces()) {
2127
+ for (WindTrackerFactory windTrackerFactory : getWindTrackerFactories()) {
2128
+ WindTracker windTracker = windTrackerFactory.getExistingWindTracker(race);
2129
+ if (windTracker != null) {
2130
+ result.add(new com.sap.sse.common.Util.Triple<Regatta, RaceDefinition, String>(regatta, race,
2131
+ windTracker.toString()));
2132
+ }
2133
+ }
2134
+ }
2135
+ }
2136
+ return result;
2137
+ }
2138
+
2139
+ @Override
2140
+ public DynamicTrackedRace getTrackedRace(Regatta regatta, RaceDefinition race) {
2141
+ return getOrCreateTrackedRegatta(regatta).getTrackedRace(race);
2142
+ }
2143
+
2144
+ private DynamicTrackedRace getExistingTrackedRace(Regatta regatta, RaceDefinition race) {
2145
+ return getOrCreateTrackedRegatta(regatta).getExistingTrackedRace(race);
2146
+ }
2147
+
2148
+ @Override
2149
+ public DynamicTrackedRegatta getOrCreateTrackedRegatta(Regatta regatta) {
2150
+ cacheAndReplicateDefaultRegatta(regatta);
2151
+ LockUtil.lockForWrite(regattaTrackingCacheLock);
2152
+ try {
2153
+ DynamicTrackedRegatta result = regattaTrackingCache.get(regatta);
2154
+ if (result == null) {
2155
+ logger.info("Creating DynamicTrackedRegattaImpl for regatta " + regatta.getName() + " with hashCode "
2156
+ + regatta.hashCode());
2157
+ result = new DynamicTrackedRegattaImpl(regatta);
2158
+ replicate(new TrackRegatta(regatta.getRegattaIdentifier()));
2159
+ regattaTrackingCache.put(regatta, result);
2160
+ ensureRegattaIsObservedForDefaultLeaderboardAndAutoLeaderboardLinking(result);
2161
+ }
2162
+ return result;
2163
+ } finally {
2164
+ LockUtil.unlockAfterWrite(regattaTrackingCacheLock);
2165
+ }
2166
+ }
2167
+
2168
+ @Override
2169
+ public DynamicTrackedRegatta getTrackedRegatta(com.sap.sailing.domain.base.Regatta regatta) {
2170
+ return regattaTrackingCache.get(regatta);
2171
+ }
2172
+
2173
+ @Override
2174
+ public void removeTrackedRegatta(Regatta regatta) {
2175
+ logger.info("Removing regatta " + regatta.getName() + " from regattaTrackingCache");
2176
+ final DynamicTrackedRegatta trackedRegatta;
2177
+ LockUtil.lockForWrite(regattaTrackingCacheLock);
2178
+ try {
2179
+ trackedRegatta = regattaTrackingCache.remove(regatta);
2180
+ } finally {
2181
+ LockUtil.unlockAfterWrite(regattaTrackingCacheLock);
2182
+ }
2183
+ stopObservingRegattaForRedaultLeaderboardAndAutoLeaderboardLinking(trackedRegatta);
2184
+ }
2185
+
2186
+ @Override
2187
+ public Regatta getRegatta(RegattaName regattaName) {
2188
+ return (Regatta) regattasByName.get(regattaName.getRegattaName());
2189
+ }
2190
+
2191
+ @Override
2192
+ public Regatta getRegatta(RegattaIdentifier regattaIdentifier) {
2193
+ return (Regatta) regattaIdentifier.getRegatta(this);
2194
+ }
2195
+
2196
+ @Override
2197
+ public DynamicTrackedRace getTrackedRace(RegattaAndRaceIdentifier raceIdentifier) {
2198
+ DynamicTrackedRace result = null;
2199
+ Regatta regatta = regattasByName.get(raceIdentifier.getRegattaName());
2200
+ if (regatta != null) {
2201
+ DynamicTrackedRegatta trackedRegatta = regattaTrackingCache.get(regatta);
2202
+ if (trackedRegatta != null) {
2203
+ RaceDefinition race = getRace(raceIdentifier);
2204
+ if (race != null) {
2205
+ result = trackedRegatta.getTrackedRace(race);
2206
+ }
2207
+ }
2208
+ }
2209
+ return result;
2210
+ }
2211
+
2212
+ @Override
2213
+ public DynamicTrackedRace getExistingTrackedRace(RegattaAndRaceIdentifier raceIdentifier) {
2214
+ Regatta regatta = getRegattaByName(raceIdentifier.getRegattaName());
2215
+ DynamicTrackedRace trackedRace = null;
2216
+ if (regatta != null) {
2217
+ RaceDefinition race = regatta.getRaceByName(raceIdentifier.getRaceName());
2218
+ trackedRace = getOrCreateTrackedRegatta(regatta).getExistingTrackedRace(race);
2219
+ }
2220
+ return trackedRace;
2221
+ }
2222
+
2223
+ @Override
2224
+ public RaceDefinition getRace(RegattaAndRaceIdentifier regattaNameAndRaceName) {
2225
+ RaceDefinition result = null;
2226
+ Regatta regatta = getRegatta(regattaNameAndRaceName);
2227
+ if (regatta != null) {
2228
+ result = regatta.getRaceByName(regattaNameAndRaceName.getRaceName());
2229
+ }
2230
+ return result;
2231
+ }
2232
+
2233
+ @Override
2234
+ public Map<String, LeaderboardGroup> getLeaderboardGroups() {
2235
+ return Collections.unmodifiableMap(new HashMap<String, LeaderboardGroup>(leaderboardGroupsByName));
2236
+ }
2237
+
2238
+ @Override
2239
+ public LeaderboardGroup getLeaderboardGroupByName(String groupName) {
2240
+ return leaderboardGroupsByName.get(groupName);
2241
+ }
2242
+
2243
+ @Override
2244
+ public LeaderboardGroup getLeaderboardGroupByID(UUID leaderboardGroupID) {
2245
+ return leaderboardGroupsByID.get(leaderboardGroupID);
2246
+ }
2247
+
2248
+ @Override
2249
+ public LeaderboardGroup addLeaderboardGroup(UUID id, String groupName, String description, String displayName,
2250
+ boolean displayGroupsInReverseOrder, List<String> leaderboardNames,
2251
+ int[] overallLeaderboardDiscardThresholds, ScoringSchemeType overallLeaderboardScoringSchemeType) {
2252
+ ArrayList<Leaderboard> leaderboards = new ArrayList<>();
2253
+ for (String leaderboardName : leaderboardNames) {
2254
+ Leaderboard leaderboard = leaderboardsByName.get(leaderboardName);
2255
+ if (leaderboard == null) {
2256
+ throw new IllegalArgumentException("No leaderboard with name " + leaderboardName + " found");
2257
+ } else {
2258
+ leaderboards.add(leaderboard);
2259
+ }
2260
+ }
2261
+ LeaderboardGroup result = new LeaderboardGroupImpl(id, groupName, description, displayName,
2262
+ displayGroupsInReverseOrder, leaderboards);
2263
+ if (overallLeaderboardScoringSchemeType != null) {
2264
+ // create overall leaderboard and its discards settings
2265
+ addOverallLeaderboardToLeaderboardGroup(result,
2266
+ getBaseDomainFactory().createScoringScheme(overallLeaderboardScoringSchemeType),
2267
+ overallLeaderboardDiscardThresholds);
2268
+ }
2269
+ LockUtil.lockForWrite(leaderboardGroupsByNameLock);
2270
+ try {
2271
+ if (leaderboardGroupsByName.containsKey(groupName)) {
2272
+ throw new IllegalArgumentException("Leaderboard group with name " + groupName + " already exists");
2273
+ }
2274
+ leaderboardGroupsByName.put(groupName, result);
2275
+ leaderboardGroupsByID.put(result.getId(), result);
2276
+ } finally {
2277
+ LockUtil.unlockAfterWrite(leaderboardGroupsByNameLock);
2278
+ }
2279
+ mongoObjectFactory.storeLeaderboardGroup(result);
2280
+ return result;
2281
+ }
2282
+
2283
+ @Override
2284
+ public void addLeaderboardGroupWithoutReplication(LeaderboardGroup leaderboardGroup) {
2285
+ LockUtil.lockForWrite(leaderboardGroupsByNameLock);
2286
+ try {
2287
+ String groupName = leaderboardGroup.getName();
2288
+ if (leaderboardGroupsByName.containsKey(groupName)) {
2289
+ throw new IllegalArgumentException("Leaderboard group with name " + groupName + " already exists");
2290
+ }
2291
+ leaderboardGroupsByName.put(groupName, leaderboardGroup);
2292
+ leaderboardGroupsByID.put(leaderboardGroup.getId(), leaderboardGroup);
2293
+ } finally {
2294
+ LockUtil.unlockAfterWrite(leaderboardGroupsByNameLock);
2295
+ }
2296
+ if (leaderboardGroup.hasOverallLeaderboard()) {
2297
+ addLeaderboard(leaderboardGroup.getOverallLeaderboard());
2298
+ }
2299
+ mongoObjectFactory.storeLeaderboardGroup(leaderboardGroup);
2300
+ }
2301
+
2302
+ @Override
2303
+ public void removeLeaderboardGroup(String groupName) {
2304
+ final LeaderboardGroup leaderboardGroup;
2305
+ LockUtil.lockForWrite(leaderboardGroupsByNameLock);
2306
+ try {
2307
+ leaderboardGroup = leaderboardGroupsByName.remove(groupName);
2308
+ if (leaderboardGroup != null) {
2309
+ leaderboardGroupsByID.remove(leaderboardGroup.getId());
2310
+ }
2311
+ } finally {
2312
+ LockUtil.unlockAfterWrite(leaderboardGroupsByNameLock);
2313
+ }
2314
+ mongoObjectFactory.removeLeaderboardGroup(groupName);
2315
+ if (leaderboardGroup != null && leaderboardGroup.getOverallLeaderboard() != null) {
2316
+ removeLeaderboard(leaderboardGroup.getOverallLeaderboard().getName());
2317
+ }
2318
+ }
2319
+
2320
+ @Override
2321
+ public void renameLeaderboardGroup(String oldName, String newName) {
2322
+ LockUtil.lockForWrite(leaderboardGroupsByNameLock);
2323
+ try {
2324
+ final LeaderboardGroup toRename = leaderboardGroupsByName.get(oldName);
2325
+ if (toRename == null) {
2326
+ throw new IllegalArgumentException("No leaderboard group with name " + oldName + " found");
2327
+ }
2328
+ if (leaderboardGroupsByName.containsKey(newName)) {
2329
+ throw new IllegalArgumentException("Leaderboard group with name " + newName + " already exists");
2330
+ }
2331
+ leaderboardGroupsByName.remove(oldName);
2332
+ toRename.setName(newName);
2333
+ leaderboardGroupsByName.put(newName, toRename);
2334
+ } finally {
2335
+ LockUtil.unlockAfterWrite(leaderboardGroupsByNameLock);
2336
+ }
2337
+ mongoObjectFactory.renameLeaderboardGroup(oldName, newName);
2338
+ }
2339
+
2340
+ @Override
2341
+ public void updateLeaderboardGroup(String oldName, String newName, String description, String displayName,
2342
+ List<String> leaderboardNames, int[] overallLeaderboardDiscardThresholds,
2343
+ ScoringSchemeType overallLeaderboardScoringSchemeType) {
2344
+ if (!oldName.equals(newName)) {
2345
+ renameLeaderboardGroup(oldName, newName);
2346
+ }
2347
+ LeaderboardGroup group = getLeaderboardGroupByName(newName);
2348
+ if (!description.equals(group.getDescription())) {
2349
+ group.setDescriptiom(description);
2350
+ }
2351
+ if (!Util.equalsWithNull(displayName, group.getDisplayName())) {
2352
+ group.setDisplayName(displayName);
2353
+ }
2354
+ group.clearLeaderboards();
2355
+ for (String leaderboardName : leaderboardNames) {
2356
+ Leaderboard leaderboard = getLeaderboardByName(leaderboardName);
2357
+ if (leaderboard != null) {
2358
+ group.addLeaderboard(leaderboard);
2359
+ }
2360
+ }
2361
+ Leaderboard overallLeaderboard = group.getOverallLeaderboard();
2362
+ if (overallLeaderboard != null) {
2363
+ if (overallLeaderboardScoringSchemeType == null) {
2364
+ group.setOverallLeaderboard(null);
2365
+ removeLeaderboard(overallLeaderboard.getName());
2366
+ } else {
2367
+ // update existing overall leaderboard's discards settings; scoring scheme cannot be updated in-place
2368
+ overallLeaderboard.setCrossLeaderboardResultDiscardingRule(new ThresholdBasedResultDiscardingRuleImpl(
2369
+ overallLeaderboardDiscardThresholds));
2370
+ updateStoredLeaderboard(overallLeaderboard);
2371
+ }
2372
+ } else if (overallLeaderboard == null && overallLeaderboardScoringSchemeType != null) {
2373
+ addOverallLeaderboardToLeaderboardGroup(group,
2374
+ getBaseDomainFactory().createScoringScheme(overallLeaderboardScoringSchemeType),
2375
+ overallLeaderboardDiscardThresholds);
2376
+ }
2377
+ updateStoredLeaderboardGroup(group);
2378
+ }
2379
+
2380
+ private void addOverallLeaderboardToLeaderboardGroup(LeaderboardGroup leaderboardGroup,
2381
+ ScoringScheme scoringScheme, int[] discardThresholds) {
2382
+ Leaderboard overallLeaderboard = new LeaderboardGroupMetaLeaderboard(leaderboardGroup, scoringScheme,
2383
+ new ThresholdBasedResultDiscardingRuleImpl(discardThresholds));
2384
+ leaderboardGroup.setOverallLeaderboard(overallLeaderboard);
2385
+ addLeaderboard(overallLeaderboard);
2386
+ updateStoredLeaderboard(overallLeaderboard);
2387
+ }
2388
+
2389
+ @Override
2390
+ public void updateStoredLeaderboardGroup(LeaderboardGroup leaderboardGroup) {
2391
+ mongoObjectFactory.storeLeaderboardGroup(leaderboardGroup);
2392
+ }
2393
+
2394
+ private ScheduledExecutorService getScheduler() {
2395
+ return scheduler;
2396
+ }
2397
+
2398
+ @Override
2399
+ public ObjectInputStream createObjectInputStreamResolvingAgainstCache(InputStream is) throws IOException {
2400
+ return getBaseDomainFactory().createObjectInputStreamResolvingAgainstThisFactory(is);
2401
+ }
2402
+
2403
+ @Override
2404
+ public ClassLoader getDeserializationClassLoader() {
2405
+ return joinedClassLoader;
2406
+ }
2407
+
2408
+ @Override
2409
+ public Serializable getId() {
2410
+ return getClass().getName();
2411
+ }
2412
+
2413
+ @Override
2414
+ public Iterable<OperationExecutionListener<RacingEventService>> getOperationExecutionListeners() {
2415
+ return operationExecutionListeners.keySet();
2416
+ }
2417
+
2418
+ @Override
2419
+ public void addOperationExecutionListener(OperationExecutionListener<RacingEventService> listener) {
2420
+ operationExecutionListeners.put(listener, listener);
2421
+ }
2422
+
2423
+ @Override
2424
+ public void removeOperationExecutionListener(OperationExecutionListener<RacingEventService> listener) {
2425
+ operationExecutionListeners.remove(listener);
2426
+ }
2427
+
2428
+ @Override
2429
+ public void serializeForInitialReplicationInternal(ObjectOutputStream oos) throws IOException {
2430
+ StringBuffer logoutput = new StringBuffer();
2431
+
2432
+ logger.info("Serializing regattas...");
2433
+ oos.writeObject(regattasByName);
2434
+ logoutput.append("Serialized " + regattasByName.size() + " regattas\n");
2435
+ for (Regatta regatta : regattasByName.values()) {
2436
+ logoutput.append(String.format("%3s\n", regatta.toString()));
2437
+ }
2438
+
2439
+ logger.info("Serializing events...");
2440
+ oos.writeObject(eventsById);
2441
+ logoutput.append("\nSerialized " + eventsById.size() + " events\n");
2442
+ for (Event event : eventsById.values()) {
2443
+ logoutput.append(String.format("%3s\n", event.toString()));
2444
+ }
2445
+
2446
+ logger.info("Serializing regattas observed...");
2447
+ oos.writeObject(regattasObservedForDefaultLeaderboard);
2448
+ logger.info("Serializing regatta tracking cache...");
2449
+ oos.writeObject(regattaTrackingCache);
2450
+ logger.info("Serializing leaderboard groups...");
2451
+ oos.writeObject(leaderboardGroupsByName);
2452
+ logoutput.append("Serialized " + leaderboardGroupsByName.size() + " leaderboard groups\n");
2453
+ for (LeaderboardGroup lg : leaderboardGroupsByName.values()) {
2454
+ logoutput.append(String.format("%3s\n", lg.toString()));
2455
+ }
2456
+ logger.info("Serializing leaderboards...");
2457
+ oos.writeObject(leaderboardsByName);
2458
+ logoutput.append("Serialized " + leaderboardsByName.size() + " leaderboards\n");
2459
+ for (Leaderboard lg : leaderboardsByName.values()) {
2460
+ logoutput.append(String.format("%3s\n", lg.toString()));
2461
+ }
2462
+ logger.info("Serializing media library...");
2463
+ mediaLibrary.serialize(oos);
2464
+ logoutput.append("Serialized " + mediaLibrary.allTracks().size() + " media tracks\n");
2465
+ for (MediaTrack lg : mediaLibrary.allTracks()) {
2466
+ logoutput.append(String.format("%3s\n", lg.toString()));
2467
+ }
2468
+ logger.info("Serializing persisted competitors...");
2469
+ oos.writeObject(competitorStore);
2470
+ logoutput.append("Serialized " + competitorStore.size() + " persisted competitors\n");
2471
+
2472
+ logger.info("Serializing configuration map...");
2473
+ oos.writeObject(configurationMap);
2474
+ logoutput.append("Serialized " + configurationMap.size() + " configuration entries\n");
2475
+ for (DeviceConfigurationMatcher matcher : configurationMap.keySet()) {
2476
+ logoutput.append(String.format("%3s\n", matcher.toString()));
2477
+ }
2478
+
2479
+ logger.info("Serializing remote sailing server references...");
2480
+ final ArrayList<RemoteSailingServerReference> remoteServerReferences = new ArrayList<>(remoteSailingServerSet
2481
+ .getCachedEventsForRemoteSailingServers().keySet());
2482
+ oos.writeObject(remoteServerReferences);
2483
+ logoutput.append("Serialized " + remoteServerReferences.size() + " remote sailing server references\n");
2484
+
2485
+ logger.info(logoutput.toString());
2486
+ }
2487
+
2488
+ @SuppressWarnings("unchecked")
2489
+ // all the casts of ois.readObject()'s return value to Map<..., ...>
2490
+ // the type-parameters in the casts of the de-serialized collection objects can't be checked
2491
+ @Override
2492
+ public void initiallyFillFromInternal(ObjectInputStream ois) throws IOException, ClassNotFoundException,
2493
+ InterruptedException {
2494
+ logger.info("Performing initial replication load on " + this);
2495
+ // Use this object's class's class loader as the context class loader which will then be used for
2496
+ // de-serialization; this will cause all classes to be visible that this bundle
2497
+ // (com.sap.sailing.server) can see
2498
+ StringBuffer logoutput = new StringBuffer();
2499
+ logger.info("Reading all regattas...");
2500
+ regattasByName.putAll((Map<String, Regatta>) ois.readObject());
2501
+ logoutput.append("Received " + regattasByName.size() + " NEW regattas\n");
2502
+ for (Regatta regatta : regattasByName.values()) {
2503
+ logoutput.append(String.format("%3s\n", regatta.toString()));
2504
+ }
2505
+
2506
+ logger.info("Reading all events...");
2507
+ eventsById.putAll((Map<Serializable, Event>) ois.readObject());
2508
+ logoutput.append("\nReceived " + eventsById.size() + " NEW events\n");
2509
+ for (Event event : eventsById.values()) {
2510
+ logoutput.append(String.format("%3s\n", event.toString()));
2511
+ }
2512
+
2513
+ // it is important that the leaderboards and tracked regattas are cleared before auto-linking to
2514
+ // old leaderboards takes place which then don't match the new ones
2515
+ logger.info("Reading all dynamic tracked regattas...");
2516
+ for (DynamicTrackedRegatta trackedRegattaToObserve : (Set<DynamicTrackedRegatta>) ois.readObject()) {
2517
+ ensureRegattaIsObservedForDefaultLeaderboardAndAutoLeaderboardLinking(trackedRegattaToObserve);
2518
+ }
2519
+
2520
+ logger.info("Reading all of the regatta tracking cache...");
2521
+ regattaTrackingCache.putAll((Map<Regatta, DynamicTrackedRegatta>) ois.readObject());
2522
+ logoutput.append("Received " + regattaTrackingCache.size() + " NEW regatta tracking cache entries\n");
2523
+
2524
+ logger.info("Reading leaderboard groups...");
2525
+ leaderboardGroupsByName.putAll((Map<String, LeaderboardGroup>) ois.readObject());
2526
+ logoutput.append("Received " + leaderboardGroupsByName.size() + " NEW leaderboard groups\n");
2527
+ for (LeaderboardGroup lg : leaderboardGroupsByName.values()) {
2528
+ leaderboardGroupsByID.put(lg.getId(), lg);
2529
+ logoutput.append(String.format("%3s\n", lg.toString()));
2530
+ }
2531
+
2532
+ logger.info("Reading leaderboards by name...");
2533
+ leaderboardsByName.putAll((Map<String, Leaderboard>) ois.readObject());
2534
+ logoutput.append("Received " + leaderboardsByName.size() + " NEW leaderboards\n");
2535
+ for (Leaderboard leaderboard : leaderboardsByName.values()) {
2536
+ logoutput.append(String.format("%3s\n", leaderboard.toString()));
2537
+ }
2538
+
2539
+ // now fix ScoreCorrectionListener setup for LeaderboardGroupMetaLeaderboard instances:
2540
+ for (Leaderboard leaderboard : leaderboardsByName.values()) {
2541
+ if (leaderboard instanceof LeaderboardGroupMetaLeaderboard) {
2542
+ ((LeaderboardGroupMetaLeaderboard) leaderboard)
2543
+ .registerAsScoreCorrectionChangeForwarderAndRaceColumnListenerOnAllLeaderboards();
2544
+ } else if (leaderboard instanceof FlexibleLeaderboard) {
2545
+ // and re-establish the RaceLogReplicator as listener on FlexibleLeaderboard objects
2546
+ leaderboard.addRaceColumnListener(raceLogReplicator);
2547
+ }
2548
+ }
2549
+
2550
+ logger.info("Reading media library...");
2551
+ mediaLibrary.deserialize(ois);
2552
+ logoutput.append("Received " + mediaLibrary.allTracks().size() + " NEW media tracks\n");
2553
+ for (MediaTrack mediatrack : mediaLibrary.allTracks()) {
2554
+ logoutput.append(String.format("%3s\n", mediatrack.toString()));
2555
+ }
2556
+
2557
+ // only copy the competitors from the deserialized competitor store; don't use it because it will have set
2558
+ // a default Mongo object factory
2559
+ logger.info("Reading competitors...");
2560
+ for (Competitor competitor : ((CompetitorStore) ois.readObject()).getCompetitors()) {
2561
+ DynamicCompetitor dynamicCompetitor = (DynamicCompetitor) competitor;
2562
+ // the following should actually be redundant because during de-serialization the Competitor objects,
2563
+ // whose classes implement IsManagedByCache, should already have been got/created from/in the
2564
+ // competitor store
2565
+ competitorStore.getOrCreateCompetitor(dynamicCompetitor.getId(), dynamicCompetitor.getName(),
2566
+ dynamicCompetitor.getColor(), dynamicCompetitor.getEmail(), dynamicCompetitor.getFlagImage(),
2567
+ dynamicCompetitor.getTeam(), dynamicCompetitor.getBoat(), dynamicCompetitor.getTimeOnTimeFactor(),
2568
+ dynamicCompetitor.getTimeOnDistanceAllowancePerNauticalMile());
2569
+ }
2570
+ logoutput.append("Received " + competitorStore.size() + " NEW competitors\n");
2571
+
2572
+ logger.info("Reading device configurations...");
2573
+ configurationMap.putAll((DeviceConfigurationMapImpl) ois.readObject());
2574
+ logoutput.append("Received " + configurationMap.size() + " NEW configuration entries\n");
2575
+ for (DeviceConfigurationMatcher matcher : configurationMap.keySet()) {
2576
+ logoutput.append(String.format("%3s\n", matcher.toString()));
2577
+ }
2578
+
2579
+ logger.info("Reading remote sailing server references...");
2580
+ for (RemoteSailingServerReference remoteSailingServerReference : (Iterable<RemoteSailingServerReference>) ois
2581
+ .readObject()) {
2582
+ remoteSailingServerSet.add(remoteSailingServerReference);
2583
+ logoutput.append("Received remote sailing server reference " + remoteSailingServerReference);
2584
+ }
2585
+
2586
+ // make sure to initialize listeners correctly
2587
+ for (Regatta regatta : regattasByName.values()) {
2588
+ RegattaImpl regattaImpl = (RegattaImpl) regatta;
2589
+ regattaImpl.initializeSeriesAfterDeserialize();
2590
+ regattaImpl.addRaceColumnListener(raceLogReplicator);
2591
+ }
2592
+ // re-establish RaceLogResolver references to this RacingEventService in all TrackedRace instances
2593
+ for (DynamicTrackedRegatta trackedRegatta : regattaTrackingCache.values()) {
2594
+ trackedRegatta.lockTrackedRacesForRead();
2595
+ try {
2596
+ for (TrackedRace trackedRace : trackedRegatta.getTrackedRaces()) {
2597
+ ((TrackedRaceImpl) trackedRace).setRaceLogResolver(this);
2598
+ }
2599
+ } finally {
2600
+ trackedRegatta.unlockTrackedRacesAfterRead();
2601
+ }
2602
+ }
2603
+ logger.info(logoutput.toString());
2604
+ }
2605
+
2606
+ @Override
2607
+ public void clearReplicaState() throws MalformedURLException, IOException, InterruptedException {
2608
+ logger.info("Clearing all data structures...");
2609
+ LockUtil.lockForWrite(regattasByNameLock);
2610
+ try {
2611
+ regattasByName.clear();
2612
+ } finally {
2613
+ LockUtil.unlockAfterWrite(regattasByNameLock);
2614
+ }
2615
+ regattasObservedForDefaultLeaderboard.clear();
2616
+
2617
+ if (raceTrackersByRegatta != null && !raceTrackersByRegatta.isEmpty()) {
2618
+ for (DynamicTrackedRegatta regatta : regattaTrackingCache.values()) {
2619
+ final Set<RaceTracker> trackers = raceTrackersByRegatta.get(regatta.getRegatta());
2620
+ if (trackers != null) {
2621
+ for (RaceTracker tracker : trackers) {
2622
+ tracker.stop(/* preemptive */true);
2623
+ }
2624
+ }
2625
+ }
2626
+ }
2627
+ LockUtil.lockForWrite(regattaTrackingCacheLock);
2628
+ try {
2629
+ regattaTrackingCache.clear();
2630
+ } finally {
2631
+ LockUtil.unlockAfterWrite(regattaTrackingCacheLock);
2632
+ }
2633
+ LockUtil.lockForWrite(raceTrackersByRegattaLock);
2634
+ try {
2635
+ raceTrackersByRegatta.clear();
2636
+ } finally {
2637
+ LockUtil.unlockAfterWrite(raceTrackersByRegattaLock);
2638
+ }
2639
+ LockUtil.lockForWrite(leaderboardGroupsByNameLock);
2640
+ try {
2641
+ leaderboardGroupsByName.clear();
2642
+ leaderboardGroupsByID.clear();
2643
+ } finally {
2644
+ LockUtil.unlockAfterWrite(leaderboardGroupsByNameLock);
2645
+ }
2646
+ LockUtil.lockForWrite(leaderboardsByNameLock);
2647
+ try {
2648
+ leaderboardsByName.clear();
2649
+ } finally {
2650
+ LockUtil.unlockAfterWrite(leaderboardsByNameLock);
2651
+ }
2652
+ eventsById.clear();
2653
+ mediaLibrary.clear();
2654
+ competitorStore.clear();
2655
+ remoteSailingServerSet.clear();
2656
+ }
2657
+
2658
+ // Used for TESTING only
2659
+ @Override
2660
+ public Event addEvent(String eventName, String eventDescription, TimePoint startDate, TimePoint endDate,
2661
+ String venue, boolean isPublic, UUID id) {
2662
+ Event result = createEventWithoutReplication(eventName, eventDescription, startDate, endDate, venue, isPublic,
2663
+ id, /* officialWebsiteURL */null, /* sailorsInfoWebsiteURLAsString */null,
2664
+ /* images */Collections.<ImageDescriptor> emptyList(), /* videos */Collections.<VideoDescriptor> emptyList());
2665
+ replicate(new CreateEvent(eventName, eventDescription, startDate, endDate, venue, isPublic, id,
2666
+ /* officialWebsiteURLAsString */null, /* sailorsInfoWebsiteURLAsString */null,
2667
+ /* images */Collections.<ImageDescriptor> emptyList(), /* videos */Collections.<VideoDescriptor> emptyList()));
2668
+ return result;
2669
+ }
2670
+
2671
+ @Override
2672
+ public void addEventWithoutReplication(Event event) {
2673
+ addEvent(event);
2674
+ }
2675
+
2676
+ @Override
2677
+ public Event createEventWithoutReplication(String eventName, String eventDescription, TimePoint startDate,
2678
+ TimePoint endDate, String venue, boolean isPublic, UUID id, URL officialWebsiteURL, URL sailorsInfoWebsiteURL,
2679
+ Iterable<ImageDescriptor> images, Iterable<VideoDescriptor> videos) {
2680
+ Event result = new EventImpl(eventName, startDate, endDate, venue, isPublic, id);
2681
+ addEvent(result);
2682
+ result.setDescription(eventDescription);
2683
+ result.setOfficialWebsiteURL(officialWebsiteURL);
2684
+ result.setImages(images);
2685
+ result.setVideos(videos);
2686
+ return result;
2687
+ }
2688
+
2689
+ private void addEvent(Event result) {
2690
+ if (eventsById.containsKey(result.getId())) {
2691
+ throw new IllegalArgumentException("Event with ID " + result.getId()
2692
+ + " already exists which is pretty surprising...");
2693
+ }
2694
+ eventsById.put(result.getId(), result);
2695
+ mongoObjectFactory.storeEvent(result);
2696
+ }
2697
+
2698
+ @Override
2699
+ public void updateEvent(UUID id, String eventName, String eventDescription, TimePoint startDate, TimePoint endDate,
2700
+ String venueName, boolean isPublic, Iterable<UUID> leaderboardGroupIds, URL officialWebsiteURL, URL sailorsInfoWebsiteURL,
2701
+ Iterable<ImageDescriptor> images, Iterable<VideoDescriptor> videos) {
2702
+ final Event event = eventsById.get(id);
2703
+ if (event == null) {
2704
+ throw new IllegalArgumentException("Sailing event with ID " + id + " does not exist.");
2705
+ }
2706
+ event.setName(eventName);
2707
+ event.setDescription(eventDescription);
2708
+ event.setStartDate(startDate);
2709
+ event.setEndDate(endDate);
2710
+ event.setPublic(isPublic);
2711
+ event.getVenue().setName(venueName);
2712
+ List<LeaderboardGroup> leaderboardGroups = new ArrayList<>();
2713
+ for (UUID lgid : leaderboardGroupIds) {
2714
+ LeaderboardGroup lg = getLeaderboardGroupByID(lgid);
2715
+ if (lg != null) {
2716
+ leaderboardGroups.add(lg);
2717
+ } else {
2718
+ logger.info("Couldn't find leaderboard group with ID " + lgid + " while updating event "
2719
+ + event.getName());
2720
+ }
2721
+ }
2722
+ event.setLeaderboardGroups(leaderboardGroups);
2723
+ event.setOfficialWebsiteURL(officialWebsiteURL);
2724
+ event.setSailorsInfoWebsiteURL(sailorsInfoWebsiteURL);
2725
+ event.setImages(images);
2726
+ event.setVideos(videos);
2727
+ // TODO consider use diffutils to compute diff between old and new leaderboard groups list and apply the patch
2728
+ // to keep changes minimial
2729
+ mongoObjectFactory.storeEvent(event);
2730
+ }
2731
+
2732
+ @Override
2733
+ public void renameEvent(UUID id, String newName) {
2734
+ final Event toRename = eventsById.get(id);
2735
+ if (toRename == null) {
2736
+ throw new IllegalArgumentException("No sailing event with ID " + id + " found.");
2737
+ }
2738
+ toRename.setName(newName);
2739
+ mongoObjectFactory.renameEvent(id, newName);
2740
+ replicate(new RenameEvent(id, newName));
2741
+ }
2742
+
2743
+ @Override
2744
+ public void removeEvent(UUID id) {
2745
+ removeEventFromEventsById(id);
2746
+ mongoObjectFactory.removeEvent(id);
2747
+ replicate(new RemoveEvent(id));
2748
+ }
2749
+
2750
+ protected void removeEventFromEventsById(Serializable id) {
2751
+ eventsById.remove(id);
2752
+ }
2753
+
2754
+ @Override
2755
+ public Regatta getRememberedRegattaForRace(Serializable raceID) {
2756
+ return persistentRegattasForRaceIDs.get(raceID.toString());
2757
+ }
2758
+
2759
+ /**
2760
+ * Persistently remembers the association of the race with its {@link RaceDefinition#getId()} to the
2761
+ * <code>regatta</code> with its {@link Regatta#getRegattaIdentifier() identifier} so that the next time
2762
+ * {@link #getRememberedRegattaForRace(RaceDefinition)} is called with <code>race</code> as argument,
2763
+ * <code>regatta</code> will be returned.
2764
+ */
2765
+ private void setRegattaForRace(Regatta regatta, RaceDefinition race) {
2766
+ setRegattaForRace(regatta, race.getId().toString());
2767
+ }
2768
+
2769
+ @Override
2770
+ public void setRegattaForRace(Regatta regatta, String raceIdAsString) {
2771
+ persistentRegattasForRaceIDs.put(raceIdAsString, regatta);
2772
+ mongoObjectFactory.storeRegattaForRaceID(raceIdAsString, regatta);
2773
+ }
2774
+
2775
+ @Override
2776
+ public CourseArea[] addCourseAreas(UUID eventId, String[] courseAreaNames, UUID[] courseAreaIds) {
2777
+ final CourseArea[] courseAreas = addCourseAreasWithoutReplication(eventId, courseAreaIds, courseAreaNames);
2778
+ replicate(new AddCourseAreas(eventId, courseAreaNames, courseAreaIds));
2779
+ return courseAreas;
2780
+ }
2781
+
2782
+ @Override
2783
+ public CourseArea[] addCourseAreasWithoutReplication(UUID eventId, UUID[] courseAreaIds, String[] courseAreaNames) {
2784
+ final CourseArea[] result = new CourseArea[courseAreaNames.length];
2785
+ for (int i=0; i<courseAreaIds.length; i++) {
2786
+ final CourseArea courseArea = getBaseDomainFactory().getOrCreateCourseArea(courseAreaIds[i], courseAreaNames[i]);
2787
+ final Event event = eventsById.get(eventId);
2788
+ if (event == null) {
2789
+ throw new IllegalArgumentException("No sailing event with ID " + eventId + " found.");
2790
+ }
2791
+ event.getVenue().addCourseArea(courseArea);
2792
+ mongoObjectFactory.storeEvent(event);
2793
+ result[i] = courseArea;
2794
+ }
2795
+ return result;
2796
+ }
2797
+
2798
+ @Override
2799
+ public CourseArea[] removeCourseAreaWithoutReplication(UUID eventId, UUID[] courseAreaIds) {
2800
+ final Event event = eventsById.get(eventId);
2801
+ if (event == null) {
2802
+ throw new IllegalArgumentException("No sailing event with ID " + eventId + " found.");
2803
+ }
2804
+ final CourseArea[] courseAreasRemoved = new CourseArea[courseAreaIds.length];
2805
+ int i=0;
2806
+ for (final UUID courseAreaId : courseAreaIds) {
2807
+ final CourseArea courseArea = getBaseDomainFactory().getExistingCourseAreaById(courseAreaId);
2808
+ if (courseArea == null) {
2809
+ throw new IllegalArgumentException("No course area with ID " + courseAreaId + " found.");
2810
+ }
2811
+ courseAreasRemoved[i++] = courseArea;
2812
+ event.getVenue().removeCourseArea(courseArea);
2813
+ mongoObjectFactory.storeEvent(event);
2814
+ }
2815
+ return courseAreasRemoved;
2816
+ }
2817
+
2818
+ @Override
2819
+ public void mediaTrackAdded(MediaTrack mediaTrack) {
2820
+ if (mediaTrack.dbId == null) {
2821
+ mediaTrack.dbId = mediaDB.insertMediaTrack(mediaTrack.title, mediaTrack.url, mediaTrack.startTime,
2822
+ mediaTrack.duration, mediaTrack.mimeType, mediaTrack.assignedRaces);
2823
+ }
2824
+ mediaLibrary.addMediaTrack(mediaTrack);
2825
+ replicate(new AddMediaTrackOperation(mediaTrack));
2826
+ }
2827
+
2828
+ @Override
2829
+ public void mediaTracksAdded(Collection<MediaTrack> mediaTracks) {
2830
+ mediaLibrary.addMediaTracks(mediaTracks);
2831
+ }
2832
+
2833
+ @Override
2834
+ public void mediaTrackTitleChanged(MediaTrack mediaTrack) {
2835
+ mediaDB.updateTitle(mediaTrack.dbId, mediaTrack.title);
2836
+ mediaLibrary.titleChanged(mediaTrack);
2837
+ replicate(new UpdateMediaTrackTitleOperation(mediaTrack));
2838
+ }
2839
+
2840
+ @Override
2841
+ public void mediaTrackUrlChanged(MediaTrack mediaTrack) {
2842
+ mediaDB.updateUrl(mediaTrack.dbId, mediaTrack.url);
2843
+ mediaLibrary.urlChanged(mediaTrack);
2844
+ replicate(new UpdateMediaTrackUrlOperation(mediaTrack));
2845
+ }
2846
+
2847
+ @Override
2848
+ public void mediaTrackStartTimeChanged(MediaTrack mediaTrack) {
2849
+ mediaDB.updateStartTime(mediaTrack.dbId, mediaTrack.startTime);
2850
+ mediaLibrary.startTimeChanged(mediaTrack);
2851
+ replicate(new UpdateMediaTrackStartTimeOperation(mediaTrack));
2852
+ }
2853
+
2854
+ @Override
2855
+ public void mediaTrackDurationChanged(MediaTrack mediaTrack) {
2856
+ mediaDB.updateDuration(mediaTrack.dbId, mediaTrack.duration);
2857
+ mediaLibrary.durationChanged(mediaTrack);
2858
+ replicate(new UpdateMediaTrackDurationOperation(mediaTrack));
2859
+ }
2860
+
2861
+ @Override
2862
+ public void mediaTrackAssignedRacesChanged(MediaTrack mediaTrack) {
2863
+ mediaDB.updateRace(mediaTrack.dbId, mediaTrack.assignedRaces);
2864
+ mediaLibrary.assignedRacesChanged(mediaTrack);
2865
+ replicate(new UpdateMediaTrackRacesOperation(mediaTrack));
2866
+
2867
+ }
2868
+
2869
+ @Override
2870
+ public void mediaTrackDeleted(MediaTrack mediaTrack) {
2871
+ mediaDB.deleteMediaTrack(mediaTrack.dbId);
2872
+ mediaLibrary.deleteMediaTrack(mediaTrack);
2873
+ replicate(new RemoveMediaTrackOperation(mediaTrack));
2874
+ }
2875
+
2876
+ @Override
2877
+ public void mediaTracksImported(Collection<MediaTrack> mediaTracksToImport, boolean override) {
2878
+ for (MediaTrack trackToImport : mediaTracksToImport) {
2879
+ MediaTrack existingTrack = mediaLibrary.lookupMediaTrack(trackToImport);
2880
+ if (existingTrack == null) {
2881
+ mediaDB.insertMediaTrackWithId(trackToImport.dbId, trackToImport.title, trackToImport.url,
2882
+ trackToImport.startTime, trackToImport.duration, trackToImport.mimeType,
2883
+ trackToImport.assignedRaces);
2884
+ mediaTrackAdded(trackToImport);
2885
+ } else if (override) {
2886
+
2887
+ // Using fine-grained update methods.
2888
+ // Rationale: Changes on more than one track property are rare
2889
+ // and don't justify the introduction of a new set
2890
+ // of methods (including replication).
2891
+ if (!Util.equalsWithNull(existingTrack.title, trackToImport.title)) {
2892
+ mediaTrackTitleChanged(trackToImport);
2893
+ }
2894
+ if (!Util.equalsWithNull(existingTrack.url, trackToImport.url)) {
2895
+ mediaTrackUrlChanged(trackToImport);
2896
+ }
2897
+ if (!Util.equalsWithNull(existingTrack.startTime, trackToImport.startTime)) {
2898
+ mediaTrackStartTimeChanged(trackToImport);
2899
+ }
2900
+ if (!Util.equalsWithNull(existingTrack.duration, trackToImport.duration)) {
2901
+ mediaTrackDurationChanged(trackToImport);
2902
+ }
2903
+ if (!Util.equalsWithNull(existingTrack.assignedRaces, trackToImport.assignedRaces)) {
2904
+ mediaTrackAssignedRacesChanged(trackToImport);
2905
+ }
2906
+ }
2907
+ }
2908
+ }
2909
+
2910
+ @Override
2911
+ public Collection<MediaTrack> getMediaTracksForRace(RegattaAndRaceIdentifier regattaAndRaceIdentifier) {
2912
+ return mediaLibrary.findMediaTracksForRace(regattaAndRaceIdentifier);
2913
+ }
2914
+
2915
+ @Override
2916
+ public Collection<MediaTrack> getMediaTracksInTimeRange(RegattaAndRaceIdentifier regattaAndRaceIdentifier) {
2917
+ TrackedRace trackedRace = getExistingTrackedRace(regattaAndRaceIdentifier);
2918
+ if (trackedRace != null) {
2919
+ if (trackedRace.isLive(MillisecondsTimePoint.now())) {
2920
+ return mediaLibrary.findLiveMediaTracks();
2921
+ } else {
2922
+ TimePoint raceStart = trackedRace.getStartOfRace() == null ? trackedRace.getStartOfTracking()
2923
+ : trackedRace.getStartOfRace();
2924
+ TimePoint raceEnd = trackedRace.getEndOfRace() == null ? trackedRace.getEndOfTracking() : trackedRace
2925
+ .getEndOfRace();
2926
+ return mediaLibrary.findMediaTracksInTimeRange(raceStart, raceEnd);
2927
+ }
2928
+ } else {
2929
+ return Collections.emptyList();
2930
+ }
2931
+ }
2932
+
2933
+ @Override
2934
+ public Collection<MediaTrack> getAllMediaTracks() {
2935
+ return mediaLibrary.allTracks();
2936
+ }
2937
+
2938
+ public String toString() {
2939
+ return "RacingEventService: " + this.hashCode() + " Build: " + ServerInfo.getBuildVersion();
2940
+ }
2941
+
2942
+ @Override
2943
+ public void reloadRaceLog(String leaderboardName, String raceColumnName, String fleetName) {
2944
+ Leaderboard leaderboard = getLeaderboardByName(leaderboardName);
2945
+ if (leaderboard != null) {
2946
+ RaceColumn raceColumn = leaderboard.getRaceColumnByName(raceColumnName);
2947
+ if (raceColumn != null) {
2948
+ Fleet fleetImpl = raceColumn.getFleetByName(fleetName);
2949
+ RaceLog racelog = raceColumn.getRaceLog(fleetImpl);
2950
+ if (racelog != null) {
2951
+ raceColumn.reloadRaceLog(fleetImpl);
2952
+ logger.info("Reloaded race log for fleet " + fleetImpl + " for race column " + raceColumn.getName()
2953
+ + " for leaderboard " + leaderboard.getName());
2954
+ }
2955
+ }
2956
+ }
2957
+ }
2958
+
2959
+ @Override
2960
+ public ConcurrentHashMap<String, Regatta> getPersistentRegattasForRaceIDs() {
2961
+ return persistentRegattasForRaceIDs;
2962
+ }
2963
+
2964
+ @Override
2965
+ public WindStore getWindStore() {
2966
+ return windStore;
2967
+ }
2968
+
2969
+ @Override
2970
+ public DeviceConfiguration getDeviceConfiguration(DeviceConfigurationIdentifier identifier) {
2971
+ return configurationMap.getByMatch(identifier);
2972
+ }
2973
+
2974
+ @Override
2975
+ public void createOrUpdateDeviceConfiguration(DeviceConfigurationMatcher matcher, DeviceConfiguration configuration) {
2976
+ configurationMap.put(matcher, configuration);
2977
+ mongoObjectFactory.storeDeviceConfiguration(matcher, configuration);
2978
+ replicate(new CreateOrUpdateDeviceConfiguration(matcher, configuration));
2979
+ }
2980
+
2981
+ @Override
2982
+ public void removeDeviceConfiguration(DeviceConfigurationMatcher matcher) {
2983
+ configurationMap.remove(matcher);
2984
+ mongoObjectFactory.removeDeviceConfiguration(matcher);
2985
+ replicate(new RemoveDeviceConfiguration(matcher));
2986
+ }
2987
+
2988
+ @Override
2989
+ public Map<DeviceConfigurationMatcher, DeviceConfiguration> getAllDeviceConfigurations() {
2990
+ return new HashMap<DeviceConfigurationMatcher, DeviceConfiguration>(configurationMap);
2991
+ }
2992
+
2993
+ @Override
2994
+ public TimePoint setStartTimeAndProcedure(String leaderboardName, String raceColumnName, String fleetName,
2995
+ String authorName, int authorPriority, int passId, TimePoint logicalTimePoint, TimePoint startTime,
2996
+ RacingProcedureType racingProcedure) {
2997
+ RaceLog raceLog = getRaceLog(leaderboardName, raceColumnName, fleetName);
2998
+ Leaderboard leaderboard = getLeaderboardByName(leaderboardName);
2999
+ final TimePoint result;
3000
+ if (leaderboard instanceof HasRegattaLike && raceLog != null) {
3001
+ RaceState state = RaceStateImpl.create(/* race log resolver */ this, raceLog, new LogEventAuthorImpl(authorName, authorPriority));
3002
+ if (passId > raceLog.getCurrentPassId()) {
3003
+ state.setAdvancePass(logicalTimePoint);
3004
+ }
3005
+ state.setRacingProcedure(logicalTimePoint, racingProcedure);
3006
+ state.forceNewStartTime(logicalTimePoint, startTime);
3007
+ result = state.getStartTime();
3008
+ } else {
3009
+ result = null;
3010
+ }
3011
+ return result;
3012
+ }
3013
+
3014
+ public RaceLog getRaceLog(String leaderboardName, String raceColumnName, String fleetName) {
3015
+ Leaderboard leaderboard = getLeaderboardByName(leaderboardName);
3016
+ if (leaderboard != null) {
3017
+ RaceColumn raceColumn = leaderboard.getRaceColumnByName(raceColumnName);
3018
+ if (raceColumn != null) {
3019
+ Fleet fleetImpl = raceColumn.getFleetByName(fleetName);
3020
+ return raceColumn.getRaceLog(fleetImpl);
3021
+ }
3022
+ }
3023
+ return null;
3024
+ }
3025
+
3026
+ @Override
3027
+ public com.sap.sse.common.Util.Triple<TimePoint, Integer, RacingProcedureType> getStartTimeAndProcedure(
3028
+ String leaderboardName, String raceColumnName, String fleetName) {
3029
+ RaceLog raceLog = getRaceLog(leaderboardName, raceColumnName, fleetName);
3030
+ Leaderboard leaderboard = getLeaderboardByName(leaderboardName);
3031
+ final Triple<TimePoint, Integer, RacingProcedureType> result;
3032
+ if (leaderboard instanceof HasRegattaLike && raceLog != null) {
3033
+ ReadonlyRaceState state = ReadonlyRaceStateImpl.create(/* race log resolver */ this, raceLog);
3034
+ result = new com.sap.sse.common.Util.Triple<TimePoint, Integer, RacingProcedureType>(state.getStartTime(),
3035
+ raceLog.getCurrentPassId(), state.getRacingProcedure().getType());
3036
+ } else {
3037
+ result = null;
3038
+ }
3039
+ return result;
3040
+ }
3041
+
3042
+ private Iterable<WindTrackerFactory> getWindTrackerFactories() {
3043
+ final Set<WindTrackerFactory> result;
3044
+ if (bundleContext == null) {
3045
+ result = Collections.singleton((WindTrackerFactory) ExpeditionWindTrackerFactory.getInstance());
3046
+ } else {
3047
+ ServiceTracker<WindTrackerFactory, WindTrackerFactory> tracker = new ServiceTracker<WindTrackerFactory, WindTrackerFactory>(
3048
+ bundleContext, WindTrackerFactory.class.getName(), null);
3049
+ tracker.open();
3050
+ result = new HashSet<>();
3051
+ for (WindTrackerFactory factory : tracker.getServices(new WindTrackerFactory[0])) {
3052
+ result.add(factory);
3053
+ }
3054
+ }
3055
+ return result;
3056
+ }
3057
+
3058
+ @Override
3059
+ public GPSFixStore getGPSFixStore() {
3060
+ return gpsFixStore;
3061
+ }
3062
+
3063
+ @Override
3064
+ public RaceTracker getRaceTrackerById(Object id) {
3065
+ return raceTrackersByID.get(id);
3066
+ }
3067
+
3068
+ @Override
3069
+ public AbstractLogEventAuthor getServerAuthor() {
3070
+ return raceLogEventAuthorForServer;
3071
+ }
3072
+
3073
+ @Override
3074
+ public CompetitorStore getCompetitorStore() {
3075
+ return competitorStore;
3076
+ }
3077
+
3078
+ @Override
3079
+ public TypeBasedServiceFinderFactory getTypeBasedServiceFinderFactory() {
3080
+ return serviceFinderFactory;
3081
+ }
3082
+
3083
+ @Override
3084
+ public DataImportLockWithProgress getDataImportLock() {
3085
+ return dataImportLock;
3086
+ }
3087
+
3088
+ @Override
3089
+ public DataImportProgress createOrUpdateDataImportProgressWithReplication(UUID importOperationId,
3090
+ double overallProgressPct, String subProgressName, double subProgressPct) {
3091
+ // Create/Update locally
3092
+ DataImportProgress progress = createOrUpdateDataImportProgressWithoutReplication(importOperationId,
3093
+ overallProgressPct, subProgressName, subProgressPct);
3094
+ // Create/Update on replicas
3095
+ replicate(new CreateOrUpdateDataImportProgress(importOperationId, overallProgressPct, subProgressName,
3096
+ subProgressPct));
3097
+ return progress;
3098
+ }
3099
+
3100
+ @Override
3101
+ public DataImportProgress createOrUpdateDataImportProgressWithoutReplication(UUID importOperationId,
3102
+ double overallProgressPct, String subProgressName, double subProgressPct) {
3103
+ DataImportProgress progress = dataImportLock.getProgress(importOperationId);
3104
+ boolean newObject = false;
3105
+ if (progress == null) {
3106
+ progress = new DataImportProgressImpl(importOperationId);
3107
+ newObject = true;
3108
+ }
3109
+ progress.setOverAllProgressPct(overallProgressPct);
3110
+ progress.setNameOfCurrentSubProgress(subProgressName);
3111
+ progress.setCurrentSubProgressPct(subProgressPct);
3112
+ if (newObject) {
3113
+ dataImportLock.addProgress(importOperationId, progress);
3114
+ }
3115
+ return progress;
3116
+ }
3117
+
3118
+ @Override
3119
+ public void setDataImportFailedWithoutReplication(UUID importOperationId, String errorMessage) {
3120
+ DataImportProgress progress = dataImportLock.getProgress(importOperationId);
3121
+ if (progress != null) {
3122
+ progress.setFailed();
3123
+ progress.setErrorMessage(errorMessage);
3124
+ }
3125
+ }
3126
+
3127
+ @Override
3128
+ public void setDataImportFailedWithReplication(UUID importOperationId, String errorMessage) {
3129
+ setDataImportFailedWithoutReplication(importOperationId, errorMessage);
3130
+ replicate(new DataImportFailed(importOperationId, errorMessage));
3131
+ }
3132
+
3133
+ @Override
3134
+ public void setDataImportDeleteProgressFromMapTimerWithReplication(UUID importOperationId) {
3135
+ setDataImportDeleteProgressFromMapTimerWithoutReplication(importOperationId);
3136
+ replicate(new SetDataImportDeleteProgressFromMapTimer(importOperationId));
3137
+ }
3138
+
3139
+ @Override
3140
+ public void setDataImportDeleteProgressFromMapTimerWithoutReplication(UUID importOperationId) {
3141
+ dataImportLock.setDeleteFromMapTimer(importOperationId);
3142
+ }
3143
+
3144
+ @Override
3145
+ public Result<LeaderboardSearchResult> search(KeywordQuery query) {
3146
+ long start = System.currentTimeMillis();
3147
+ logger.info("Searching local server for " + query);
3148
+ Result<LeaderboardSearchResult> result = new RegattaByKeywordSearchService().search(this, query);
3149
+ logger.fine("Search for " + query + " took " + (System.currentTimeMillis() - start) + "ms");
3150
+ return result;
3151
+ }
3152
+
3153
+ @Override
3154
+ public Result<LeaderboardSearchResultBase> searchRemotely(String remoteServerReferenceName, KeywordQuery query) {
3155
+ long start = System.currentTimeMillis();
3156
+ ResultImpl<LeaderboardSearchResultBase> result = null;
3157
+ RemoteSailingServerReference remoteRef = remoteSailingServerSet
3158
+ .getServerReferenceByName(remoteServerReferenceName);
3159
+ if (remoteRef == null) {
3160
+ result = null;
3161
+ } else {
3162
+ BufferedReader bufferedReader = null;
3163
+ try {
3164
+ try {
3165
+ final URL eventsURL = new URL(remoteRef.getURL(), "sailingserver/api/v1/search?q="
3166
+ + URLEncoder.encode(query.toString(), "UTF-8"));
3167
+ logger.info("Searching remote server " + remoteRef + " for " + query);
3168
+ URLConnection urlConnection = eventsURL.openConnection();
3169
+ urlConnection.connect();
3170
+ bufferedReader = new BufferedReader(new InputStreamReader(urlConnection.getInputStream(), "UTF-8"));
3171
+ JSONParser parser = new JSONParser();
3172
+ Object eventsAsObject = parser.parse(bufferedReader);
3173
+ final LeaderboardGroupBaseJsonDeserializer leaderboardGroupBaseJsonDeserializer = new LeaderboardGroupBaseJsonDeserializer();
3174
+ LeaderboardSearchResultBaseJsonDeserializer deserializer = new LeaderboardSearchResultBaseJsonDeserializer(
3175
+ new EventBaseJsonDeserializer(new VenueJsonDeserializer(new CourseAreaJsonDeserializer(
3176
+ DomainFactory.INSTANCE)), leaderboardGroupBaseJsonDeserializer),
3177
+ leaderboardGroupBaseJsonDeserializer);
3178
+ result = new ResultImpl<LeaderboardSearchResultBase>(query,
3179
+ new LeaderboardSearchResultBaseRanker<LeaderboardSearchResultBase>());
3180
+ JSONArray hitsAsJsonArray = (JSONArray) eventsAsObject;
3181
+ for (Object hitAsObject : hitsAsJsonArray) {
3182
+ JSONObject hitAsJson = (JSONObject) hitAsObject;
3183
+ LeaderboardSearchResultBase hit = deserializer.deserialize(hitAsJson);
3184
+ result.addHit(hit);
3185
+ }
3186
+ } finally {
3187
+ if (bufferedReader != null) {
3188
+ bufferedReader.close();
3189
+ }
3190
+ }
3191
+ } catch (IOException | ParseException e) {
3192
+ logger.log(Level.INFO,
3193
+ "Exception trying to fetch events from remote server " + remoteRef + ": " + e.getMessage(), e);
3194
+ }
3195
+ }
3196
+ logger.fine("Remote search on " + remoteRef + " for " + query + " took " + (System.currentTimeMillis() - start)
3197
+ + "ms");
3198
+ return result;
3199
+ }
3200
+
3201
+ @Override
3202
+ public ReplicationMasterDescriptor getMasterDescriptor() {
3203
+ return replicatingFromMaster;
3204
+ }
3205
+
3206
+ @Override
3207
+ public void startedReplicatingFrom(ReplicationMasterDescriptor master) {
3208
+ this.replicatingFromMaster = master;
3209
+ }
3210
+
3211
+ @Override
3212
+ public void stoppedReplicatingFrom(ReplicationMasterDescriptor master) {
3213
+ this.replicatingFromMaster = null;
3214
+ }
3215
+
3216
+ @Override
3217
+ public void addOperationSentToMasterForReplication(
3218
+ OperationWithResultWithIdWrapper<RacingEventService, ?> operationWithResultWithIdWrapper) {
3219
+ this.operationsSentToMasterForReplication.add(operationWithResultWithIdWrapper);
3220
+ }
3221
+
3222
+ @Override
3223
+ public boolean hasSentOperationToMaster(OperationWithResult<RacingEventService, ?> operation) {
3224
+ return this.operationsSentToMasterForReplication.remove(operation);
3225
+ }
3226
+
3227
+ @Override
3228
+ public FileStorageManagementService getFileStorageManagementService() {
3229
+ ServiceReference<FileStorageManagementService> ref = bundleContext
3230
+ .getServiceReference(FileStorageManagementService.class);
3231
+ if (ref == null) {
3232
+ logger.warning("No file storage management service registered");
3233
+ return null;
3234
+ }
3235
+ return bundleContext.getService(ref);
3236
+ }
3237
+
3238
+ public void addMasterDataClassLoader(ClassLoader classLoader) {
3239
+ masterDataClassLoaders.add(classLoader);
3240
+ }
3241
+
3242
+ public void removeMasterDataClassLoader(ClassLoader classLoader) {
3243
+ masterDataClassLoaders.remove(classLoader);
3244
+ }
3245
+
3246
+ @Override
3247
+ public ClassLoader getCombinedMasterDataClassLoader() {
3248
+ JoinedClassLoader joinedClassLoader = new JoinedClassLoader(masterDataClassLoaders);
3249
+ return joinedClassLoader;
3250
+ }
3251
+
3252
+ public void setPolarDataService(PolarDataService service) {
3253
+ if (this.polarDataService == null && service != null) {
3254
+ polarDataService = service;
3255
+ polarDataService.registerDomainFactory(baseDomainFactory);
3256
+ setPolarDataServiceOnAllTrackedRaces(service);
3257
+ }
3258
+ }
3259
+
3260
+ private void setPolarDataServiceOnAllTrackedRaces(PolarDataService service) {
3261
+ Iterable<Regatta> allRegattas = getAllRegattas();
3262
+ for (Regatta regatta : allRegattas) {
3263
+ DynamicTrackedRegatta trackedRegatta = getTrackedRegatta(regatta);
3264
+ if (trackedRegatta != null) {
3265
+ trackedRegatta.lockTrackedRacesForRead();
3266
+ try {
3267
+ Iterable<DynamicTrackedRace> trackedRaces = trackedRegatta.getTrackedRaces();
3268
+ for (TrackedRace trackedRace : trackedRaces) {
3269
+ trackedRace.setPolarDataService(service);
3270
+ service.insertExistingFixes(trackedRace);
3271
+ }
3272
+ } finally {
3273
+ trackedRegatta.unlockTrackedRacesAfterRead();
3274
+ }
3275
+ }
3276
+ }
3277
+ }
3278
+
3279
+ public void unsetPolarDataService(PolarDataService service) {
3280
+ if (polarDataService == service) {
3281
+ polarDataService.unregisterDomainFactory(baseDomainFactory);
3282
+ polarDataService = null;
3283
+ setPolarDataService(null);
3284
+ }
3285
+ }
3286
+
3287
+ @Override
3288
+ public Iterable<Competitor> getCompetitorInOrderOfWindwardDistanceTraveledFarthestFirst(TrackedRace trackedRace, TimePoint timePoint) {
3289
+ final RankingInfo rankingInfo = trackedRace.getRankingMetric().getRankingInfo(timePoint);
3290
+ final List<Competitor> result = new ArrayList<>();
3291
+ final Map<Competitor, Distance> windwardDistanceSailedPerCompetitor = new HashMap<>();
3292
+ for (final Competitor competitor : trackedRace.getRace().getCompetitors()) {
3293
+ result.add(competitor);
3294
+ final CompetitorRankingInfo competitorRankingInfo = rankingInfo.getCompetitorRankingInfo().apply(competitor);
3295
+ windwardDistanceSailedPerCompetitor.put(competitor, competitorRankingInfo == null ? null : competitorRankingInfo.getWindwardDistanceSailed());
3296
+ }
3297
+ final Comparator<Distance> durationComparatorNullsLast = Comparator.nullsLast(Comparator.naturalOrder());
3298
+ result.sort((c1, c2) -> durationComparatorNullsLast.compare(windwardDistanceSailedPerCompetitor.get(c2),
3299
+ windwardDistanceSailedPerCompetitor.get(c1)));
3300
+ return result;
3301
+ }
3302
+
3303
+ /**
3304
+ * A {@link SimpleRaceLogIdentifier} in particular has a {@link SimpleRaceLogIdentifier#getRegattaLikeParentName()}
3305
+ * which identifies either a regatta by name or a flexible leaderboard by name. Here is why this can luckily be
3306
+ * resolved unanimously: A regatta leaderboard always uses as its name the regatta name (see
3307
+ * {@link RegattaImpl#getName()}). Trying to {@link RegattaLeaderboardImpl#setName(String) set} the regatta leaderboard's
3308
+ * name can only update its {@link Leaderboard#getDisplayName() display name}. Therefore, regatta leaderboards are always
3309
+ * keyed in {@link #leaderboardsByName} by their regatta's name. Thus, no flexible leaderboard can have a regatta's name
3310
+ * as its name, and therefore leaderboard names <em>and</em> regatta names are unitedly unique.
3311
+ */
3312
+ @Override
3313
+ public RaceLog resolve(SimpleRaceLogIdentifier identifier) {
3314
+ final RaceLog result;
3315
+ final IsRegattaLike regattaLike;
3316
+ final Regatta regatta = regattasByName.get(identifier.getRegattaLikeParentName());
3317
+ if (regatta != null) {
3318
+ regattaLike = regatta;
3319
+ } else {
3320
+ final Leaderboard leaderboard = leaderboardsByName.get(identifier.getRegattaLikeParentName());
3321
+ if (leaderboard != null && leaderboard instanceof FlexibleLeaderboard) {
3322
+ regattaLike = (FlexibleLeaderboard) leaderboard;
3323
+ } else {
3324
+ regattaLike = null;
3325
+ }
3326
+ }
3327
+ if (regattaLike != null) {
3328
+ final RaceColumn raceColumn = regattaLike.getRaceColumnByName(identifier.getRaceColumnName());
3329
+ if (raceColumn != null) {
3330
+ final Fleet fleet = raceColumn.getFleetByName(identifier.getFleetName());
3331
+ if (fleet != null) {
3332
+ result = raceColumn.getRaceLog(fleet);
3333
+ } else {
3334
+ result = null;
3335
+ }
3336
+ } else {
3337
+ result = null;
3338
+ }
3339
+ } else {
3340
+ result = null;
3341
+ }
3342
+ return result;
3343
+ }
3344
+}
java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/CreateEvent.java
... ...
@@ -1,70 +1,70 @@
1
-package com.sap.sailing.server.operationaltransformation;
2
-
3
-import java.net.URL;
4
-import java.util.UUID;
5
-
6
-import com.sap.sailing.domain.base.Event;
7
-import com.sap.sailing.server.RacingEventService;
8
-import com.sap.sailing.server.RacingEventServiceOperation;
9
-import com.sap.sse.common.TimePoint;
10
-import com.sap.sse.common.media.ImageDescriptor;
11
-import com.sap.sse.common.media.VideoDescriptor;
12
-
13
-/**
14
- * Creates an {@link Event} in the server, with a new venue and an empty course area list.
15
- * See the {@link AddCourseAreas} operation for adding course areas to the event's venue.
16
- *
17
- * @author Axel Uhl (d043530)
18
- *
19
- */
20
-public class CreateEvent extends AbstractEventOperation<Event> {
21
- private static final long serialVersionUID = 308389324918359960L;
22
- private final String venue;
23
- private final TimePoint startDate;
24
- private final TimePoint endDate;
25
- private final boolean isPublic;
26
- private final String eventName;
27
- private final String eventDescription;
28
- private final Iterable<ImageDescriptor> images;
29
- private final Iterable<VideoDescriptor> videos;
30
- private final URL officialWebsiteURL;
31
- private final URL sailorsInfoWebsiteURL;
32
-
33
- public CreateEvent(String eventName, String eventDescription, TimePoint startDate, TimePoint endDate, String venue,
34
- boolean isPublic, UUID id, URL officialWebsiteURL, URL sailorsInfoWebsiteURL, Iterable<ImageDescriptor> images, Iterable<VideoDescriptor> videos) {
35
- super(id);
36
- this.eventName = eventName;
37
- this.eventDescription = eventDescription;
38
- this.startDate = startDate;
39
- this.endDate = endDate;
40
- this.venue = venue;
41
- this.isPublic = isPublic;
42
- this.officialWebsiteURL = officialWebsiteURL;
43
- this.sailorsInfoWebsiteURL = sailorsInfoWebsiteURL;
44
- this.images = images;
45
- this.videos = videos;
46
- }
47
-
48
- @Override
49
- public RacingEventServiceOperation<?> transformClientOp(RacingEventServiceOperation<?> serverOp) {
50
- // TODO Auto-generated method stub
51
- return null;
52
- }
53
-
54
- @Override
55
- public RacingEventServiceOperation<?> transformServerOp(RacingEventServiceOperation<?> clientOp) {
56
- // TODO Auto-generated method stub
57
- return null;
58
- }
59
-
60
- protected String getEventName() {
61
- return eventName;
62
- }
63
-
64
- @Override
65
- public Event internalApplyTo(RacingEventService toState) {
66
- return toState.createEventWithoutReplication(getEventName(), eventDescription, startDate, endDate, venue, isPublic,
67
- getId(), officialWebsiteURL, sailorsInfoWebsiteURL, images, videos);
68
- }
69
-
70
-}
1
+package com.sap.sailing.server.operationaltransformation;
2
+
3
+import java.net.URL;
4
+import java.util.UUID;
5
+
6
+import com.sap.sailing.domain.base.Event;
7
+import com.sap.sailing.server.RacingEventService;
8
+import com.sap.sailing.server.RacingEventServiceOperation;
9
+import com.sap.sse.common.TimePoint;
10
+import com.sap.sse.shared.media.ImageDescriptor;
11
+import com.sap.sse.shared.media.VideoDescriptor;
12
+
13
+/**
14
+ * Creates an {@link Event} in the server, with a new venue and an empty course area list.
15
+ * See the {@link AddCourseAreas} operation for adding course areas to the event's venue.
16
+ *
17
+ * @author Axel Uhl (d043530)
18
+ *
19
+ */
20
+public class CreateEvent extends AbstractEventOperation<Event> {
21
+ private static final long serialVersionUID = 308389324918359960L;
22
+ private final String venue;
23
+ private final TimePoint startDate;
24
+ private final TimePoint endDate;
25
+ private final boolean isPublic;
26
+ private final String eventName;
27
+ private final String eventDescription;
28
+ private final Iterable<ImageDescriptor> images;
29
+ private final Iterable<VideoDescriptor> videos;
30
+ private final URL officialWebsiteURL;
31
+ private final URL sailorsInfoWebsiteURL;
32
+
33
+ public CreateEvent(String eventName, String eventDescription, TimePoint startDate, TimePoint endDate, String venue,
34
+ boolean isPublic, UUID id, URL officialWebsiteURL, URL sailorsInfoWebsiteURL, Iterable<ImageDescriptor> images, Iterable<VideoDescriptor> videos) {
35
+ super(id);
36
+ this.eventName = eventName;
37
+ this.eventDescription = eventDescription;
38
+ this.startDate = startDate;
39
+ this.endDate = endDate;
40
+ this.venue = venue;
41
+ this.isPublic = isPublic;
42
+ this.officialWebsiteURL = officialWebsiteURL;
43
+ this.sailorsInfoWebsiteURL = sailorsInfoWebsiteURL;
44
+ this.images = images;
45
+ this.videos = videos;
46
+ }
47
+
48
+ @Override
49
+ public RacingEventServiceOperation<?> transformClientOp(RacingEventServiceOperation<?> serverOp) {
50
+ // TODO Auto-generated method stub
51
+ return null;
52
+ }
53
+
54
+ @Override
55
+ public RacingEventServiceOperation<?> transformServerOp(RacingEventServiceOperation<?> clientOp) {
56
+ // TODO Auto-generated method stub
57
+ return null;
58
+ }
59
+
60
+ protected String getEventName() {
61
+ return eventName;
62
+ }
63
+
64
+ @Override
65
+ public Event internalApplyTo(RacingEventService toState) {
66
+ return toState.createEventWithoutReplication(getEventName(), eventDescription, startDate, endDate, venue, isPublic,
67
+ getId(), officialWebsiteURL, sailorsInfoWebsiteURL, images, videos);
68
+ }
69
+
70
+}
java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/UpdateEvent.java
... ...
@@ -1,61 +1,61 @@
1
-package com.sap.sailing.server.operationaltransformation;
2
-
3
-import java.net.URL;
4
-import java.util.UUID;
5
-
6
-import com.sap.sailing.server.RacingEventService;
7
-import com.sap.sailing.server.RacingEventServiceOperation;
8
-import com.sap.sse.common.TimePoint;
9
-import com.sap.sse.common.media.ImageDescriptor;
10
-import com.sap.sse.common.media.VideoDescriptor;
11
-
12
-public class UpdateEvent extends AbstractEventOperation<Void> {
13
- private static final long serialVersionUID = -8271559266421161532L;
14
- private final String venueName;
15
- private final TimePoint startDate;
16
- private final TimePoint endDate;
17
- private final boolean isPublic;
18
- private final Iterable<UUID> leaderboardGroupIds;
19
- private final String eventName;
20
- private final String eventDescription;
21
- private final URL officialWebsiteURL;
22
- private final URL sailorsInfoWebsiteURL;
23
- private final Iterable<ImageDescriptor> images;
24
- private final Iterable<VideoDescriptor> videos;
25
-
26
- public UpdateEvent(UUID id, String eventName, String eventDescription, TimePoint startDate, TimePoint endDate,
27
- String venueName, boolean isPublic, Iterable<UUID> leaderboardGroupIds, URL officialWebsiteURL, URL sailorsInfoWebsiteURL,
28
- Iterable<ImageDescriptor> images, Iterable<VideoDescriptor> videos) {
29
- super(id);
30
- this.eventName = eventName;
31
- this.eventDescription = eventDescription;
32
- this.startDate = startDate;
33
- this.endDate = endDate;
34
- this.venueName = venueName;
35
- this.isPublic = isPublic;
36
- this.leaderboardGroupIds = leaderboardGroupIds;
37
- this.officialWebsiteURL = officialWebsiteURL;
38
- this.sailorsInfoWebsiteURL = sailorsInfoWebsiteURL;
39
- this.images = images;
40
- this.videos = videos;
41
- }
42
-
43
- @Override
44
- public RacingEventServiceOperation<?> transformClientOp(RacingEventServiceOperation<?> serverOp) {
45
- // TODO Auto-generated method stub
46
- return null;
47
- }
48
-
49
- @Override
50
- public RacingEventServiceOperation<?> transformServerOp(RacingEventServiceOperation<?> clientOp) {
51
- // TODO Auto-generated method stub
52
- return null;
53
- }
54
-
55
- @Override
56
- public Void internalApplyTo(RacingEventService toState) {
57
- toState.updateEvent(getId(), eventName, eventDescription, startDate, endDate, venueName, isPublic,
58
- leaderboardGroupIds, officialWebsiteURL, sailorsInfoWebsiteURL, images, videos);
59
- return null;
60
- }
61
-}
1
+package com.sap.sailing.server.operationaltransformation;
2
+
3
+import java.net.URL;
4
+import java.util.UUID;
5
+
6
+import com.sap.sailing.server.RacingEventService;
7
+import com.sap.sailing.server.RacingEventServiceOperation;
8
+import com.sap.sse.common.TimePoint;
9
+import com.sap.sse.shared.media.ImageDescriptor;
10
+import com.sap.sse.shared.media.VideoDescriptor;
11
+
12
+public class UpdateEvent extends AbstractEventOperation<Void> {
13
+ private static final long serialVersionUID = -8271559266421161532L;
14
+ private final String venueName;
15
+ private final TimePoint startDate;
16
+ private final TimePoint endDate;
17
+ private final boolean isPublic;
18
+ private final Iterable<UUID> leaderboardGroupIds;
19
+ private final String eventName;
20
+ private final String eventDescription;
21
+ private final URL officialWebsiteURL;
22
+ private final URL sailorsInfoWebsiteURL;
23
+ private final Iterable<ImageDescriptor> images;
24
+ private final Iterable<VideoDescriptor> videos;
25
+
26
+ public UpdateEvent(UUID id, String eventName, String eventDescription, TimePoint startDate, TimePoint endDate,
27
+ String venueName, boolean isPublic, Iterable<UUID> leaderboardGroupIds, URL officialWebsiteURL, URL sailorsInfoWebsiteURL,
28
+ Iterable<ImageDescriptor> images, Iterable<VideoDescriptor> videos) {
29
+ super(id);
30
+ this.eventName = eventName;
31
+ this.eventDescription = eventDescription;
32
+ this.startDate = startDate;
33
+ this.endDate = endDate;
34
+ this.venueName = venueName;
35
+ this.isPublic = isPublic;
36
+ this.leaderboardGroupIds = leaderboardGroupIds;
37
+ this.officialWebsiteURL = officialWebsiteURL;
38
+ this.sailorsInfoWebsiteURL = sailorsInfoWebsiteURL;
39
+ this.images = images;
40
+ this.videos = videos;
41
+ }
42
+
43
+ @Override
44
+ public RacingEventServiceOperation<?> transformClientOp(RacingEventServiceOperation<?> serverOp) {
45
+ // TODO Auto-generated method stub
46
+ return null;
47
+ }
48
+
49
+ @Override
50
+ public RacingEventServiceOperation<?> transformServerOp(RacingEventServiceOperation<?> clientOp) {
51
+ // TODO Auto-generated method stub
52
+ return null;
53
+ }
54
+
55
+ @Override
56
+ public Void internalApplyTo(RacingEventService toState) {
57
+ toState.updateEvent(getId(), eventName, eventDescription, startDate, endDate, venueName, isPublic,
58
+ leaderboardGroupIds, officialWebsiteURL, sailorsInfoWebsiteURL, images, videos);
59
+ return null;
60
+ }
61
+}
java/com.sap.sse.common/pom.xml
... ...
@@ -11,30 +11,30 @@
11 11
<packaging>eclipse-plugin</packaging>
12 12
13 13
<build>
14
- <plugins>
15
- <plugin>
16
- <groupId>org.eclipse.tycho</groupId>
17
- <artifactId>tycho-compiler-plugin</artifactId>
18
- <version>${tycho-version}</version>
19
- <configuration>
20
- <source>1.7</source>
21
- <target>1.7</target>
22
- </configuration>
23
- </plugin>
24
- <plugin>
25
- <groupId>org.eclipse.tycho</groupId>
26
- <artifactId>tycho-source-plugin</artifactId>
27
- <version>${tycho-version}</version>
28
- <executions>
29
- <execution>
30
- <id>plugin-source</id>
31
- <phase>generate-sources</phase>
32
- <goals>
33
- <goal>plugin-source</goal>
34
- </goals>
35
- </execution>
36
- </executions>
37
- </plugin>
38
- </plugins>
14
+ <plugins>
15
+ <plugin>
16
+ <groupId>org.eclipse.tycho</groupId>
17
+ <artifactId>tycho-compiler-plugin</artifactId>
18
+ <version>${tycho-version}</version>
19
+ <configuration>
20
+ <source>1.7</source>
21
+ <target>1.7</target>
22
+ </configuration>
23
+ </plugin>
24
+ <plugin>
25
+ <groupId>org.eclipse.tycho</groupId>
26
+ <artifactId>tycho-source-plugin</artifactId>
27
+ <version>${tycho-version}</version>
28
+ <executions>
29
+ <execution>
30
+ <id>plugin-source</id>
31
+ <phase>generate-sources</phase>
32
+ <goals>
33
+ <goal>plugin-source</goal>
34
+ </goals>
35
+ </execution>
36
+ </executions>
37
+ </plugin>
38
+ </plugins>
39 39
</build>
40 40
</project>
java/com.sap.sse.common/src/com/sap/sse/common/media/AbstractMediaDescriptor.java
... ...
@@ -1,136 +0,0 @@
1
-package com.sap.sse.common.media;
2
-
3
-import java.io.Serializable;
4
-import java.net.URL;
5
-import java.util.LinkedHashSet;
6
-import java.util.Locale;
7
-import java.util.Set;
8
-
9
-import com.sap.sse.common.TimePoint;
10
-import com.sap.sse.common.Util;
11
-
12
-/**
13
- * Common media data for media items
14
- *
15
- * @author pgtaboada
16
- *
17
- */
18
-public abstract class AbstractMediaDescriptor implements MediaDescriptor, Serializable {
19
- private static final long serialVersionUID = -6671425870632517274L;
20
-
21
- protected String title;
22
-
23
- protected String subtitle;
24
-
25
- protected TimePoint createdAtDate;
26
-
27
- protected String copyright;
28
-
29
- protected MimeType mimeType;
30
-
31
- protected Set<String> tags = new LinkedHashSet<String>();
32
-
33
- protected URL url;
34
-
35
- protected Locale locale;
36
-
37
- /**
38
- * Media item with minimal set of information
39
- * @param url
40
- * @param mimeType
41
- */
42
- public AbstractMediaDescriptor(URL url, MimeType mimeType, TimePoint createdAtDate) {
43
- this.mimeType = mimeType;
44
- this.url = url;
45
- this.createdAtDate = createdAtDate;
46
- }
47
-
48
- @Override
49
- public MimeType getMimeType() {
50
- return mimeType;
51
- }
52
-
53
- @Override
54
- public URL getURL() {
55
- return url;
56
- }
57
-
58
- @Override
59
- public String getTitle() {
60
- return title;
61
- }
62
-
63
- @Override
64
- public void setTitle(String title) {
65
- this.title = title;
66
- }
67
-
68
- @Override
69
- public Set<String> getTags() {
70
- return tags;
71
- }
72
-
73
- @Override
74
- public void setTags(Iterable<String> tags) {
75
- this.tags.clear();
76
- if (tags != null) {
77
- Util.addAll(tags, this.tags);
78
- }
79
- }
80
-
81
-
82
- @Override
83
- public boolean addTag(String tagName) {
84
- return tags.add(tagName);
85
- }
86
-
87
- @Override
88
- public boolean removeTag(String tagName) {
89
- return tags.remove(tagName);
90
- }
91
-
92
- @Override
93
- public String getSubtitle() {
94
- return subtitle;
95
- }
96
-
97
- @Override
98
- public void setSubtitle(String subtitle) {
99
- this.subtitle = subtitle;
100
- }
101
-
102
- @Override
103
- public TimePoint getCreatedAtDate() {
104
- return createdAtDate;
105
- }
106
-
107
- @Override
108
- public void setCreatedAtDate(TimePoint createdAtDate) {
109
- this.createdAtDate = createdAtDate;
110
- }
111
-
112
- @Override
113
- public String getCopyright() {
114
- return copyright;
115
- }
116
-
117
- @Override
118
- public void setCopyright(String copyright) {
119
- this.copyright = copyright;
120
- }
121
-
122
- @Override
123
- public Locale getLocale() {
124
- return locale;
125
- }
126
-
127
- @Override
128
- public void setLocale(Locale locale) {
129
- this.locale = locale;
130
- }
131
-
132
- @Override
133
- public boolean hasTag(String tagName) {
134
- return tags.contains(tagName);
135
- }
136
-}
java/com.sap.sse.common/src/com/sap/sse/common/media/ImageDescriptor.java
... ...
@@ -1,14 +0,0 @@
1
-package com.sap.sse.common.media;
2
-
3
-import com.sap.sse.common.Util.Pair;
4
-
5
-public interface ImageDescriptor extends MediaDescriptor {
6
- void setSize(Pair<Integer, Integer> size);
7
- void setSize(Integer widthInPx, Integer heightInPx);
8
-
9
- Integer getWidthInPx();
10
- Integer getHeightInPx();
11
-
12
- boolean hasSize();
13
- int getArea();
14
-}
java/com.sap.sse.common/src/com/sap/sse/common/media/ImageDescriptorImpl.java
... ...
@@ -1,62 +0,0 @@
1
-package com.sap.sse.common.media;
2
-
3
-import java.net.URL;
4
-
5
-import com.sap.sse.common.TimePoint;
6
-import com.sap.sse.common.Util.Pair;
7
-
8
-
9
-public class ImageDescriptorImpl extends AbstractMediaDescriptor implements ImageDescriptor {
10
- private static final long serialVersionUID = -702731462768602331L;
11
-
12
- private Integer widthInPx;
13
- private Integer heightInPx;
14
-
15
- /**
16
- * @param imageURL
17
- * @param size
18
- */
19
- public ImageDescriptorImpl(URL imageURL, TimePoint createdAtDate) {
20
- super(imageURL, MimeType.image, createdAtDate);
21
- }
22
-
23
- @Override
24
- public Integer getWidthInPx() {
25
- return widthInPx;
26
- }
27
-
28
- @Override
29
- public Integer getHeightInPx() {
30
- return heightInPx;
31
- }
32
-
33
- @Override
34
- public void setSize(Pair<Integer, Integer> size) {
35
- if (size != null) {
36
- this.widthInPx = size.getA();
37
- this.heightInPx = size.getB();
38
- } else {
39
- this.widthInPx = null;
40
- this.heightInPx = null;
41
- }
42
- }
43
-
44
- @Override
45
- public void setSize(Integer widthInPx, Integer heightInPx) {
46
- this.widthInPx = widthInPx;
47
- this.heightInPx = heightInPx;
48
- }
49
-
50
- @Override
51
- public boolean hasSize() {
52
- return widthInPx != null && heightInPx != null;
53
- }
54
-
55
- @Override
56
- public int getArea() {
57
- if(hasSize()) {
58
- return widthInPx * heightInPx;
59
- }
60
- return 0;
61
- }
62
-}
java/com.sap.sse.common/src/com/sap/sse/common/media/MediaDescriptor.java
... ...
@@ -1,36 +0,0 @@
1
-package com.sap.sse.common.media;
2
-
3
-import java.io.Serializable;
4
-import java.net.URL;
5
-import java.util.Locale;
6
-
7
-import com.sap.sse.common.TimePoint;
8
-
9
-/**
10
- * A common media interface for all kinds of media like images or videos.
11
- */
12
-public interface MediaDescriptor extends Serializable {
13
- MimeType getMimeType();
14
- URL getURL();
15
-
16
- String getTitle();
17
- void setTitle(String title);
18
-
19
- Iterable<String> getTags();
20
- void setTags(Iterable<String> tags);
21
- boolean addTag(String tagName);
22
- boolean removeTag(String tagName);
23
- boolean hasTag(String tagName);
24
-
25
- String getSubtitle();
26
- void setSubtitle(String subtitle);
27
-
28
- TimePoint getCreatedAtDate();
29
- void setCreatedAtDate(TimePoint createdAtDate);
30
-
31
- String getCopyright();
32
- void setCopyright(String copyright);
33
-
34
- Locale getLocale();
35
- void setLocale(Locale locale);
36
-}
java/com.sap.sse.common/src/com/sap/sse/common/media/MediaUtils.java
... ...
@@ -1,109 +0,0 @@
1
-package com.sap.sse.common.media;
2
-
3
-import java.io.IOException;
4
-import java.net.URL;
5
-import java.net.URLConnection;
6
-import java.util.Iterator;
7
-import java.util.concurrent.Callable;
8
-import java.util.concurrent.ExecutorService;
9
-import java.util.concurrent.Executors;
10
-import java.util.concurrent.Future;
11
-import java.util.logging.Level;
12
-import java.util.logging.Logger;
13
-import java.util.regex.Pattern;
14
-
15
-import javax.imageio.ImageIO;
16
-import javax.imageio.ImageReader;
17
-import javax.imageio.stream.ImageInputStream;
18
-
19
-import com.sap.sse.common.Util;
20
-import com.sap.sse.common.Util.Pair;
21
-
22
-public class MediaUtils {
23
- private static final Logger logger = Logger.getLogger(MediaUtils.class.getName());
24
-
25
- /**
26
- * Youtube regex detection from:
27
- * http://stackoverflow.com/questions/3452546/javascript-regex-how-to-get-youtube-video-id-from-url, mantish Mar 4
28
- * at 15:33
29
- */
30
- private static final Pattern YOUTUBE_ID_REGEX = Pattern
31
- .compile("^.*(youtu.be/|v/|u/\\w/|embed/|watch\\?v=|\\&v=)([^#\\&\\?]+).*$");
32
-
33
- private static final Pattern VIMEO_REGEX = Pattern.compile("^.*(vimeo\\.com\\/).*");
34
-
35
- private static final Pattern MP4_REGEX = Pattern.compile(".*\\.mp4$");
36
-
37
- /**
38
- * Detect mimetype for given url.
39
- *
40
- * @param url
41
- * the source pointing to the video mediafile
42
- * @return mimetype detected or MimeType.unknown
43
- */
44
- public static MimeType detectMimeTypeFromUrl(String url) {
45
-
46
- if (YOUTUBE_ID_REGEX.matcher(url).matches()) {
47
- return MimeType.youtube;
48
- } else if (VIMEO_REGEX.matcher(url).matches()) {
49
- return MimeType.vimeo;
50
- } else if (MP4_REGEX.matcher(url).matches()) {
51
- return MimeType.mp4;
52
- } else {
53
- return MimeType.unknown;
54
- }
55
- }
56
-
57
- public static Pair<Integer, Integer> getImageDimensions(URL imageURL) {
58
- Future<Pair<Integer, Integer>> imageSizeCalculator = getOrCreateImageSizeCalculator(imageURL);
59
- try {
60
- return imageSizeCalculator.get();
61
- } catch (Exception e) {
62
- return null;
63
- }
64
- }
65
-
66
- private static final ExecutorService executor = Executors.newCachedThreadPool();
67
-
68
- private static Future<Pair<Integer, Integer>> getOrCreateImageSizeCalculator(final URL imageURL) {
69
- Future<Pair<Integer, Integer>> imageSizeFetcher = executor.submit(new Callable<Pair<Integer, Integer>>() {
70
- @Override
71
- public Pair<Integer, Integer> call() throws IOException {
72
- Pair<Integer, Integer> result = null;
73
- ImageInputStream in = null;
74
- try {
75
- URLConnection conn = imageURL.openConnection();
76
- in = ImageIO.createImageInputStream(conn.getInputStream());
77
- final Iterator<ImageReader> readers = ImageIO.getImageReaders(in);
78
- if (readers.hasNext()) {
79
- ImageReader reader = readers.next();
80
- try {
81
- reader.setInput(in);
82
- result = new Pair<>(reader.getWidth(0), reader.getHeight(0));
83
- } finally {
84
- reader.dispose();
85
- }
86
- }
87
- } catch (IOException ioe) {
88
- logger.log(Level.SEVERE, "Stale image URL: "+imageURL, ioe);
89
- throw ioe;
90
- } finally {
91
- if (in != null) {
92
- in.close();
93
- }
94
- }
95
- return result;
96
- }
97
- });
98
- return imageSizeFetcher;
99
- }
100
-
101
- public static Util.Pair<Integer, Integer> fitImageSizeToBox(int boxWidth, int boxHeight, int imageWidth, int imageHeight, boolean neverScaleUp) {
102
- double scale = Math.min((double) boxWidth / (double) imageWidth, (double) boxHeight / (double) imageHeight);
103
-
104
- int h = (int) (!neverScaleUp || scale < 1.0 ? scale * imageHeight : imageHeight);
105
- int w = (int) (!neverScaleUp || scale < 1.0 ? scale * imageWidth : imageWidth);
106
-
107
- return new Util.Pair<Integer, Integer>(w,h);
108
- }
109
-}
java/com.sap.sse.common/src/com/sap/sse/common/media/VideoDescriptor.java
... ...
@@ -1,11 +0,0 @@
1
-package com.sap.sse.common.media;
2
-
3
-import java.net.URL;
4
-
5
-public interface VideoDescriptor extends MediaDescriptor {
6
- Integer getLengthInSeconds();
7
- void setLengthInSeconds(Integer lengthInSeconds);
8
-
9
- URL getThumbnailURL();
10
- void setThumbnailURL(URL thumbnailURL);
11
-}
java/com.sap.sse.common/src/com/sap/sse/common/media/VideoDescriptorImpl.java
... ...
@@ -1,40 +0,0 @@
1
-package com.sap.sse.common.media;
2
-
3
-import java.net.URL;
4
-
5
-import com.sap.sse.common.TimePoint;
6
-
7
-public class VideoDescriptorImpl extends AbstractMediaDescriptor implements VideoDescriptor {
8
- private static final long serialVersionUID = 2651747912466590862L;
9
-
10
- private Integer lengthInSeconds;
11
-
12
- /**
13
- * URL to thumbnail image. This information works as override for youtube videos or as missing thumbnail
14
- * information for other formats. It can be either a link to thumbnail or even be a data/url contaning the base64
15
- * encoded image.
16
- */
17
- private URL thumbnailURL;
18
-
19
- public VideoDescriptorImpl(URL url, MimeType mimeType, TimePoint createdAtDate) {
20
- super(url, mimeType, createdAtDate);
21
- }
22
-
23
- @Override
24
- public Integer getLengthInSeconds() {
25
- return lengthInSeconds;
26
- }
27
-
28
- public void setLengthInSeconds(Integer lengthInSeconds) {
29
- this.lengthInSeconds = lengthInSeconds;
30
- }
31
-
32
- @Override
33
- public URL getThumbnailURL() {
34
- return thumbnailURL;
35
- }
36
-
37
- public void setThumbnailURL(URL thumbnailURL) {
38
- this.thumbnailURL = thumbnailURL;
39
- }
40
-}
java/com.sap.sse.common/src/com/sap/sse/common/media/WithImages.java
... ...
@@ -1,25 +0,0 @@
1
-package com.sap.sse.common.media;
2
-
3
-public interface WithImages {
4
- /**
5
- * Returns a non-<code>null</code> live but unmodifiable collection of image resources that can be
6
- * used to represent the event, e.g., on a web page.
7
- *
8
- * @return a non-<code>null</code> value which may be empty
9
- */
10
- Iterable<ImageDescriptor> getImages();
11
-
12
- /**
13
- * Replaces the {@link #getImages() current contents of the image sequence by the images in
14
- * <code>images</code>.
15
- *
16
- * @param images
17
- * if <code>null</code>, the internal sequence of images is cleared but remains valid (non-
18
- * <code>null</code>)
19
- */
20
- void setImages(Iterable<ImageDescriptor> images);
21
-
22
- void addImage(ImageDescriptor image);
23
-
24
- void removeImage(ImageDescriptor image);
25
-}
java/com.sap.sse.common/src/com/sap/sse/common/media/WithMedia.java
... ...
@@ -1,12 +0,0 @@
1
-package com.sap.sse.common.media;
2
-
3
-import java.util.List;
4
-
5
-public interface WithMedia extends WithImages, WithVideos {
6
- ImageDescriptor findImageWithTag(String tagName);
7
- List<ImageDescriptor> findImagesWithTag(String tagName);
8
- boolean hasImageWithTag(String tagName);
9
-
10
- VideoDescriptor findVideoWithTag(String tagName);
11
- List<VideoDescriptor> findVideosWithTag(String tagName);
12
-}
java/com.sap.sse.common/src/com/sap/sse/common/media/WithVideos.java
... ...
@@ -1,26 +0,0 @@
1
-package com.sap.sse.common.media;
2
-
3
-public interface WithVideos {
4
-
5
- /**
6
- * Returns a non-<code>null</code> live but unmodifiable collection of video resources that can be
7
- * used to represent the event, e.g., on a web page.
8
- *
9
- * @return a non-<code>null</code> value which may be empty
10
- */
11
- Iterable<VideoDescriptor> getVideos();
12
-
13
- /**
14
- * Replaces the {@link #getVideos() current contents of the video sequence by the videos in
15
- * <code>videos</code>.
16
- *
17
- * @param videos
18
- * if <code>null</code>, the internal sequence of videos is cleared but remains valid (non-
19
- * <code>null</code>)
20
- */
21
- void setVideos(Iterable<VideoDescriptor> videos);
22
-
23
- void addVideo(VideoDescriptor video);
24
-
25
- void removeVideo(VideoDescriptor video);
26
-}
java/com.sap.sse.datamining.annotations/pom.xml
... ...
@@ -1,31 +1,31 @@
1 1
<?xml version="1.0" encoding="UTF-8"?>
2 2
<project
3
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
4
- xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
5
- <modelVersion>4.0.0</modelVersion>
6
- <parent>
7
- <artifactId>root</artifactId>
8
- <groupId>com.sap.sailing</groupId>
9
- <version>1.0.0-SNAPSHOT</version>
10
- </parent>
11
- <artifactId>com.sap.sse.datamining.annotations</artifactId>
12
- <packaging>eclipse-plugin</packaging>
13
- <build>
14
- <plugins>
15
- <plugin>
16
- <groupId>org.eclipse.tycho</groupId>
17
- <artifactId>tycho-source-plugin</artifactId>
18
- <version>${tycho-version}</version>
19
- <executions>
20
- <execution>
21
- <id>plugin-source</id>
22
- <phase>generate-sources</phase>
23
- <goals>
24
- <goal>plugin-source</goal>
25
- </goals>
26
- </execution>
27
- </executions>
28
- </plugin>
29
- </plugins>
30
- </build>
3
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
4
+ xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
5
+ <modelVersion>4.0.0</modelVersion>
6
+ <parent>
7
+ <artifactId>root</artifactId>
8
+ <groupId>com.sap.sailing</groupId>
9
+ <version>1.0.0-SNAPSHOT</version>
10
+ </parent>
11
+ <artifactId>com.sap.sse.datamining.annotations</artifactId>
12
+ <packaging>eclipse-plugin</packaging>
13
+ <build>
14
+ <plugins>
15
+ <plugin>
16
+ <groupId>org.eclipse.tycho</groupId>
17
+ <artifactId>tycho-source-plugin</artifactId>
18
+ <version>${tycho-version}</version>
19
+ <executions>
20
+ <execution>
21
+ <id>plugin-source</id>
22
+ <phase>generate-sources</phase>
23
+ <goals>
24
+ <goal>plugin-source</goal>
25
+ </goals>
26
+ </execution>
27
+ </executions>
28
+ </plugin>
29
+ </plugins>
30
+ </build>
31 31
</project>
java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/IntegerBox.java
... ...
@@ -7,6 +7,9 @@ import com.google.gwt.text.client.IntegerParser;
7 7
import com.google.gwt.text.shared.Renderer;
8 8
import com.google.gwt.user.client.ui.ValueBox;
9 9
10
+/**
11
+ * Renders integers in their box without any separators, non-localized, plain.
12
+ */
10 13
public class IntegerBox extends ValueBox<Integer> {
11 14
private static final Renderer<Integer> RENDERER = new Renderer<Integer>() {
12 15
public String render(Integer object) {
java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/slider/SliderBar.java
... ...
@@ -1,1256 +1,1256 @@
1
-package com.sap.sse.gwt.client.controls.slider;
2
-
3
-/*
4
- * Copyright 2008 Google Inc.
5
- *
6
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
7
- * use this file except in compliance with the License. You may obtain a copy of
8
- * the License at
9
- *
10
- * http://www.apache.org/licenses/LICENSE-2.0
11
- *
12
- * Unless required by applicable law or agreed to in writing, software
13
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15
- * License for the specific language governing permissions and limitations under
16
- * the License.
17
- */
18
-
19
-import java.util.ArrayList;
20
-import java.util.Iterator;
21
-import java.util.List;
22
-
23
-import com.google.gwt.core.client.GWT;
24
-import com.google.gwt.core.client.Scheduler;
25
-import com.google.gwt.core.client.Scheduler.ScheduledCommand;
26
-import com.google.gwt.dom.client.Element;
27
-import com.google.gwt.dom.client.Style.Display;
28
-import com.google.gwt.dom.client.Style.Position;
29
-import com.google.gwt.dom.client.Style.Unit;
30
-import com.google.gwt.dom.client.Style.Visibility;
31
-import com.google.gwt.event.dom.client.KeyCodes;
32
-import com.google.gwt.event.logical.shared.HasValueChangeHandlers;
33
-import com.google.gwt.event.logical.shared.ValueChangeEvent;
34
-import com.google.gwt.event.logical.shared.ValueChangeHandler;
35
-import com.google.gwt.event.shared.HandlerRegistration;
36
-import com.google.gwt.resources.client.ClientBundle;
37
-import com.google.gwt.resources.client.CssResource;
38
-import com.google.gwt.resources.client.CssResource.NotStrict;
39
-import com.google.gwt.resources.client.ImageResource;
40
-import com.google.gwt.user.client.DOM;
41
-import com.google.gwt.user.client.Event;
42
-import com.google.gwt.user.client.Timer;
43
-import com.google.gwt.user.client.Window;
44
-import com.google.gwt.user.client.ui.FocusPanel;
45
-import com.google.gwt.user.client.ui.HasValue;
46
-import com.google.gwt.user.client.ui.Image;
47
-import com.google.gwt.user.client.ui.RequiresResize;
48
-import com.sap.sse.common.Util;
49
-
50
-/**
51
- * A widget that allows the user to select a value within a range of possible values using a sliding bar that responds
52
- * to mouse events.
53
- *
54
- * <h3>Keyboard Events</h3>
55
- * <p>
56
- * SliderBar listens for the following key events. Holding down a key will repeat the action until the key is released.
57
- * <ul class='css'>
58
- * <li>left arrow - shift left one step</li>
59
- * <li>right arrow - shift right one step</li>
60
- * <li>ctrl+left arrow - jump left 10% of the distance</li>
61
- * <li>ctrl+right arrow - jump right 10% of the distance</li>
62
- * <li>home - jump to min value</li>
63
- * <li>end - jump to max value</li>
64
- * <li>space - jump to middle value</li>
65
- * </ul>
66
- * </p>
67
- *
68
- * <h3>CSS Style Rules</h3>
69
- * <ul class='css'>
70
- * <li>.gwt-SliderBar-shell { primary style }</li>
71
- * <li>.gwt-SliderBar-shell-focused { primary style when focused }</li>
72
- * <li>.gwt-SliderBar-shell gwt-SliderBar-line { the line that the knob moves along }</li>
73
- * <li>.gwt-SliderBar-shell gwt-SliderBar-line-sliding { the line that the knob moves along when sliding }</li>
74
- * <li>.gwt-SliderBar-shell .gwt-SliderBar-knob { the sliding knob }</li>
75
- * <li>.gwt-SliderBar-shell .gwt-SliderBar-knob-sliding { the sliding knob when sliding }</li>
76
- * <li>
77
- * .gwt-SliderBar-shell .gwt-SliderBar-tick { the ticks along the line }</li>
78
- * <li>.gwt-SliderBar-shell .gwt-SliderBar-ticklabel { the text labels along the line }</li>
79
- * </ul>
80
- */
81
-public class SliderBar extends FocusPanel implements RequiresResize, HasValue<Double>, HasValueChangeHandlers<Double> {
82
- /**
83
- * A formatter used to format the labels displayed in the widget.
84
- */
85
- public static interface LabelFormatter {
86
- /**
87
- * Generate the text to display in each label based on the label's value.
88
- *
89
- * Override this method to change the text displayed within the SliderBar.
90
- *
91
- * @param slider
92
- * the Slider bar
93
- * @param value
94
- * the value the label displays
95
- * @return the text to display for the label
96
- */
97
- String formatLabel(SliderBar slider, Double value, Double previousValue);
98
- }
99
-
100
- /**
101
- * A {@link ClientBundle} that provides images for {@link SliderBar}.
102
- */
103
- public static interface SliderBarImages extends ClientBundle {
104
- public static final SliderBarImages INSTANCE = GWT.create(SliderBarImages.class);
105
-
106
- /**
107
- * An image used for the sliding knob.
108
- *
109
- * @return a prototype of this image
110
- */
111
- @Source("slider_blue.png")
112
- ImageResource slider();
113
-
114
- /**
115
- * An image used for the disabled sliding knob.
116
- *
117
- * @return a prototype of this image
118
- */
119
- @Source("slider_blue.png")
120
- ImageResource sliderDisabled();
121
-
122
- /**
123
- * An image used for the sliding knob while sliding.
124
- *
125
- * @return a prototype of this image
126
- */
127
- @Source("slider_blue.png")
128
- ImageResource sliderSliding();
129
-
130
- @NotStrict
131
- @Source("SliderBar.css")
132
- CssResource sliderBarCss();
133
- }
134
-
135
- /**
136
- * The timer used to continue to shift the knob as the user holds down one of the left/right arrow keys. Only IE
137
- * auto-repeats, so we just keep catching the events.
138
- */
139
- private class KeyTimer extends Timer {
140
- /**
141
- * A bit indicating that this is the first run.
142
- */
143
- private boolean firstRun = true;
144
-
145
- /**
146
- * The delay between shifts, which shortens as the user holds down the button.
147
- */
148
- private int repeatDelay = 30;
149
-
150
- /**
151
- * A bit indicating whether we are shifting to a higher or lower value.
152
- */
153
- private boolean shiftRight = false;
154
-
155
- /**
156
- * The number of steps to shift with each press.
157
- */
158
- private int multiplier = 1;
159
-
160
- /**
161
- * This method will be called when a timer fires. Override it to implement the timer's logic.
162
- */
163
- @Override
164
- public void run() {
165
- // Highlight the knob on first run
166
- if (firstRun) {
167
- firstRun = false;
168
- startSliding(true, false);
169
- }
170
-
171
- // Slide the slider bar
172
- if (shiftRight) {
173
- setCurrentValue(curValue + multiplier * stepSize);
174
- } else {
175
- setCurrentValue(curValue - multiplier * stepSize);
176
- }
177
-
178
- // Repeat this timer until cancelled by keyup event
179
- schedule(repeatDelay);
180
- }
181
-
182
- /**
183
- * Schedules a timer to elapse in the future.
184
- *
185
- * @param delayMillis
186
- * how long to wait before the timer elapses, in milliseconds
187
- * @param shiftRight
188
- * whether to shift up or not
189
- * @param multiplier
190
- * the number of steps to shift
191
- */
192
- public void schedule(int delayMillis, boolean shiftRight, int multiplier) {
193
- firstRun = true;
194
- this.shiftRight = shiftRight;
195
- this.multiplier = multiplier;
196
- super.schedule(delayMillis);
197
- }
198
- }
199
-
200
- /**
201
- * The current value.
202
- */
203
- protected Double curValue;
204
-
205
- /**
206
- * The knob that slides across the line.
207
- */
208
- protected Image knobImage = new Image();
209
-
210
- /**
211
- * The timer used to continue to shift the knob if the user holds down a key.
212
- */
213
- private KeyTimer keyTimer = new KeyTimer();
214
-
215
- /**
216
- * The elements used to display labels above the ticks.
217
- */
218
- protected List<Element> tickLabelElements = new ArrayList<Element>();
219
-
220
- /**
221
- * The elements used to display the marker labels.
222
- */
223
- protected List<Element> markerLabelElements = new ArrayList<Element>();
224
-
225
- /**
226
- * The formatter used to generate label text.
227
- */
228
- protected LabelFormatter tickLabelFormatter;
229
-
230
- /**
231
- * The line that the knob moves over.
232
- */
233
- protected Element lineElement;
234
-
235
- /**
236
- * The offset between the edge of the shell and the line.
237
- */
238
- protected int lineLeftOffset = 0;
239
-
240
- /**
241
- * The maximum slider value.
242
- */
243
- protected Double maxValue;
244
-
245
- /**
246
- * The minimum slider value.
247
- */
248
- protected Double minValue;
249
-
250
- /**
251
- * The number of labels to show.
252
- */
253
- private int numTickLabels = 0;
254
-
255
- /**
256
- * The number of tick marks to show.
257
- */
258
- protected int numTicks = 0;
259
-
260
- /**
261
- * A bit indicating whether or not we are currently sliding the slider bar due to keyboard events.
262
- */
263
- private boolean slidingKeyboard = false;
264
-
265
- /**
266
- * A bit indicating whether or not we are currently sliding the slider bar due to mouse events.
267
- */
268
- private boolean slidingMouse = false;
269
-
270
- /**
271
- * A bit indicating whether or not the slider is enabled
272
- */
273
- protected boolean enabled = true;
274
-
275
- /**
276
- * The images used with the sliding bar.
277
- */
278
- private SliderBarImages images;
279
-
280
- /**
281
- * The size of the increments between knob positions.
282
- */
283
- protected double stepSize;
284
-
285
- /**
286
- * The elements used to display tick marks, which are the vertical lines along the slider bar.
287
- */
288
- protected List<Element> tickElements = new ArrayList<Element>();
289
-
290
- /**
291
- * The elements used to display additional markers on the slider bar.
292
- */
293
- protected List<Element> markerElements = new ArrayList<Element>();
294
-
295
- private List<Marker> markers = new ArrayList<Marker>();
296
-
297
- private class Marker {
298
- String name;
299
- Double position;
300
-
301
- public Marker(String name, Double position) {
302
- super();
303
- this.name = name;
304
- this.position = position;
305
- }
306
- }
307
-
308
- /**
309
- * Create a slider bar.
310
- */
311
- public SliderBar() {
312
- this(null, null, null);
313
- }
314
-
315
- /**
316
- * Create a slider bar.
317
- *
318
- * @param minValue
319
- * the minimum value in the range
320
- * @param maxValue
321
- * the maximum value in the range
322
- */
323
- public SliderBar(double minValue, double maxValue) {
324
- this(minValue, maxValue, null);
325
- }
326
-
327
- /**
328
- * Create a slider bar.
329
- *
330
- * @param minValue
331
- * the minimum value in the range
332
- * @param maxValue
333
- * the maximum value in the range
334
- * @param tickLabelFormatter
335
- * the label formatter
336
- */
337
- public SliderBar(Double minValue, Double maxValue, LabelFormatter tickLabelFormatter) {
338
- this(minValue, maxValue, tickLabelFormatter, SliderBarImages.INSTANCE);
339
- }
340
-
341
- /**
342
- * Create a slider bar.
343
- *
344
- * @param minValue
345
- * the minimum value in the range
346
- * @param maxValue
347
- * the maximum value in the range
348
- * @param tickLabelFormatter
349
- * the label formatter
350
- * @param images
351
- * the images to use for the slider
352
- */
353
- public SliderBar(Double minValue, Double maxValue, LabelFormatter tickLabelFormatter, SliderBarImages images) {
354
- super();
355
- images.sliderBarCss().ensureInjected();
356
- this.minValue = minValue;
357
- this.maxValue = maxValue;
358
- this.images = images;
359
- setLabelFormatter(tickLabelFormatter);
360
-
361
- // Create the outer shell
362
- getElement().getStyle().setPosition(Position.RELATIVE);
363
- setStyleName("gwt-SliderBar-shell");
364
-
365
- // Create the line
366
- lineElement = DOM.createDiv();
367
- DOM.appendChild(getElement(), lineElement);
368
- lineElement.getStyle().setPosition(Position.ABSOLUTE);
369
- lineElement.setPropertyString("className", "gwt-SliderBar-line");
370
-
371
- // Create the knob
372
- knobImage.setResource(images.slider());
373
- Element knobElement = knobImage.getElement();
374
- DOM.appendChild(getElement(), knobElement);
375
- knobElement.getStyle().setPosition(Position.ABSOLUTE);
376
- knobElement.setPropertyString("className", "gwt-SliderBar-knob");
377
-
378
- sinkEvents(Event.MOUSEEVENTS | Event.KEYEVENTS | Event.FOCUSEVENTS);
379
-
380
- // workaround to render properly when parent Widget does not
381
- // implement ProvidesResize since DOM doesn't provide element
382
- // height and width until onModuleLoad() finishes.
383
- Scheduler.get().scheduleDeferred(new ScheduledCommand() {
384
- @Override
385
- public void execute() {
386
- onResize();
387
- }
388
- });
389
- }
390
-
391
- public boolean isMinMaxInitialized() {
392
- return minValue != null && maxValue != null;
393
- }
394
-
395
- public HandlerRegistration addValueChangeHandler(ValueChangeHandler<Double> handler) {
396
- return addHandler(handler, ValueChangeEvent.getType());
397
- }
398
-
399
- /**
400
- * Return the current value.
401
- *
402
- * @return the current value
403
- */
404
- public Double getCurrentValue() {
405
- return curValue;
406
- }
407
-
408
- /**
409
- * Return the label formatter.
410
- *
411
- * @return the label formatter
412
- */
413
- public LabelFormatter getLabelFormatter() {
414
- return tickLabelFormatter;
415
- }
416
-
417
- /**
418
- * Return the max value.
419
- *
420
- * @return the max value
421
- */
422
- public Double getMaxValue() {
423
- return maxValue;
424
- }
425
-
426
- /**
427
- * Return the minimum value.
428
- *
429
- * @return the minimum value
430
- */
431
- public Double getMinValue() {
432
- return minValue;
433
- }
434
-
435
- /**
436
- * Return the number of labels.
437
- *
438
- * @return the number of labels
439
- */
440
- public int getNumLabels() {
441
- return numTickLabels;
442
- }
443
-
444
- /**
445
- * Return the number of ticks.
446
- *
447
- * @return the number of ticks
448
- */
449
- public int getNumTicks() {
450
- return numTicks;
451
- }
452
-
453
- /**
454
- * Return the step size.
455
- *
456
- * @return the step size
457
- */
458
- public double getStepSize() {
459
- return stepSize;
460
- }
461
-
462
- /**
463
- * Return the total range between the minimum and maximum values.
464
- *
465
- * @return the total range
466
- */
467
- public double getTotalRange() {
468
- if (minValue == null || maxValue == null || minValue > maxValue) {
469
- return 0;
470
- } else {
471
- return maxValue - minValue;
472
- }
473
- }
474
-
475
- public Double getValue() {
476
- return curValue;
477
- }
478
-
479
- /**
480
- * @return Gets whether this widget is enabled
481
- */
482
- public boolean isEnabled() {
483
- return enabled;
484
- }
485
-
486
- /**
487
- * Listen for events that will move the knob.
488
- *
489
- * @param event
490
- * the event that occurred
491
- */
492
- @Override
493
- public void onBrowserEvent(Event event) {
494
- super.onBrowserEvent(event);
495
- if (enabled) {
496
- switch (DOM.eventGetType(event)) {
497
- // Unhighlight and cancel keyboard events
498
- case Event.ONBLUR:
499
- keyTimer.cancel();
500
- if (slidingMouse) {
501
- DOM.releaseCapture(getElement());
502
- slidingMouse = false;
503
- slideKnob(event);
504
- stopSliding(true, true);
505
- } else if (slidingKeyboard) {
506
- slidingKeyboard = false;
507
- stopSliding(true, true);
508
- }
509
- unhighlight();
510
- break;
511
-
512
- // Highlight on focus
513
- case Event.ONFOCUS:
514
- highlight();
515
- break;
516
-
517
- // Mousewheel events
518
- case Event.ONMOUSEWHEEL:
519
- int velocityY = event.getMouseWheelVelocityY();
520
- event.preventDefault();
521
- if (velocityY > 0) {
522
- shiftRight(1);
523
- } else {
524
- shiftLeft(1);
525
- }
526
- break;
527
-
528
- // Shift left or right on key press
529
- case Event.ONKEYDOWN:
530
- if (!slidingKeyboard) {
531
- int multiplier = 1;
532
- if (event.getCtrlKey()) {
533
- multiplier = (int) (getTotalRange() / stepSize / 10);
534
- }
535
-
536
- switch (event.getKeyCode()) {
537
- case KeyCodes.KEY_HOME:
538
- event.preventDefault();
539
- setCurrentValue(minValue);
540
- break;
541
- case KeyCodes.KEY_END:
542
- event.preventDefault();
543
- setCurrentValue(maxValue);
544
- break;
545
- case KeyCodes.KEY_LEFT:
546
- event.preventDefault();
547
- slidingKeyboard = true;
548
- startSliding(false, true);
549
- shiftLeft(multiplier);
550
- keyTimer.schedule(400, false, multiplier);
551
- break;
552
- case KeyCodes.KEY_RIGHT:
553
- event.preventDefault();
554
- slidingKeyboard = true;
555
- startSliding(false, true);
556
- shiftRight(multiplier);
557
- keyTimer.schedule(400, true, multiplier);
558
- break;
559
- case 32:
560
- event.preventDefault();
561
- setCurrentValue(minValue + getTotalRange() / 2);
562
- break;
563
- }
564
- }
565
- break;
566
- // Stop shifting on key up
567
- case Event.ONKEYUP:
568
- keyTimer.cancel();
569
- if (slidingKeyboard) {
570
- slidingKeyboard = false;
571
- stopSliding(true, true);
572
- }
573
- break;
574
-
575
- // Mouse Events
576
- case Event.ONMOUSEDOWN:
577
- setFocus(true);
578
- slidingMouse = true;
579
- DOM.setCapture(getElement());
580
- startSliding(true, true);
581
- event.preventDefault();
582
- slideKnob(event);
583
- break;
584
- case Event.ONMOUSEUP:
585
- if (slidingMouse) {
586
- DOM.releaseCapture(getElement());
587
- slidingMouse = false;
588
- slideKnob(event);
589
- stopSliding(true, true);
590
- }
591
- break;
592
- case Event.ONMOUSEMOVE:
593
- if (slidingMouse) {
594
- slideKnob(event);
595
- }
596
- break;
597
- }
598
- }
599
- }
600
-
601
- /**
602
- * This method is called when the dimensions of the parent element change. Subclasses should override this method as
603
- * needed.
604
- *
605
- * @param width
606
- * the new client width of the element
607
- * @param height
608
- * the new client height of the element
609
- */
610
- public void onResize(int width, int height) {
611
- // Center the line in the shell
612
- int lineWidth = lineElement.getOffsetWidth();
613
- lineLeftOffset = (width / 2) - (lineWidth / 2);
614
- lineElement.getStyle().setLeft(lineLeftOffset, Unit.PX);
615
-
616
- // Draw the other components
617
- drawTickLabels();
618
- drawTicks();
619
- drawMarkers();
620
- drawMarkerLabels();
621
- drawKnob();
622
- }
623
-
624
- /**
625
- * Redraw the progress bar when something changes the layout.
626
- */
627
- public void redraw() {
628
- if (isAttached()) {
629
- int width = getElement().getClientWidth();
630
- int height = getElement().getClientHeight();
631
- onResize(width, height);
632
- }
633
- }
634
-
635
- /**
636
- * Set the current value and fire the onValueChange event.
637
- *
638
- * @param curValue
639
- * the current value
640
- */
641
- public synchronized void setCurrentValue(Double curValue) {
642
- setCurrentValue(curValue, true);
643
- }
644
-
645
- /**
646
- * Set the current value and optionally fire the onValueChange event.
647
- *
648
- * @param curValue
649
- * the current value
650
- * @param fireEvent
651
- * fire the onValue change event if true
652
- */
653
- public synchronized void setCurrentValue(Double curValue, boolean fireEvent) {
654
- // Confine the value to the range
655
- if (!isMinMaxInitialized() || curValue == null) {
656
- return;
657
- }
658
- this.curValue = Math.max(minValue, Math.min(maxValue, curValue));
659
- double remainder = (this.curValue - minValue) % stepSize;
660
- this.curValue -= remainder;
661
- // Go to next step if more than halfway there
662
- if ((remainder > (stepSize / 2)) && ((this.curValue + stepSize) <= maxValue)) {
663
- this.curValue += stepSize;
664
- }
665
- // Redraw the knob
666
- drawKnob();
667
- // Fire the ValueChangeEvent if the value actually changed
668
- if (fireEvent && !curValue.equals(this.curValue)) {
669
- ValueChangeEvent.fire(this, this.curValue);
670
- }
671
- }
672
-
673
- /**
674
- * Sets whether this widget is enabled.
675
- *
676
- * @param enabled
677
- * true to enable the widget, false to disable it
678
- */
679
- public void setEnabled(boolean enabled) {
680
- this.enabled = enabled;
681
- if (enabled) {
682
- knobImage.setResource(images.slider());
683
- lineElement.setPropertyString("className", "gwt-SliderBar-line");
684
- } else {
685
- knobImage.setResource(images.sliderDisabled());
686
- lineElement.setPropertyString("className", "gwt-SliderBar-line gwt-SliderBar-line-disabled");
687
- }
688
- redraw();
689
- }
690
-
691
- /**
692
- * Set the label formatter.
693
- *
694
- * @param labelFormatter
695
- * the label formatter
696
- */
697
- public void setLabelFormatter(LabelFormatter labelFormatter) {
698
- this.tickLabelFormatter = labelFormatter;
699
- }
700
-
701
- /**
702
- * Set the max value.
703
- *
704
- * @param maxValue
705
- * the current value
706
- */
707
- public void setMaxValue(Double maxValue, boolean fireEvent) {
708
- if (!Util.equalsWithNull(maxValue, this.maxValue)) {
709
- this.maxValue = maxValue;
710
- onMinMaxValueChanged(fireEvent);
711
- }
712
- }
713
-
714
- /**
715
- * Set the minimum value.
716
- *
717
- * @param minValue
718
- * the current value
719
- */
720
- public void setMinValue(Double minValue, boolean fireEvent) {
721
- if (!Util.equalsWithNull(minValue, this.minValue)) {
722
- this.minValue = minValue;
723
- onMinMaxValueChanged(fireEvent);
724
- }
725
- }
726
-
727
- /**
728
- * Set the minimum and maximum value
729
- *
730
- * @param minValue
731
- * the current value for min
732
- * @param maxValue
733
- * the current valuefor max
734
- */
735
- public void setMinAndMaxValue(Double minValue, Double maxValue, boolean fireEvent) {
736
- boolean changed = false;
737
- if (!Util.equalsWithNull(minValue, this.minValue)) {
738
- this.minValue = minValue;
739
- changed = true;
740
- }
741
- if (!Util.equalsWithNull(maxValue, this.maxValue)) {
742
- this.maxValue = maxValue;
743
- changed = true;
744
- }
745
- if (changed) {
746
- onMinMaxValueChanged(fireEvent);
747
- }
748
- }
749
-
750
- /**
751
- * Handle value changes of min and/or max value
752
- */
753
- protected void onMinMaxValueChanged(boolean fireEvent) {
754
- redraw();
755
- resetCurrentValue(fireEvent);
756
- }
757
-
758
- /**
759
- * Set the number of tick labels to show on the line. Tick labels indicate the value of the slider at that point.
760
- * Use this method to enable tick labels.
761
- *
762
- * If you set the number of tick labels equal to the total range divided by the step size, you will get a properly
763
- * aligned "jumping" effect where the knob jumps between tick labels.
764
- *
765
- * Note that the number of tick labels displayed will be one more than the number you specify, so specify 1 labels
766
- * to show labels on either end of the line. In other words, numTickLabels is really the number of slots between the
767
- * labels.
768
- *
769
- * setNumTickLabels(0) will disable the labels.
770
- *
771
- * @param numTickLabels
772
- * the number of tick labels to show
773
- */
774
- public void setNumTickLabels(int numTickLabels) {
775
- this.numTickLabels = numTickLabels;
776
- drawTickLabels();
777
- }
778
-
779
- /**
780
- * Set the number of ticks to show on the line. A tick is a vertical line that represents a division of the overall
781
- * line. Use this method to enable ticks.
782
- *
783
- * If you set the number of ticks equal to the total range divided by the step size, you will get a properly aligned
784
- * "jumping" effect where the knob jumps between ticks.
785
- *
786
- * Note that the number of ticks displayed will be one more than the number you specify, so specify 1 tick to show
787
- * ticks on either end of the line. In other words, numTicks is really the number of slots between the ticks.
788
- *
789
- * setNumTicks(0) will disable ticks.
790
- *
791
- * @param numTicks
792
- * the number of ticks to show
793
- */
794
- public void setNumTicks(int numTicks) {
795
- this.numTicks = numTicks;
796
- drawTicks();
797
- }
798
-
799
- /**
800
- * Set the step size.
801
- *
802
- * @param stepSize
803
- * the current value
804
- */
805
- public void setStepSize(double stepSize, boolean fireEvent) {
806
- this.stepSize = stepSize;
807
- resetCurrentValue(fireEvent);
808
- }
809
-
810
- public void setValue(Double value) {
811
- setCurrentValue(value, false);
812
- }
813
-
814
- public void setValue(Double value, boolean fireEvent) {
815
- setCurrentValue(value, fireEvent);
816
- }
817
-
818
- /**
819
- * Shift to the left (smaller value).
820
- *
821
- * @param numSteps
822
- * the number of steps to shift
823
- */
824
- public void shiftLeft(int numSteps) {
825
- setCurrentValue(getCurrentValue() - numSteps * stepSize);
826
- }
827
-
828
- /**
829
- * Shift to the right (greater value).
830
- *
831
- * @param numSteps
832
- * the number of steps to shift
833
- */
834
- public void shiftRight(int numSteps) {
835
- setCurrentValue(getCurrentValue() + numSteps * stepSize);
836
- }
837
-
838
- /**
839
- * Format the label to display above the ticks
840
- *
841
- * Override this method in a subclass to customize the format. By default, this method returns the integer portion
842
- * of the value.
843
- *
844
- * @param value
845
- * the value at the label
846
- * @return the text to put in the label
847
- */
848
- protected String formatTickLabel(Double value, Double previousValue) {
849
- if (tickLabelFormatter != null) {
850
- return tickLabelFormatter.formatLabel(this, value, previousValue);
851
- } else {
852
- return (int) (10 * value) / 10.0 + "";
853
- }
854
- }
855
-
856
- /**
857
- * Get the percentage of the knob's position relative to the size of the line. The return value will be between 0.0
858
- * and 1.0.
859
- *
860
- * @return the current percent complete
861
- */
862
- protected double getKnobPercent() {
863
- // If we have no range
864
- if (maxValue <= minValue) {
865
- return 0;
866
- }
867
-
868
- // Calculate the relative progress
869
- double percent = (curValue - minValue) / (maxValue - minValue);
870
- return Math.max(0.0, Math.min(1.0, percent));
871
- }
872
-
873
- /**
874
- * This method is called immediately after a widget becomes attached to the browser's document.
875
- */
876
- @Override
877
- protected void onLoad() {
878
- // Reset the position attribute of the parent element
879
- getElement().getStyle().setPosition(Position.RELATIVE);
880
- }
881
-
882
- /**
883
- * Draw the knob where it is supposed to be relative to the line.
884
- */
885
- protected void drawKnob() {
886
- if (isAttached() && isMinMaxInitialized()) {
887
- // Move the knob to the correct position
888
- Element knobElement = knobImage.getElement();
889
- int lineWidth = lineElement.getOffsetWidth();
890
- int knobWidth = knobElement.getOffsetWidth();
891
- int knobLeftOffset = (int) (lineLeftOffset + (getKnobPercent() * lineWidth) - (knobWidth / 2));
892
- knobLeftOffset = Math.min(knobLeftOffset, lineLeftOffset + lineWidth - (knobWidth / 2) - 1);
893
- knobElement.getStyle().setLeft(knobLeftOffset, Unit.PX);
894
- }
895
- }
896
-
897
- /**
898
- * Draw the labels along the line.
899
- */
900
- protected void drawTickLabels() {
901
- if (isAttached() && isMinMaxInitialized()) {
902
- // Draw the tick labels
903
- int lineWidth = lineElement.getOffsetWidth();
904
- if (numTickLabels > 0) {
905
- // Create the labels or make them visible
906
- Double previousValue = null;
907
- for (int i = 0; i <= numTickLabels; i++) {
908
- Element label = null;
909
- if (i < tickLabelElements.size()) {
910
- label = tickLabelElements.get(i);
911
- } else { // Create the new label
912
- label = DOM.createDiv();
913
- label.getStyle().setPosition(Position.ABSOLUTE);
914
- label.getStyle().setDisplay(Display.NONE);
915
- if (enabled) {
916
- label.setPropertyString("className", "gwt-SliderBar-ticklabel");
917
- } else {
918
- label.setPropertyString("className", "gwt-SliderBar-ticklabel-disabled");
919
- }
920
- DOM.appendChild(getElement(), label);
921
- tickLabelElements.add(label);
922
- }
923
-
924
- // Set the label text
925
- double value = minValue + (getTotalRange() * i / numTickLabels);
926
- label.getStyle().setVisibility(Visibility.HIDDEN);
927
- label.getStyle().setProperty("display", "");
928
- label.setPropertyString("innerHTML", formatTickLabel(value, previousValue));
929
-
930
- // Move to the left so the label width is not clipped by the shell
931
- label.getStyle().setLeft(0, Unit.PX);
932
-
933
- // Position the label and make it visible
934
- int labelWidth = label.getOffsetWidth();
935
- int labelLeftOffset = lineLeftOffset + (lineWidth * i / numTickLabels) - (labelWidth / 2);
936
- labelLeftOffset = Math.min(labelLeftOffset, lineLeftOffset + lineWidth - labelWidth);
937
- labelLeftOffset = Math.max(labelLeftOffset, lineLeftOffset);
938
- label.getStyle().setLeft(labelLeftOffset, Unit.PX);
939
- label.getStyle().setVisibility(Visibility.VISIBLE);
940
-
941
- previousValue = value;
942
- }
943
-
944
- // Hide unused labels
945
- for (int i = (numTickLabels + 1); i < tickLabelElements.size(); i++) {
946
- tickLabelElements.get(i).getStyle().setDisplay(Display.NONE);
947
- }
948
- } else { // Hide all labels
949
- for (Element elem : tickLabelElements) {
950
- elem.getStyle().setDisplay(Display.NONE);
951
- }
952
- }
953
- }
954
- }
955
-
956
- /**
957
- * Draw the tick along the line.
958
- */
959
- protected void drawTicks() {
960
- if (isAttached() && isMinMaxInitialized()) {
961
- // Draw the ticks
962
- int lineWidth = lineElement.getOffsetWidth();
963
- if (numTicks > 0) {
964
- // Create the ticks or make them visible
965
- for (int i = 0; i <= numTicks; i++) {
966
- Element tick = null;
967
- if (i < tickElements.size()) {
968
- tick = tickElements.get(i);
969
- } else { // Create the new tick
970
- tick = DOM.createDiv();
971
- tick.getStyle().setPosition(Position.ABSOLUTE);
972
- tick.getStyle().setDisplay(Display.NONE);
973
- DOM.appendChild(getElement(), tick);
974
- tickElements.add(tick);
975
- }
976
- if (enabled) {
977
- tick.setPropertyString("className", "gwt-SliderBar-tick");
978
- } else {
979
- tick.setPropertyString("className", "gwt-SliderBar-tick gwt-SliderBar-tick-disabled");
980
- }
981
- // Position the tick and make it visible
982
- tick.getStyle().setVisibility(Visibility.HIDDEN);
983
- tick.getStyle().setProperty("display", "");
984
- int tickWidth = tick.getOffsetWidth();
985
- int tickLeftOffset = lineLeftOffset + (lineWidth * i / numTicks) - (tickWidth / 2);
986
- tickLeftOffset = Math.min(tickLeftOffset, lineLeftOffset + lineWidth - tickWidth);
987
- tick.getStyle().setLeft(tickLeftOffset, Unit.PX);
988
- tick.getStyle().setVisibility(Visibility.VISIBLE);
989
- }
990
-
991
- // Hide unused ticks
992
- for (int i = (numTicks + 1); i < tickElements.size(); i++) {
993
- tickElements.get(i).getStyle().setDisplay(Display.NONE);
994
- }
995
- } else { // Hide all ticks
996
- for (Element elem : tickElements) {
997
- elem.getStyle().setDisplay(Display.NONE);
998
- }
999
- }
1000
- }
1001
- }
1002
-
1003
- /**
1004
- * Draw the markers.
1005
- */
1006
- private void drawMarkers() {
1007
- if (isAttached() && isMinMaxInitialized()) {
1008
- int numMarkers = markers.size();
1009
- // Draw the markers
1010
- int lineWidth = lineElement.getOffsetWidth();
1011
- if (numMarkers > 0) {
1012
- // Create the markers or make them visible
1013
- Element lastMarkerElement = null;
1014
- int lastMarkerOffsetLeft = 0;
1015
- for (int i = 0; i < numMarkers; i++) {
1016
- Marker marker = markers.get(i);
1017
- Element markerElem = null;
1018
- if (i < markerElements.size()) {
1019
- markerElem = markerElements.get(i);
1020
- } else { // Create the new markes
1021
- markerElem = DOM.createDiv();
1022
- markerElem.getStyle().setPosition(Position.ABSOLUTE);
1023
- markerElem.getStyle().setDisplay(Display.NONE);
1024
- DOM.appendChild(getElement(), markerElem);
1025
- markerElements.add(markerElem);
1026
- }
1027
- if (enabled) {
1028
- markerElem.setPropertyString("className", "gwt-SliderBar-mark");
1029
- } else {
1030
- markerElem.setPropertyString("className", "gwt-SliderBar-mark gwt-SliderBar-mark-disabled");
1031
- }
1032
- // Position the marker and make it visible
1033
- markerElem.getStyle().setVisibility(Visibility.HIDDEN);
1034
- markerElem.getStyle().setProperty("display", "");
1035
- double markerLinePosition = (marker.position - minValue) * lineWidth / getTotalRange();
1036
- int markerWidth = markerElem.getOffsetWidth();
1037
- int markerLeftOffset = lineLeftOffset + (int) markerLinePosition - (markerWidth / 2);
1038
- markerLeftOffset = Math.min(markerLeftOffset, lineLeftOffset + lineWidth - markerWidth);
1039
- markerElem.getStyle().setLeft(markerLeftOffset, Unit.PX);
1040
- markerElem.getStyle().setVisibility(Visibility.VISIBLE);
1041
- // change class of last marker if it is too close to the one just created
1042
- if (lastMarkerOffsetLeft > 0 && (markerLeftOffset-lastMarkerOffsetLeft) <= markerWidth) {
1043
- if (lastMarkerElement != null) {
1044
- lastMarkerElement.setPropertyString("className", "gwt-SliderBar-mark-tooClose");
1045
- lastMarkerElement.getStyle().setLeft(markerLeftOffset-10, Unit.PX);
1046
- }
1047
- }
1048
- lastMarkerElement = markerElem;
1049
- lastMarkerOffsetLeft = markerLeftOffset;
1050
- }
1051
-
1052
- // Hide unused markers
1053
- for (int i = numMarkers; i < markerElements.size(); i++) {
1054
- markerElements.get(i).getStyle().setDisplay(Display.NONE);
1055
- }
1056
- } else { // Hide all markers
1057
- for (Element elem : markerElements) {
1058
- elem.getStyle().setDisplay(Display.NONE);
1059
- }
1060
- }
1061
- }
1062
- }
1063
-
1064
- /**
1065
- * Draw the marker labels.
1066
- */
1067
- private void drawMarkerLabels() {
1068
- if (isAttached() && isMinMaxInitialized()) {
1069
- int numMarkers = markers.size();
1070
- // Draw the marker labels
1071
- int lineWidth = lineElement.getOffsetWidth();
1072
- if (numMarkers > 0) {
1073
- Element lastMarkerLabel = null;
1074
- Marker lastMarker = null;
1075
- int lastMarkerLabelOffsetLeft = 0;
1076
- // Create the labels or make them visible
1077
- for (int i = 0; i < numMarkers; i++) {
1078
- Marker marker = markers.get(i);
1079
- Element label = null;
1080
- if (i < markerLabelElements.size()) {
1081
- label = markerLabelElements.get(i);
1082
- } else { // Create the new label
1083
- label = DOM.createDiv();
1084
- label.getStyle().setPosition(Position.ABSOLUTE);
1085
- label.getStyle().setDisplay(Display.NONE);
1086
- if (enabled) {
1087
- label.setPropertyString("className", "gwt-SliderBar-markerlabel");
1088
- } else {
1089
- label.setPropertyString("className", "gwt-SliderBar-markerlabel-disabled");
1090
- }
1091
- DOM.appendChild(getElement(), label);
1092
- markerLabelElements.add(label);
1093
- }
1094
-
1095
- // Set the marker label text
1096
- label.getStyle().setVisibility(Visibility.HIDDEN);
1097
- label.getStyle().setProperty("display", "");
1098
- label.setPropertyString("innerHTML", marker.name);
1099
-
1100
- // Move to the left so the label width is not clipped by the shell
1101
- label.getStyle().setLeft(0, Unit.PX);
1102
-
1103
- // Position the label and make it visible
1104
- double markerLinePosition = (marker.position - minValue) * lineWidth / getTotalRange();
1105
- int labelWidth = label.getOffsetWidth();
1106
- int labelLeftOffset = lineLeftOffset + (int) markerLinePosition - (labelWidth / 2);
1107
- labelLeftOffset = Math.min(labelLeftOffset, lineLeftOffset + lineWidth - labelWidth);
1108
-
1109
- label.getStyle().setLeft(labelLeftOffset, Unit.PX);
1110
- label.getStyle().setVisibility(Visibility.VISIBLE);
1111
-
1112
- // hide last marker label if it is too close to the one just created
1113
- if (lastMarkerLabelOffsetLeft > 0 && (labelLeftOffset-lastMarkerLabelOffsetLeft) <= 20) {
1114
- if (lastMarkerLabel != null && lastMarker != null) {
1115
- lastMarkerLabel.setPropertyString("className", "gwt-SliderBar-markerlabel-tooClose");
1116
- lastMarkerLabel.setPropertyString("innerHTML", lastMarker.name); // no space for more
1117
- int currentMarkerOffsetLeft = labelLeftOffset;
1118
- try {
1119
- currentMarkerOffsetLeft = Integer.parseInt(markerElements.get(i).getStyle().getLeft().replace("px", ""));
1120
- } catch (NumberFormatException ex) {
1121
- // ignore, can't do anything
1122
- }
1123
- lastMarkerLabel.getStyle().setLeft(currentMarkerOffsetLeft-8, Unit.PX);
1124
- }
1125
- }
1126
- lastMarker = marker;
1127
- lastMarkerLabel = label;
1128
- lastMarkerLabelOffsetLeft = labelLeftOffset;
1129
- }
1130
-
1131
- // Hide unused labels
1132
- for (int i = (numMarkers + 1); i < markerLabelElements.size(); i++) {
1133
- markerLabelElements.get(i).getStyle().setDisplay(Display.NONE);
1134
- }
1135
- } else { // Hide all labels
1136
- for (Element elem : markerLabelElements) {
1137
- elem.getStyle().setDisplay(Display.NONE);
1138
- }
1139
- }
1140
- }
1141
- }
1142
-
1143
- /**
1144
- * Highlight this widget.
1145
- */
1146
- private void highlight() {
1147
- String styleName = getStylePrimaryName();
1148
- getElement().setPropertyString("className", styleName + " " + styleName + "-focused");
1149
- }
1150
-
1151
- /**
1152
- * Reset the progress to constrain the progress to the current range and redraw the knob as needed.
1153
- */
1154
- private synchronized void resetCurrentValue(boolean fireEvent) {
1155
- setCurrentValue(getCurrentValue(), fireEvent);
1156
- }
1157
-
1158
- /**
1159
- * Slide the knob to a new location.
1160
- *
1161
- * @param event
1162
- * the mouse event
1163
- */
1164
- private void slideKnob(Event event) {
1165
- // Adding scrollLeft to adjust the position, if the user had scrolled with the lower scroll bar
1166
- int x = event.getClientX() + Window.getScrollLeft();
1167
- if (x > 0) {
1168
- int lineWidth = lineElement.getOffsetWidth();
1169
- int lineLeft = lineElement.getAbsoluteLeft();
1170
- double percent = (double) (x - lineLeft) / lineWidth * 1.0;
1171
- setCurrentValue(getTotalRange() * percent + minValue, true);
1172
- }
1173
- }
1174
-
1175
- /**
1176
- * Start sliding the knob.
1177
- *
1178
- * @param highlight
1179
- * true to change the style
1180
- * @param fireEvent
1181
- * true to fire the event
1182
- */
1183
- private void startSliding(boolean highlight, boolean fireEvent) {
1184
- if (highlight) {
1185
- lineElement.setPropertyString("className", "gwt-SliderBar-line gwt-SliderBar-line-sliding");
1186
- knobImage.getElement().setPropertyString("className", "gwt-SliderBar-knob gwt-SliderBar-knob-sliding");
1187
- knobImage.setResource(images.sliderSliding());
1188
- }
1189
- }
1190
-
1191
- /**
1192
- * Stop sliding the knob.
1193
- *
1194
- * @param unhighlight
1195
- * true to change the style
1196
- * @param fireEvent
1197
- * true to fire the event
1198
- */
1199
- private void stopSliding(boolean unhighlight, boolean fireEvent) {
1200
- if (unhighlight) {
1201
- lineElement.setPropertyString("className", "gwt-SliderBar-line");
1202
- knobImage.getElement().setPropertyString("className", "gwt-SliderBar-knob");
1203
- knobImage.setResource(images.slider());
1204
- }
1205
- }
1206
-
1207
- /**
1208
- * Unhighlight this widget.
1209
- */
1210
- private void unhighlight() {
1211
- getElement().setPropertyString("className", getStylePrimaryName());
1212
- }
1213
-
1214
- @Override
1215
- public void onResize() {
1216
- redraw();
1217
- }
1218
-
1219
- public void clearMarkers() {
1220
- markers.clear();
1221
- }
1222
-
1223
- public boolean addMarker(String markerName, Double markerPosition) {
1224
- return markers.add(new Marker(markerName, markerPosition));
1225
- }
1226
-
1227
- public boolean setMarker(String markerName, Double markerPosition) {
1228
- Marker marker = findMarkerByName(markerName);
1229
- if (marker != null) {
1230
- marker.position = markerPosition;
1231
- return true;
1232
- }
1233
- return false;
1234
- }
1235
-
1236
- public boolean removeMarker(String markerName) {
1237
- Marker marker = findMarkerByName(markerName);
1238
- if (marker != null) {
1239
- return markers.remove(marker);
1240
- }
1241
- return false;
1242
- }
1243
-
1244
- public Iterator<Marker> getMarkers() {
1245
- return markers.iterator();
1246
- }
1247
-
1248
- private Marker findMarkerByName(String markerName) {
1249
- for (Marker marker : markers) {
1250
- if (marker.name.equals(markerName)) {
1251
- return marker;
1252
- }
1253
- }
1254
- return null;
1255
- }
1256
-}
1
+package com.sap.sse.gwt.client.controls.slider;
2
+
3
+/*
4
+ * Copyright 2008 Google Inc.
5
+ *
6
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
7
+ * use this file except in compliance with the License. You may obtain a copy of
8
+ * the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing, software
13
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15
+ * License for the specific language governing permissions and limitations under
16
+ * the License.
17
+ */
18
+
19
+import java.util.ArrayList;
20
+import java.util.Iterator;
21
+import java.util.List;
22
+
23
+import com.google.gwt.core.client.GWT;
24
+import com.google.gwt.core.client.Scheduler;
25
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
26
+import com.google.gwt.dom.client.Element;
27
+import com.google.gwt.dom.client.Style.Display;
28
+import com.google.gwt.dom.client.Style.Position;
29
+import com.google.gwt.dom.client.Style.Unit;
30
+import com.google.gwt.dom.client.Style.Visibility;
31
+import com.google.gwt.event.dom.client.KeyCodes;
32
+import com.google.gwt.event.logical.shared.HasValueChangeHandlers;
33
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
34
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
35
+import com.google.gwt.event.shared.HandlerRegistration;
36
+import com.google.gwt.resources.client.ClientBundle;
37
+import com.google.gwt.resources.client.CssResource;
38
+import com.google.gwt.resources.client.CssResource.NotStrict;
39
+import com.google.gwt.resources.client.ImageResource;
40
+import com.google.gwt.user.client.DOM;
41
+import com.google.gwt.user.client.Event;
42
+import com.google.gwt.user.client.Timer;
43
+import com.google.gwt.user.client.Window;
44
+import com.google.gwt.user.client.ui.FocusPanel;
45
+import com.google.gwt.user.client.ui.HasValue;
46
+import com.google.gwt.user.client.ui.Image;
47
+import com.google.gwt.user.client.ui.RequiresResize;
48
+import com.sap.sse.common.Util;
49
+
50
+/**
51
+ * A widget that allows the user to select a value within a range of possible values using a sliding bar that responds
52
+ * to mouse events.
53
+ *
54
+ * <h3>Keyboard Events</h3>
55
+ * <p>
56
+ * SliderBar listens for the following key events. Holding down a key will repeat the action until the key is released.
57
+ * <ul class='css'>
58
+ * <li>left arrow - shift left one step</li>
59
+ * <li>right arrow - shift right one step</li>
60
+ * <li>ctrl+left arrow - jump left 10% of the distance</li>
61
+ * <li>ctrl+right arrow - jump right 10% of the distance</li>
62
+ * <li>home - jump to min value</li>
63
+ * <li>end - jump to max value</li>
64
+ * <li>space - jump to middle value</li>
65
+ * </ul>
66
+ * </p>
67
+ *
68
+ * <h3>CSS Style Rules</h3>
69
+ * <ul class='css'>
70
+ * <li>.gwt-SliderBar-shell { primary style }</li>
71
+ * <li>.gwt-SliderBar-shell-focused { primary style when focused }</li>
72
+ * <li>.gwt-SliderBar-shell gwt-SliderBar-line { the line that the knob moves along }</li>
73
+ * <li>.gwt-SliderBar-shell gwt-SliderBar-line-sliding { the line that the knob moves along when sliding }</li>
74
+ * <li>.gwt-SliderBar-shell .gwt-SliderBar-knob { the sliding knob }</li>
75
+ * <li>.gwt-SliderBar-shell .gwt-SliderBar-knob-sliding { the sliding knob when sliding }</li>
76
+ * <li>
77
+ * .gwt-SliderBar-shell .gwt-SliderBar-tick { the ticks along the line }</li>
78
+ * <li>.gwt-SliderBar-shell .gwt-SliderBar-ticklabel { the text labels along the line }</li>
79
+ * </ul>
80
+ */
81
+public class SliderBar extends FocusPanel implements RequiresResize, HasValue<Double>, HasValueChangeHandlers<Double> {
82
+ /**
83
+ * A formatter used to format the labels displayed in the widget.
84
+ */
85
+ public static interface LabelFormatter {
86
+ /**
87
+ * Generate the text to display in each label based on the label's value.
88
+ *
89
+ * Override this method to change the text displayed within the SliderBar.
90
+ *
91
+ * @param slider
92
+ * the Slider bar
93
+ * @param value
94
+ * the value the label displays
95
+ * @return the text to display for the label
96
+ */
97
+ String formatLabel(SliderBar slider, Double value, Double previousValue);
98
+ }
99
+
100
+ /**
101
+ * A {@link ClientBundle} that provides images for {@link SliderBar}.
102
+ */
103
+ public static interface SliderBarImages extends ClientBundle {
104
+ public static final SliderBarImages INSTANCE = GWT.create(SliderBarImages.class);
105
+
106
+ /**
107
+ * An image used for the sliding knob.
108
+ *
109
+ * @return a prototype of this image
110
+ */
111
+ @Source("slider_blue.png")
112
+ ImageResource slider();
113
+
114
+ /**
115
+ * An image used for the disabled sliding knob.
116
+ *
117
+ * @return a prototype of this image
118
+ */
119
+ @Source("slider_blue.png")
120
+ ImageResource sliderDisabled();
121
+
122
+ /**
123
+ * An image used for the sliding knob while sliding.
124
+ *
125
+ * @return a prototype of this image
126
+ */
127
+ @Source("slider_blue.png")
128
+ ImageResource sliderSliding();
129
+
130
+ @NotStrict
131
+ @Source("SliderBar.css")
132
+ CssResource sliderBarCss();
133
+ }
134
+
135
+ /**
136
+ * The timer used to continue to shift the knob as the user holds down one of the left/right arrow keys. Only IE
137
+ * auto-repeats, so we just keep catching the events.
138
+ */
139
+ private class KeyTimer extends Timer {
140
+ /**
141
+ * A bit indicating that this is the first run.
142
+ */
143
+ private boolean firstRun = true;
144
+
145
+ /**
146
+ * The delay between shifts, which shortens as the user holds down the button.
147
+ */
148
+ private int repeatDelay = 30;
149
+
150
+ /**
151
+ * A bit indicating whether we are shifting to a higher or lower value.
152
+ */
153
+ private boolean shiftRight = false;
154
+
155
+ /**
156
+ * The number of steps to shift with each press.
157
+ */
158
+ private int multiplier = 1;
159
+
160
+ /**
161
+ * This method will be called when a timer fires. Override it to implement the timer's logic.
162
+ */
163
+ @Override
164
+ public void run() {
165
+ // Highlight the knob on first run
166
+ if (firstRun) {
167
+ firstRun = false;
168
+ startSliding(true, false);
169
+ }
170
+
171
+ // Slide the slider bar
172
+ if (shiftRight) {
173
+ setCurrentValue(curValue + multiplier * stepSize);
174
+ } else {
175
+ setCurrentValue(curValue - multiplier * stepSize);
176
+ }
177
+
178
+ // Repeat this timer until cancelled by keyup event
179
+ schedule(repeatDelay);
180
+ }
181
+
182
+ /**
183
+ * Schedules a timer to elapse in the future.
184
+ *
185
+ * @param delayMillis
186
+ * how long to wait before the timer elapses, in milliseconds
187
+ * @param shiftRight
188
+ * whether to shift up or not
189
+ * @param multiplier
190
+ * the number of steps to shift
191
+ */
192
+ public void schedule(int delayMillis, boolean shiftRight, int multiplier) {
193
+ firstRun = true;
194
+ this.shiftRight = shiftRight;
195
+ this.multiplier = multiplier;
196
+ super.schedule(delayMillis);
197
+ }
198
+ }
199
+
200
+ /**
201
+ * The current value.
202
+ */
203
+ protected Double curValue;
204
+
205
+ /**
206
+ * The knob that slides across the line.
207
+ */
208
+ protected Image knobImage = new Image();
209
+
210
+ /**
211
+ * The timer used to continue to shift the knob if the user holds down a key.
212
+ */
213
+ private KeyTimer keyTimer = new KeyTimer();
214
+
215
+ /**
216
+ * The elements used to display labels above the ticks.
217
+ */
218
+ protected List<Element> tickLabelElements = new ArrayList<Element>();
219
+
220
+ /**
221
+ * The elements used to display the marker labels.
222
+ */
223
+ protected List<Element> markerLabelElements = new ArrayList<Element>();
224
+
225
+ /**
226
+ * The formatter used to generate label text.
227
+ */
228
+ protected LabelFormatter tickLabelFormatter;
229
+
230
+ /**
231
+ * The line that the knob moves over.
232
+ */
233
+ protected Element lineElement;
234
+
235
+ /**
236
+ * The offset between the edge of the shell and the line.
237
+ */
238
+ protected int lineLeftOffset = 0;
239
+
240
+ /**
241
+ * The maximum slider value.
242
+ */
243
+ protected Double maxValue;
244
+
245
+ /**
246
+ * The minimum slider value.
247
+ */
248
+ protected Double minValue;
249
+
250
+ /**
251
+ * The number of labels to show.
252
+ */
253
+ private int numTickLabels = 0;
254
+
255
+ /**
256
+ * The number of tick marks to show.
257
+ */
258
+ protected int numTicks = 0;
259
+
260
+ /**
261
+ * A bit indicating whether or not we are currently sliding the slider bar due to keyboard events.
262
+ */
263
+ private boolean slidingKeyboard = false;
264
+
265
+ /**
266
+ * A bit indicating whether or not we are currently sliding the slider bar due to mouse events.
267
+ */
268
+ private boolean slidingMouse = false;
269
+
270
+ /**
271
+ * A bit indicating whether or not the slider is enabled
272
+ */
273
+ protected boolean enabled = true;
274
+
275
+ /**
276
+ * The images used with the sliding bar.
277
+ */
278
+ private SliderBarImages images;
279
+
280
+ /**
281
+ * The size of the increments between knob positions.
282
+ */
283
+ protected double stepSize;
284
+
285
+ /**
286
+ * The elements used to display tick marks, which are the vertical lines along the slider bar.
287
+ */
288
+ protected List<Element> tickElements = new ArrayList<Element>();
289
+
290
+ /**
291
+ * The elements used to display additional markers on the slider bar.
292
+ */
293
+ protected List<Element> markerElements = new ArrayList<Element>();
294
+
295
+ private List<Marker> markers = new ArrayList<Marker>();
296
+
297
+ private class Marker {
298
+ String name;
299
+ Double position;
300
+
301
+ public Marker(String name, Double position) {
302
+ super();
303
+ this.name = name;
304
+ this.position = position;
305
+ }
306
+ }
307
+
308
+ /**
309
+ * Create a slider bar.
310
+ */
311
+ public SliderBar() {
312
+ this(null, null, null);
313
+ }
314
+
315
+ /**
316
+ * Create a slider bar.
317
+ *
318
+ * @param minValue
319
+ * the minimum value in the range
320
+ * @param maxValue
321
+ * the maximum value in the range
322
+ */
323
+ public SliderBar(double minValue, double maxValue) {
324
+ this(minValue, maxValue, null);
325
+ }
326
+
327
+ /**
328
+ * Create a slider bar.
329
+ *
330
+ * @param minValue
331
+ * the minimum value in the range
332
+ * @param maxValue
333
+ * the maximum value in the range
334
+ * @param tickLabelFormatter
335
+ * the label formatter
336
+ */
337
+ public SliderBar(Double minValue, Double maxValue, LabelFormatter tickLabelFormatter) {
338
+ this(minValue, maxValue, tickLabelFormatter, SliderBarImages.INSTANCE);
339
+ }
340
+
341
+ /**
342
+ * Create a slider bar.
343
+ *
344
+ * @param minValue
345
+ * the minimum value in the range
346
+ * @param maxValue
347
+ * the maximum value in the range
348
+ * @param tickLabelFormatter
349
+ * the label formatter
350
+ * @param images
351
+ * the images to use for the slider
352
+ */
353
+ public SliderBar(Double minValue, Double maxValue, LabelFormatter tickLabelFormatter, SliderBarImages images) {
354
+ super();
355
+ images.sliderBarCss().ensureInjected();
356
+ this.minValue = minValue;
357
+ this.maxValue = maxValue;
358
+ this.images = images;
359
+ setLabelFormatter(tickLabelFormatter);
360
+
361
+ // Create the outer shell
362
+ getElement().getStyle().setPosition(Position.RELATIVE);
363
+ setStyleName("gwt-SliderBar-shell");
364
+
365
+ // Create the line
366
+ lineElement = DOM.createDiv();
367
+ DOM.appendChild(getElement(), lineElement);
368
+ lineElement.getStyle().setPosition(Position.ABSOLUTE);
369
+ lineElement.setPropertyString("className", "gwt-SliderBar-line");
370
+
371
+ // Create the knob
372
+ knobImage.setResource(images.slider());
373
+ Element knobElement = knobImage.getElement();
374
+ DOM.appendChild(getElement(), knobElement);
375
+ knobElement.getStyle().setPosition(Position.ABSOLUTE);
376
+ knobElement.setPropertyString("className", "gwt-SliderBar-knob");
377
+
378
+ sinkEvents(Event.MOUSEEVENTS | Event.KEYEVENTS | Event.FOCUSEVENTS);
379
+
380
+ // workaround to render properly when parent Widget does not
381
+ // implement ProvidesResize since DOM doesn't provide element
382
+ // height and width until onModuleLoad() finishes.
383
+ Scheduler.get().scheduleDeferred(new ScheduledCommand() {
384
+ @Override
385
+ public void execute() {
386
+ onResize();
387
+ }
388
+ });
389
+ }
390
+
391
+ public boolean isMinMaxInitialized() {
392
+ return minValue != null && maxValue != null;
393
+ }
394
+
395
+ public HandlerRegistration addValueChangeHandler(ValueChangeHandler<Double> handler) {
396
+ return addHandler(handler, ValueChangeEvent.getType());
397
+ }
398
+
399
+ /**
400
+ * Return the current value.
401
+ *
402
+ * @return the current value
403
+ */
404
+ public Double getCurrentValue() {
405
+ return curValue;
406
+ }
407
+
408
+ /**
409
+ * Return the label formatter.
410
+ *
411
+ * @return the label formatter
412
+ */
413
+ public LabelFormatter getLabelFormatter() {
414
+ return tickLabelFormatter;
415
+ }
416
+
417
+ /**
418
+ * Return the max value.
419
+ *
420
+ * @return the max value
421
+ */
422
+ public Double getMaxValue() {
423
+ return maxValue;
424
+ }
425
+
426
+ /**
427
+ * Return the minimum value.
428
+ *
429
+ * @return the minimum value
430
+ */
431
+ public Double getMinValue() {
432
+ return minValue;
433
+ }
434
+
435
+ /**
436
+ * Return the number of labels.
437
+ *
438
+ * @return the number of labels
439
+ */
440
+ public int getNumLabels() {
441
+ return numTickLabels;
442
+ }
443
+
444
+ /**
445
+ * Return the number of ticks.
446
+ *
447
+ * @return the number of ticks
448
+ */
449
+ public int getNumTicks() {
450
+ return numTicks;
451
+ }
452
+
453
+ /**
454
+ * Return the step size.
455
+ *
456
+ * @return the step size
457
+ */
458
+ public double getStepSize() {
459
+ return stepSize;
460
+ }
461
+
462
+ /**
463
+ * Return the total range between the minimum and maximum values.
464
+ *
465
+ * @return the total range
466
+ */
467
+ public double getTotalRange() {
468
+ if (minValue == null || maxValue == null || minValue > maxValue) {
469
+ return 0;
470
+ } else {
471
+ return maxValue - minValue;
472
+ }
473
+ }
474
+
475
+ public Double getValue() {
476
+ return curValue;
477
+ }
478
+
479
+ /**
480
+ * @return Gets whether this widget is enabled
481
+ */
482
+ public boolean isEnabled() {
483
+ return enabled;
484
+ }
485
+
486
+ /**
487
+ * Listen for events that will move the knob.
488
+ *
489
+ * @param event
490
+ * the event that occurred
491
+ */
492
+ @Override
493
+ public void onBrowserEvent(Event event) {
494
+ super.onBrowserEvent(event);
495
+ if (enabled) {
496
+ switch (DOM.eventGetType(event)) {
497
+ // Unhighlight and cancel keyboard events
498
+ case Event.ONBLUR:
499
+ keyTimer.cancel();
500
+ if (slidingMouse) {
501
+ DOM.releaseCapture(getElement());
502
+ slidingMouse = false;
503
+ slideKnob(event);
504
+ stopSliding(true, true);
505
+ } else if (slidingKeyboard) {
506
+ slidingKeyboard = false;
507
+ stopSliding(true, true);
508
+ }
509
+ unhighlight();
510
+ break;
511
+
512
+ // Highlight on focus
513
+ case Event.ONFOCUS:
514
+ highlight();
515
+ break;
516
+
517
+ // Mousewheel events
518
+ case Event.ONMOUSEWHEEL:
519
+ int velocityY = event.getMouseWheelVelocityY();
520
+ event.preventDefault();
521
+ if (velocityY > 0) {
522
+ shiftRight(1);
523
+ } else {
524
+ shiftLeft(1);
525
+ }
526
+ break;
527
+
528
+ // Shift left or right on key press
529
+ case Event.ONKEYDOWN:
530
+ if (!slidingKeyboard) {
531
+ int multiplier = 1;
532
+ if (event.getCtrlKey()) {
533
+ multiplier = (int) (getTotalRange() / stepSize / 10);
534
+ }
535
+
536
+ switch (event.getKeyCode()) {
537
+ case KeyCodes.KEY_HOME:
538
+ event.preventDefault();
539
+ setCurrentValue(minValue);
540
+ break;
541
+ case KeyCodes.KEY_END:
542
+ event.preventDefault();
543
+ setCurrentValue(maxValue);
544
+ break;
545
+ case KeyCodes.KEY_LEFT:
546
+ event.preventDefault();
547
+ slidingKeyboard = true;
548
+ startSliding(false, true);
549
+ shiftLeft(multiplier);
550
+ keyTimer.schedule(400, false, multiplier);
551
+ break;
552
+ case KeyCodes.KEY_RIGHT:
553
+ event.preventDefault();
554
+ slidingKeyboard = true;
555
+ startSliding(false, true);
556
+ shiftRight(multiplier);
557
+ keyTimer.schedule(400, true, multiplier);
558
+ break;
559
+ case 32:
560
+ event.preventDefault();
561
+ setCurrentValue(minValue + getTotalRange() / 2);
562
+ break;
563
+ }
564
+ }
565
+ break;
566
+ // Stop shifting on key up
567
+ case Event.ONKEYUP:
568
+ keyTimer.cancel();
569
+ if (slidingKeyboard) {
570
+ slidingKeyboard = false;
571
+ stopSliding(true, true);
572
+ }
573
+ break;
574
+
575
+ // Mouse Events
576
+ case Event.ONMOUSEDOWN:
577
+ setFocus(true);
578
+ slidingMouse = true;
579
+ DOM.setCapture(getElement());
580
+ startSliding(true, true);
581
+ event.preventDefault();
582
+ slideKnob(event);
583
+ break;
584
+ case Event.ONMOUSEUP:
585
+ if (slidingMouse) {
586
+ DOM.releaseCapture(getElement());
587
+ slidingMouse = false;
588
+ slideKnob(event);
589
+ stopSliding(true, true);
590
+ }
591
+ break;
592
+ case Event.ONMOUSEMOVE:
593
+ if (slidingMouse) {
594
+ slideKnob(event);
595
+ }
596
+ break;
597
+ }
598
+ }
599
+ }
600
+
601
+ /**
602
+ * This method is called when the dimensions of the parent element change. Subclasses should override this method as
603
+ * needed.
604
+ *
605
+ * @param width
606
+ * the new client width of the element
607
+ * @param height
608
+ * the new client height of the element
609
+ */
610
+ public void onResize(int width, int height) {
611
+ // Center the line in the shell
612
+ int lineWidth = lineElement.getOffsetWidth();
613
+ lineLeftOffset = (width / 2) - (lineWidth / 2);
614
+ lineElement.getStyle().setLeft(lineLeftOffset, Unit.PX);
615
+
616
+ // Draw the other components
617
+ drawTickLabels();
618
+ drawTicks();
619
+ drawMarkers();
620
+ drawMarkerLabels();
621
+ drawKnob();
622
+ }
623
+
624
+ /**
625
+ * Redraw the progress bar when something changes the layout.
626
+ */
627
+ public void redraw() {
628
+ if (isAttached()) {
629
+ int width = getElement().getClientWidth();
630
+ int height = getElement().getClientHeight();
631
+ onResize(width, height);
632
+ }
633
+ }
634
+
635
+ /**
636
+ * Set the current value and fire the onValueChange event.
637
+ *
638
+ * @param curValue
639
+ * the current value
640
+ */
641
+ public synchronized void setCurrentValue(Double curValue) {
642
+ setCurrentValue(curValue, true);
643
+ }
644
+
645
+ /**
646
+ * Set the current value and optionally fire the onValueChange event.
647
+ *
648
+ * @param curValue
649
+ * the current value
650
+ * @param fireEvent
651
+ * fire the onValue change event if true
652
+ */
653
+ public synchronized void setCurrentValue(Double curValue, boolean fireEvent) {
654
+ // Confine the value to the range
655
+ if (!isMinMaxInitialized() || curValue == null) {
656
+ return;
657
+ }
658
+ this.curValue = Math.max(minValue, Math.min(maxValue, curValue));
659
+ double remainder = (this.curValue - minValue) % stepSize;
660
+ this.curValue -= remainder;
661
+ // Go to next step if more than halfway there
662
+ if ((remainder > (stepSize / 2)) && ((this.curValue + stepSize) <= maxValue)) {
663
+ this.curValue += stepSize;
664
+ }
665
+ // Redraw the knob
666
+ drawKnob();
667
+ // Fire the ValueChangeEvent if the value actually changed
668
+ if (fireEvent && !curValue.equals(this.curValue)) {
669
+ ValueChangeEvent.fire(this, this.curValue);
670
+ }
671
+ }
672
+
673
+ /**
674
+ * Sets whether this widget is enabled.
675
+ *
676
+ * @param enabled
677
+ * true to enable the widget, false to disable it
678
+ */
679
+ public void setEnabled(boolean enabled) {
680
+ this.enabled = enabled;
681
+ if (enabled) {
682
+ knobImage.setResource(images.slider());
683
+ lineElement.setPropertyString("className", "gwt-SliderBar-line");
684
+ } else {
685
+ knobImage.setResource(images.sliderDisabled());
686
+ lineElement.setPropertyString("className", "gwt-SliderBar-line gwt-SliderBar-line-disabled");
687
+ }
688
+ redraw();
689
+ }
690
+
691
+ /**
692
+ * Set the label formatter.
693
+ *
694
+ * @param labelFormatter
695
+ * the label formatter
696
+ */
697
+ public void setLabelFormatter(LabelFormatter labelFormatter) {
698
+ this.tickLabelFormatter = labelFormatter;
699
+ }
700
+
701
+ /**
702
+ * Set the max value.
703
+ *
704
+ * @param maxValue
705
+ * the current value
706
+ */
707
+ public void setMaxValue(Double maxValue, boolean fireEvent) {
708
+ if (!Util.equalsWithNull(maxValue, this.maxValue)) {
709
+ this.maxValue = maxValue;
710
+ onMinMaxValueChanged(fireEvent);
711
+ }
712
+ }
713
+
714
+ /**
715
+ * Set the minimum value.
716
+ *
717
+ * @param minValue
718
+ * the current value
719
+ */
720
+ public void setMinValue(Double minValue, boolean fireEvent) {
721
+ if (!Util.equalsWithNull(minValue, this.minValue)) {
722
+ this.minValue = minValue;
723
+ onMinMaxValueChanged(fireEvent);
724
+ }
725
+ }
726
+
727
+ /**
728
+ * Set the minimum and maximum value
729
+ *
730
+ * @param minValue
731
+ * the current value for min
732
+ * @param maxValue
733
+ * the current valuefor max
734
+ */
735
+ public void setMinAndMaxValue(Double minValue, Double maxValue, boolean fireEvent) {
736
+ boolean changed = false;
737
+ if (!Util.equalsWithNull(minValue, this.minValue)) {
738
+ this.minValue = minValue;
739
+ changed = true;
740
+ }
741
+ if (!Util.equalsWithNull(maxValue, this.maxValue)) {
742
+ this.maxValue = maxValue;
743
+ changed = true;
744
+ }
745
+ if (changed) {
746
+ onMinMaxValueChanged(fireEvent);
747
+ }
748
+ }
749
+
750
+ /**
751
+ * Handle value changes of min and/or max value
752
+ */
753
+ protected void onMinMaxValueChanged(boolean fireEvent) {
754
+ redraw();
755
+ resetCurrentValue(fireEvent);
756
+ }
757
+
758
+ /**
759
+ * Set the number of tick labels to show on the line. Tick labels indicate the value of the slider at that point.
760
+ * Use this method to enable tick labels.
761
+ *
762
+ * If you set the number of tick labels equal to the total range divided by the step size, you will get a properly
763
+ * aligned "jumping" effect where the knob jumps between tick labels.
764
+ *
765
+ * Note that the number of tick labels displayed will be one more than the number you specify, so specify 1 labels
766
+ * to show labels on either end of the line. In other words, numTickLabels is really the number of slots between the
767
+ * labels.
768
+ *
769
+ * setNumTickLabels(0) will disable the labels.
770
+ *
771
+ * @param numTickLabels
772
+ * the number of tick labels to show
773
+ */
774
+ public void setNumTickLabels(int numTickLabels) {
775
+ this.numTickLabels = numTickLabels;
776
+ drawTickLabels();
777
+ }
778
+
779
+ /**
780
+ * Set the number of ticks to show on the line. A tick is a vertical line that represents a division of the overall
781
+ * line. Use this method to enable ticks.
782
+ *
783
+ * If you set the number of ticks equal to the total range divided by the step size, you will get a properly aligned
784
+ * "jumping" effect where the knob jumps between ticks.
785
+ *
786
+ * Note that the number of ticks displayed will be one more than the number you specify, so specify 1 tick to show
787
+ * ticks on either end of the line. In other words, numTicks is really the number of slots between the ticks.
788
+ *
789
+ * setNumTicks(0) will disable ticks.
790
+ *
791
+ * @param numTicks
792
+ * the number of ticks to show
793
+ */
794
+ public void setNumTicks(int numTicks) {
795
+ this.numTicks = numTicks;
796
+ drawTicks();
797
+ }
798
+
799
+ /**
800
+ * Set the step size.
801
+ *
802
+ * @param stepSize
803
+ * the current value
804
+ */
805
+ public void setStepSize(double stepSize, boolean fireEvent) {
806
+ this.stepSize = stepSize;
807
+ resetCurrentValue(fireEvent);
808
+ }
809
+
810
+ public void setValue(Double value) {
811
+ setCurrentValue(value, false);
812
+ }
813
+
814
+ public void setValue(Double value, boolean fireEvent) {
815
+ setCurrentValue(value, fireEvent);
816
+ }
817
+
818
+ /**
819
+ * Shift to the left (smaller value).
820
+ *
821
+ * @param numSteps
822
+ * the number of steps to shift
823
+ */
824
+ public void shiftLeft(int numSteps) {
825
+ setCurrentValue(getCurrentValue() - numSteps * stepSize);
826
+ }
827
+
828
+ /**
829
+ * Shift to the right (greater value).
830
+ *
831
+ * @param numSteps
832
+ * the number of steps to shift
833
+ */
834
+ public void shiftRight(int numSteps) {
835
+ setCurrentValue(getCurrentValue() + numSteps * stepSize);
836
+ }
837
+
838
+ /**
839
+ * Format the label to display above the ticks
840
+ *
841
+ * Override this method in a subclass to customize the format. By default, this method returns the integer portion
842
+ * of the value.
843
+ *
844
+ * @param value
845
+ * the value at the label
846
+ * @return the text to put in the label
847
+ */
848
+ protected String formatTickLabel(Double value, Double previousValue) {
849
+ if (tickLabelFormatter != null) {
850
+ return tickLabelFormatter.formatLabel(this, value, previousValue);
851
+ } else {
852
+ return (int) (10 * value) / 10.0 + "";
853
+ }
854
+ }
855
+
856
+ /**
857
+ * Get the percentage of the knob's position relative to the size of the line. The return value will be between 0.0
858
+ * and 1.0.
859
+ *
860
+ * @return the current percent complete
861
+ */
862
+ protected double getKnobPercent() {
863
+ // If we have no range
864
+ if (maxValue <= minValue) {
865
+ return 0;
866
+ }
867
+
868
+ // Calculate the relative progress
869
+ double percent = (curValue - minValue) / (maxValue - minValue);
870
+ return Math.max(0.0, Math.min(1.0, percent));
871
+ }
872
+
873
+ /**
874
+ * This method is called immediately after a widget becomes attached to the browser's document.
875
+ */
876
+ @Override
877
+ protected void onLoad() {
878
+ // Reset the position attribute of the parent element
879
+ getElement().getStyle().setPosition(Position.RELATIVE);
880
+ }
881
+
882
+ /**
883
+ * Draw the knob where it is supposed to be relative to the line.
884
+ */
885
+ protected void drawKnob() {
886
+ if (isAttached() && isMinMaxInitialized()) {
887
+ // Move the knob to the correct position
888
+ Element knobElement = knobImage.getElement();
889
+ int lineWidth = lineElement.getOffsetWidth();
890
+ int knobWidth = knobElement.getOffsetWidth();
891
+ int knobLeftOffset = (int) (lineLeftOffset + (getKnobPercent() * lineWidth) - (knobWidth / 2));
892
+ knobLeftOffset = Math.min(knobLeftOffset, lineLeftOffset + lineWidth - (knobWidth / 2) - 1);
893
+ knobElement.getStyle().setLeft(knobLeftOffset, Unit.PX);
894
+ }
895
+ }
896
+
897
+ /**
898
+ * Draw the labels along the line.
899
+ */
900
+ protected void drawTickLabels() {
901
+ if (isAttached() && isMinMaxInitialized()) {
902
+ // Draw the tick labels
903
+ int lineWidth = lineElement.getOffsetWidth();
904
+ if (numTickLabels > 0) {
905
+ // Create the labels or make them visible
906
+ Double previousValue = null;
907
+ for (int i = 0; i <= numTickLabels; i++) {
908
+ Element label = null;
909
+ if (i < tickLabelElements.size()) {
910
+ label = tickLabelElements.get(i);
911
+ } else { // Create the new label
912
+ label = DOM.createDiv();
913
+ label.getStyle().setPosition(Position.ABSOLUTE);
914
+ label.getStyle().setDisplay(Display.NONE);
915
+ if (enabled) {
916
+ label.setPropertyString("className", "gwt-SliderBar-ticklabel");
917
+ } else {
918
+ label.setPropertyString("className", "gwt-SliderBar-ticklabel-disabled");
919
+ }
920
+ DOM.appendChild(getElement(), label);
921
+ tickLabelElements.add(label);
922
+ }
923
+
924
+ // Set the label text
925
+ double value = minValue + (getTotalRange() * i / numTickLabels);
926
+ label.getStyle().setVisibility(Visibility.HIDDEN);
927
+ label.getStyle().setProperty("display", "");
928
+ label.setPropertyString("innerHTML", formatTickLabel(value, previousValue));
929
+
930
+ // Move to the left so the label width is not clipped by the shell
931
+ label.getStyle().setLeft(0, Unit.PX);
932
+
933
+ // Position the label and make it visible
934
+ int labelWidth = label.getOffsetWidth();
935
+ int labelLeftOffset = lineLeftOffset + (lineWidth * i / numTickLabels) - (labelWidth / 2);
936
+ labelLeftOffset = Math.min(labelLeftOffset, lineLeftOffset + lineWidth - labelWidth);
937
+ labelLeftOffset = Math.max(labelLeftOffset, lineLeftOffset);
938
+ label.getStyle().setLeft(labelLeftOffset, Unit.PX);
939
+ label.getStyle().setVisibility(Visibility.VISIBLE);
940
+
941
+ previousValue = value;
942
+ }
943
+
944
+ // Hide unused labels
945
+ for (int i = (numTickLabels + 1); i < tickLabelElements.size(); i++) {
946
+ tickLabelElements.get(i).getStyle().setDisplay(Display.NONE);
947
+ }
948
+ } else { // Hide all labels
949
+ for (Element elem : tickLabelElements) {
950
+ elem.getStyle().setDisplay(Display.NONE);
951
+ }
952
+ }
953
+ }
954
+ }
955
+
956
+ /**
957
+ * Draw the tick along the line.
958
+ */
959
+ protected void drawTicks() {
960
+ if (isAttached() && isMinMaxInitialized()) {
961
+ // Draw the ticks
962
+ int lineWidth = lineElement.getOffsetWidth();
963
+ if (numTicks > 0) {
964
+ // Create the ticks or make them visible
965
+ for (int i = 0; i <= numTicks; i++) {
966
+ Element tick = null;
967
+ if (i < tickElements.size()) {
968
+ tick = tickElements.get(i);
969
+ } else { // Create the new tick
970
+ tick = DOM.createDiv();
971
+ tick.getStyle().setPosition(Position.ABSOLUTE);
972
+ tick.getStyle().setDisplay(Display.NONE);
973
+ DOM.appendChild(getElement(), tick);
974
+ tickElements.add(tick);
975
+ }
976
+ if (enabled) {
977
+ tick.setPropertyString("className", "gwt-SliderBar-tick");
978
+ } else {
979
+ tick.setPropertyString("className", "gwt-SliderBar-tick gwt-SliderBar-tick-disabled");
980
+ }
981
+ // Position the tick and make it visible
982
+ tick.getStyle().setVisibility(Visibility.HIDDEN);
983
+ tick.getStyle().setProperty("display", "");
984
+ int tickWidth = tick.getOffsetWidth();
985
+ int tickLeftOffset = lineLeftOffset + (lineWidth * i / numTicks) - (tickWidth / 2);
986
+ tickLeftOffset = Math.min(tickLeftOffset, lineLeftOffset + lineWidth - tickWidth);
987
+ tick.getStyle().setLeft(tickLeftOffset, Unit.PX);
988
+ tick.getStyle().setVisibility(Visibility.VISIBLE);
989
+ }
990
+
991
+ // Hide unused ticks
992
+ for (int i = (numTicks + 1); i < tickElements.size(); i++) {
993
+ tickElements.get(i).getStyle().setDisplay(Display.NONE);
994
+ }
995
+ } else { // Hide all ticks
996
+ for (Element elem : tickElements) {
997
+ elem.getStyle().setDisplay(Display.NONE);
998
+ }
999
+ }
1000
+ }
1001
+ }
1002
+
1003
+ /**
1004
+ * Draw the markers.
1005
+ */
1006
+ private void drawMarkers() {
1007
+ if (isAttached() && isMinMaxInitialized()) {
1008
+ int numMarkers = markers.size();
1009
+ // Draw the markers
1010
+ int lineWidth = lineElement.getOffsetWidth();
1011
+ if (numMarkers > 0) {
1012
+ // Create the markers or make them visible
1013
+ Element lastMarkerElement = null;
1014
+ int lastMarkerOffsetLeft = 0;
1015
+ for (int i = 0; i < numMarkers; i++) {
1016
+ Marker marker = markers.get(i);
1017
+ Element markerElem = null;
1018
+ if (i < markerElements.size()) {
1019
+ markerElem = markerElements.get(i);
1020
+ } else { // Create the new markes
1021
+ markerElem = DOM.createDiv();
1022
+ markerElem.getStyle().setPosition(Position.ABSOLUTE);
1023
+ markerElem.getStyle().setDisplay(Display.NONE);
1024
+ DOM.appendChild(getElement(), markerElem);
1025
+ markerElements.add(markerElem);
1026
+ }
1027
+ if (enabled) {
1028
+ markerElem.setPropertyString("className", "gwt-SliderBar-mark");
1029
+ } else {
1030
+ markerElem.setPropertyString("className", "gwt-SliderBar-mark gwt-SliderBar-mark-disabled");
1031
+ }
1032
+ // Position the marker and make it visible
1033
+ markerElem.getStyle().setVisibility(Visibility.HIDDEN);
1034
+ markerElem.getStyle().setProperty("display", "");
1035
+ double markerLinePosition = (marker.position - minValue) * lineWidth / getTotalRange();
1036
+ int markerWidth = markerElem.getOffsetWidth();
1037
+ int markerLeftOffset = lineLeftOffset + (int) markerLinePosition - (markerWidth / 2);
1038
+ markerLeftOffset = Math.min(markerLeftOffset, lineLeftOffset + lineWidth - markerWidth);
1039
+ markerElem.getStyle().setLeft(markerLeftOffset, Unit.PX);
1040
+ markerElem.getStyle().setVisibility(Visibility.VISIBLE);
1041
+ // change class of last marker if it is too close to the one just created
1042
+ if (lastMarkerOffsetLeft > 0 && (markerLeftOffset-lastMarkerOffsetLeft) <= markerWidth) {
1043
+ if (lastMarkerElement != null) {
1044
+ lastMarkerElement.setPropertyString("className", "gwt-SliderBar-mark-tooClose");
1045
+ lastMarkerElement.getStyle().setLeft(markerLeftOffset-10, Unit.PX);
1046
+ }
1047
+ }
1048
+ lastMarkerElement = markerElem;
1049
+ lastMarkerOffsetLeft = markerLeftOffset;
1050
+ }
1051
+
1052
+ // Hide unused markers
1053
+ for (int i = numMarkers; i < markerElements.size(); i++) {
1054
+ markerElements.get(i).getStyle().setDisplay(Display.NONE);
1055
+ }
1056
+ } else { // Hide all markers
1057
+ for (Element elem : markerElements) {
1058
+ elem.getStyle().setDisplay(Display.NONE);
1059
+ }
1060
+ }
1061
+ }
1062
+ }
1063
+
1064
+ /**
1065
+ * Draw the marker labels.
1066
+ */
1067
+ private void drawMarkerLabels() {
1068
+ if (isAttached() && isMinMaxInitialized()) {
1069
+ int numMarkers = markers.size();
1070
+ // Draw the marker labels
1071
+ int lineWidth = lineElement.getOffsetWidth();
1072
+ if (numMarkers > 0) {
1073
+ Element lastMarkerLabel = null;
1074
+ Marker lastMarker = null;
1075
+ int lastMarkerLabelOffsetLeft = 0;
1076
+ // Create the labels or make them visible
1077
+ for (int i = 0; i < numMarkers; i++) {
1078
+ Marker marker = markers.get(i);
1079
+ Element label = null;
1080
+ if (i < markerLabelElements.size()) {
1081
+ label = markerLabelElements.get(i);
1082
+ } else { // Create the new label
1083
+ label = DOM.createDiv();
1084
+ label.getStyle().setPosition(Position.ABSOLUTE);
1085
+ label.getStyle().setDisplay(Display.NONE);
1086
+ DOM.appendChild(getElement(), label);
1087
+ markerLabelElements.add(label);
1088
+ }
1089
+ if (enabled) {
1090
+ label.setPropertyString("className", "gwt-SliderBar-markerlabel");
1091
+ } else {
1092
+ label.setPropertyString("className", "gwt-SliderBar-markerlabel-disabled");
1093
+ }
1094
+
1095
+ // Set the marker label text
1096
+ label.getStyle().setVisibility(Visibility.HIDDEN);
1097
+ label.getStyle().setProperty("display", "");
1098
+ label.setPropertyString("innerHTML", marker.name);
1099
+
1100
+ // Move to the left so the label width is not clipped by the shell
1101
+ label.getStyle().setLeft(0, Unit.PX);
1102
+
1103
+ // Position the label and make it visible
1104
+ double markerLinePosition = (marker.position - minValue) * lineWidth / getTotalRange();
1105
+ int labelWidth = label.getOffsetWidth();
1106
+ int labelLeftOffset = lineLeftOffset + (int) markerLinePosition - (labelWidth / 2);
1107
+ labelLeftOffset = Math.min(labelLeftOffset, lineLeftOffset + lineWidth - labelWidth);
1108
+
1109
+ label.getStyle().setLeft(labelLeftOffset, Unit.PX);
1110
+ label.getStyle().setVisibility(Visibility.VISIBLE);
1111
+
1112
+ // hide last marker label if it is too close to the one just created
1113
+ if (lastMarkerLabelOffsetLeft > 0 && (labelLeftOffset-lastMarkerLabelOffsetLeft) <= 20) {
1114
+ if (lastMarkerLabel != null && lastMarker != null) {
1115
+ lastMarkerLabel.setPropertyString("className", "gwt-SliderBar-markerlabel-tooClose");
1116
+ lastMarkerLabel.setPropertyString("innerHTML", lastMarker.name); // no space for more
1117
+ int currentMarkerOffsetLeft = labelLeftOffset;
1118
+ try {
1119
+ currentMarkerOffsetLeft = Integer.parseInt(markerElements.get(i).getStyle().getLeft().replace("px", ""));
1120
+ } catch (NumberFormatException ex) {
1121
+ // ignore, can't do anything
1122
+ }
1123
+ lastMarkerLabel.getStyle().setLeft(currentMarkerOffsetLeft-8, Unit.PX);
1124
+ }
1125
+ }
1126
+ lastMarker = marker;
1127
+ lastMarkerLabel = label;
1128
+ lastMarkerLabelOffsetLeft = labelLeftOffset;
1129
+ }
1130
+
1131
+ // Hide unused labels
1132
+ for (int i = (numMarkers + 1); i < markerLabelElements.size(); i++) {
1133
+ markerLabelElements.get(i).getStyle().setDisplay(Display.NONE);
1134
+ }
1135
+ } else { // Hide all labels
1136
+ for (Element elem : markerLabelElements) {
1137
+ elem.getStyle().setDisplay(Display.NONE);
1138
+ }
1139
+ }
1140
+ }
1141
+ }
1142
+
1143
+ /**
1144
+ * Highlight this widget.
1145
+ */
1146
+ private void highlight() {
1147
+ String styleName = getStylePrimaryName();
1148
+ getElement().setPropertyString("className", styleName + " " + styleName + "-focused");
1149
+ }
1150
+
1151
+ /**
1152
+ * Reset the progress to constrain the progress to the current range and redraw the knob as needed.
1153
+ */
1154
+ private synchronized void resetCurrentValue(boolean fireEvent) {
1155
+ setCurrentValue(getCurrentValue(), fireEvent);
1156
+ }
1157
+
1158
+ /**
1159
+ * Slide the knob to a new location.
1160
+ *
1161
+ * @param event
1162
+ * the mouse event
1163
+ */
1164
+ private void slideKnob(Event event) {
1165
+ // Adding scrollLeft to adjust the position, if the user had scrolled with the lower scroll bar
1166
+ int x = event.getClientX() + Window.getScrollLeft();
1167
+ if (x > 0) {
1168
+ int lineWidth = lineElement.getOffsetWidth();
1169
+ int lineLeft = lineElement.getAbsoluteLeft();
1170
+ double percent = (double) (x - lineLeft) / lineWidth * 1.0;
1171
+ setCurrentValue(getTotalRange() * percent + minValue, true);
1172
+ }
1173
+ }
1174
+
1175
+ /**
1176
+ * Start sliding the knob.
1177
+ *
1178
+ * @param highlight
1179
+ * true to change the style
1180
+ * @param fireEvent
1181
+ * true to fire the event
1182
+ */
1183
+ private void startSliding(boolean highlight, boolean fireEvent) {
1184
+ if (highlight) {
1185
+ lineElement.setPropertyString("className", "gwt-SliderBar-line gwt-SliderBar-line-sliding");
1186
+ knobImage.getElement().setPropertyString("className", "gwt-SliderBar-knob gwt-SliderBar-knob-sliding");
1187
+ knobImage.setResource(images.sliderSliding());
1188
+ }
1189
+ }
1190
+
1191
+ /**
1192
+ * Stop sliding the knob.
1193
+ *
1194
+ * @param unhighlight
1195
+ * true to change the style
1196
+ * @param fireEvent
1197
+ * true to fire the event
1198
+ */
1199
+ private void stopSliding(boolean unhighlight, boolean fireEvent) {
1200
+ if (unhighlight) {
1201
+ lineElement.setPropertyString("className", "gwt-SliderBar-line");
1202
+ knobImage.getElement().setPropertyString("className", "gwt-SliderBar-knob");
1203
+ knobImage.setResource(images.slider());
1204
+ }
1205
+ }
1206
+
1207
+ /**
1208
+ * Unhighlight this widget.
1209
+ */
1210
+ private void unhighlight() {
1211
+ getElement().setPropertyString("className", getStylePrimaryName());
1212
+ }
1213
+
1214
+ @Override
1215
+ public void onResize() {
1216
+ redraw();
1217
+ }
1218
+
1219
+ public void clearMarkers() {
1220
+ markers.clear();
1221
+ }
1222
+
1223
+ public boolean addMarker(String markerName, Double markerPosition) {
1224
+ return markers.add(new Marker(markerName, markerPosition));
1225
+ }
1226
+
1227
+ public boolean setMarker(String markerName, Double markerPosition) {
1228
+ Marker marker = findMarkerByName(markerName);
1229
+ if (marker != null) {
1230
+ marker.position = markerPosition;
1231
+ return true;
1232
+ }
1233
+ return false;
1234
+ }
1235
+
1236
+ public boolean removeMarker(String markerName) {
1237
+ Marker marker = findMarkerByName(markerName);
1238
+ if (marker != null) {
1239
+ return markers.remove(marker);
1240
+ }
1241
+ return false;
1242
+ }
1243
+
1244
+ public Iterator<Marker> getMarkers() {
1245
+ return markers.iterator();
1246
+ }
1247
+
1248
+ private Marker findMarkerByName(String markerName) {
1249
+ for (Marker marker : markers) {
1250
+ if (marker.name.equals(markerName)) {
1251
+ return marker;
1252
+ }
1253
+ }
1254
+ return null;
1255
+ }
1256
+}
java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/slider/TimeSlider.java
... ...
@@ -26,7 +26,9 @@ public class TimeSlider extends SliderBar {
26 26
private List<TickPosition> calculatedTimeTicks;
27 27
28 28
private boolean isZoomed;
29
-
29
+
30
+ private int visibleLabelsInterval = 1;
31
+
30 32
private final int TICKCOUNT = 10;
31 33
32 34
public TimeSlider() {
... ...
@@ -84,7 +86,7 @@ public class TimeSlider extends SliderBar {
84 86
int tickLeftOffset = lineLeftOffset + (int) pos - (tickWidth / 2);
85 87
tickLeftOffset = Math.min(tickLeftOffset, lineLeftOffset + lineWidth - tickWidth);
86 88
tick.getStyle().setLeft(tickLeftOffset, Unit.PX);
87
- tick.getStyle().setVisibility(Visibility.VISIBLE);
89
+ tick.getStyle().setVisibility(isTickInVisibleRange(tickPosition) ? Visibility.VISIBLE : Visibility.HIDDEN);
88 90
}
89 91
}
90 92
... ...
@@ -106,7 +108,7 @@ public class TimeSlider extends SliderBar {
106 108
int lineWidth = lineElement.getOffsetWidth();
107 109
// Create the labels or make them visible
108 110
Double previousValue = null;
109
- for (int i = 0; i < calculatedTimeTicks.size(); i++) {
111
+ for (int i = 0, ticksInVisibleRange = 0; i < calculatedTimeTicks.size(); i++) {
110 112
TickPosition tickPosition = calculatedTimeTicks.get(i);
111 113
Element label = null;
112 114
if (i < tickLabelElements.size()) {
... ...
@@ -142,7 +144,8 @@ public class TimeSlider extends SliderBar {
142 144
labelLeftOffset = Math.min(labelLeftOffset, lineLeftOffset + lineWidth - labelWidth);
143 145
labelLeftOffset = Math.max(labelLeftOffset, lineLeftOffset);
144 146
label.getStyle().setLeft(labelLeftOffset, Unit.PX);
145
- label.getStyle().setVisibility(Visibility.VISIBLE);
147
+ boolean visible = isTickInVisibleRange(tickPosition) && ticksInVisibleRange++ % visibleLabelsInterval == 0;
148
+ label.getStyle().setVisibility(visible ? Visibility.VISIBLE : Visibility.HIDDEN);
146 149
}
147 150
previousValue = value;
148 151
}
... ...
@@ -152,6 +155,11 @@ public class TimeSlider extends SliderBar {
152 155
tickLabelElements.get(i).getStyle().setDisplay(Display.NONE);
153 156
}
154 157
}
158
+
159
+ private boolean isTickInVisibleRange(TickPosition tickPosition) {
160
+ long tickValue = tickPosition.getPosition().getTime();
161
+ return tickValue >= minValue && tickValue <= maxValue;
162
+ }
155 163
156 164
public void clearMarkersAndLabelsAndTicks() {
157 165
clearMarkers();
... ...
@@ -245,4 +253,10 @@ public class TimeSlider extends SliderBar {
245 253
public void setZoomed(boolean isZoomed) {
246 254
this.isZoomed = isZoomed;
247 255
}
248
-}
256
+
257
+ @Override
258
+ public void onResize() {
259
+ visibleLabelsInterval = (TICKCOUNT / Math.max(getOffsetWidth() / 35, 1)) + 1;
260
+ super.onResize();
261
+ }
262
+}
... ...
\ No newline at end of file
java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/slider/TimeTicksCalculator.java
... ...
@@ -1,246 +1,247 @@
1
-package com.sap.sse.gwt.client.controls.slider;
2
-
3
-import java.util.ArrayList;
4
-import java.util.Date;
5
-import java.util.List;
6
-
7
-/**
8
- * This calculator emulates the way the HighCharts charting library is computing the chart ticks
9
- * in case of a x-axis based on time values.
10
- */
11
-public class TimeTicksCalculator {
12
-
13
- public TimeTicksCalculator() {
14
- }
15
-
16
- /**
17
- * Get a normalized tick interval for dates. Returns a configuration object with unit range (interval), count and
18
- * name. Used to prepare data for getTimeTicks. Previously this logic was part of getTimeTicks, but as getTimeTicks
19
- * now runs of segments in stock charts, the normalizing logic was extracted in order to prevent it for running over
20
- * again for each segment having the same interval.
21
- */
22
- public NormalizedInterval normalizeTimeTickInterval(long tickInterval) {
23
- TimeUnits[] units = TimeUnits.values();
24
-
25
- TimeUnits unit = units[units.length - 1]; // default unit is years
26
- long interval = unit.unitInMs;
27
- int[] multiples = unit.allowedMultiples;
28
- int count;
29
-
30
- // loop through the units to find the one that best fits the tickInterval
31
- for (int i = 0; i < units.length; i++) {
32
- unit = units[i];
33
- interval = unit.unitInMs;
34
- multiples = unit.allowedMultiples;
35
-
36
- if (i + 1 < units.length) {
37
- // lessThan is in the middle between the highest multiple and the next unit.
38
- long lessThan = (interval * multiples[multiples.length - 1] + units[i + 1].unitInMs) / 2;
39
-
40
- // break and keep the current unit
41
- if (tickInterval <= lessThan) {
42
- break;
43
- }
44
- }
45
- }
46
-
47
- // prevent 2.5 years intervals, though 25, 250 etc. are allowed
48
- if (interval == TimeUnits.YEAR.unitInMs && tickInterval < 5 * interval) {
49
- multiples = new int[] { 1, 2, 5 };
50
- }
51
-
52
- // get the count
53
- count = (int) normalizeTickInterval(tickInterval / interval, multiples, 1);
54
-
55
- return new NormalizedInterval(unit.name(), interval, count);
56
- }
57
-
58
- /**
59
- * Take an interval and normalize it to multiples of 1, 2, 2.5 and 5
60
- *
61
- * @param {Number} interval
62
- * @param {Array} multiples
63
- * @param {Number} magnitude
64
- * @param {Object} options
65
- */
66
- public long normalizeTickInterval(long interval, int[] multiples, int magnitude) {
67
- long normalized;
68
-
69
- // round to a tenfold of 1, 2, 2.5 or 5
70
- normalized = interval / magnitude;
71
-
72
- // normalize the interval to the nearest multiple
73
- for (int i = 0; i < multiples.length; i++) {
74
- interval = multiples[i];
75
- int addedValue = i + 1 < multiples.length ? multiples[i + 1] : multiples[i];
76
- if (normalized <= (multiples[i] + addedValue) / 2) {
77
- break;
78
- }
79
- }
80
-
81
- // multiply back to the correct magnitude
82
- interval *= magnitude;
83
-
84
- return interval;
85
- }
86
-
87
- private long makeTime(int year, int month) {
88
- return makeTime(year, month, 1, 0, 0, 0);
89
- }
90
-
91
- private long makeTime(int year, int month, int date) {
92
- return makeTime(year, month, date, 0, 0, 0);
93
- }
94
-
95
- @SuppressWarnings("deprecation")
96
- private long makeTime(int year, int month, int date, int hours, int minutes, int seconds) {
97
- return new Date(year, month, date, hours, minutes, seconds).getTime();
98
- }
99
-
100
- /**
101
- * Set the tick positions to a time unit that makes sense, for example on the first of each month or on every
102
- * Monday. Return an array with the time positions. Used in datetime axes as well as for grouping data on a datetime
103
- * axis.
104
- *
105
- * @param {Object} normalizedInterval The interval in axis values (ms) and the count
106
- * @param {Number} min The minimum in axis values
107
- * @param {Number} max The maximum in axis values
108
- * @param {Number} startOfWeek
109
- */
110
- @SuppressWarnings("deprecation")
111
- public List<TickPosition> calculateTimeTicks(NormalizedInterval normalizedInterval, long min, long max,
112
- long startOfWeek) {
113
- List<TickPosition> tickPositions = new ArrayList<TickPosition>();
114
- int i;
115
- boolean useUTC = true;
116
- int minYear = 0; // used in months and years as a basis for Date.UTC()
117
- Date minDate = new Date(min);
118
- long interval = normalizedInterval.unitRange;
119
- int count = (int) normalizedInterval.count;
120
-
121
-
122
- if (interval >= TimeUnits.SECOND.unitInMs) { // second
123
- minDate.setTime(min - (min % 1000));
124
- minDate.setSeconds(interval >= TimeUnits.MINUTE.unitInMs ? 0 : count
125
- * Math.round(minDate.getSeconds() / count));
126
- }
127
-
128
- if (interval >= TimeUnits.MINUTE.unitInMs) { // minute
129
- minDate.setMinutes(interval >= TimeUnits.HOUR.unitInMs ? 0 : count
130
- * Math.round(minDate.getMinutes() / count));
131
- }
132
-
133
- if (interval >= TimeUnits.HOUR.unitInMs) { // hour
134
- minDate.setHours(interval >= TimeUnits.DAY.unitInMs ? 0 : count * Math.round(minDate.getHours() / count));
135
- }
136
-
137
- if (interval >= TimeUnits.DAY.unitInMs) { // day
138
- minDate.setDate(interval >= TimeUnits.MONTH.unitInMs ? 1 : count * Math.round(minDate.getDate() / count));
139
- }
140
-
141
- if (interval >= TimeUnits.MONTH.unitInMs) { // month
142
- minDate.setMonth(interval >= TimeUnits.YEAR.unitInMs ? 0 : count * Math.round(minDate.getMonth() + 1 / count) - 1);
143
- minYear = minDate.getYear() + 1900;
144
- }
145
-
146
- if (interval >= TimeUnits.YEAR.unitInMs) { // year
147
- minYear -= minYear % count;
148
- minDate.setYear(minYear - 1900);
149
- }
150
-
151
- // week is a special case that runs outside the hierarchy
152
- if (interval == TimeUnits.WEEK.unitInMs) {
153
- // get start of current week, independent of count
154
- long value = minDate.getDate() - minDate.getDay() + startOfWeek;
155
- minDate.setDate((int) value);
156
- }
157
-
158
- // get tick positions
159
- i = 1;
160
- minYear = minDate.getYear() + 1900;
161
- int minMonth = minDate.getMonth() + 1;
162
- int minDateDate = minDate.getDate();
163
- long time = minDate.getTime();
164
-
165
- // iterate and add tick positions at appropriate values
166
- while (time < max) {
167
- if(time >= min)
168
- tickPositions.add(new TickPosition(time));
169
-
170
- // if the interval is years, use Date.UTC to increase years
171
- if (interval == TimeUnits.YEAR.unitInMs) {
172
- time = makeTime(minYear + i * count, 0);
173
-
174
- // if the interval is months, use Date.UTC to increase months
175
- } else if (interval == TimeUnits.MONTH.unitInMs) {
176
- time = makeTime(minYear, minMonth + i * count);
177
-
178
- // if we're using global time, the interval is not fixed as it jumps
179
- // one hour at the DST crossover
180
- } else if (!useUTC && (interval == TimeUnits.DAY.unitInMs || interval == TimeUnits.WEEK.unitInMs)) {
181
- time = makeTime(minYear, minMonth, minDateDate + i * count * (interval == TimeUnits.DAY.unitInMs ? 1 : 7));
182
-
183
- // else, the interval is fixed and we use simple addition
184
- } else {
185
- time += interval * count;
186
- }
187
-
188
- i++;
189
- }
190
-
191
- // push the last time
192
- if(time <= max)
193
- tickPositions.add(new TickPosition(time));
194
-
195
- return tickPositions;
196
- }
197
-
198
- public class NormalizedInterval {
199
- public String unitName;
200
- public long unitRange;
201
- public int count;
202
-
203
- public NormalizedInterval(String unitName, long unitRange, int count) {
204
- this.unitName = unitName;
205
- this.unitRange = unitRange;
206
- this.count = count;
207
- }
208
-
209
- @Override
210
- public String toString() {
211
- return "NormalizedInterval [unitName=" + unitName + ", unitRange=" + unitRange + ", count=" + count + "]";
212
- }
213
- }
214
-
215
- public class TickPosition {
216
- private Date position;
217
-
218
- public TickPosition(long time) {
219
- this.position = new Date(time);
220
- }
221
-
222
- public Date getPosition() {
223
- return position;
224
- }
225
- }
226
-
227
- enum TimeUnits {
228
- MILLISECOND(1, new int[] { 1, 2, 5, 10, 20, 25, 50, 100, 200, 500 }),
229
- SECOND(1000, new int[] { 1, 2, 5, 10, 15, 30 }),
230
- MINUTE(60 * 1000, new int[] { 1, 2, 5, 10, 15, 30 }),
231
- HOUR(60 * 60 * 1000, new int[] { 1, 2, 3, 4, 6, 8, 12 }),
232
- DAY(24 * 3600000l, new int[] { 1, 2 }),
233
- WEEK(7 * 24 * 3600000l, new int[] { 1, 2 }),
234
- MONTH(30 * 24 * 3600000l, new int[] { 1, 2, 3, 4, 6 }),
235
- YEAR(31556952000l, new int[] {});
236
-
237
- int[] allowedMultiples;
238
-
239
- long unitInMs;
240
-
241
- TimeUnits(long unitInMs, int[] allowedMultiples) {
242
- this.unitInMs = unitInMs;
243
- this.allowedMultiples = allowedMultiples;
244
- }
245
- }
246
-}
1
+package com.sap.sse.gwt.client.controls.slider;
2
+
3
+import java.util.ArrayList;
4
+import java.util.Date;
5
+import java.util.List;
6
+
7
+/**
8
+ * This calculator emulates the way the HighCharts charting library is computing the chart ticks
9
+ * in case of a x-axis based on time values.
10
+ */
11
+public class TimeTicksCalculator {
12
+
13
+ public TimeTicksCalculator() {
14
+ }
15
+
16
+ /**
17
+ * Get a normalized tick interval for dates. Returns a configuration object with unit range (interval), count and
18
+ * name. Used to prepare data for getTimeTicks. Previously this logic was part of getTimeTicks, but as getTimeTicks
19
+ * now runs of segments in stock charts, the normalizing logic was extracted in order to prevent it for running over
20
+ * again for each segment having the same interval.
21
+ */
22
+ public NormalizedInterval normalizeTimeTickInterval(long tickInterval) {
23
+ TimeUnits[] units = TimeUnits.values();
24
+
25
+ TimeUnits unit = units[units.length - 1]; // default unit is years
26
+ long interval = unit.unitInMs;
27
+ int[] multiples = unit.allowedMultiples;
28
+ int count;
29
+
30
+ // loop through the units to find the one that best fits the tickInterval
31
+ for (int i = 0; i < units.length; i++) {
32
+ unit = units[i];
33
+ interval = unit.unitInMs;
34
+ multiples = unit.allowedMultiples;
35
+
36
+ if (i + 1 < units.length) {
37
+ // lessThan is in the middle between the highest multiple and the next unit.
38
+ double lessThan = (interval * multiples[multiples.length - 1] + units[i + 1].unitInMs) / 2;
39
+
40
+ // break and keep the current unit
41
+ if (tickInterval <= lessThan) {
42
+ break;
43
+ }
44
+ }
45
+ }
46
+
47
+ // prevent 2.5 years intervals, though 25, 250 etc. are allowed
48
+ if (interval == TimeUnits.YEAR.unitInMs && tickInterval < 5 * interval) {
49
+ multiples = new int[] { 1, 2, 5 };
50
+ }
51
+
52
+ // get the count
53
+ double temp = ((double) tickInterval) / ((double) interval);
54
+ count = (int) normalizeTickInterval(temp, multiples, 1);
55
+
56
+ return new NormalizedInterval(unit.name(), interval, count);
57
+ }
58
+
59
+ /**
60
+ * Take an interval and normalize it to multiples of 1, 2, 2.5 and 5
61
+ *
62
+ * @param {Number} interval
63
+ * @param {Array} multiples
64
+ * @param {Number} magnitude
65
+ * @param {Object} options
66
+ */
67
+ public long normalizeTickInterval(double interval, int[] multiples, int magnitude) {
68
+ double normalized;
69
+
70
+ // round to a tenfold of 1, 2, 2.5 or 5
71
+ normalized = interval / magnitude;
72
+
73
+ // normalize the interval to the nearest multiple
74
+ for (int i = 0; i < multiples.length; i++) {
75
+ interval = multiples[i];
76
+ int addedValue = i + 1 < multiples.length ? multiples[i + 1] : multiples[i];
77
+ if (normalized <= (multiples[i] + addedValue) / 2) {
78
+ break;
79
+ }
80
+ }
81
+
82
+ // multiply back to the correct magnitude
83
+ interval *= magnitude;
84
+
85
+ return Math.round(interval);
86
+ }
87
+
88
+ private long makeTime(int year, int month) {
89
+ return makeTime(year, month, 1, 0, 0, 0);
90
+ }
91
+
92
+ private long makeTime(int year, int month, int date) {
93
+ return makeTime(year, month, date, 0, 0, 0);
94
+ }
95
+
96
+ @SuppressWarnings("deprecation")
97
+ private long makeTime(int year, int month, int date, int hours, int minutes, int seconds) {
98
+ return new Date(year, month, date, hours, minutes, seconds).getTime();
99
+ }
100
+
101
+ /**
102
+ * Set the tick positions to a time unit that makes sense, for example on the first of each month or on every
103
+ * Monday. Return an array with the time positions. Used in datetime axes as well as for grouping data on a datetime
104
+ * axis.
105
+ *
106
+ * @param {Object} normalizedInterval The interval in axis values (ms) and the count
107
+ * @param {Number} min The minimum in axis values
108
+ * @param {Number} max The maximum in axis values
109
+ * @param {Number} startOfWeek
110
+ */
111
+ @SuppressWarnings("deprecation")
112
+ public List<TickPosition> calculateTimeTicks(NormalizedInterval normalizedInterval, long min, long max,
113
+ long startOfWeek) {
114
+ List<TickPosition> tickPositions = new ArrayList<TickPosition>();
115
+ int i;
116
+ boolean useUTC = true;
117
+ int minYear = 0; // used in months and years as a basis for Date.UTC()
118
+ Date minDate = new Date(min);
119
+ long interval = normalizedInterval.unitRange;
120
+ int count = (int) normalizedInterval.count;
121
+
122
+
123
+ if (interval >= TimeUnits.SECOND.unitInMs) { // second
124
+ minDate.setTime(min - (min % 1000));
125
+ minDate.setSeconds(interval >= TimeUnits.MINUTE.unitInMs ? 0 : count
126
+ * Math.round(minDate.getSeconds() / count));
127
+ }
128
+
129
+ if (interval >= TimeUnits.MINUTE.unitInMs) { // minute
130
+ minDate.setMinutes(interval >= TimeUnits.HOUR.unitInMs ? 0 : count
131
+ * Math.round(minDate.getMinutes() / count));
132
+ }
133
+
134
+ if (interval >= TimeUnits.HOUR.unitInMs) { // hour
135
+ minDate.setHours(interval >= TimeUnits.DAY.unitInMs ? 0 : count * Math.round(minDate.getHours() / count));
136
+ }
137
+
138
+ if (interval >= TimeUnits.DAY.unitInMs) { // day
139
+ minDate.setDate(interval >= TimeUnits.MONTH.unitInMs ? 1 : count * Math.round(minDate.getDate() / count));
140
+ }
141
+
142
+ if (interval >= TimeUnits.MONTH.unitInMs) { // month
143
+ minDate.setMonth(interval >= TimeUnits.YEAR.unitInMs ? 0 : count * Math.round(minDate.getMonth() + 1 / count) - 1);
144
+ minYear = minDate.getYear() + 1900;
145
+ }
146
+
147
+ if (interval >= TimeUnits.YEAR.unitInMs) { // year
148
+ minYear -= minYear % count;
149
+ minDate.setYear(minYear - 1900);
150
+ }
151
+
152
+ // week is a special case that runs outside the hierarchy
153
+ if (interval == TimeUnits.WEEK.unitInMs) {
154
+ // get start of current week, independent of count
155
+ long value = minDate.getDate() - minDate.getDay() + startOfWeek;
156
+ minDate.setDate((int) value);
157
+ }
158
+
159
+ // get tick positions
160
+ i = 1;
161
+ minYear = minDate.getYear() + 1900;
162
+ int minMonth = minDate.getMonth() + 1;
163
+ int minDateDate = minDate.getDate();
164
+ long time = minDate.getTime();
165
+
166
+ // iterate and add tick positions at appropriate values
167
+ while (time < max) {
168
+ if(time >= min)
169
+ tickPositions.add(new TickPosition(time));
170
+
171
+ // if the interval is years, use Date.UTC to increase years
172
+ if (interval == TimeUnits.YEAR.unitInMs) {
173
+ time = makeTime(minYear + i * count, 0);
174
+
175
+ // if the interval is months, use Date.UTC to increase months
176
+ } else if (interval == TimeUnits.MONTH.unitInMs) {
177
+ time = makeTime(minYear, minMonth + i * count);
178
+
179
+ // if we're using global time, the interval is not fixed as it jumps
180
+ // one hour at the DST crossover
181
+ } else if (!useUTC && (interval == TimeUnits.DAY.unitInMs || interval == TimeUnits.WEEK.unitInMs)) {
182
+ time = makeTime(minYear, minMonth, minDateDate + i * count * (interval == TimeUnits.DAY.unitInMs ? 1 : 7));
183
+
184
+ // else, the interval is fixed and we use simple addition
185
+ } else {
186
+ time += interval * count;
187
+ }
188
+
189
+ i++;
190
+ }
191
+
192
+ // push the last time
193
+ if(time <= max)
194
+ tickPositions.add(new TickPosition(time));
195
+
196
+ return tickPositions;
197
+ }
198
+
199
+ public class NormalizedInterval {
200
+ public String unitName;
201
+ public long unitRange;
202
+ public int count;
203
+
204
+ public NormalizedInterval(String unitName, long unitRange, int count) {
205
+ this.unitName = unitName;
206
+ this.unitRange = unitRange;
207
+ this.count = count;
208
+ }
209
+
210
+ @Override
211
+ public String toString() {
212
+ return "NormalizedInterval [unitName=" + unitName + ", unitRange=" + unitRange + ", count=" + count + "]";
213
+ }
214
+ }
215
+
216
+ public class TickPosition {
217
+ private Date position;
218
+
219
+ public TickPosition(long time) {
220
+ this.position = new Date(time);
221
+ }
222
+
223
+ public Date getPosition() {
224
+ return position;
225
+ }
226
+ }
227
+
228
+ enum TimeUnits {
229
+ MILLISECOND(1l, 1, 2, 5, 10, 20, 25, 50, 100, 200, 500),
230
+ SECOND(1000l, 1, 2, 5, 10, 15, 30),
231
+ MINUTE(60 * 1000l, 1, 2, 5, 10, 15, 30),
232
+ HOUR(60 * 60 * 1000l, 1, 2, 3, 4, 6, 8, 12),
233
+ DAY(24 * 3600000l, 1, 2),
234
+ WEEK(7 * 24 * 3600000l, 1, 2),
235
+ MONTH(30 * 24 * 3600000l, 1, 2, 3, 4, 6),
236
+ YEAR(31556952000l);
237
+
238
+ int[] allowedMultiples;
239
+
240
+ long unitInMs;
241
+
242
+ TimeUnits(long unitInMs, int... allowedMultiples) {
243
+ this.unitInMs = unitInMs;
244
+ this.allowedMultiples = allowedMultiples;
245
+ }
246
+ }
247
+}
java/com.sap.sse.gwt/src/com/sap/sse/gwt/resources/css/CommonControls.css
... ...
@@ -169,8 +169,7 @@ body ::-webkit-scrollbar-track {
169 169
170 170
.gwt-SplitLayoutPanel-EastToggleButton-Panel {
171 171
position: absolute;
172
- bottom: 77px;
173
- margin-left: -35px;
172
+ bottom: -10px;
174 173
}
175 174
176 175
.gwt-SplitLayoutPanel-EastToggleButton {
... ...
@@ -181,9 +180,14 @@ body ::-webkit-scrollbar-track {
181 180
-moz-transform: rotate(-90deg);
182 181
-o-transform: rotate(-90deg);
183 182
-ms-transform: rotate(-90deg);
183
+ transform: rotate(-90deg);
184
+ -webkit-transform-origin: 0 0;
185
+ -moz-transform-origin: 0 0;
186
+ -o-transform-origin: 0 0;
187
+ -ms-transform-origin: 0 0;
188
+ transform-origin: 0 0;
184 189
padding-right: 25px;
185 190
padding-left: 30px;
186
- margin-left: -15px;
187 191
border-bottom: 1px solid #cecece;
188 192
border-right: 1px solid #cecece;
189 193
border-left: 1px solid #cecece;
java/com.sap.sse.security.common/pom.xml
... ...
@@ -1,32 +1,32 @@
1
-<?xml version="1.0" encoding="UTF-8"?>
2
-<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
3
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
4
- <modelVersion>4.0.0</modelVersion>
5
- <parent>
6
- <artifactId>root</artifactId>
7
- <groupId>com.sap.sailing</groupId>
8
- <version>1.0.0-SNAPSHOT</version>
9
- </parent>
10
- <artifactId>com.sap.sse.security.common</artifactId>
11
- <packaging>eclipse-plugin</packaging>
12
-
13
- <build>
14
- <plugins>
15
- <plugin>
16
- <groupId>org.eclipse.tycho</groupId>
17
- <artifactId>tycho-source-plugin</artifactId>
18
- <version>${tycho-version}</version>
19
- <executions>
20
- <execution>
21
- <id>plugin-source</id>
22
- <phase>generate-sources</phase>
23
- <goals>
24
- <goal>plugin-source</goal>
25
- </goals>
26
- </execution>
27
- </executions>
28
- </plugin>
29
- </plugins>
30
- </build>
31
-
32
-</project>
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
3
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
4
+ <modelVersion>4.0.0</modelVersion>
5
+ <parent>
6
+ <artifactId>root</artifactId>
7
+ <groupId>com.sap.sailing</groupId>
8
+ <version>1.0.0-SNAPSHOT</version>
9
+ </parent>
10
+ <artifactId>com.sap.sse.security.common</artifactId>
11
+ <packaging>eclipse-plugin</packaging>
12
+
13
+ <build>
14
+ <plugins>
15
+ <plugin>
16
+ <groupId>org.eclipse.tycho</groupId>
17
+ <artifactId>tycho-source-plugin</artifactId>
18
+ <version>${tycho-version}</version>
19
+ <executions>
20
+ <execution>
21
+ <id>plugin-source</id>
22
+ <phase>generate-sources</phase>
23
+ <goals>
24
+ <goal>plugin-source</goal>
25
+ </goals>
26
+ </execution>
27
+ </executions>
28
+ </plugin>
29
+ </plugins>
30
+ </build>
31
+
32
+</project>
java/com.sap.sse.shared.android/META-INF/MANIFEST.MF
... ...
@@ -7,4 +7,6 @@ Bundle-Vendor: SAP
7 7
Bundle-RequiredExecutionEnvironment: JavaSE-1.7
8 8
Require-Bundle: com.sap.sse.common
9 9
Export-Package: com.sap.sse.concurrent,
10
+ com.sap.sse.shared.media,
11
+ com.sap.sse.shared.media.impl,
10 12
com.sap.sse.util.impl
java/com.sap.sse.shared.android/src/com/sap/sse/shared/media/ImageDescriptor.java
... ...
@@ -0,0 +1,14 @@
1
+package com.sap.sse.shared.media;
2
+
3
+import com.sap.sse.common.Util.Pair;
4
+
5
+public interface ImageDescriptor extends MediaDescriptor {
6
+ void setSize(Pair<Integer, Integer> size);
7
+ void setSize(Integer widthInPx, Integer heightInPx);
8
+
9
+ Integer getWidthInPx();
10
+ Integer getHeightInPx();
11
+
12
+ boolean hasSize();
13
+ int getArea();
14
+}
java/com.sap.sse.shared.android/src/com/sap/sse/shared/media/MediaDescriptor.java
... ...
@@ -0,0 +1,37 @@
1
+package com.sap.sse.shared.media;
2
+
3
+import java.io.Serializable;
4
+import java.net.URL;
5
+import java.util.Locale;
6
+
7
+import com.sap.sse.common.TimePoint;
8
+import com.sap.sse.common.media.MimeType;
9
+
10
+/**
11
+ * A common media interface for all kinds of media like images or videos.
12
+ */
13
+public interface MediaDescriptor extends Serializable {
14
+ MimeType getMimeType();
15
+ URL getURL();
16
+
17
+ String getTitle();
18
+ void setTitle(String title);
19
+
20
+ Iterable<String> getTags();
21
+ void setTags(Iterable<String> tags);
22
+ boolean addTag(String tagName);
23
+ boolean removeTag(String tagName);
24
+ boolean hasTag(String tagName);
25
+
26
+ String getSubtitle();
27
+ void setSubtitle(String subtitle);
28
+
29
+ TimePoint getCreatedAtDate();
30
+ void setCreatedAtDate(TimePoint createdAtDate);
31
+
32
+ String getCopyright();
33
+ void setCopyright(String copyright);
34
+
35
+ Locale getLocale();
36
+ void setLocale(Locale locale);
37
+}
java/com.sap.sse.shared.android/src/com/sap/sse/shared/media/MediaUtils.java
... ...
@@ -0,0 +1,110 @@
1
+package com.sap.sse.shared.media;
2
+
3
+import java.io.IOException;
4
+import java.net.URL;
5
+import java.net.URLConnection;
6
+import java.util.Iterator;
7
+import java.util.concurrent.Callable;
8
+import java.util.concurrent.ExecutorService;
9
+import java.util.concurrent.Executors;
10
+import java.util.concurrent.Future;
11
+import java.util.logging.Level;
12
+import java.util.logging.Logger;
13
+import java.util.regex.Pattern;
14
+
15
+import javax.imageio.ImageIO;
16
+import javax.imageio.ImageReader;
17
+import javax.imageio.stream.ImageInputStream;
18
+
19
+import com.sap.sse.common.Util;
20
+import com.sap.sse.common.Util.Pair;
21
+import com.sap.sse.common.media.MimeType;
22
+
23
+public class MediaUtils {
24
+ private static final Logger logger = Logger.getLogger(MediaUtils.class.getName());
25
+
26
+ /**
27
+ * Youtube regex detection from:
28
+ * http://stackoverflow.com/questions/3452546/javascript-regex-how-to-get-youtube-video-id-from-url, mantish Mar 4
29
+ * at 15:33
30
+ */
31
+ private static final Pattern YOUTUBE_ID_REGEX = Pattern
32
+ .compile("^.*(youtu.be/|v/|u/\\w/|embed/|watch\\?v=|\\&v=)([^#\\&\\?]+).*$");
33
+
34
+ private static final Pattern VIMEO_REGEX = Pattern.compile("^.*(vimeo\\.com\\/).*");
35
+
36
+ private static final Pattern MP4_REGEX = Pattern.compile(".*\\.mp4$");
37
+
38
+ /**
39
+ * Detect mimetype for given url.
40
+ *
41
+ * @param url
42
+ * the source pointing to the video mediafile
43
+ * @return mimetype detected or MimeType.unknown
44
+ */
45
+ public static MimeType detectMimeTypeFromUrl(String url) {
46
+
47
+ if (YOUTUBE_ID_REGEX.matcher(url).matches()) {
48
+ return MimeType.youtube;
49
+ } else if (VIMEO_REGEX.matcher(url).matches()) {
50
+ return MimeType.vimeo;
51
+ } else if (MP4_REGEX.matcher(url).matches()) {
52
+ return MimeType.mp4;
53
+ } else {
54
+ return MimeType.unknown;
55
+ }
56
+ }
57
+
58
+ public static Pair<Integer, Integer> getImageDimensions(URL imageURL) {
59
+ Future<Pair<Integer, Integer>> imageSizeCalculator = getOrCreateImageSizeCalculator(imageURL);
60
+ try {
61
+ return imageSizeCalculator.get();
62
+ } catch (Exception e) {
63
+ return null;
64
+ }
65
+ }
66
+
67
+ private static final ExecutorService executor = Executors.newCachedThreadPool();
68
+
69
+ private static Future<Pair<Integer, Integer>> getOrCreateImageSizeCalculator(final URL imageURL) {
70
+ Future<Pair<Integer, Integer>> imageSizeFetcher = executor.submit(new Callable<Pair<Integer, Integer>>() {
71
+ @Override
72
+ public Pair<Integer, Integer> call() throws IOException {
73
+ Pair<Integer, Integer> result = null;
74
+ ImageInputStream in = null;
75
+ try {
76
+ URLConnection conn = imageURL.openConnection();
77
+ in = ImageIO.createImageInputStream(conn.getInputStream());
78
+ final Iterator<ImageReader> readers = ImageIO.getImageReaders(in);
79
+ if (readers.hasNext()) {
80
+ ImageReader reader = readers.next();
81
+ try {
82
+ reader.setInput(in);
83
+ result = new Pair<>(reader.getWidth(0), reader.getHeight(0));
84
+ } finally {
85
+ reader.dispose();
86
+ }
87
+ }
88
+ } catch (IOException ioe) {
89
+ logger.log(Level.SEVERE, "Stale image URL: "+imageURL, ioe);
90
+ throw ioe;
91
+ } finally {
92
+ if (in != null) {
93
+ in.close();
94
+ }
95
+ }
96
+ return result;
97
+ }
98
+ });
99
+ return imageSizeFetcher;
100
+ }
101
+
102
+ public static Util.Pair<Integer, Integer> fitImageSizeToBox(int boxWidth, int boxHeight, int imageWidth, int imageHeight, boolean neverScaleUp) {
103
+ double scale = Math.min((double) boxWidth / (double) imageWidth, (double) boxHeight / (double) imageHeight);
104
+
105
+ int h = (int) (!neverScaleUp || scale < 1.0 ? scale * imageHeight : imageHeight);
106
+ int w = (int) (!neverScaleUp || scale < 1.0 ? scale * imageWidth : imageWidth);
107
+
108
+ return new Util.Pair<Integer, Integer>(w,h);
109
+ }
110
+}
java/com.sap.sse.shared.android/src/com/sap/sse/shared/media/VideoDescriptor.java
... ...
@@ -0,0 +1,11 @@
1
+package com.sap.sse.shared.media;
2
+
3
+import java.net.URL;
4
+
5
+public interface VideoDescriptor extends MediaDescriptor {
6
+ Integer getLengthInSeconds();
7
+ void setLengthInSeconds(Integer lengthInSeconds);
8
+
9
+ URL getThumbnailURL();
10
+ void setThumbnailURL(URL thumbnailURL);
11
+}
java/com.sap.sse.shared.android/src/com/sap/sse/shared/media/WithImages.java
... ...
@@ -0,0 +1,26 @@
1
+package com.sap.sse.shared.media;
2
+
3
+
4
+public interface WithImages {
5
+ /**
6
+ * Returns a non-<code>null</code> live but unmodifiable collection of image resources that can be
7
+ * used to represent the event, e.g., on a web page.
8
+ *
9
+ * @return a non-<code>null</code> value which may be empty
10
+ */
11
+ Iterable<ImageDescriptor> getImages();
12
+
13
+ /**
14
+ * Replaces the {@link #getImages() current contents of the image sequence by the images in
15
+ * <code>images</code>.
16
+ *
17
+ * @param images
18
+ * if <code>null</code>, the internal sequence of images is cleared but remains valid (non-
19
+ * <code>null</code>)
20
+ */
21
+ void setImages(Iterable<ImageDescriptor> images);
22
+
23
+ void addImage(ImageDescriptor image);
24
+
25
+ void removeImage(ImageDescriptor image);
26
+}
java/com.sap.sse.shared.android/src/com/sap/sse/shared/media/WithMedia.java
... ...
@@ -0,0 +1,12 @@
1
+package com.sap.sse.shared.media;
2
+
3
+import java.util.List;
4
+
5
+public interface WithMedia extends WithImages, WithVideos {
6
+ ImageDescriptor findImageWithTag(String tagName);
7
+ List<ImageDescriptor> findImagesWithTag(String tagName);
8
+ boolean hasImageWithTag(String tagName);
9
+
10
+ VideoDescriptor findVideoWithTag(String tagName);
11
+ List<VideoDescriptor> findVideosWithTag(String tagName);
12
+}
java/com.sap.sse.shared.android/src/com/sap/sse/shared/media/WithVideos.java
... ...
@@ -0,0 +1,27 @@
1
+package com.sap.sse.shared.media;
2
+
3
+
4
+public interface WithVideos {
5
+
6
+ /**
7
+ * Returns a non-<code>null</code> live but unmodifiable collection of video resources that can be
8
+ * used to represent the event, e.g., on a web page.
9
+ *
10
+ * @return a non-<code>null</code> value which may be empty
11
+ */
12
+ Iterable<VideoDescriptor> getVideos();
13
+
14
+ /**
15
+ * Replaces the {@link #getVideos() current contents of the video sequence by the videos in
16
+ * <code>videos</code>.
17
+ *
18
+ * @param videos
19
+ * if <code>null</code>, the internal sequence of videos is cleared but remains valid (non-
20
+ * <code>null</code>)
21
+ */
22
+ void setVideos(Iterable<VideoDescriptor> videos);
23
+
24
+ void addVideo(VideoDescriptor video);
25
+
26
+ void removeVideo(VideoDescriptor video);
27
+}
java/com.sap.sse.shared.android/src/com/sap/sse/shared/media/impl/AbstractMediaDescriptor.java
... ...
@@ -0,0 +1,138 @@
1
+package com.sap.sse.shared.media.impl;
2
+
3
+import java.io.Serializable;
4
+import java.net.URL;
5
+import java.util.LinkedHashSet;
6
+import java.util.Locale;
7
+import java.util.Set;
8
+
9
+import com.sap.sse.common.TimePoint;
10
+import com.sap.sse.common.Util;
11
+import com.sap.sse.common.media.MimeType;
12
+import com.sap.sse.shared.media.MediaDescriptor;
13
+
14
+/**
15
+ * Common media data for media items
16
+ *
17
+ * @author pgtaboada
18
+ *
19
+ */
20
+public abstract class AbstractMediaDescriptor implements MediaDescriptor, Serializable {
21
+ private static final long serialVersionUID = -6671425870632517274L;
22
+
23
+ protected String title;
24
+
25
+ protected String subtitle;
26
+
27
+ protected TimePoint createdAtDate;
28
+
29
+ protected String copyright;
30
+
31
+ protected MimeType mimeType;
32
+
33
+ protected Set<String> tags = new LinkedHashSet<String>();
34
+
35
+ protected URL url;
36
+
37
+ protected Locale locale;
38
+
39
+ /**
40
+ * Media item with minimal set of information
41
+ * @param url
42
+ * @param mimeType
43
+ */
44
+ public AbstractMediaDescriptor(URL url, MimeType mimeType, TimePoint createdAtDate) {
45
+ this.mimeType = mimeType;
46
+ this.url = url;
47
+ this.createdAtDate = createdAtDate;
48
+ }
49
+
50
+ @Override
51
+ public MimeType getMimeType() {
52
+ return mimeType;
53
+ }
54
+
55
+ @Override
56
+ public URL getURL() {
57
+ return url;
58
+ }
59
+
60
+ @Override
61
+ public String getTitle() {
62
+ return title;
63
+ }
64
+
65
+ @Override
66
+ public void setTitle(String title) {
67
+ this.title = title;
68
+ }
69
+
70
+ @Override
71
+ public Set<String> getTags() {
72
+ return tags;
73
+ }
74
+
75
+ @Override
76
+ public void setTags(Iterable<String> tags) {
77
+ this.tags.clear();
78
+ if (tags != null) {
79
+ Util.addAll(tags, this.tags);
80
+ }
81
+ }
82
+
83
+
84
+ @Override
85
+ public boolean addTag(String tagName) {
86
+ return tags.add(tagName);
87
+ }
88
+
89
+ @Override
90
+ public boolean removeTag(String tagName) {
91
+ return tags.remove(tagName);
92
+ }
93
+
94
+ @Override
95
+ public String getSubtitle() {
96
+ return subtitle;
97
+ }
98
+
99
+ @Override
100
+ public void setSubtitle(String subtitle) {
101
+ this.subtitle = subtitle;
102
+ }
103
+
104
+ @Override
105
+ public TimePoint getCreatedAtDate() {
106
+ return createdAtDate;
107
+ }
108
+
109
+ @Override
110
+ public void setCreatedAtDate(TimePoint createdAtDate) {
111
+ this.createdAtDate = createdAtDate;
112
+ }
113
+
114
+ @Override
115
+ public String getCopyright() {
116
+ return copyright;
117
+ }
118
+
119
+ @Override
120
+ public void setCopyright(String copyright) {
121
+ this.copyright = copyright;
122
+ }
123
+
124
+ @Override
125
+ public Locale getLocale() {
126
+ return locale;
127
+ }
128
+
129
+ @Override
130
+ public void setLocale(Locale locale) {
131
+ this.locale = locale;
132
+ }
133
+
134
+ @Override
135
+ public boolean hasTag(String tagName) {
136
+ return tags.contains(tagName);
137
+ }
138
+}
java/com.sap.sse.shared.android/src/com/sap/sse/shared/media/impl/ImageDescriptorImpl.java
... ...
@@ -0,0 +1,64 @@
1
+package com.sap.sse.shared.media.impl;
2
+
3
+import java.net.URL;
4
+
5
+import com.sap.sse.common.TimePoint;
6
+import com.sap.sse.common.Util.Pair;
7
+import com.sap.sse.common.media.MimeType;
8
+import com.sap.sse.shared.media.ImageDescriptor;
9
+
10
+
11
+public class ImageDescriptorImpl extends AbstractMediaDescriptor implements ImageDescriptor {
12
+ private static final long serialVersionUID = -702731462768602331L;
13
+
14
+ private Integer widthInPx;
15
+ private Integer heightInPx;
16
+
17
+ /**
18
+ * @param imageURL
19
+ * @param size
20
+ */
21
+ public ImageDescriptorImpl(URL imageURL, TimePoint createdAtDate) {
22
+ super(imageURL, MimeType.image, createdAtDate);
23
+ }
24
+
25
+ @Override
26
+ public Integer getWidthInPx() {
27
+ return widthInPx;
28
+ }
29
+
30
+ @Override
31
+ public Integer getHeightInPx() {
32
+ return heightInPx;
33
+ }
34
+
35
+ @Override
36
+ public void setSize(Pair<Integer, Integer> size) {
37
+ if (size != null) {
38
+ this.widthInPx = size.getA();
39
+ this.heightInPx = size.getB();
40
+ } else {
41
+ this.widthInPx = null;
42
+ this.heightInPx = null;
43
+ }
44
+ }
45
+
46
+ @Override
47
+ public void setSize(Integer widthInPx, Integer heightInPx) {
48
+ this.widthInPx = widthInPx;
49
+ this.heightInPx = heightInPx;
50
+ }
51
+
52
+ @Override
53
+ public boolean hasSize() {
54
+ return widthInPx != null && heightInPx != null;
55
+ }
56
+
57
+ @Override
58
+ public int getArea() {
59
+ if(hasSize()) {
60
+ return widthInPx * heightInPx;
61
+ }
62
+ return 0;
63
+ }
64
+}
java/com.sap.sse.shared.android/src/com/sap/sse/shared/media/impl/VideoDescriptorImpl.java
... ...
@@ -0,0 +1,42 @@
1
+package com.sap.sse.shared.media.impl;
2
+
3
+import java.net.URL;
4
+
5
+import com.sap.sse.common.TimePoint;
6
+import com.sap.sse.common.media.MimeType;
7
+import com.sap.sse.shared.media.VideoDescriptor;
8
+
9
+public class VideoDescriptorImpl extends AbstractMediaDescriptor implements VideoDescriptor {
10
+ private static final long serialVersionUID = 2651747912466590862L;
11
+
12
+ private Integer lengthInSeconds;
13
+
14
+ /**
15
+ * URL to thumbnail image. This information works as override for youtube videos or as missing thumbnail
16
+ * information for other formats. It can be either a link to thumbnail or even be a data/url contaning the base64
17
+ * encoded image.
18
+ */
19
+ private URL thumbnailURL;
20
+
21
+ public VideoDescriptorImpl(URL url, MimeType mimeType, TimePoint createdAtDate) {
22
+ super(url, mimeType, createdAtDate);
23
+ }
24
+
25
+ @Override
26
+ public Integer getLengthInSeconds() {
27
+ return lengthInSeconds;
28
+ }
29
+
30
+ public void setLengthInSeconds(Integer lengthInSeconds) {
31
+ this.lengthInSeconds = lengthInSeconds;
32
+ }
33
+
34
+ @Override
35
+ public URL getThumbnailURL() {
36
+ return thumbnailURL;
37
+ }
38
+
39
+ public void setThumbnailURL(URL thumbnailURL) {
40
+ this.thumbnailURL = thumbnailURL;
41
+ }
42
+}
java/com.sap.sse/src/com/sap/sse/ServerInfo.java
... ...
@@ -3,6 +3,8 @@ package com.sap.sse;
3 3
import java.io.BufferedReader;
4 4
import java.io.File;
5 5
import java.io.FileReader;
6
+import java.util.logging.Level;
7
+import java.util.logging.Logger;
6 8
7 9
/**
8 10
* A class providing static information about the running server.
... ...
@@ -10,14 +12,16 @@ import java.io.FileReader;
10 12
*
11 13
*/
12 14
public class ServerInfo {
15
+ private static final Logger logger = Logger.getLogger(ServerInfo.class.getName());
16
+
13 17
public static String getBuildVersion() {
14 18
String version = "Unknown or Development (" + ServerStartupConstants.SERVER_NAME + ")";
15 19
File versionfile = new File(ServerStartupConstants.JETTY_HOME + File.separator + "version.txt");
16 20
if (versionfile.exists()) {
17
- try {
18
- version = new BufferedReader(new FileReader(versionfile)).readLine();
21
+ try (BufferedReader bufferedReader = new BufferedReader(new FileReader(versionfile))) {
22
+ version = bufferedReader.readLine();
19 23
} catch (Exception ex) {
20
- /* ignore */
24
+ logger.log(Level.WARNING, "Error trying to obtain version info", ex);
21 25
}
22 26
}
23 27
return version;
java/com.sap.sse/src/com/sap/sse/util/IdentityWrapper.java
... ...
@@ -0,0 +1,34 @@
1
+package com.sap.sse.util;
2
+
3
+import java.io.Serializable;
4
+
5
+/**
6
+ * Implements {@link Object#equals(Object)} and {@link Object#hashCode()} based on the wrapped object's
7
+ * identity, no matter what the object itself defines as {@code equals} and {@code hashCode}.
8
+ *
9
+ * @author Axel Uhl (D043530)
10
+ *
11
+ */
12
+public class IdentityWrapper<T> implements Serializable {
13
+ private static final long serialVersionUID = 3514488568026067341L;
14
+ private final T t;
15
+
16
+ public IdentityWrapper(T t) {
17
+ this.t = t;
18
+ }
19
+
20
+ @Override
21
+ public int hashCode() {
22
+ return System.identityHashCode(t);
23
+ }
24
+
25
+ @Override
26
+ public boolean equals(Object obj) {
27
+ return (obj instanceof IdentityWrapper<?>) ? ((IdentityWrapper<?>) obj).t == this.t : obj == t;
28
+ }
29
+
30
+ @Override
31
+ public String toString() {
32
+ return "IdentityWrapper for "+t;
33
+ }
34
+}
java/org.json.simple/pom.xml
... ...
@@ -1,40 +1,40 @@
1
-<?xml version="1.0" encoding="UTF-8"?>
2
-<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
3
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
4
- <modelVersion>4.0.0</modelVersion>
5
- <parent>
6
- <artifactId>root</artifactId>
7
- <groupId>com.sap.sailing</groupId>
8
- <version>1.0.0-SNAPSHOT</version>
9
- </parent>
10
- <artifactId>org.json.simple</artifactId>
11
- <version>1.1.0-SNAPSHOT</version>
12
- <packaging>eclipse-plugin</packaging>
13
- <build>
14
- <plugins>
15
- <plugin>
16
- <groupId>org.eclipse.tycho</groupId>
17
- <artifactId>tycho-compiler-plugin</artifactId>
18
- <version>${tycho-version}</version>
19
- <configuration>
20
- <source>1.7</source>
21
- <target>1.7</target>
22
- </configuration>
23
- </plugin>
24
- <plugin>
25
- <groupId>org.eclipse.tycho</groupId>
26
- <artifactId>tycho-source-plugin</artifactId>
27
- <version>${tycho-version}</version>
28
- <executions>
29
- <execution>
30
- <id>plugin-source</id>
31
- <phase>generate-sources</phase>
32
- <goals>
33
- <goal>plugin-source</goal>
34
- </goals>
35
- </execution>
36
- </executions>
37
- </plugin>
38
- </plugins>
39
- </build>
40
-</project>
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
3
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
4
+ <modelVersion>4.0.0</modelVersion>
5
+ <parent>
6
+ <artifactId>root</artifactId>
7
+ <groupId>com.sap.sailing</groupId>
8
+ <version>1.0.0-SNAPSHOT</version>
9
+ </parent>
10
+ <artifactId>org.json.simple</artifactId>
11
+ <version>1.1.0-SNAPSHOT</version>
12
+ <packaging>eclipse-plugin</packaging>
13
+ <build>
14
+ <plugins>
15
+ <plugin>
16
+ <groupId>org.eclipse.tycho</groupId>
17
+ <artifactId>tycho-compiler-plugin</artifactId>
18
+ <version>${tycho-version}</version>
19
+ <configuration>
20
+ <source>1.7</source>
21
+ <target>1.7</target>
22
+ </configuration>
23
+ </plugin>
24
+ <plugin>
25
+ <groupId>org.eclipse.tycho</groupId>
26
+ <artifactId>tycho-source-plugin</artifactId>
27
+ <version>${tycho-version}</version>
28
+ <executions>
29
+ <execution>
30
+ <id>plugin-source</id>
31
+ <phase>generate-sources</phase>
32
+ <goals>
33
+ <goal>plugin-source</goal>
34
+ </goals>
35
+ </execution>
36
+ </executions>
37
+ </plugin>
38
+ </plugins>
39
+ </build>
40
+</project>
java/pom.xml
... ...
@@ -134,8 +134,8 @@
134 134
<module>com.sap.sailing.autoload</module>
135 135
<module>com.sap.sailing.feature.p2build</module>
136 136
<module>com.sap.sailing.polars</module>
137
- <module>com.sap.sailing.polars.datamining</module>
138
- <module>com.sap.sailing.polars.datamining.shared</module>
137
+ <module>com.sap.sailing.polars.datamining</module>
138
+ <module>com.sap.sailing.polars.datamining.shared</module>
139 139
<module>com.sap.sailing.polars.test</module>
140 140
<!--
141 141
com.sap.sailing.targetplatform.base
... ...
@@ -264,15 +264,15 @@
264 264
<version>${tycho-version}</version>
265 265
<configuration>
266 266
<resolver>p2</resolver>
267
- <dependency-resolution>
268
- <extraRequirements>
269
- <requirement>
270
- <type>eclipse-plugin</type>
271
- <id>com.google.gwt.dev</id>
272
- <versionRange>0.0.0</versionRange>
273
- </requirement>
274
- </extraRequirements>
275
- </dependency-resolution>
267
+ <dependency-resolution>
268
+ <extraRequirements>
269
+ <requirement>
270
+ <type>eclipse-plugin</type>
271
+ <id>com.google.gwt.dev</id>
272
+ <versionRange>0.0.0</versionRange>
273
+ </requirement>
274
+ </extraRequirements>
275
+ </dependency-resolution>
276 276
<target>
277 277
<artifact>
278 278
<groupId>${project.groupId}</groupId>
java/target/configuration/jetty/etc/jetty.xml
... ...
@@ -31,7 +31,7 @@
31 31
<Arg name="threadpool">
32 32
<New id="threadpool" class="org.eclipse.jetty.util.thread.QueuedThreadPool">
33 33
<Arg name="minThreads" type="int">10</Arg>
34
- <Arg name="maxThreads" type="int">3000</Arg>
34
+ <Arg name="maxThreads" type="int">64</Arg>
35 35
<Arg name="idleTimeout" type="int">25000</Arg>
36 36
<Set name="detailedDump">false</Set>
37 37
</New>
mobile/com.sap.sailing.android.shared/pom.xml
... ...
@@ -1,17 +1,17 @@
1 1
<?xml version="1.0" encoding="UTF-8"?>
2 2
<project
3
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
4
- xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
5
- <modelVersion>4.0.0</modelVersion>
6
- <parent>
7
- <groupId>com.sap.sailing</groupId>
8
- <artifactId>mobile</artifactId>
9
- <version>1.0.0-SNAPSHOT</version>
10
- </parent>
11
- <artifactId>com.sap.sailing.android.shared</artifactId>
12
- <packaging>apklib</packaging>
3
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
4
+ xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
5
+ <modelVersion>4.0.0</modelVersion>
6
+ <parent>
7
+ <groupId>com.sap.sailing</groupId>
8
+ <artifactId>mobile</artifactId>
9
+ <version>1.0.0-SNAPSHOT</version>
10
+ </parent>
11
+ <artifactId>com.sap.sailing.android.shared</artifactId>
12
+ <packaging>apklib</packaging>
13 13
14
- <dependencies>
14
+ <dependencies>
15 15
<dependency>
16 16
<groupId>com.sap.sailing</groupId>
17 17
<artifactId>org.json.simple</artifactId>
... ...
@@ -86,12 +86,12 @@
86 86
<groupId>android</groupId>
87 87
<artifactId>android</artifactId>
88 88
</dependency>
89
- </dependencies>
89
+ </dependencies>
90 90
91
- <build>
92
- <sourceDirectory>src</sourceDirectory>
93
- <finalName>${project.artifactId}</finalName>
94
- <plugins>
91
+ <build>
92
+ <sourceDirectory>src</sourceDirectory>
93
+ <finalName>${project.artifactId}</finalName>
94
+ <plugins>
95 95
<plugin>
96 96
<groupId>com.jayway.maven.plugins.android.generation2</groupId>
97 97
<artifactId>android-maven-plugin</artifactId>
... ...
@@ -101,6 +101,6 @@
101 101
</sdk>
102 102
</configuration>
103 103
</plugin>
104
- </plugins>
105
- </build>
104
+ </plugins>
105
+ </build>
106 106
</project>
mobile/com.sap.sailing.racecommittee.app/pom.xml
... ...
@@ -120,7 +120,7 @@
120 120
</executions>
121 121
</plugin>
122 122
123
- <!-- plugins for generating version file and copying .apk -->
123
+ <!-- plugins for generating version file and copying .apk -->
124 124
<plugin>
125 125
<groupId>org.apache.maven.plugins</groupId>
126 126
<artifactId>maven-dependency-plugin</artifactId>
mobile/pom.xml
... ...
@@ -16,13 +16,13 @@
16 16
<!--
17 17
<module>com.sap.sailing.racecommittee.app</module>
18 18
<module>com.sap.sailing.android.shared</module>
19
- <module>com.sap.sailing.adnroid.buoy.positioning.app</module>
19
+ <module>com.sap.sailing.adnroid.buoy.positioning.app</module>
20 20
<module>com.sap.sailing.android.tracking.app</module>
21 21
-->
22 22
</modules>
23 23
24 24
<properties>
25
- <!-- used to copy android apps to the static web dir (auto-update functionality) -->
25
+ <!-- used to copy android apps to the static web dir (auto-update functionality) -->
26 26
<!-- relative dir as seen from the child modules -->
27 27
<additional-deployment-dir>../../java/com.sap.sailing.www/apps</additional-deployment-dir>
28 28
<!-- redefine in child poms -->
... ...
@@ -32,21 +32,21 @@
32 32
33 33
<dependencyManagement>
34 34
<dependencies>
35
- <!-- needed on remote repository -->
36
- <dependency>
37
- <groupId>android</groupId>
38
- <artifactId>android</artifactId>
39
- <version>4.4.2_r4</version>
40
- <scope>provided</scope>
41
- </dependency>
35
+ <!-- needed on remote repository -->
36
+ <dependency>
37
+ <groupId>android</groupId>
38
+ <artifactId>android</artifactId>
39
+ <version>4.4.2_r4</version>
40
+ <scope>provided</scope>
41
+ </dependency>
42 42
43
- <!-- needed on remote repository -->
44
- <dependency>
45
- <groupId>android.support</groupId>
46
- <artifactId>compatibility-v4</artifactId>
47
- <version>19</version>
48
- </dependency>
49
- </dependencies>
43
+ <!-- needed on remote repository -->
44
+ <dependency>
45
+ <groupId>android.support</groupId>
46
+ <artifactId>compatibility-v4</artifactId>
47
+ <version>19</version>
48
+ </dependency>
49
+ </dependencies>
50 50
</dependencyManagement>
51 51
52 52
<build>
... ...
@@ -55,90 +55,90 @@
55 55
<plugin>
56 56
<groupId>com.jayway.maven.plugins.android.generation2</groupId>
57 57
<artifactId>android-maven-plugin</artifactId>
58
- <version>3.9.0-rc.1</version>
59
- <configuration>
60
- <sdk>
61
- <platform>19</platform>
62
- </sdk>
63
- </configuration>
58
+ <version>3.9.0-rc.1</version>
59
+ <configuration>
60
+ <sdk>
61
+ <platform>19</platform>
62
+ </sdk>
63
+ </configuration>
64 64
<extensions>true</extensions>
65 65
</plugin>
66
- <plugin>
67
- <groupId>org.apache.maven.plugins</groupId>
68
- <artifactId>maven-dependency-plugin</artifactId>
69
- <version>2.8</version>
70
- <executions>
71
- <execution>
72
- <id>copy-installed</id>
73
- <phase>install</phase>
74
- <goals>
75
- <goal>copy</goal>
76
- </goals>
77
- <configuration>
78
- <artifactItems>
79
- <artifactItem>
80
- <groupId>${project.groupId}</groupId>
81
- <artifactId>${project.artifactId}</artifactId>
82
- <version>${project.version}</version>
83
- <type>${project.packaging}</type>
84
- </artifactItem>
85
- </artifactItems>
86
- <outputDirectory>${additional-deployment-dir}</outputDirectory>
87
- <stripVersion>true</stripVersion>
88
- </configuration>
89
- </execution>
90
- </executions>
91
- </plugin>
92
- <plugin>
93
- <groupId>org.apache.maven.plugins</groupId>
94
- <artifactId>maven-antrun-plugin</artifactId>
95
- <version>1.3</version>
96
- <executions>
97
- <execution>
98
- <id>generate-version</id>
99
- <phase>install</phase>
100
- <goals>
101
- <goal>run</goal>
102
- </goals>
103
- <configuration>
104
- <tasks>
105
- <property name="target.dir" value="${additional-deployment-dir}" />
106
- <property name="target.name" value="${additional-deployment-version-file}" />
107
-
108
- <echo file="${target.dir}/${target.name}" message="${project.build.finalName}.apk=${app-version}" />
109
- <echo message="Version file generated in ${target.dir}/${target.name}." />
110
- </tasks>
111
- </configuration>
112
- </execution>
113
- </executions>
114
- </plugin>
115
- <plugin>
116
- <artifactId>maven-clean-plugin</artifactId>
117
- <version>2.5</version>
118
- <configuration>
119
- <filesets>
120
- <fileset>
121
- <directory>${additional-deployment-dir}</directory>
122
- <includes>
123
- <include>${project.build.finalName}.apk</include>
124
- <include>${additional-deployment-version-file}</include>
125
- </includes>
126
- <excludes>
127
- <exclude>*.gitignore</exclude>
128
- </excludes>
129
- <followSymlinks>false</followSymlinks>
130
- </fileset>
131
- </filesets>
132
- </configuration>
133
- </plugin>
134
- <plugin>
135
- <groupId>org.apache.maven.plugins</groupId>
136
- <artifactId>maven-compiler-plugin</artifactId>
137
- <configuration>
138
- <source>1.7</source>
139
- <target>1.7</target>
140
- </configuration>
141
- </plugin>
66
+ <plugin>
67
+ <groupId>org.apache.maven.plugins</groupId>
68
+ <artifactId>maven-dependency-plugin</artifactId>
69
+ <version>2.8</version>
70
+ <executions>
71
+ <execution>
72
+ <id>copy-installed</id>
73
+ <phase>install</phase>
74
+ <goals>
75
+ <goal>copy</goal>
76
+ </goals>
77
+ <configuration>
78
+ <artifactItems>
79
+ <artifactItem>
80
+ <groupId>${project.groupId}</groupId>
81
+ <artifactId>${project.artifactId}</artifactId>
82
+ <version>${project.version}</version>
83
+ <type>${project.packaging}</type>
84
+ </artifactItem>
85
+ </artifactItems>
86
+ <outputDirectory>${additional-deployment-dir}</outputDirectory>
87
+ <stripVersion>true</stripVersion>
88
+ </configuration>
89
+ </execution>
90
+ </executions>
91
+ </plugin>
92
+ <plugin>
93
+ <groupId>org.apache.maven.plugins</groupId>
94
+ <artifactId>maven-antrun-plugin</artifactId>
95
+ <version>1.3</version>
96
+ <executions>
97
+ <execution>
98
+ <id>generate-version</id>
99
+ <phase>install</phase>
100
+ <goals>
101
+ <goal>run</goal>
102
+ </goals>
103
+ <configuration>
104
+ <tasks>
105
+ <property name="target.dir" value="${additional-deployment-dir}" />
106
+ <property name="target.name" value="${additional-deployment-version-file}" />
107
+
108
+ <echo file="${target.dir}/${target.name}" message="${project.build.finalName}.apk=${app-version}" />
109
+ <echo message="Version file generated in ${target.dir}/${target.name}." />
110
+ </tasks>
111
+ </configuration>
112
+ </execution>
113
+ </executions>
114
+ </plugin>
115
+ <plugin>
116
+ <artifactId>maven-clean-plugin</artifactId>
117
+ <version>2.5</version>
118
+ <configuration>
119
+ <filesets>
120
+ <fileset>
121
+ <directory>${additional-deployment-dir}</directory>
122
+ <includes>
123
+ <include>${project.build.finalName}.apk</include>
124
+ <include>${additional-deployment-version-file}</include>
125
+ </includes>
126
+ <excludes>
127
+ <exclude>*.gitignore</exclude>
128
+ </excludes>
129
+ <followSymlinks>false</followSymlinks>
130
+ </fileset>
131
+ </filesets>
132
+ </configuration>
133
+ </plugin>
134
+ <plugin>
135
+ <groupId>org.apache.maven.plugins</groupId>
136
+ <artifactId>maven-compiler-plugin</artifactId>
137
+ <configuration>
138
+ <source>1.7</source>
139
+ <target>1.7</target>
140
+ </configuration>
141
+ </plugin>
142 142
</plugins>
143 143
</pluginManagement>
144 144
</build>
pom.xml
... ...
@@ -1,68 +1,68 @@
1 1
<?xml version="1.0" encoding="UTF-8"?>
2 2
<project
3
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
4
- xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
5
- <modelVersion>4.0.0</modelVersion>
3
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
4
+ xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
5
+ <modelVersion>4.0.0</modelVersion>
6 6
7
- <groupId>com.sap.sailing</groupId>
8
- <artifactId>workspace</artifactId>
9
- <version>1.0.0-SNAPSHOT</version>
10
- <packaging>pom</packaging>
7
+ <groupId>com.sap.sailing</groupId>
8
+ <artifactId>workspace</artifactId>
9
+ <version>1.0.0-SNAPSHOT</version>
10
+ <packaging>pom</packaging>
11 11
12
- <modules>
13
- <module>java</module>
14
- </modules>
12
+ <modules>
13
+ <module>java</module>
14
+ </modules>
15 15
16
- <profiles>
17
- <profile>
18
- <!-- deactivate with parameter '-P !with-mobile'-->
19
- <id>with-mobile</id>
20
- <activation>
21
- <activeByDefault>true</activeByDefault>
22
- </activation>
23
- <modules>
24
- <module>mobile</module>
25
- </modules>
26
- </profile>
27
- <profile>
28
- <!-- activate with parameter '-P with-ios'-->
29
- <id>with-ios</id>
30
- <modules>
31
- <module>ios</module>
32
- </modules>
33
- </profile>
34
- </profiles>
16
+ <profiles>
17
+ <profile>
18
+ <!-- deactivate with parameter '-P !with-mobile'-->
19
+ <id>with-mobile</id>
20
+ <activation>
21
+ <activeByDefault>true</activeByDefault>
22
+ </activation>
23
+ <modules>
24
+ <module>mobile</module>
25
+ </modules>
26
+ </profile>
27
+ <profile>
28
+ <!-- activate with parameter '-P with-ios'-->
29
+ <id>with-ios</id>
30
+ <modules>
31
+ <module>ios</module>
32
+ </modules>
33
+ </profile>
34
+ </profiles>
35 35
36
- <properties>
37
- <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
38
- <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
39
- </properties>
40
-
41
- <repositories>
42
- <repository>
43
- <id>equinox-sdk-3.9</id>
44
- <layout>p2</layout>
45
- <url>http://download.eclipse.org/releases/luna/</url>
46
- </repository>
47
-<!--
48
- <repository>
49
- <id>sap-nexus</id>
50
- <url>http://nexus.wdf.sap.corp:8081/nexus/content/groups/build.snapshots</url>
51
- </repository>
36
+ <properties>
37
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
38
+ <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
39
+ </properties>
40
+
41
+ <repositories>
42
+ <repository>
43
+ <id>equinox-sdk-3.9</id>
44
+ <layout>p2</layout>
45
+ <url>http://download.eclipse.org/releases/luna/</url>
46
+ </repository>
47
+<!--
48
+ <repository>
49
+ <id>sap-nexus</id>
50
+ <url>http://nexus.wdf.sap.corp:8081/nexus/content/groups/build.snapshots</url>
51
+ </repository>
52 52
-->
53
- <repository>
54
- <id>sailing-server-maven</id>
55
- <url>http://maven.sapsailing.com/maven</url>
56
- </repository>
53
+ <repository>
54
+ <id>sailing-server-maven</id>
55
+ <url>http://maven.sapsailing.com/maven</url>
56
+ </repository>
57 57
58
- <repository>
59
- <id>central</id>
60
- <url>http://repo1.maven.org/maven2</url>
61
- </repository>
58
+ <repository>
59
+ <id>central</id>
60
+ <url>http://repo1.maven.org/maven2</url>
61
+ </repository>
62 62
63
- <repository>
64
- <id>uni-luebeck</id>
65
- <url>http://www.itm.uni-luebeck.de/projects/maven/releases</url>
66
- </repository>
67
- </repositories>
63
+ <repository>
64
+ <id>uni-luebeck</id>
65
+ <url>http://www.itm.uni-luebeck.de/projects/maven/releases</url>
66
+ </repository>
67
+ </repositories>
68 68
</project>
wiki/Log-File-Analysis.md
... ...
@@ -64,33 +64,103 @@ This uses the SetEnvIf module, tries to match an IP address at the start of the
64 64
65 65
### Amazon EC2 Elastic Load Balancer (ELB) Logs
66 66
67
-An Amazon ELB can be configured to write log files to the S3 storage. The general format is [explained here](http://docs.aws.amazon.com/ElasticLoadBalancing/latest/DeveloperGuide/access-log-collection.html#access-log-entry-format). It contains in particular the client IP where the request originated and can tell timing parameters for request forwarding and processing that otherwise would not be available. It seems a good idea to always capture these logs for archiving purposes. They end up on an S3 bucket, and by default we use `sapsailing-access-logs`.
67
+An Amazon ELB can be configured to write log files to the S3 storage. The general format is [explained here](http://docs.aws.amazon.com/ElasticLoadBalancing/latest/DeveloperGuide/access-log-collection.html#access-log-entry-format). It contains in particular the client IP where the request originated and can tell timing parameters for request forwarding and processing that otherwise would not be available.
68
+
69
+The format for these logs is
70
+
71
+<pre>
72
+timestamp elb client:port backend:port request_processing_time backend_processing_time response_processing_time elb_status_code backend_status_code received_bytes sent_bytes "request" "user_agent" ssl_cipher ssl_protocol
73
+</pre>
74
+
75
+It seems a good idea to always capture these logs for archiving purposes. They end up on an S3 bucket, and by default we use `sapsailing-access-logs`.
68 76
69 77
Content can be synced from there to a local directory using the following command:
70 78
71 79
``s3cmd sync s3://sapsailing-access-logs/elb-access-logs ./elb-access-logs/``
72 80
81
+Our central web server at `www.sapsailing.com` does this periodically every day, sync'ing the logs to `/var/log/old/elb-access-logs/`. The corresponding cron job is defined in `/etc/cron.daily/syncEC2ElbLogs` which is a script also found in git in the `configuration/` folder.
82
+
83
+### Broken or Partly Missing Logs and How We Recover
84
+
85
+During a few events we unfortunately failed to create proper Apache log files according to the above rules for scenarios using a load-balanced setup ("ELB scenario"). In some cases, the Apache log format was missing the referrer URL. This is easy to patch into the files because we collected the logs on a per-server basis and we knew which server ran which event. For example, for Kieler Woche 2015 and Travemünder Woche 2015 we needed to insert `kielerwoche2015.sapsailing.com` and `tw2015.sapsailing.com`, respectively, to match the general log format used for analysis.
86
+
87
+The other problem, as described already above, is that of the original client IP address. Before we encountered how to log the original client IP from the `X-Forwarded-For` header field, only the ELB's IP addresses were logged in the Apache logs. While counting the correct number of hits is possible with such logs, they don't reveal the true number of unique visitors. They do, however, contain a valid referrer URL, telling us which leaderboards and races were the most popular.
88
+
89
+In case we had an ELB log active, storing the Amazon ELB logs to an S3 bucket, we were able to convert those with a script stored in our git at `configuration/convertELBLogToApacheFormat` into our general Apache log format. This allows us to count the unique visitors for those events, but the ELB logs don't contain the referrer URL, so we patch that to always be the plain event URL, such as `http://kielerwoche2015.sapsailing.com`. Therefore, while these converted logs tell the correct number of unique visitors and the correct number of hits, they don't allow for an analysis of the most popular leaderboards and races.
90
+
91
+We hope that these partially-logged events remain the exception and that future events are logged homogeneously according to the rules above. The following naming conventions have been established under `/var/log/old` that shall allow the automatic log analyzers to identify the log file contents by file name patterns:
92
+
93
+ - `access_log*`: A proper Apache log file with virtual host name as the first field, followed by the original client's IP address and a valid referrer URL as the second-to-last field
94
+ - `elb-origin-access_log*`: An Apache log file whose origin IP addresses are usually only the ELB IP addresses, therefore not allowing for unique visitor analysis
95
+ - `bundesliga2015_elb_access_log.gz` and `tw2015_elb_access_log.gz`: ELB log files converted to Apache format, appended in order of ascending time stamps, prefixed with the virtual host name as the first field, using the original client IP address which allows for unique visitor analysis, and the virtual host name used again as generic referrer URL
96
+ - `original-access_log*`: Apache log file without leading virtual host name field, also usually only with ELB IP addresses instead of an original client IP; not good for any log file analysis without further conversion, but kept for reference purposes
97
+
73 98
## Automatic Log File Rotation to /var/log/old
74 99
75
- - describe how logrotate is used and automatically configured during instance start-up and where the logs go
100
+When an EC2 server is launched from its Amazon Machine Image (AMI), the /etc/init.d/sailing script is executed with the "start" parameter. This script is really just a link to `/home/sailing/code/configuration/sailing` which comes from the git version checked out to the workspace at `/home/sailing/code`. This script patches the file `/etc/logrotate.d/httpd` such that when log rotation happens, the existence of the directory `/var/log/old/$SERVER_NAME/$SERVER_IP` is ensured and the log files are copied there.
101
+
102
+This way, logs end up in a per server-name and per server-ip directory.
103
+
104
+The general log rotation rules (size, time, compression, etc.) is governed by `/etc/logrotate.conf`.
76 105
77 106
## Analysis Tools
78 107
79 108
### goaccess
80 109
81
- - mention specific parameters and .goaccess config file required to parse our format
110
+The file `/root/.goaccessrc` on `www.sapsailing.com` looks as follows:
111
+
112
+```
113
+color_scheme 1
114
+date_format %d/%b/%Y
115
+time_format %H:%M:%S
116
+log_format %^ %h %^[%d:%^] "%r" %s %b "%R" "%u"
117
+log_file STDIN
118
+```
119
+
120
+Still, specific parameters are required for `goaccess` in order to parse our regular Apache logs that have the virtual host name as their first field:
121
+
122
+`goaccess --no-global-config -a -f /var/log/httpd/access_log`
123
+
124
+The unique visitors that goaccess lists are based on the common definition that counts requests as originating from the same visitor if they happened on the same day with equal user agent and IP address.
82 125
83 126
### apachetop
84 127
128
+The simple command `apachetop` shows the traffic on the Apache httpd server running on the host where the command is invoked, based on the access log file in the usual location `/var/log/httpd/access_log`.
129
+
85 130
### AWStats
86 131
87
- - specific log collection approach configured in /etc/awstats/*.conf script
132
+We use AWStats to analyze our Apache access log files and produce per-month reports at [http://awstats.sapsailing.com](http://awstats.sapsailing.com/awstats/awstats.pl?output=main&config=www.sapsailing.com&framename=index). The username is `awstats`. Ask axel.uhl@sap.com or stefan.lacher@sap.com for the password.
133
+
134
+AWStats report production is controlled by three things: a cron job hooked up by `/etc/cron.weekly/awstats` which is basically a one-liner launching the `awstats` command like this:
88 135
89
- - extension for per-virtual-host hit count
136
+`exec /usr/share/awstats/tools/awstats_updateall.pl now -configdir="/etc/awstats" -awstatsprog="/usr/share/awstats/wwwroot/cgi-bin/awstats.pl"`
90 137
91
- - configuration of central apache to present results (awstats.sapsailing.com)
138
+and a configuration file located at `/etc/awstats/awstats.www.sapsailing.com.conf` which describes in its `LogFile` directive the filename pattern to use for collecting the log files that shall be analyzed. Currently, the file name pattern is this:
139
+
140
+`/var/log/httpd/access_log /var/log/old/access_log-???????? /var/log/old/access_log-????????.gz /var/log/old/*/elb-origin-access_log* /var/log/old/*/*/elb-origin-access_log* /var/log/old/*/access_log* /var/log/old/*/*/access_log*`
141
+
142
+and all log entries that contain `HealthChecker` are eliminated as they are only a "ping" request sent by the load balancer to check the instance's health status which is not to be counted as a "hit." Note that according to the above explanations of our log file formats and variants this focuses on hit analysis for those events with broken or partial / incomplete log files such as Kieler Woche 2015 and Travemünder Woche 2015. It uses the `elb-origin-*` flavors, tolerating the fact that these don't have the original client's IP address.
143
+
144
+The third element of AWStats configuration is how it is published through Apache. This is described in `/etc/httpd/conf.d/awstats.conf` and uses the password definition file `/etc/httpd/conf/passwd.awstats` which can be updated using the `htpasswd` command.
92 145
93 146
### Custom Scripts
94 147
95
- - unique_ips_per_referrer
96
- - convertELBLogToApacheFormat
... ...
\ No newline at end of file
0
+#### `unique_ips_per_referrer`
1
+
2
+The script is located in git at `configuration/unique_ips_per_referrer`. It has two variants next to it: `unique_ips_per_referrer_generate_results_only` and `unique_ips_per_referrer_generate_month_results_only`. The basic idea of this family of scripts is to count the unique visitors for each virtual host name (usually corresponding to an event), in total and by month. Neither the `goaccess` nor the AWStats tool provide us with these numbers. AWStats can only compute the _hits_ per virtual host name, and `goaccess` only lists unique visitors per day, not per virtual host.
3
+
4
+The script can be fed with a list of log files in our common Apache format, with virtual host name in first, original client IP in second, time stamp in third and user agent in last column. For all new files (files already analyzed are recorded in `/var/log/old/cache/unique-ips-per-referrer/visited) the script then reduces each line to its timestamp, original client IP and user agent fields, constituting the key for identifying a unique visitor. Each log entry that has been analyzed and reduced this way is then appended to a file under `/var/log/old/cache/unique-ips-per-referrer/stats/<virtual-host-name>.ips`. When done with all inputs, the `*.ips` files produced are filtered for unique entries (using `sort -u`) and number of lines are counted, resulting in the total number of unique visitors per virtual host name since the beginning of our log file records.
5
+
6
+Additionally, a per-month analysis is carried out by splitting the `*.ips` files by the months found in the time stamps and again applying a unique count. The resulting files can be found at `/var/log/old/cache/unique-ips-per-referrer/stats/unique-ips-days-useragents-per-event` and `/var/log/old/cache/unique-ips-per-referrer/stats/unique-ips-days-useragents-per-event-MMM-YYYY` where `MMM` represents the three-letter month name such as `Apr` and `YYYY` stands for the year.
7
+
8
+The `unique_ips_per_referrer` script is registered as a weekly cron job in `/etc/cron.weekly` and is fed the special converted log files of Travemünder Woche 2015 and the Bundesliga logs from 2015, as well as all files matching the file name pattern `access_log-*` anywhere under `/var/log/old`. This only matches proper Apache log files with original client IP addresses, not those with ELB IP addresses only (which are named `elb-origin-access_log*` by convention, see above).
9
+
10
+The variant `unique_ips_per_referrer_generate_results_only` assumes that the `*.ips` files have already been updated and produces the total and per-month output files.
11
+
12
+The variant `unique_ips_per_referrer_generate_month_results_only` produces only the per-month output files, also assuming that the `*.ips` files have already been updated.
13
+
14
+The results are published under the link [http://awstats.sapsailing.com/unique-visitors/](http://awstats.sapsailing.com/unique-visitors/) which uses the same credentials as the main AWStats publishing page.
15
+
16
+#### `convertELBLogToApacheFormat`
17
+
18
+This script was already briefly mentioned above. It is found at `configuration/convertELBLogToApacheFormat` and converts and Amazon Elastic Load Balancer (ELB) log file to our common Apache log file format with leading virtual host name. The virtual host name is expected as the first parameter; all subsequent parameters are treated as file names of ELB log files, either in GZIP format with `.gz` extension or uncompressed. The conversion result is streamed to the standard output and may therefore be redirected into a file.
... ...
\ No newline at end of file
wiki/creating-ec2-image-from-scratch.md
... ...
@@ -19,7 +19,8 @@ I then did a `yum update` and added the following packages:
19 19
- goaccess
20 20
- postfix (for sending e-mail, e.g., to invite competitors and buoy pingers)
21 21
- tigervnc-server
22
- - xdm (to have a rudimentary window manager available for the VNC server)
22
+ - WindowMaker
23
+ - xterm
23 24
24 25
Then I created a mount point /home/sailing and copied the following lines from the /etc/fstab file from an existing SL instance:
25 26
wiki/onboarding.md
... ...
@@ -67,11 +67,13 @@ First of all, make sure you've looked at http://www.amazon.de/Patterns-Elements-
67 67
* Rebuild all projects
68 68
4. Run the Race Analysis Suite
69 69
* Start the MongoDB
70
- * Start the appropriate Eclipse launch configuration (e.g. 'Sailing Server (Proxy)') You´ll find this in the run dropdown
71
- * Run "Security UI sdm" in the run dropdown
72
- * Run "SailingGWT" in the run dropdown
73
-5. Within the Race Analysis Suite
70
+ * Start the appropriate Eclipse launch configuration (e.g. 'Sailing Server (Proxy)') You´ll find this in the debug dropdown
71
+ * Run "Security UI sdm" in the debug dropdown
72
+ * Run "SailingGWT" in the debug dropdown
73
+5. Import races within the Race Analysis Suite
74
+ * Choose "Security UI sdm" in the upper left corner of the "Development Mode" Tab in Eclipse and open "...Login.html" in your browser
74 75
* Default Login: user "admin", password "admin"
76
+ * Choose "Sailing GWT" in the "Development Mode" Tab and open "...AdminConsole.html..." (It is normal that the first try fails. Reload the page after the first try)
75 77
* For TracTrac Events: (Date 27.11.2012) Use Live URI tcp://10.18.22.156:4412, Stored URI tcp://10.18.22.156:4413, JSON URL http://germanmaster.traclive.dk/events/event_20120905_erEuropean/jsonservice.php
76 78
* Press List Races
77 79