configuration/environments_scripts/central_mongo_setup/files/usr/local/bin/notify-unhealthy-mongodb-of-sailing-instance
... ...
@@ -4,11 +4,12 @@ SAILING_INSTANCES=$( aws ec2 describe-instances --filters Name=tag-key,Values=sa
4 4
for i in ${SAILING_INSTANCES}; do
5 5
for j in `ssh -o StrictHostKeyChecking=no sailing@$i "ls /home/sailing/servers"`; do
6 6
if ssh -o StrictHostKeyChecking=no sailing@$i "/home/sailing/servers/$j/status 2>/dev/null | jq -r '.mongoDbConfiguration.servers[].type'" | grep -q UNKNOWN; then
7
- MAIL_BODY='A DB for a Sailing Analytics process of replica set $j running on node $i is down!
7
+ MAIL_BODY='A DB for a Sailing Analytics process of replica set '${j}' running on node '${i}' is down!
8 8
9 9
Check status of DB urgently. To unsubscribe, change '${MAILINGLIST}' on '`hostname`'."'
10 10
if [ -n "${MAIL_BODY}" ]; then
11 11
echo "${MAIL_BODY}" | mail -s "MongoDB Replica Set of Application $j Unhealthy" `cat "${MAILINGLIST}"`
12
+ logger -t unhealthy-mongo "${MAIL_BODY}"
12 13
fi
13 14
fi
14 15
done
java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/racelog/tracking/impl/MongoSensorFixStoreImpl.java
... ...
@@ -192,7 +192,6 @@ public class MongoSensorFixStoreImpl extends MongoFixHandler implements MongoSen
192 192
}
193 193
}
194 194
progressConsumer.accept(1d);
195
-
196 195
return fixLoaded;
197 196
}
198 197
... ...
@@ -257,7 +256,8 @@ public class MongoSensorFixStoreImpl extends MongoFixHandler implements MongoSen
257 256
});
258 257
for (FixT fix : fixes) {
259 258
for (FixReceivedListener<FixT> listener : listenersToInform) {
260
- final Iterable<Triple<RegattaAndRaceIdentifier, Boolean, Duration>> racesWithManeuverChangeFromListener = listener.fixReceived(device, fix, returnManeuverChanges, returnLiveDelay);
259
+ final Iterable<Triple<RegattaAndRaceIdentifier, Boolean, Duration>> racesWithManeuverChangeFromListener =
260
+ listener.fixReceived(device, fix, returnManeuverChanges, returnLiveDelay);
261 261
Util.addAll(racesWithManeuverChangeFromListener, raceWithChangedManeuver);
262 262
}
263 263
}
java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/CreateAndTrackWithRaceLogTest.java
... ...
@@ -94,7 +94,6 @@ public class CreateAndTrackWithRaceLogTest extends RaceLogTrackingTestHelper {
94 94
private RaceLogTrackingAdapter adapter;
95 95
private Regatta regatta;
96 96
private SensorFixStore sensorFixStore;
97
-
98 97
private long time = 0;
99 98
100 99
@BeforeEach
java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/RaceLogFixTrackerManagerTest.java
... ...
@@ -76,7 +76,7 @@ public class RaceLogFixTrackerManagerTest {
76 76
77 77
protected final AbstractLogEventAuthor author = new LogEventAuthorImpl("author", 0);
78 78
private DynamicTrackedRace trackedRace;
79
-
79
+
80 80
@BeforeEach
81 81
public void setUp() throws UnknownHostException, MongoException {
82 82
raceLog = new RaceLogImpl("racelog");
java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogRaceTracker.java
... ...
@@ -228,7 +228,10 @@ public class RaceLogRaceTracker extends AbstractRaceTrackerBaseImpl<RaceLogConne
228 228
protected void onStop(boolean preemptive, boolean willBeRemoved) {
229 229
RaceLog raceLog = params.getRaceLog();
230 230
final Pair<TimePointSpecificationFoundInLog, TimePointSpecificationFoundInLog> trackingTimes = new TrackingTimesFinder(raceLog).analyze();
231
- if (!trackedRegatta.getRegatta().isControlTrackingFromStartAndFinishTimes() &&
231
+ // if willBeRemoved is true, this is probably a replication start or a server shut-down and not
232
+ // just a user stopping the tracking of a single race; therefore, don't capture the current time
233
+ // as the end-of-tracking if willBeRemoved==true. See also bug6228
234
+ if (!willBeRemoved && !trackedRegatta.getRegatta().isControlTrackingFromStartAndFinishTimes() &&
232 235
(trackingTimes == null || trackingTimes.getB() == null || trackingTimes.getB().getTimePoint() == null)) {
233 236
// seems the first time tracking for this race is stopped; enter "now" as end of tracking
234 237
// into the race log
java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/fixtracker/FixLoaderAndTracker.java
... ...
@@ -206,7 +206,8 @@ public class FixLoaderAndTracker implements TrackingDataLoader {
206 206
207 207
private final FixReceivedListener<Timed> listener = new FixReceivedListener<Timed>() {
208 208
@Override
209
- public Iterable<Triple<RegattaAndRaceIdentifier, Boolean, Duration>> fixReceived(DeviceIdentifier device, Timed fix, boolean returnManeuverChanges, boolean returnLiveDelay) {
209
+ public Iterable<Triple<RegattaAndRaceIdentifier, Boolean, Duration>> fixReceived(DeviceIdentifier device,
210
+ Timed fix, boolean returnManeuverChanges, boolean returnLiveDelay) {
210 211
final Set<RegattaAndRaceIdentifier> maneuverChanged = new HashSet<>();
211 212
final Map<RegattaAndRaceIdentifier, Duration> delayToLive = new HashMap<>();
212 213
if (!preemptiveStopRequested.get() && trackedRace.getStartOfTracking() != null) {
... ...
@@ -272,7 +273,7 @@ public class FixLoaderAndTracker implements TrackingDataLoader {
272 273
// check for maneuvers; otherwise, the fix may not have been accepted
273 274
// by the race or the track, e.g., because the race's end-of-tracking
274 275
// comes before the fix's time point
275
- if (trackedRace.recordFix(comp, (GPSFixMoving) fix)) {
276
+ if (trackedRace.recordFix(comp, (GPSFixMoving) fix)) { // TOOD bug6229: this checks the TrackedRace's tracking interval, but for an MDI we'd also want to intersect with Event/Regatta end date if set
276 277
if (returnManeuverChanges) {
277 278
RegattaAndRaceIdentifier maneuverChangedAnswer = detectIfManeuverChanged(comp);
278 279
if (maneuverChangedAnswer != null) {
... ...
@@ -313,7 +314,7 @@ public class FixLoaderAndTracker implements TrackingDataLoader {
313 314
} else {
314 315
// checking if the given fix is "better" than an existing one
315 316
TimePoint startOfTracking = trackedRace.getStartOfTracking();
316
- TimePoint endOfTracking = trackedRace.getStartOfTracking();
317
+ TimePoint endOfTracking = trackedRace.getEndOfTracking();
317 318
if (startOfTracking != null) {
318 319
GPSFix fixAfterStartOfTracking = markTrack
319 320
.getFirstFixAtOrAfter(startOfTracking);
... ...
@@ -441,7 +442,7 @@ public class FixLoaderAndTracker implements TrackingDataLoader {
441 442
}
442 443
}
443 444
}
444
-
445
+
445 446
/**
446 447
* Loads fixes defined by the given mapping and {@link MultiTimeRange}. Only those fixes that are in the mapping
447 448
* time range are being loaded.
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/Venue.java
... ...
@@ -10,6 +10,10 @@ import com.sap.sse.common.Renamable;
10 10
*
11 11
*/
12 12
public interface Venue extends Named, Renamable {
13
+ void addListener(VenueListener listener);
14
+
15
+ void removeListener(VenueListener listener);
16
+
13 17
Iterable<CourseArea> getCourseAreas();
14 18
15 19
void addCourseArea(CourseArea courseArea);
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/VenueListener.java
... ...
@@ -0,0 +1,6 @@
1
+package com.sap.sailing.domain.base;
2
+
3
+public interface VenueListener {
4
+ void courseAreaAdded(Venue venue, CourseArea courseArea);
5
+ void courseAreaRemoved(Venue venue, CourseArea courseArea);
6
+}
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/VenueImpl.java
... ...
@@ -5,15 +5,19 @@ import java.io.ObjectInputStream;
5 5
import java.util.ArrayList;
6 6
import java.util.Collections;
7 7
import java.util.List;
8
+import java.util.Set;
9
+import java.util.concurrent.ConcurrentHashMap;
8 10
9 11
import com.sap.sailing.domain.base.CourseArea;
10 12
import com.sap.sailing.domain.base.Venue;
13
+import com.sap.sailing.domain.base.VenueListener;
11 14
import com.sap.sse.concurrent.LockUtil;
12 15
import com.sap.sse.concurrent.NamedReentrantReadWriteLock;
13 16
14 17
public class VenueImpl implements Venue {
15 18
private static final long serialVersionUID = 6854152040737643290L;
16 19
private String name;
20
+ private transient Set<VenueListener> listeners;
17 21
18 22
/**
19 23
* The course areas are ordered because they typically follow an ordered naming pattern borrowed from the
... ...
@@ -25,10 +29,21 @@ public class VenueImpl implements Venue {
25 29
26 30
public VenueImpl(String name) {
27 31
this.name = name;
32
+ this.listeners = Collections.newSetFromMap(new ConcurrentHashMap<>());
28 33
courseAreas = new ArrayList<CourseArea>();
29 34
courseAreasLock = createCourseAreasLock(name);
30 35
}
31 36
37
+ @Override
38
+ public void addListener(VenueListener listener) {
39
+ listeners.add(listener);
40
+ }
41
+
42
+ @Override
43
+ public void removeListener(VenueListener listener) {
44
+ listeners.remove(listener);
45
+ }
46
+
32 47
private NamedReentrantReadWriteLock createCourseAreasLock(String name) {
33 48
return new NamedReentrantReadWriteLock("Course Areas for venue "+name, /* fair */ false);
34 49
}
... ...
@@ -38,6 +53,7 @@ public class VenueImpl implements Venue {
38 53
if (courseAreasLock == null) {
39 54
courseAreasLock = createCourseAreasLock(getName());
40 55
}
56
+ this.listeners = Collections.newSetFromMap(new ConcurrentHashMap<>());
41 57
}
42 58
43 59
@Override
... ...
@@ -58,6 +74,9 @@ public class VenueImpl implements Venue {
58 74
} finally {
59 75
LockUtil.unlockAfterWrite(courseAreasLock);
60 76
}
77
+ for (VenueListener listener : listeners) {
78
+ listener.courseAreaAdded(this, courseArea);
79
+ }
61 80
}
62 81
63 82
@Override
... ...
@@ -68,6 +87,9 @@ public class VenueImpl implements Venue {
68 87
} finally {
69 88
LockUtil.unlockAfterWrite(courseAreasLock);
70 89
}
90
+ for (VenueListener listener : listeners) {
91
+ listener.courseAreaRemoved(this, courseArea);
92
+ }
71 93
}
72 94
73 95
@Override
java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRace.java
... ...
@@ -48,6 +48,7 @@ import com.sap.sailing.domain.common.racelog.Flags;
48 48
import com.sap.sailing.domain.common.tracking.GPSFix;
49 49
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
50 50
import com.sap.sailing.domain.common.tracking.SensorFix;
51
+import com.sap.sailing.domain.leaderboard.HasCourseAreasListener;
51 52
import com.sap.sailing.domain.leaderboard.ScoringScheme;
52 53
import com.sap.sailing.domain.leaderboard.caching.LeaderboardDTOCalculationReuseCache;
53 54
import com.sap.sailing.domain.leaderboard.impl.CompetitorAndRankComparable;
... ...
@@ -649,6 +650,14 @@ public class MockedTrackedRace implements DynamicTrackedRace {
649 650
public void setAutoRestartTrackingUponCompetitorSetChange(
650 651
boolean autoRestartTrackingUponCompetitorSetChange) {
651 652
}
653
+
654
+ @Override
655
+ public void addCourseAreaChangeListener(HasCourseAreasListener listener) {
656
+ }
657
+
658
+ @Override
659
+ public void removeCourseAreaChangeListener(HasCourseAreasListener listener) {
660
+ }
652 661
};
653 662
}
654 663
java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/Regatta.java
... ...
@@ -7,6 +7,7 @@ import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
7 7
import com.sap.sailing.domain.common.RegattaIdentifier;
8 8
import com.sap.sailing.domain.common.RegattaName;
9 9
import com.sap.sailing.domain.common.security.SecuredDomainType;
10
+import com.sap.sailing.domain.leaderboard.HasCourseAreas;
10 11
import com.sap.sailing.domain.leaderboard.HasRaceColumnsAndRegattaLike;
11 12
import com.sap.sailing.domain.leaderboard.ScoringScheme;
12 13
import com.sap.sailing.domain.ranking.RankingMetricConstructor;
... ...
@@ -34,7 +35,7 @@ import com.sap.sse.security.shared.WithQualifiedObjectIdentifier;
34 35
*
35 36
*/
36 37
public interface Regatta
37
- extends NamedWithID, IsRegattaLike, HasRaceColumnsAndRegattaLike, WithQualifiedObjectIdentifier, HasCPUMeter {
38
+ extends NamedWithID, IsRegattaLike, HasRaceColumnsAndRegattaLike, WithQualifiedObjectIdentifier, HasCPUMeter, HasCourseAreas {
38 39
39 40
/**
40 41
* As taken from the Racing Rules of Sailing:
... ...
@@ -68,6 +69,7 @@ public interface Regatta
68 69
* {@code null}, but may be empty; callers need to {@code synchronize} on the object returned
69 70
* if they want to iterate.
70 71
*/
72
+ @Override
71 73
Iterable<CourseArea> getCourseAreas();
72 74
73 75
/**
java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RegattaImpl.java
... ...
@@ -52,6 +52,7 @@ import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
52 52
import com.sap.sailing.domain.common.RegattaIdentifier;
53 53
import com.sap.sailing.domain.common.RegattaName;
54 54
import com.sap.sailing.domain.common.RegattaNameAndRaceName;
55
+import com.sap.sailing.domain.leaderboard.HasCourseAreasListener;
55 56
import com.sap.sailing.domain.leaderboard.ResultDiscardingRule;
56 57
import com.sap.sailing.domain.leaderboard.ScoringScheme;
57 58
import com.sap.sailing.domain.leaderboard.impl.AbstractLeaderboardImpl;
... ...
@@ -176,6 +177,7 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene
176 177
AbstractLeaderboardImpl.class.getName(), 0);
177 178
178 179
private transient CPUMeter cpuMeter;
180
+ private transient Set<HasCourseAreasListener> courseAreaChangeListeners;
179 181
180 182
/**
181 183
* Constructs a regatta with an empty {@link RaceLogStore} and with
... ...
@@ -267,6 +269,7 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene
267 269
boolean controlTrackingFromStartAndFinishTimes, boolean autoRestartTrackingUponCompetitorSetChange,
268 270
RankingMetricConstructor rankingMetricConstructor, String registrationLinkSecret) {
269 271
super(name);
272
+ this.courseAreaChangeListeners = Collections.newSetFromMap(new ConcurrentHashMap<>());
270 273
this.cpuMeter = CPUMeter.create();
271 274
this.registrationLinkSecret = registrationLinkSecret;
272 275
this.rankingMetricConstructor = rankingMetricConstructor;
... ...
@@ -371,6 +374,7 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene
371 374
ois.defaultReadObject();
372 375
this.cpuMeter = CPUMeter.create();
373 376
regattaListeners = new HashSet<RegattaListener>();
377
+ this.courseAreaChangeListeners = Collections.newSetFromMap(new ConcurrentHashMap<>());
374 378
MasterDataImportInformation masterDataImportInformation = ongoingMasterDataImportInformation.get();
375 379
if (masterDataImportInformation != null) {
376 380
raceLogStore = masterDataImportInformation.getRaceLogStore();
... ...
@@ -717,10 +721,24 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene
717 721
718 722
@Override
719 723
public void setCourseAreas(Iterable<CourseArea> newCourseAreas) {
724
+ final Iterable<CourseArea> oldCourseAreas = this.courseAreas;
720 725
synchronized (this.courseAreas) {
721 726
this.courseAreas.clear();
722 727
Util.addAll(newCourseAreas, this.courseAreas);
723 728
}
729
+ for (HasCourseAreasListener listener : courseAreaChangeListeners) {
730
+ listener.courseAreasChanged(this, oldCourseAreas, newCourseAreas);
731
+ }
732
+ }
733
+
734
+ @Override
735
+ public void addCourseAreaChangeListener(HasCourseAreasListener listener) {
736
+ courseAreaChangeListeners.add(listener);
737
+ }
738
+
739
+ @Override
740
+ public void removeCourseAreaChangeListener(HasCourseAreasListener listener) {
741
+ courseAreaChangeListeners.remove(listener);
724 742
}
725 743
726 744
@Override
java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/EventResolver.java
... ...
@@ -5,12 +5,25 @@ import java.io.Serializable;
5 5
import com.sap.sailing.domain.base.Event;
6 6
7 7
/**
8
- * Manages a set of events and can resolve one by the event's ID
8
+ * Manages a set of events and can resolve one by the event's ID. Real, non-testing implementations
9
+ * must override the {@link #addEventResolverListener(Listener)} and {@link #removeEventResolverListener(Listener)}
10
+ * default methods which are default-implemented here to do nothing.
9 11
*
10 12
* @author Axel Uhl (d043530)
11 13
*
12 14
*/
13 15
public interface EventResolver {
16
+ public static interface Listener {
17
+ void eventAdded(Event event);
18
+ void eventRemoved(Event event);
19
+ }
20
+
21
+ default void addEventResolverListener(Listener listener) {
22
+ }
23
+
24
+ default void removeEventResolverListener(Listener listener) {
25
+ }
26
+
14 27
/**
15 28
* Returns the event with given id. When no event is found, <b>null</b> is returned.
16 29
*
... ...
@@ -19,4 +32,6 @@ public interface EventResolver {
19 32
* @return The event with given id.
20 33
*/
21 34
Event getEvent(Serializable id);
35
+
36
+ Iterable<Event> getAllEvents();
22 37
}
java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/HasCourseAreas.java
... ...
@@ -0,0 +1,9 @@
1
+package com.sap.sailing.domain.leaderboard;
2
+
3
+import com.sap.sailing.domain.base.CourseArea;
4
+
5
+public interface HasCourseAreas {
6
+ Iterable<CourseArea> getCourseAreas();
7
+ void addCourseAreaChangeListener(HasCourseAreasListener listener);
8
+ void removeCourseAreaChangeListener(HasCourseAreasListener listener);
9
+}
java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/HasCourseAreasListener.java
... ...
@@ -0,0 +1,7 @@
1
+package com.sap.sailing.domain.leaderboard;
2
+
3
+import com.sap.sailing.domain.base.CourseArea;
4
+
5
+public interface HasCourseAreasListener {
6
+ void courseAreasChanged(HasCourseAreas hasCourseAreas, Iterable<CourseArea> oldCourseAreas, Iterable<CourseArea> newCourseAreas);
7
+}
java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/HasRaceColumns.java
... ...
@@ -31,4 +31,8 @@ public interface HasRaceColumns {
31 31
}
32 32
return null;
33 33
}
34
+
35
+ default boolean hasTrackedRace(TrackedRace trackedRace) {
36
+ return getRaceColumnAndFleet(trackedRace) != null;
37
+ }
34 38
}
java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/Leaderboard.java
... ...
@@ -64,7 +64,7 @@ import com.sap.sse.security.shared.TypeRelativeObjectIdentifier;
64 64
* @author Axel Uhl (d043530)
65 65
*
66 66
*/
67
-public interface Leaderboard extends LeaderboardBase, HasRaceColumns, HasCPUMeter {
67
+public interface Leaderboard extends LeaderboardBase, HasRaceColumns, HasCPUMeter, HasCourseAreas {
68 68
/**
69 69
* If the leaderboard is a "matrix" with the cells being defined by a competitor / race "coordinate,"
70 70
* then this interface defines the structure of the "cells."
... ...
@@ -701,6 +701,7 @@ public interface Leaderboard extends LeaderboardBase, HasRaceColumns, HasCPUMete
701 701
* @return the {@link CourseArea} objects on which races of this leaderboard may run; always valid, never
702 702
* {@code null}, but may be empty
703 703
*/
704
+ @Override
704 705
Iterable<CourseArea> getCourseAreas();
705 706
706 707
/**
java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/DelegatingRegattaLeaderboardWithCompetitorElimination.java
... ...
@@ -26,6 +26,7 @@ import com.sap.sailing.domain.common.LeaderboardType;
26 26
import com.sap.sailing.domain.common.MaxPointsReason;
27 27
import com.sap.sailing.domain.common.NoWindException;
28 28
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
29
+import com.sap.sailing.domain.leaderboard.HasCourseAreasListener;
29 30
import com.sap.sailing.domain.leaderboard.NumberOfCompetitorsInLeaderboardFetcher;
30 31
import com.sap.sailing.domain.leaderboard.RegattaLeaderboard;
31 32
import com.sap.sailing.domain.leaderboard.RegattaLeaderboardWithEliminations;
... ...
@@ -469,6 +470,16 @@ public class DelegatingRegattaLeaderboardWithCompetitorElimination extends Abstr
469 470
}
470 471
471 472
@Override
473
+ public void addCourseAreaChangeListener(HasCourseAreasListener listener) {
474
+ getDelegateLeaderboard().addCourseAreaChangeListener(listener);
475
+ }
476
+
477
+ @Override
478
+ public void removeCourseAreaChangeListener(HasCourseAreasListener listener) {
479
+ getDelegateLeaderboard().removeCourseAreaChangeListener(listener);
480
+ }
481
+
482
+ @Override
472 483
public NumberOfCompetitorsInLeaderboardFetcher getNumberOfCompetitorsInLeaderboardFetcher() {
473 484
return getDelegateLeaderboard().getNumberOfCompetitorsInLeaderboardFetcher();
474 485
}
java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/FlexibleLeaderboardImpl.java
... ...
@@ -11,6 +11,8 @@ import java.util.HashMap;
11 11
import java.util.List;
12 12
import java.util.Map;
13 13
import java.util.Optional;
14
+import java.util.Set;
15
+import java.util.concurrent.ConcurrentHashMap;
14 16
import java.util.logging.Logger;
15 17
16 18
import com.sap.sailing.domain.abstractlog.regatta.RegattaLog;
... ...
@@ -27,6 +29,7 @@ import com.sap.sailing.domain.common.CompetitorRegistrationType;
27 29
import com.sap.sailing.domain.common.LeaderboardType;
28 30
import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard;
29 31
import com.sap.sailing.domain.leaderboard.FlexibleRaceColumn;
32
+import com.sap.sailing.domain.leaderboard.HasCourseAreasListener;
30 33
import com.sap.sailing.domain.leaderboard.ScoringScheme;
31 34
import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule;
32 35
import com.sap.sailing.domain.racelog.RaceLogStore;
... ...
@@ -93,6 +96,8 @@ public class FlexibleLeaderboardImpl extends AbstractLeaderboardImpl implements
93 96
*/
94 97
private final IsRegattaLike regattaLikeHelper;
95 98
99
+ private transient Set<HasCourseAreasListener> courseAreaChangeListeners;
100
+
96 101
public FlexibleLeaderboardImpl(String name, ThresholdBasedResultDiscardingRule resultDiscardingRule,
97 102
ScoringScheme scoringScheme, CourseArea courseArea) {
98 103
this(EmptyRaceLogStore.INSTANCE, EmptyRegattaLogStore.INSTANCE,
... ...
@@ -111,6 +116,7 @@ public class FlexibleLeaderboardImpl extends AbstractLeaderboardImpl implements
111 116
ScoringScheme scoringScheme, Iterable<CourseArea> courseAreas) {
112 117
super(resultDiscardingRule);
113 118
assert courseAreas != null;
119
+ this.courseAreaChangeListeners = Collections.newSetFromMap(new ConcurrentHashMap<>());
114 120
this.cpuMeter = CompositeCPUMetrics.create();
115 121
this.scoringScheme = scoringScheme;
116 122
if (name == null) {
... ...
@@ -148,6 +154,7 @@ public class FlexibleLeaderboardImpl extends AbstractLeaderboardImpl implements
148 154
ois.defaultReadObject();
149 155
raceLogStore = EmptyRaceLogStore.INSTANCE;
150 156
cpuMeter = CompositeCPUMetrics.create();
157
+ this.courseAreaChangeListeners = Collections.newSetFromMap(new ConcurrentHashMap<>());
151 158
for (RaceColumn column : getRaceColumns()) {
152 159
column.setRaceLogInformation(raceLogStore, new FlexibleLeaderboardAsRegattaLikeIdentifier(this));
153 160
final TrackedRace trackedRace = column.getTrackedRace(defaultFleet);
... ...
@@ -339,13 +346,27 @@ public class FlexibleLeaderboardImpl extends AbstractLeaderboardImpl implements
339 346
340 347
@Override
341 348
public void setCourseAreas(Iterable<CourseArea> newCourseAreas) {
349
+ final Iterable<CourseArea> oldCourseAreas = this.courseAreas;
342 350
synchronized (this.courseAreas) {
343 351
this.courseAreas.clear();
344 352
Util.addAll(newCourseAreas, this.courseAreas);
345 353
}
354
+ for (HasCourseAreasListener listener : courseAreaChangeListeners) {
355
+ listener.courseAreasChanged(this, oldCourseAreas, newCourseAreas);
356
+ }
346 357
}
347 358
348 359
@Override
360
+ public void addCourseAreaChangeListener(HasCourseAreasListener listener) {
361
+ courseAreaChangeListeners.add(listener);
362
+ }
363
+
364
+ @Override
365
+ public void removeCourseAreaChangeListener(HasCourseAreasListener listener) {
366
+ courseAreaChangeListeners.remove(listener);
367
+ }
368
+
369
+ @Override
349 370
public IsRegattaLike getRegattaLike() {
350 371
return regattaLikeHelper;
351 372
}
java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/RegattaLeaderboardImpl.java
... ...
@@ -16,6 +16,7 @@ import com.sap.sailing.domain.base.Regatta;
16 16
import com.sap.sailing.domain.base.Series;
17 17
import com.sap.sailing.domain.base.impl.RaceColumnInSeriesImpl;
18 18
import com.sap.sailing.domain.common.LeaderboardType;
19
+import com.sap.sailing.domain.leaderboard.HasCourseAreasListener;
19 20
import com.sap.sailing.domain.leaderboard.RegattaLeaderboard;
20 21
import com.sap.sailing.domain.leaderboard.ResultDiscardingRule;
21 22
import com.sap.sailing.domain.leaderboard.ScoringScheme;
... ...
@@ -88,6 +89,16 @@ public class RegattaLeaderboardImpl extends AbstractLeaderboardImpl implements R
88 89
public Iterable<CourseArea> getCourseAreas() {
89 90
return regatta.getCourseAreas();
90 91
}
92
+
93
+ @Override
94
+ public void addCourseAreaChangeListener(HasCourseAreasListener listener) {
95
+ regatta.addCourseAreaChangeListener(listener);
96
+ }
97
+
98
+ @Override
99
+ public void removeCourseAreaChangeListener(HasCourseAreasListener listener) {
100
+ regatta.removeCourseAreaChangeListener(listener);
101
+ }
91 102
92 103
/**
93 104
* If the regatta' series {@link Regatta#definesSeriesDiscardThresholds() define} their own result discarding rules, this leaderboard uses
java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/meta/FlexibleMetaLeaderboard.java
... ...
@@ -6,6 +6,7 @@ import java.util.List;
6 6
7 7
import com.sap.sailing.domain.base.CourseArea;
8 8
import com.sap.sailing.domain.common.LeaderboardType;
9
+import com.sap.sailing.domain.leaderboard.HasCourseAreasListener;
9 10
import com.sap.sailing.domain.leaderboard.Leaderboard;
10 11
import com.sap.sailing.domain.leaderboard.ScoringScheme;
11 12
import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule;
... ...
@@ -65,6 +66,14 @@ public class FlexibleMetaLeaderboard extends AbstractMetaLeaderboard {
65 66
}
66 67
67 68
@Override
69
+ public void addCourseAreaChangeListener(HasCourseAreasListener listener) {
70
+ }
71
+
72
+ @Override
73
+ public void removeCourseAreaChangeListener(HasCourseAreasListener listener) {
74
+ }
75
+
76
+ @Override
68 77
public LeaderboardType getLeaderboardType() {
69 78
return LeaderboardType.FlexibleMetaLeaderboard;
70 79
}
java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/meta/LeaderboardGroupMetaLeaderboard.java
... ...
@@ -9,6 +9,7 @@ import com.sap.sailing.domain.base.RaceColumnListener;
9 9
import com.sap.sailing.domain.base.impl.TrackedRaces;
10 10
import com.sap.sailing.domain.common.LeaderboardNameConstants;
11 11
import com.sap.sailing.domain.common.LeaderboardType;
12
+import com.sap.sailing.domain.leaderboard.HasCourseAreasListener;
12 13
import com.sap.sailing.domain.leaderboard.Leaderboard;
13 14
import com.sap.sailing.domain.leaderboard.LeaderboardGroup;
14 15
import com.sap.sailing.domain.leaderboard.LeaderboardGroupListener;
... ...
@@ -98,6 +99,14 @@ public class LeaderboardGroupMetaLeaderboard extends AbstractMetaLeaderboard imp
98 99
}
99 100
100 101
@Override
102
+ public void addCourseAreaChangeListener(HasCourseAreasListener listener) {
103
+ }
104
+
105
+ @Override
106
+ public void removeCourseAreaChangeListener(HasCourseAreasListener listener) {
107
+ }
108
+
109
+ @Override
101 110
public LeaderboardType getLeaderboardType() {
102 111
return LeaderboardType.RegattaMetaLeaderboard;
103 112
}
java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/tracking/FixReceivedListener.java
... ...
@@ -19,16 +19,16 @@ public interface FixReceivedListener<FixT extends Timed> {
19 19
* the device that recorded the fix. Cannot be <code>null</code>.
20 20
* @param fix
21 21
* The fix that was stored. Cannot be <code>null</code>.
22
+ * @param returnLiveDelay
23
+ * if {@code true} then all listeners to which the fix is forwarded shall check to which races the fix
24
+ * maps and report the live delay for all those races as the third component of the resulting
25
+ * {@link Triple}s.
22 26
* @param returnManeuverUpdate
23 27
* if {@code true}, all listeners to which this fix is forwarded shall check whether the fix feeds into a
24 28
* competitor's track in the scope of a race where for that competitor the maneuver list has changed
25 29
* since the last call of this type; if so, the race identifier will be part of the result, with the
26 30
* {@link Boolean} component being {@code true} for that race. Otherwise, the {@link Boolean} component
27 31
* is {@code false} or the race is not listed in the result.
28
- * @param returnLiveDelay
29
- * if {@code true} then all listeners to which the fix is forwarded shall check to which races the fix
30
- * maps and report the live delay for all those races as the third component of the resulting
31
- * {@link Triple}s.
32 32
* @return An {@link Iterable} with {@link RegattaAndRaceIdentifier}s is returned that will contain races with new
33 33
* maneuvers which were not available at the last time the given device stored a fix. The {@link Iterable}
34 34
* returned can be empty but is never {@code null}. It can also contain multiple identifiers if the device
java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/tracking/SensorFixStore.java
... ...
@@ -73,6 +73,8 @@ public interface SensorFixStore {
73 73
*
74 74
* @param device
75 75
* the device to store the fix for. Must not be <code>null</code>.
76
+ * @param fixes
77
+ * The fixes to store. Must not be <code>null</code>.
76 78
* @param returnManeuverUpdate
77 79
* if {@code true}, all listeners to which this fix is forwarded shall check whether the fix feeds into a
78 80
* competitor's track in the scope of a race where for that competitor the maneuver list has changed
... ...
@@ -83,8 +85,6 @@ public interface SensorFixStore {
83 85
* if {@code true} then all listeners to which the fix is forwarded shall check to which races the fix
84 86
* maps and report the live delay for all those races as the third component of the resulting
85 87
* {@link Triple}s.
86
- * @param fixes
87
- * The fixes to store. Must not be <code>null</code>.
88 88
* @return An {@link Iterable} with {@link RegattaAndRaceIdentifier}s in their first component is returned that will
89 89
* contain races with new maneuvers which were not available at the last time the given device stored a fix
90 90
* in case the {@code returnManeuverUpdate} parameter was set to {@code true}, and all races with their live
java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndLoadingEventsAndRegattas.java
... ...
@@ -178,6 +178,11 @@ public class TestStoringAndLoadingEventsAndRegattas extends AbstractMongoDBTest
178 178
public Event getEvent(Serializable id) {
179 179
return id.equals(loadedEvent.getId()) ? loadedEvent : null;
180 180
}
181
+
182
+ @Override
183
+ public Iterable<Event> getAllEvents() {
184
+ return Collections.singleton(loadedEvent);
185
+ }
181 186
}, new LeaderboardGroupResolver() {
182 187
@Override
183 188
public LeaderboardGroup getLeaderboardGroupByName(String leaderboardGroupName) {
java/com.sap.sailing.server.interface/src/com/sap/sailing/server/interfaces/RacingEventService.java
... ...
@@ -1079,7 +1079,7 @@ public interface RacingEventService extends TrackedRegattaRegistry, RegattaFetch
1079 1079
* Identifies all Events, that use the given {@link Leaderboard}'s {@link CourseArea}s and contain it in their
1080 1080
* {@link LeaderboardGroup}
1081 1081
*
1082
- * @return A Set of Events, may be empty, but never {@code null}
1082
+ * @return A Set of Events, may be empty, but never {@code null}; search is restricted to only these events
1083 1083
*/
1084 1084
Set<Event> findEventsContainingLeaderboardAndMatchingAtLeastOneCourseArea(Leaderboard leaderboard, Iterable<Event> events);
1085 1085
java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RacingEventServiceImpl.java
... ...
@@ -175,6 +175,7 @@ import com.sap.sailing.domain.common.tracking.BravoFix;
175 175
import com.sap.sailing.domain.common.tracking.GPSFix;
176 176
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
177 177
import com.sap.sailing.domain.common.tracking.SensorFix;
178
+import com.sap.sailing.domain.leaderboard.EventResolver;
178 179
import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard;
179 180
import com.sap.sailing.domain.leaderboard.FlexibleRaceColumn;
180 181
import com.sap.sailing.domain.leaderboard.Leaderboard;
... ...
@@ -398,6 +399,8 @@ Replicator {
398 399
* {@link Event} objects that exist outside this service for events not (yet) registered here.
399 400
*/
400 401
private final ConcurrentHashMap<Serializable, Event> eventsById;
402
+
403
+ private final Set<EventResolver.Listener> eventResolverListeners;
401 404
402 405
private final RemoteSailingServerSet remoteSailingServerSet;
403 406
... ...
@@ -843,6 +846,7 @@ Replicator {
843 846
ServiceTracker<CompetitorProvider, CompetitorProvider> competitorProviderServiceTracker,
844 847
ServiceTracker<ResultUrlRegistry, ResultUrlRegistry> resultUrlRegistryServiceTracker) {
845 848
logger.info("Created " + this);
849
+ this.eventResolverListeners = Collections.newSetFromMap(new ConcurrentHashMap<>());
846 850
this.securityServiceTracker = securityServiceTracker;
847 851
this.numberOfTrackedRacesRestored = new AtomicInteger();
848 852
this.numberOfTrackedRacesRestoredDoneLoading = new AtomicInteger();
... ...
@@ -3884,13 +3888,26 @@ Replicator {
3884 3888
return result;
3885 3889
}
3886 3890
3887
- private void addEvent(Event result) {
3888
- if (eventsById.containsKey(result.getId())) {
3889
- throw new IllegalArgumentException("Event with ID " + result.getId()
3891
+ private void addEvent(Event event) {
3892
+ if (eventsById.containsKey(event.getId())) {
3893
+ throw new IllegalArgumentException("Event with ID " + event.getId()
3890 3894
+ " already exists which is pretty surprising...");
3891 3895
}
3892
- eventsById.put(result.getId(), result);
3893
- mongoObjectFactory.storeEvent(result);
3896
+ eventsById.put(event.getId(), event);
3897
+ mongoObjectFactory.storeEvent(event);
3898
+ for (final EventResolver.Listener listener : eventResolverListeners) {
3899
+ listener.eventAdded(event);
3900
+ }
3901
+ }
3902
+
3903
+ @Override
3904
+ public void addEventResolverListener(Listener listener) {
3905
+ eventResolverListeners.add(listener);
3906
+ }
3907
+
3908
+ @Override
3909
+ public void removeEventResolverListener(Listener listener) {
3910
+ eventResolverListeners.remove(listener);
3894 3911
}
3895 3912
3896 3913
@Override
... ...
@@ -3947,7 +3964,12 @@ Replicator {
3947 3964
}
3948 3965
3949 3966
protected void removeEventFromEventsById(Serializable id) {
3950
- eventsById.remove(id);
3967
+ final Event removedEvent = eventsById.remove(id);
3968
+ if (removedEvent != null) {
3969
+ for (final EventResolver.Listener listener : eventResolverListeners) {
3970
+ listener.eventRemoved(removedEvent);
3971
+ }
3972
+ }
3951 3973
}
3952 3974
3953 3975
@Override
java/com.sap.sailing.www/release_notes_admin.html
... ...
@@ -30,6 +30,15 @@
30 30
<li>Users can now opt out of feature and community e-mails. This flag is available in
31 31
Data Mining as a dimension for filtering and grouping.</li>
32 32
</ul>
33
+ <h2 class="articleSubheadline">March 2026</h2>
34
+ <ul class="bulletList">
35
+ <li>When a server is re-started, e.g., for an upgrade or when switching to larger
36
+ or smaller infrastructure, ongoing smartphone-tracked races will no longer have
37
+ an "end-of-tracking" time set to the time point of server re-start. This should
38
+ help in leaving live races running.
39
+ See also <a href="https://bugzilla.sapsailing.com/bugzilla/show_bug.cgi?id=6228">bug 6228</a>
40
+ </li>
41
+ </ul>
33 42
<h2 class="articleSubheadline">February 2026</h2>
34 43
<ul class="bulletList">
35 44
<li>Implemented an automated procedure for upgrading the ARCHIVE server. This is now available
java/com.sap.sse.security/src/com/sap/sse/security/UsernamePasswordRealm.java
... ...
@@ -55,7 +55,7 @@ public class UsernamePasswordRealm extends AbstractCompositeAuthorizingRealm {
55 55
throw new LockedAccountException("Password authentication for user "+username+" is currently locked");
56 56
}
57 57
final UsernamePasswordAccount upa = (UsernamePasswordAccount) user.getAccount(AccountType.USERNAME_PASSWORD);
58
- if (upa == null){
58
+ if (upa == null) {
59 59
return null;
60 60
}
61 61
saltedPassword = upa.getSaltedPassword();