From de70631edea3ef550fdb2e045b2af501914ece40 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 20 Dec 2017 00:45:18 +0100 Subject: [PATCH] using TrackedRegattaRegistry.removeRace to ensure consistent duplicate race removal Change-Id: I245828fbd6d49f36e0f1f7d240471e5ea709a481 --- .../impl/MongoObjectFactoryImpl.java | 2 +- .../test/DummyTrackedRegattaRegistry.java | 5 ++ .../domain/test/LeaderboardOfflineTest.java | 4 + .../domain/tractracadapter/DomainFactory.java | 87 ++++++++----------- .../impl/DomainFactoryImpl.java | 56 +++++------- ...aceTrackingConnectivityParametersImpl.java | 10 +-- .../impl/TracTracRaceTrackerImpl.java | 10 ++- .../RaceTrackingConnectivityParameters.java | 21 +++-- .../tracking/TrackedRegattaRegistry.java | 23 +++++ .../sailing/server/RacingEventService.java | 22 ----- .../server/impl/RacingEventServiceImpl.java | 16 +++- 11 files changed, 127 insertions(+), 129 deletions(-) diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoObjectFactoryImpl.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoObjectFactoryImpl.java index 78c18534f25..9eebac3b5ce 100644 --- a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoObjectFactoryImpl.java +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoObjectFactoryImpl.java @@ -1620,7 +1620,7 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory { DBObject key = new BasicDBObject(); key.putAll(paramsPersistenceService.getKey(params)); DBObject dbObject = new BasicDBObject(); - dbObject.putAll(paramsPersistenceService.mapFrom(params)); + dbObject.putAll(paramsPersistenceService.mapFrom(params)); // TODO bug 4402: should we use the Tracker ID as key here to avoid duplicates? collection.update(key, dbObject, /* upsert */ true, /* multi */ false, WriteConcern.SAFE); } catch (NoCorrespondingServiceRegisteredException e) { logger.log(Level.WARNING, "Couldn't find a persistence service for connectivity parameters of type "+typeIdentifier+ diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/DummyTrackedRegattaRegistry.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/DummyTrackedRegattaRegistry.java index cf0816dde29..4d9cce796f3 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/DummyTrackedRegattaRegistry.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/DummyTrackedRegattaRegistry.java @@ -47,6 +47,11 @@ public class DummyTrackedRegattaRegistry implements TrackedRegattaRegistry { return null; } + @Override + public void removeRace(Regatta regatta, RaceDefinition race) + throws MalformedURLException, IOException, InterruptedException { + } + @Override public boolean isRaceBeingTracked(Regatta regattaContext, RaceDefinition r) { return false; diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardOfflineTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardOfflineTest.java index 7c776f63ae2..0593214c8ac 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardOfflineTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardOfflineTest.java @@ -145,6 +145,10 @@ public class LeaderboardOfflineTest extends AbstractLeaderboardTest { public void stopTracking(Regatta regatta, RaceDefinition race) throws MalformedURLException, IOException, InterruptedException { } + @Override + public void removeRace(Regatta regatta, RaceDefinition race) + throws MalformedURLException, IOException, InterruptedException { + } }; LeaderboardDTO leaderboardDTO = leaderboard.getLeaderboardDTO(now, emptySet, /* addOverallDetails */ true, trackedRegattaRegistry, DomainFactory.INSTANCE, /* fillTotalPointsUncorrected */ false); assertNotNull(leaderboardDTO); diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/DomainFactory.java b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/DomainFactory.java index d46a3080ca5..e47a7f8254a 100755 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/DomainFactory.java +++ b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/DomainFactory.java @@ -132,21 +132,19 @@ public interface DomainFactory { * Provides the capability to obtain the {@link WindTrack}s for the different wind sources. A trivial * implementation is {@link EmptyWindStore} which simply provides new, empty tracks. This is always * available but loses track of the wind, e.g., during server restarts. - * @param timeoutInMilliseconds TODO */ TracTracRaceTracker createRaceTracker(RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, WindStore windStore, TrackedRegattaRegistry trackedRegattaRegistry, RaceLogResolver raceLogResolver, RaceTrackingConnectivityParametersImpl connectivityParams, long timeoutInMilliseconds) - throws MalformedURLException, FileNotFoundException, URISyntaxException, CreateModelException, SubscriberInitializationException; + throws MalformedURLException, FileNotFoundException, URISyntaxException, CreateModelException, SubscriberInitializationException, IOException, InterruptedException; /** * Same as {@link #createRaceTracker(URL, URI, URI, URI, TimePoint, TimePoint, WindStore, TrackedRegattaRegistry)}, * only that a predefined {@link Regatta} is used to hold the resulting races. - * @param timeoutInMilliseconds TODO */ RaceTracker createRaceTracker(Regatta regatta, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, WindStore windStore, TrackedRegattaRegistry trackedRegattaRegistry, RaceLogResolver raceLogResolver, RaceTrackingConnectivityParametersImpl connectivityParams, long timeoutInMilliseconds) throws MalformedURLException, FileNotFoundException, URISyntaxException, CreateModelException, - SubscriberInitializationException; + SubscriberInitializationException, IOException, InterruptedException; BoatClass getOrCreateBoatClass(String competitorClassName); @@ -161,7 +159,6 @@ public interface DomainFactory { * must have been created before through * {@link #getOrCreateTrackedRegatta(com.sap.sailing.domain.base.Regatta)} because otherwise the link to * the {@link IEvent} can't be established - * @param timeoutInMilliseconds TODO * @param tokenToRetrieveAssociatedRace * used to update the set of{@link RaceDefinition}s received by the {@link RaceCourseReceiver} created by * this call @@ -173,47 +170,39 @@ public interface DomainFactory { IEventSubscriber eventSubscriber, IRaceSubscriber raceSubscriber, boolean useInternalMarkPassingAlgorithm, long timeoutInMilliseconds); /** - * Creates a {@link RaceDefinition} from a TracTrac {@link IRace} and a - * domain {@link Course} definition. The resulting {@link RaceDefinition} is - * added to the {@link com.sap.sailing.domain.base.Regatta} to which - * trackedRegatta belongs (see - * {@link TrackedRegatta#getRegatta()}). It is added to the internal race - * cache. The corresponding {@link TrackedRace} object is also created, and - * the notification of threads waiting on the race cache such as a blocking - * {@link #getAndWaitForRaceDefinition(UUID)} happens only after - * the tracked race has been created and the {@link RaceDefinition} was - * {@link com.sap.sailing.domain.base.Regatta#addRace(RaceDefinition) added} - * to the domain event. This ensures that waiters for the - * {@link RaceDefinition} are guaranteed to obtain a valid, - * non-null tracked race already immediately after the - * notification was sent, and that the {@link RaceDefinition} is already - * {@link com.sap.sailing.domain.base.Regatta#getAllRaces() known} by its - * containing {@link com.sap.sailing.domain.base.Regatta}. + * Creates a {@link RaceDefinition} from a TracTrac {@link IRace} and a domain {@link Course} definition. The + * resulting {@link RaceDefinition} is added to the {@link com.sap.sailing.domain.base.Regatta} to which + * trackedRegatta belongs (see {@link TrackedRegatta#getRegatta()}). It is added to the internal race + * cache. The corresponding {@link TrackedRace} object is also created, and the notification of threads waiting on + * the race cache such as a blocking {@link #getAndWaitForRaceDefinition(UUID)} happens only after the + * tracked race has been created and the {@link RaceDefinition} was + * {@link com.sap.sailing.domain.base.Regatta#addRace(RaceDefinition) added} to the domain event. This ensures that + * waiters for the {@link RaceDefinition} are guaranteed to obtain a valid, non-null tracked race + * already immediately after the notification was sent, and that the {@link RaceDefinition} is already + * {@link com.sap.sailing.domain.base.Regatta#getAllRaces() known} by its containing + * {@link com.sap.sailing.domain.base.Regatta}. + * * @param raceDefinitionSetToUpdate - * if not null, after creating the - * {@link TrackedRace}, the {@link RaceDefinition} is - * {@link DynamicRaceDefinitionSet#addRaceDefinition(RaceDefinition, DynamicTrackedRace) - * added} to that object. + * if not null, after creating the {@link TrackedRace}, the {@link RaceDefinition} is + * {@link DynamicRaceDefinitionSet#addRaceDefinition(RaceDefinition, DynamicTrackedRace) added} to that + * object. * @param runBeforeExposingRace - * if not {@code null} then this consumer will be passed the - * {@link DynamicTrackedRace} if it was actually created by this - * call. This happens while still in the - * {@code synchronized(raceCache)} block, therefore before calls - * waiting for the race (e.g., - * {@link #getAndWaitForRaceDefinition(UUID)}) return the race. - * @param tractracRace TODO - */ - DynamicTrackedRace getOrCreateRaceDefinitionAndTrackedRace(DynamicTrackedRegatta trackedRegatta, UUID raceId, - String raceName, Iterable competitors, BoatClass boatClass, - Map competitorBoats, Course course, Iterable sidelines, WindStore windStore, - long delayToLiveInMillis, long millisecondsOverWhichToAverageWind, - DynamicRaceDefinitionSet raceDefinitionSetToUpdate, URI courseDesignUpdateURI, UUID tracTracEventUuid, - String tracTracUsername, String tracTracPassword, boolean ignoreTracTracMarkPassings, RaceLogResolver raceLogResolver, Consumer runBeforeExposingRace, IRace tractracRace); + * if not {@code null} then this consumer will be passed the {@link DynamicTrackedRace} if it was + * actually created by this call. This happens while still in the {@code synchronized(raceCache)} block, + * therefore before calls waiting for the race (e.g., {@link #getAndWaitForRaceDefinition(UUID)}) return + * the race. + */ + DynamicTrackedRace getOrCreateRaceDefinitionAndTrackedRace(DynamicTrackedRegatta trackedRegatta, UUID raceId, + String raceName, Iterable competitors, BoatClass boatClass, + Map competitorBoats, Course course, Iterable sidelines, WindStore windStore, + long delayToLiveInMillis, long millisecondsOverWhichToAverageWind, + DynamicRaceDefinitionSet raceDefinitionSetToUpdate, URI courseDesignUpdateURI, UUID tracTracEventUuid, + String tracTracUsername, String tracTracPassword, boolean ignoreTracTracMarkPassings, + RaceLogResolver raceLogResolver, Consumer runBeforeExposingRace, IRace tractracRace); /** - * The record may be for a single mark or a gate. If for a gate, the - * {@link ControlPointPositionData#getIndex() index} is used to determine - * which of its marks is affected. + * The record may be for a single mark or a gate. If for a gate, the {@link ControlPointPositionData#getIndex() + * index} is used to determine which of its marks is affected. */ Mark getMark(TracTracControlPoint controlPoint, int zeroBasedMarkIndex); @@ -223,8 +212,6 @@ public interface DomainFactory { /** * If the vm argument tractrac.usemarkpassings=false, the RecieverType MARKPASSINGS will not return anything - * @param raceLogResolver TODO - * @param timeoutInMilliseconds TODO */ Iterable getUpdateReceivers(DynamicTrackedRegatta trackedRegatta, IRace tractracRace, WindStore windStore, long delayToLiveInMillis, Simulator simulator, DynamicRaceDefinitionSet raceDefinitionSetToUpdate, TrackedRegattaRegistry trackedRegattaRegistry, @@ -274,23 +261,17 @@ public interface DomainFactory { * {@code File}) then if this parameter is {@code true} the race will be loaded from the replay file * instead of the {@code storedURI}/{@code liveURI} specified. This is particularly useful for restoring * races if since the last connection the race was migrated to a replay file format. - * @param timeoutInMillis TODO */ RaceTrackingConnectivityParameters createTrackingConnectivityParameters(URL paramURL, URI liveURI, URI storedURI, URI courseDesignUpdateURI, TimePoint startOfTracking, TimePoint endOfTracking, long delayToLiveInMillis, Duration offsetToStartTimeOfSimulatedRace, boolean useInternalMarkPassingAlgorithm, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, String tracTracUsername, String tracTracPassword, String raceStatus, String raceVisibility, boolean trackWind, boolean correctWindDirectionByMagneticDeclination, boolean preferReplayIfAvailable, int timeoutInMillis) throws Exception; + /** - * Removes all knowledge about tractracRace which includes removing it from the race cache, from the - * {@link com.sap.sailing.domain.base.Regatta} and, if a {@link TrackedRace} for the corresponding - * {@link RaceDefinition} exists, from the {@link TrackedRegatta}. If removing the race from the event, the event is - * removed from the event cache such that {@link #getOrCreateDefaultRegatta(IEvent, TrackedRegattaRegistry)} will have to create a new one. Similarly, - * if the {@link TrackedRace} that was removed from the {@link TrackedRegatta} was the last one, the - * {@link TrackedRegatta} is removed such that {@link #getOrCreateTrackedRegatta(com.sap.sailing.domain.base.Regatta)} - * will have to create a new one. + * Removes all knowledge about tractracRace from the race cache. */ - void removeRace(IEvent tractracEvent, IRace tractracRace, TrackedRegattaRegistry trackedRegattaRegistry); + RaceDefinition removeRace(IEvent tractracEvent, IRace tractracRace, Regatta regattaToLoadRaceInto, TrackedRegattaRegistry trackedRegattaRegistry); /** * Computes an ID to use for a {@link RaceDefinition} based on the TracTrac race. diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/DomainFactoryImpl.java b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/DomainFactoryImpl.java index a35b5f17f0c..093d59e7c46 100755 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/DomainFactoryImpl.java +++ b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/DomainFactoryImpl.java @@ -1,9 +1,7 @@ package com.sap.sailing.domain.tractracadapter.impl; -import java.io.FileNotFoundException; import java.io.IOException; import java.io.Serializable; -import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; @@ -457,7 +455,7 @@ public class DomainFactoryImpl implements DomainFactory { } @Override - public void removeRace(IEvent tractracEvent, IRace tractracRace, TrackedRegattaRegistry trackedRegattaRegistry) { + public RaceDefinition removeRace(IEvent tractracEvent, IRace tractracRace, Regatta regattaToLoadRaceInto, TrackedRegattaRegistry trackedRegattaRegistry) { RaceDefinition raceDefinition; synchronized (raceCache) { raceDefinition = getExistingRaceDefinitionForRace(tractracRace.getId()); @@ -467,45 +465,33 @@ public class DomainFactoryImpl implements DomainFactory { } } if (raceDefinition != null) { - // FIXME this only removes the race from its default regatta, not any explicit regatta - Pair defaultRegattaNameAndBoatClass = getDefaultRegattaNameAndBoatClass(tractracRace); - Pair key = new Pair(defaultRegattaNameAndBoatClass.getA(), - defaultRegattaNameAndBoatClass.getB() == null ? null : - defaultRegattaNameAndBoatClass.getB().getName()); synchronized (regattaCache) { - Regatta regatta = regattaCache.get(key); + final Regatta regatta; + final Pair key; + if (regattaToLoadRaceInto != null) { + regatta = regattaToLoadRaceInto; + key = new Pair<>(regatta.getName(), regatta.getBoatClass().getName()); + } else { + Pair defaultRegattaNameAndBoatClass = getDefaultRegattaNameAndBoatClass(tractracRace); + key = new Pair(defaultRegattaNameAndBoatClass.getA(), + defaultRegattaNameAndBoatClass.getB() == null ? null : + defaultRegattaNameAndBoatClass.getB().getName()); + regatta = regattaCache.get(key); + } if (regatta != null) { // The following fixes bug 202: when tracking of multiple races of the same event has been started, this may not // remove any race; however, the event may already have been created by another tracker whose race hasn't // arrived yet and therefore the races list is still empty; therefore, only remove the event if its // race list became empty by the removal performed here. - int oldSize = Util.size(regatta.getAllRaces()); - regatta.removeRace(raceDefinition); - if (oldSize > 0 && Util.size(regatta.getAllRaces()) == 0) { + if (Util.contains(regatta.getAllRaces(), raceDefinition) && Util.size(regatta.getAllRaces()) == 1) { logger.info("Removing regatta "+regatta.getName()+" ("+regatta.hashCode()+") from TracTrac DomainFactoryImpl"); regattaCache.remove(key); weakDefaultRegattaCache.remove(tractracRace); } - TrackedRegatta trackedRegatta = trackedRegattaRegistry.getTrackedRegatta(regatta); - if (trackedRegatta != null) { - // see above; only remove tracked regatta if it *became* empty because of the tracked race removal here - final int oldSizeOfTrackedRaces; - final int newSizeOfTrackedRaces; - trackedRegatta.lockTrackedRacesForWrite(); - try { - oldSizeOfTrackedRaces = Util.size(trackedRegatta.getTrackedRaces()); - trackedRegatta.removeTrackedRace(raceDefinition); - newSizeOfTrackedRaces = Util.size(trackedRegatta.getTrackedRaces()); - } finally { - trackedRegatta.unlockTrackedRacesAfterWrite(); - } - if (oldSizeOfTrackedRaces > 0 && newSizeOfTrackedRaces == 0) { - trackedRegattaRegistry.removeTrackedRegatta(regatta); - } - } } } } + return raceDefinition; } @Override @@ -716,17 +702,19 @@ public class DomainFactoryImpl implements DomainFactory { } @Override - public TracTracRaceTracker createRaceTracker(RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, WindStore windStore, TrackedRegattaRegistry trackedRegattaRegistry, - RaceLogResolver raceLogResolver, RaceTrackingConnectivityParametersImpl connectivityParams, long timeoutInMilliseconds) throws MalformedURLException, - FileNotFoundException, URISyntaxException, CreateModelException, SubscriberInitializationException { + public TracTracRaceTracker createRaceTracker(RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, + WindStore windStore, TrackedRegattaRegistry trackedRegattaRegistry, RaceLogResolver raceLogResolver, + RaceTrackingConnectivityParametersImpl connectivityParams, long timeoutInMilliseconds) + throws URISyntaxException, CreateModelException, SubscriberInitializationException, IOException, + InterruptedException { return new TracTracRaceTrackerImpl(this, raceLogStore, regattaLogStore, windStore, trackedRegattaRegistry, raceLogResolver, connectivityParams, timeoutInMilliseconds); } @Override public RaceTracker createRaceTracker(Regatta regatta, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, WindStore windStore, TrackedRegattaRegistry trackedRegattaRegistry, RaceLogResolver raceLogResolver, RaceTrackingConnectivityParametersImpl connectivityParams, long timeoutInMilliseconds) - throws MalformedURLException, FileNotFoundException, URISyntaxException, CreateModelException, - SubscriberInitializationException { + throws URISyntaxException, CreateModelException, + SubscriberInitializationException, IOException, InterruptedException { return new TracTracRaceTrackerImpl(regatta, this, raceLogStore, regattaLogStore, windStore, trackedRegattaRegistry, raceLogResolver, connectivityParams, timeoutInMilliseconds); } diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/RaceTrackingConnectivityParametersImpl.java b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/RaceTrackingConnectivityParametersImpl.java index 8fc51ca8a55..ffeef4a5d56 100755 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/RaceTrackingConnectivityParametersImpl.java +++ b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/RaceTrackingConnectivityParametersImpl.java @@ -1,7 +1,6 @@ package com.sap.sailing.domain.tractracadapter.impl; -import java.io.FileNotFoundException; -import java.net.MalformedURLException; +import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; @@ -116,9 +115,10 @@ public class RaceTrackingConnectivityParametersImpl extends AbstractRaceTracking @Override public RaceTracker createRaceTracker(TrackedRegattaRegistry trackedRegattaRegistry, WindStore windStore, - RaceLogResolver raceLogResolver, long timeoutInMilliseconds) throws MalformedURLException, FileNotFoundException, URISyntaxException, - CreateModelException, SubscriberInitializationException { - RaceTracker tracker = domainFactory.createRaceTracker(raceLogStore, regattaLogStore, windStore, trackedRegattaRegistry, raceLogResolver, this, timeoutInMilliseconds); + RaceLogResolver raceLogResolver, long timeoutInMilliseconds) throws URISyntaxException, + CreateModelException, SubscriberInitializationException, IOException, InterruptedException { + RaceTracker tracker = domainFactory.createRaceTracker(raceLogStore, regattaLogStore, windStore, + trackedRegattaRegistry, raceLogResolver, this, timeoutInMilliseconds); return tracker; } diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/TracTracRaceTrackerImpl.java b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/TracTracRaceTrackerImpl.java index 7f1899fcfd0..6c1572d18f8 100755 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/TracTracRaceTrackerImpl.java +++ b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/TracTracRaceTrackerImpl.java @@ -1,7 +1,6 @@ package com.sap.sailing.domain.tractracadapter.impl; import java.io.File; -import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; @@ -263,7 +262,7 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl TracTracRaceTrackerImpl(DomainFactory domainFactory, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, WindStore windStore, TrackedRegattaRegistry trackedRegattaRegistry, RaceLogResolver raceLogResolver, RaceTrackingConnectivityParametersImpl connectivityParams, long timeoutInMilliseconds) - throws URISyntaxException, MalformedURLException, FileNotFoundException, SubscriberInitializationException { + throws URISyntaxException, SubscriberInitializationException, IOException, InterruptedException { this(/* regatta */ null, domainFactory, raceLogStore, regattaLogStore, windStore, trackedRegattaRegistry, raceLogResolver, connectivityParams, timeoutInMilliseconds); } @@ -285,7 +284,7 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl TracTracRaceTrackerImpl(final Regatta regatta, DomainFactory domainFactory, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, WindStore windStore, TrackedRegattaRegistry trackedRegattaRegistry, RaceLogResolver raceLogResolver, RaceTrackingConnectivityParametersImpl connectivityParams, long timeoutInMilliseconds) - throws URISyntaxException, MalformedURLException, FileNotFoundException, SubscriberInitializationException { + throws URISyntaxException, SubscriberInitializationException, IOException, InterruptedException { super(connectivityParams); final URL paramURL = connectivityParams.getParamURL(); final URI liveURI = connectivityParams.getLiveURI(); @@ -388,7 +387,10 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl // removeRace may detach the domain regatta from the domain factory if that // removed the last race; therefore, it's important to getOrCreate the // domain regatta *after* calling removeRace - domainFactory.removeRace(tractracRace.getEvent(), tractracRace, trackedRegattaRegistry); + final RaceDefinition raceDefinition = domainFactory.removeRace(tractracRace.getEvent(), tractracRace, effectiveRegatta, trackedRegattaRegistry); + if (raceDefinition != null) { + trackedRegattaRegistry.removeRace(effectiveRegatta, raceDefinition); + } // if regatta is still null, no previous assignment of any of the races in this TracTrac event to a Regatta was // found; // in this case, create a default regatta based on the TracTrac event data diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/RaceTrackingConnectivityParameters.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/RaceTrackingConnectivityParameters.java index 8115bdbb1eb..3add0406c9f 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/RaceTrackingConnectivityParameters.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/RaceTrackingConnectivityParameters.java @@ -24,7 +24,12 @@ public interface RaceTrackingConnectivityParameters { * Starts a {@link RaceTracker} using the connectivity parameters provided by this object. As no specific * {@link Regatta} is provided, this will first look up a regatta for the race from * {@link TrackedRegattaRegistry#getRememberedRegattaForRace(java.io.Serializable)} and if not found will look up or - * create a default regatta based on race data such as an event name and the boat class. + * create a default regatta based on race data such as an event name and the boat class.

+ * + * If a race equal to the one being created by this call already exists in the default regatta, its tracking + * will be stopped (if any), the {@link RaceDefinition} will be removed from the default regatta, and the + * {@link TrackedRace} will be removed from the {@link TrackedRegatta}. All this happens by means of + * calling {@link TrackedRegattaRegistry#removeRace(Regatta, com.sap.sailing.domain.base.RaceDefinition)}. * * @param timeoutInMilliseconds * gives the tracker a possibility to abort tracking the race after so many milliseconds of @@ -37,11 +42,15 @@ public interface RaceTrackingConnectivityParameters { RaceLogResolver raceLogResolver, long timeoutInMilliseconds) throws Exception; /** - * Starts a {@link RaceTracker}, associating the resulting races with the {@link Regatta} passed as argument - * instead of using the tracker's domain factory to obtain a default {@link Regatta} object for the tracking - * parameters. This is particularly useful if a predefined regatta with {@link Series} and {@link Fleet}s - * is to be used. - * @param timeoutInMilliseconds TODO + * Starts a {@link RaceTracker}, associating the resulting races with the {@link Regatta} passed as argument instead + * of using the tracker's domain factory to obtain a default {@link Regatta} object for the tracking parameters. + * This is particularly useful if a predefined regatta with {@link Series} and {@link Fleet}s is to be used. + *

+ * + * If a race equal to the one being created by this call already exists in the {@code regatta}, its tracking will be + * stopped (if any), the {@link RaceDefinition} will be removed from the default regatta, and the + * {@link TrackedRace} will be removed from the {@link TrackedRegatta}. All this happens by means of calling + * {@link TrackedRegattaRegistry#removeRace(Regatta, com.sap.sailing.domain.base.RaceDefinition)}. */ RaceTracker createRaceTracker(Regatta regatta, TrackedRegattaRegistry trackedRegattaRegistry, WindStore windStore, RaceLogResolver raceLogResolver, long timeoutInMilliseconds) throws Exception; diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRegattaRegistry.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRegattaRegistry.java index 8f9fd9eebbe..d14fa5ebb18 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRegattaRegistry.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRegattaRegistry.java @@ -4,6 +4,8 @@ import java.io.IOException; import java.io.Serializable; import java.net.MalformedURLException; +import com.sap.sailing.domain.base.Fleet; +import com.sap.sailing.domain.base.RaceColumn; import com.sap.sailing.domain.base.RaceDefinition; import com.sap.sailing.domain.base.Regatta; @@ -28,6 +30,27 @@ public interface TrackedRegattaRegistry { */ DynamicTrackedRegatta getTrackedRegatta(Regatta regatta); + /** + * Removes race and any corresponding {@link #getTrackedRace(Regatta, RaceDefinition) tracked race} + * from this service. If it was the last {@link RaceDefinition} in its {@link Regatta} and the regatta + * {@link Regatta#isPersistent() is not stored persistently}, the regatta is removed as well and will no + * longer be returned by {@link #getAllRegattas()}. The wind tracking is stopped for race. + *

+ * + * Any {@link RaceTracker} for which race is the last race tracked that is still reachable + * from {@link #getAllRegattas()} will be {@link RaceTracker#stop(boolean) stopped}. + * + * The race will be also removed from all leaderboards containing a column that has race's + * {@link #getTrackedRace(Regatta, RaceDefinition) corresponding} {@link TrackedRace} as its + * {@link RaceColumn#getTrackedRace(Fleet)}. + * + * @param regatta + * the regatta from which to remove the race + * @param race + * the race to remove + */ + void removeRace(Regatta regatta, RaceDefinition race) throws MalformedURLException, IOException,InterruptedException; + void removeTrackedRegatta(Regatta regatta); /** diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/RacingEventService.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/RacingEventService.java index d1d1d7f3b83..f78699bd494 100755 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/RacingEventService.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/RacingEventService.java @@ -29,7 +29,6 @@ import com.sap.sailing.domain.base.CourseArea; import com.sap.sailing.domain.base.DomainFactory; import com.sap.sailing.domain.base.Event; import com.sap.sailing.domain.base.EventBase; -import com.sap.sailing.domain.base.Fleet; import com.sap.sailing.domain.base.LeaderboardSearchResult; import com.sap.sailing.domain.base.LeaderboardSearchResultBase; import com.sap.sailing.domain.base.Mark; @@ -174,27 +173,6 @@ public interface RacingEventService extends TrackedRegattaRegistry, RegattaFetch */ void stopTracking(Regatta regatta, boolean willBeRemoved) throws MalformedURLException, IOException, InterruptedException; - /** - * Removes race and any corresponding {@link #getTrackedRace(Regatta, RaceDefinition) tracked race} - * from this service. If it was the last {@link RaceDefinition} in its {@link Regatta} and the regatta - * {@link Regatta#isPersistent() is not stored persistently}, the regatta is removed as well and will no - * longer be returned by {@link #getAllRegattas()}. The wind tracking is stopped for race. - *

- * - * Any {@link RaceTracker} for which race is the last race tracked that is still reachable - * from {@link #getAllRegattas()} will be {@link RaceTracker#stop(boolean) stopped}. - * - * The race will be also removed from all leaderboards containing a column that has race's - * {@link #getTrackedRace(Regatta, RaceDefinition) corresponding} {@link TrackedRace} as its - * {@link RaceColumn#getTrackedRace(Fleet)}. - * - * @param regatta - * the regatta from which to remove the race - * @param race - * the race to remove - */ - void removeRace(Regatta regatta, RaceDefinition race) throws MalformedURLException, IOException,InterruptedException; - /** * @param port * the UDP port on which to listen for incoming messages from Expedition clients diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RacingEventServiceImpl.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RacingEventServiceImpl.java index 50d97532689..37625d3b383 100644 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RacingEventServiceImpl.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RacingEventServiceImpl.java @@ -2415,19 +2415,27 @@ public class RacingEventServiceImpl implements RacingEventService, ClearStateTes TrackedRace trackedRace = getExistingTrackedRace(regatta, race); if (trackedRace != null) { TrackedRegatta trackedRegatta = getTrackedRegatta(regatta); - final boolean isTrackedRacesEmpty; + final boolean isTrackedRacesBecameEmpty; if (trackedRegatta != null) { trackedRegatta.lockTrackedRacesForWrite(); + // The following fixes bug 202: when tracking of multiple races of the same event has been started, this may not + // remove any race; however, the event may already have been created by another tracker whose race hasn't + // arrived yet and therefore the races list is still empty; therefore, only remove the event if its + // race list became empty by the removal performed here. + final int oldSizeOfTrackedRaces; + final int newSizeOfTrackedRaces; + oldSizeOfTrackedRaces = Util.size(trackedRegatta.getTrackedRaces()); try { trackedRegatta.removeTrackedRace(trackedRace); - isTrackedRacesEmpty = Util.isEmpty(trackedRegatta.getTrackedRaces()); + newSizeOfTrackedRaces = Util.size(trackedRegatta.getTrackedRaces()); + isTrackedRacesBecameEmpty = (oldSizeOfTrackedRaces > 0 && newSizeOfTrackedRaces == 0); } finally { trackedRegatta.unlockTrackedRacesAfterWrite(); } } else { - isTrackedRacesEmpty = false; + isTrackedRacesBecameEmpty = false; } - if (isTrackedRacesEmpty) { + if (isTrackedRacesBecameEmpty) { removeTrackedRegatta(regatta); } // remove tracked race from RaceColumns of regatta