implemented RaceCreationListener callback pattern on RaceTracker as prerequisite for linking connectivity parameters to Race/TrackedRace

Change-Id: Ib74b2a1c1defe7c89f9a3584cbb230fe73358a13
This commit is contained in:
Axel Uhl
2016-12-29 20:01:24 +01:00
parent 704e4e2a64
commit 9aed690264
11 changed files with 113 additions and 25 deletions
@@ -1539,17 +1539,21 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory {
@Override
public void addConnectivityParametersForRaceToRestore(RaceTrackingConnectivityParameters params) {
final String typeIdentifier = params.getTypeIdentifier();
try {
final RaceTrackingConnectivityParametersHandler paramsPersistenceService = raceTrackingConnectivityParamsServiceFinder.findService(typeIdentifier);
final DBCollection collection = database.getCollection(CollectionNames.CONNECTIVITY_PARAMS_FOR_RACES_TO_BE_RESTORED.name());
DBObject key = new BasicDBObject();
key.putAll(paramsPersistenceService.getKey(params));
DBObject dbObject = new BasicDBObject();
dbObject.putAll(paramsPersistenceService.mapFrom(params));
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+
". Couldn't store race "+params.getTrackerID()+" for restoring.", e);
if (raceTrackingConnectivityParamsServiceFinder == null) {
logger.warning("No service finder has been configured to find connectivity parameter persistence services. Can't add connectivity parameters to DB for restore.");
} else {
try {
final RaceTrackingConnectivityParametersHandler paramsPersistenceService = raceTrackingConnectivityParamsServiceFinder.findService(typeIdentifier);
final DBCollection collection = database.getCollection(CollectionNames.CONNECTIVITY_PARAMS_FOR_RACES_TO_BE_RESTORED.name());
DBObject key = new BasicDBObject();
key.putAll(paramsPersistenceService.getKey(params));
DBObject dbObject = new BasicDBObject();
dbObject.putAll(paramsPersistenceService.mapFrom(params));
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+
". Couldn't store race "+params.getTrackerID()+" for restoring.", e);
}
}
}
@@ -312,6 +312,7 @@ public class RaceLogRaceTracker extends AbstractRaceTrackerBaseImpl {
trackedRace.addStartTimeChangedListener(startOfTrackingController);
endOfTrackingController = new EndOfTrackingController(trackedRace, raceLog, raceLogEventAuthor);
trackedRace.addListener(endOfTrackingController);
notifyRaceCreationListeners();
logger.info(String.format("Started tracking race-log race (%s)", raceLog));
// this wakes up all waiting race handles
synchronized (this) {
@@ -485,6 +485,7 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl
assert SwissTimingRaceTrackerImpl.this.race == race;
}
}, useInternalMarkPassingAlgorithm, raceLogResolver);
notifyRaceCreationListeners();
logger.info("Created SwissTiming RaceDefinition and TrackedRace for "+race.getName());
}
@@ -183,7 +183,7 @@ public class SwissTimingReplayAdapterServiceTest {
public void testRaceData_SAW010955_20120802_WithDomainAdapter() throws Exception {
SwissTimingReplayToDomainAdapter replayListener = new SwissTimingReplayToDomainAdapter(null, /* raceName */ "SAW005906",
"SAW005906", /* boatClass */ null, DomainFactory.INSTANCE, new DummyTrackedRegattaRegistry(), /* useInternalMarkPassingAlgorithm */ false,
mock(RaceLogResolver.class), EmptyRaceLogStore.INSTANCE, EmptyRegattaLogStore.INSTANCE);
mock(RaceLogResolver.class), EmptyRaceLogStore.INSTANCE, EmptyRegattaLogStore.INSTANCE, /* tracker creator */ null);
new SwissTimingReplayParserImpl().readData(getClass().getResourceAsStream("/SAW005906.20120805.replay"), replayListener);
Iterable<? extends TrackedRace> trackedRaces = replayListener.getTrackedRaces();
assertFalse(Util.isEmpty(trackedRaces));
@@ -198,7 +198,7 @@ public class SwissTimingReplayAdapterServiceTest {
public void testStartPerformanceDetection() throws Exception {
SwissTimingReplayToDomainAdapter replayListener = new SwissTimingReplayToDomainAdapter(null, /* raceName */ "SAW005905",
"SAW005905", /* boatClass */ null, DomainFactory.INSTANCE, new DummyTrackedRegattaRegistry(), /* useInternalMarkPassingAlgorithm */ false,
mock(RaceLogResolver.class), EmptyRaceLogStore.INSTANCE, EmptyRegattaLogStore.INSTANCE);
mock(RaceLogResolver.class), EmptyRaceLogStore.INSTANCE, EmptyRegattaLogStore.INSTANCE, /* tracker creator */ null);
new SwissTimingReplayParserImpl().readData(getClass().getResourceAsStream("/SAW005905.20120805.replay"), replayListener);
Iterable<? extends TrackedRace> trackedRaces = replayListener.getTrackedRaces();
TrackedRace trackedRace = trackedRaces.iterator().next();
@@ -29,11 +29,12 @@ public class SwissTimingReplayConnectivityParameters implements RaceTrackingConn
private final String link;
private final SwissTimingReplayService replayService;
private class SwissTimingReplayRaceTracker extends AbstractRaceTrackerImpl {
class SwissTimingReplayRaceTracker extends AbstractRaceTrackerImpl {
private final WindStore windStore;
private SwissTimingReplayToDomainAdapter listener;
private SwissTimingReplayRaceTracker(WindStore windStore, SwissTimingReplayToDomainAdapter listener, SwissTimingReplayConnectivityParameters connectivityParams) {
public SwissTimingReplayRaceTracker(WindStore windStore, SwissTimingReplayToDomainAdapter listener,
SwissTimingReplayConnectivityParameters connectivityParams) {
super(connectivityParams);
this.windStore = windStore;
this.listener = listener;
@@ -43,6 +44,11 @@ public class SwissTimingReplayConnectivityParameters implements RaceTrackingConn
public Regatta getRegatta() {
return listener.getRegatta();
}
@Override
public void notifyRaceCreationListeners() {
super.notifyRaceCreationListeners();
}
@Override
public RaceDefinition getRace() {
@@ -129,9 +135,9 @@ public class SwissTimingReplayConnectivityParameters implements RaceTrackingConn
SwissTimingReplayToDomainAdapter listener = new SwissTimingReplayToDomainAdapter(/* regatta */ null, raceName,
raceID, domainFactory.getBaseDomainFactory().getOrCreateBoatClass(boatClassName), domainFactory,
trackedRegattaRegistry, useInternalMarkPassingAlgorithm, raceLogResolver, raceLogStore,
regattaLogStore);
regattaLogStore, l->new SwissTimingReplayRaceTracker(windStore, l, this));
replayService.loadRaceData(link, listener);
return new SwissTimingReplayRaceTracker(windStore, listener, this);
return listener.getTracker();
}
@Override
@@ -140,9 +146,9 @@ public class SwissTimingReplayConnectivityParameters implements RaceTrackingConn
SwissTimingReplayToDomainAdapter listener = new SwissTimingReplayToDomainAdapter(regatta, raceName,
raceID, domainFactory.getBaseDomainFactory().getOrCreateBoatClass(boatClassName),
domainFactory, trackedRegattaRegistry, useInternalMarkPassingAlgorithm, raceLogResolver,
raceLogStore, regattaLogStore);
raceLogStore, regattaLogStore, l->new SwissTimingReplayRaceTracker(windStore, l, this));
replayService.loadRaceData(link, listener);
return new SwissTimingReplayRaceTracker(windStore, listener, this);
return listener.getTracker();
}
@Override
@@ -44,9 +44,12 @@ import com.sap.sailing.domain.swisstimingadapter.DomainFactory;
import com.sap.sailing.domain.swisstimingreplayadapter.CompetitorStatus;
import com.sap.sailing.domain.swisstimingreplayadapter.SwissTimingReplayListener;
import com.sap.sailing.domain.swisstimingreplayadapter.SwissTimingReplayParser;
import com.sap.sailing.domain.swisstimingreplayadapter.impl.SwissTimingReplayConnectivityParameters.SwissTimingReplayRaceTracker;
import com.sap.sailing.domain.tracking.DynamicRaceDefinitionSet;
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
import com.sap.sailing.domain.tracking.DynamicTrackedRegatta;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.RaceTracker;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
import com.sap.sailing.domain.tracking.TrackingDataLoader;
@@ -149,17 +152,38 @@ public class SwissTimingReplayToDomainAdapter extends SwissTimingReplayAdapter i
private final String raceIdForRaceDefinition;
/**
* Optional callback to which to announce the arrival of the {@link RaceDefinition}
*/
private final DynamicRaceDefinitionSet dynamicRaceDefinitionSet;
private final SwissTimingReplayRaceTracker tracker;
@FunctionalInterface
public static interface TrackerConstructor {
SwissTimingReplayRaceTracker createTracker(SwissTimingReplayToDomainAdapter adapter);
}
/**
* @param regatta
* the regatta to associate the race(s) received by the listener with, or <code>null</code> to force the
* use / creation of a default regatta per race
* @param useInternalMarkPassingAlgorithm use our own instead of the SwissTiming-provided mark rounding / split times
* @param useInternalMarkPassingAlgorithm
* use our own instead of the SwissTiming-provided mark rounding / split times
* @param trackerConstructor
* if not {@code null} this object is used to construct the {@link SwissTimingReplayRaceTracker} that can
* be obtained from {@link #getTracker()}. This tracker's {@link RaceTracker.RaceCreationListener}s will
* be {@link SwissTimingReplayRaceTracker#notifyRaceCreationListeners() notified} when this adapter has
* created the race.
*/
public SwissTimingReplayToDomainAdapter(Regatta regatta, String raceName, String raceIdForRaceDefinition,
BoatClass boatClass, DomainFactory domainFactory,
TrackedRegattaRegistry trackedRegattaRegistry, boolean useInternalMarkPassingAlgorithm, RaceLogResolver raceLogResolver,
RaceLogStore raceLogStore, RegattaLogStore regattaLogStore) {
RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, TrackerConstructor trackerConstructor) {
this.tracker = trackerConstructor == null ? null : trackerConstructor.createTracker(this);
this.raceLogResolver = raceLogResolver;
// when the race is created, notify the tracker's race creation listeners
this.dynamicRaceDefinitionSet = trackerConstructor == null ? null : (race, trackedRace)->this.tracker.notifyRaceCreationListeners();
this.raceName = raceName;
this.raceIdForRaceDefinition = raceIdForRaceDefinition;
final Regatta effectiveRegatta;
@@ -188,6 +212,10 @@ public class SwissTimingReplayToDomainAdapter extends SwissTimingReplayAdapter i
this.useInternalMarkPassingAlgorithm = useInternalMarkPassingAlgorithm;
}
public RaceTracker getTracker() {
return tracker;
}
public Regatta getRegatta() {
return regatta;
}
@@ -379,6 +407,9 @@ public class SwissTimingReplayToDomainAdapter extends SwissTimingReplayAdapter i
trackedRace.setStartTimeReceived(bestStartTimeKnownSoFar);
}
trackedRacePerRaceID.put(currentRaceID, trackedRace);
if (dynamicRaceDefinitionSet != null) {
dynamicRaceDefinitionSet.addRaceDefinition(race, trackedRace);
}
}
public DynamicTrackedRegatta getTrackedRegatta() {
@@ -656,6 +656,7 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl
public void addRaceDefinition(final RaceDefinition race, final DynamicTrackedRace trackedRace) {
this.race = race;
updateStatusOfTrackedRace(trackedRace);
notifyRaceCreationListeners();
}
@Override
@@ -2,12 +2,17 @@ package com.sap.sailing.domain.tracking;
import java.io.IOException;
import java.net.MalformedURLException;
import java.util.concurrent.ConcurrentHashMap;
import com.sap.sailing.domain.base.RaceDefinition;
/**
* Base class for all {@link RaceTracker}s that must implement listener notifications
*/
public abstract class AbstractRaceTrackerBaseImpl implements RaceTracker {
private final RaceTrackerListeners listeners = new RaceTrackerListeners();
private final ConcurrentHashMap<RaceTracker.RaceCreationListener, Void> raceCreationListeners = new ConcurrentHashMap<>();
private final RaceTrackingConnectivityParameters connectivityParams;
public AbstractRaceTrackerBaseImpl(RaceTrackingConnectivityParameters connectivityParams) {
@@ -48,6 +53,25 @@ public abstract class AbstractRaceTrackerBaseImpl implements RaceTracker {
listeners.removeListener(listener);
}
@Override
public void add(RaceCreationListener listener) {
raceCreationListeners.put(listener, null);
final RaceDefinition race = getRace();
if (race != null) {
listener.onRaceCreated(this);
remove(listener);
}
}
@Override
public void remove(RaceCreationListener listener) {
raceCreationListeners.remove(listener);
}
protected void notifyRaceCreationListeners() {
raceCreationListeners.keySet().forEach(l->l.onRaceCreated(this));
}
@Override
public RaceTrackingConnectivityParameters getConnectivityParams() {
return connectivityParams;
@@ -15,6 +15,7 @@ import com.sap.sailing.domain.base.RaceDefinition;
* @author Axel Uhl (d043530)
*
*/
@FunctionalInterface
public interface DynamicRaceDefinitionSet {
void addRaceDefinition(RaceDefinition race, DynamicTrackedRace trackedRace);
}
@@ -87,11 +87,26 @@ public interface RaceTracker {
*/
void onTrackerWillStop(boolean preemptive);
}
interface RaceCreationListener {
/**
* Tracker has received its {@link RaceDefinition}, so that now {@link RaceTracker#getRace} no longer returns
* {@code null} but a valid {@link RaceDefinition}
*
* @param tracker
* this tracker is passed to the listener which can then obtain the {@link RaceTracker#getRace()
* race} and the {@link RaceTracker#getConnectivityParams() connectivity parameters}, etc. If the
* {@link RaceTracker} already has created its race, this method is called immediately upon
* {@link RaceTracker#add(Listener) adding} this listener so that the listener will always receive
* the call for a valid race if the race is created at any point in time, regardless the point in
* time of the listener registration. This helps avoid race conditions.
*/
void onRaceCreated(RaceTracker tracker);
}
/**
* Register a new RaceTracker.Listener for this race tracker.
*
* @param newListener
* @return true if listener has been added
*/
boolean add(RaceTracker.Listener newListener);
@@ -99,10 +114,14 @@ public interface RaceTracker {
/**
* Remove listener from race tracker
*
* @param newListener
* @param listener
* @return the listener registration for listener removal
*/
void remove(RaceTracker.Listener newListener);
void remove(RaceTracker.Listener listener);
void add(RaceTracker.RaceCreationListener listener);
void remove(RaceTracker.RaceCreationListener listener);
/**
* The connectivity parameters used to create this tracker. Can be used, e.g., to add or remove those parameters to
@@ -22,7 +22,7 @@
<stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-os ${target.os} -ws ${target.ws} -arch ${target.arch} -nl ${target.nl} -consoleLog -console -clean"/>
<stringAttribute key="org.eclipse.jdt.launching.SOURCE_PATH_PROVIDER" value="org.eclipse.pde.ui.workbenchClasspathProvider"/>
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-ea -Declipse.ignoreApp=true -Dosgi.noShutdown=true -Dfile.encoding=cp1252 -Dexpedition.udp.port=2010 -Xmx6000m -XX:+UseG1GC -Djetty.home=${project_loc:com.sap.sailing.server}/../target/configuration/jetty -Djava.util.logging.config.file=${project_loc:com.sap.sailing.server}/../target/configuration/logging_debug.properties -Dkiwo.results=${project_loc:com.sap.sailing.kiworesultimport.test}/resources -Dpersistentcompetitors.clear=false -XX:+UseMembar -Digtimi.client.id=7fcdd217e0aa16090edb4ad55b09ec43b2021090e209541fc9b7003c2a2b70c6 -Digtimi.client.secret=aa569cf4909bdc7b0e04b11873f3c4ea20687421e010fcc25b771cca9e6f3f9a -Digtimi.client.redirecturi=http://127.0.0.1:8888/igtimi/oauth/v1/authorizationcallback -Dpolardata.source.url=https://dev.sapsailing.com"/>
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-ea -Declipse.ignoreApp=true -Dosgi.noShutdown=true -Dfile.encoding=cp1252 -Dexpedition.udp.port=2010 -Xmx6000m -XX:+UseG1GC -Djetty.home=${project_loc:com.sap.sailing.server}/../target/configuration/jetty -Djava.util.logging.config.file=${project_loc:com.sap.sailing.server}/../target/configuration/logging_debug.properties -Dkiwo.results=${project_loc:com.sap.sailing.kiworesultimport.test}/resources -Dpersistentcompetitors.clear=false -XX:+UseMembar -Digtimi.client.id=7fcdd217e0aa16090edb4ad55b09ec43b2021090e209541fc9b7003c2a2b70c6 -Digtimi.client.secret=aa569cf4909bdc7b0e04b11873f3c4ea20687421e010fcc25b771cca9e6f3f9a -Digtimi.client.redirecturi=http://127.0.0.1:8888/igtimi/oauth/v1/authorizationcallback -Dpolardata.source.url=https://dev.sapsailing.com -Drestore.tracked.races=true"/>
<stringAttribute key="org.eclipse.jdt.launching.WORKING_DIRECTORY" value="${workspace_loc}"/>
<stringAttribute key="pde.version" value="3.3"/>
<stringAttribute key="profilingTraceType-ALLOCATION_TRACE" value="KEY_APPLICATION_FILTER%CTX_KEY%*%CTX_ENTRY%INCREASE_COUNT%CTX_KEY%8192%CTX_ENTRY%KEY_MIN_SIZE%CTX_KEY%32%CTX_ENTRY%KEY_MAX_SIZE%CTX_KEY%65536%CTX_ENTRY%KEY_INC_LINE_NRS%CTX_KEY%true%CTX_ENTRY%KEY_SESSION_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_ENABLEMENT%CTX_KEY%false%CTX_ENTRY%CLASS_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_USER_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_REQUEST_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_TENANT_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_ADAPTIVE%CTX_KEY%false%CTX_ENTRY%"/>