81b3fc9f7c2acf236931a231ff54c29f0c380d7a
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(); |