Merge branch 'master' into ubilabs--rcapp--develop

This commit is contained in:
Peter Siegmund committed 2015-03-30 10:47:18 +02:00
commit e03cae1d39
28 files changed
+358 -92

No files matched your search

@@ -47,10 +47,18 @@ public interface ReadonlyRaceState extends RaceStateEventProcessor {
RaceLog getRaceLog();
/**
* Gets the currently attached {@link ReadonlyRacingProcedure}.
* Gets the currently attached {@link ReadonlyRacingProcedure}, providing a default in case there is none provided
* in the race log nor the regatta configuration.
*/
ReadonlyRacingProcedure getRacingProcedure();
/**
* If no racing procedure is defined in the underlying race log, <code>null</code> is returned; otherwise
* the racing procedure as it would be returned by {@link #getRacingProcedure()}.
* @return
*/
ReadonlyRacingProcedure getRacingProcedureNoFallback();
/**
* Gets the currently attached {@link ReadonlyRacingProcedure}. Use this method to avoid casting in cases you are
* sure about the {@link ReadonlyRacingProcedure}'s type.
@@ -92,7 +92,18 @@ public class ReadonlyRaceStateImpl implements ReadonlyRaceState, RaceLogChangedL
private final LastPublishedCourseDesignFinder courseDesignerAnalyzer;
private final LastWindFixFinder lastWindFixAnalyzer;
/**
* The cached racing procedure type. If no racing procedure type specification is found in the underlying race log,
* a default is taken from {@link #fallbackInitialProcedureType}.
*/
private RacingProcedureType cachedRacingProcedureType;
/**
* The result of {@link #determineInitialProcedureType()}; may be {@link RacingProcedureType#UNKNOWN} in case no
* specification is found in the underlying race log.
*/
private RacingProcedureType cachedRacingProcedureTypeNoFallback;
private RaceLogRaceStatus cachedRaceStatus;
private int cachedPassId;
private TimePoint cachedStartTime;
@@ -130,7 +141,12 @@ public class ReadonlyRaceStateImpl implements ReadonlyRaceState, RaceLogChangedL
this.courseDesignerAnalyzer = new LastPublishedCourseDesignFinder(raceLog);
this.lastWindFixAnalyzer = new LastWindFixFinder(raceLog);
this.cachedRacingProcedureType = determineInitialProcedureType();
this.cachedRacingProcedureTypeNoFallback = determineInitialProcedureType();
if (this.cachedRacingProcedureTypeNoFallback == null || this.cachedRacingProcedureTypeNoFallback == RacingProcedureType.UNKNOWN) {
this.cachedRacingProcedureType = fallbackInitialProcedureType;
} else {
cachedRacingProcedureType = cachedRacingProcedureTypeNoFallback;
}
this.cachedRaceStatus = RaceLogRaceStatus.UNKNOWN;
this.cachedPassId = raceLog.getCurrentPassId();
// see resolved bug 2083: make sure the listener registration is "weak" in the sense that it is removed when
@@ -148,11 +164,8 @@ public class ReadonlyRaceStateImpl implements ReadonlyRaceState, RaceLogChangedL
RacingProcedureType inRaceLogType = racingProcedureAnalyzer.analyze();
if (inRaceLogType != RacingProcedureType.UNKNOWN) {
return inRaceLogType;
} else if (configuration.getDefaultRacingProcedureType() != null
&& configuration.getDefaultRacingProcedureType() != RacingProcedureType.UNKNOWN) {
return configuration.getDefaultRacingProcedureType();
} else {
return fallbackInitialProcedureType;
return configuration.getDefaultRacingProcedureType();
}
}
@@ -166,6 +179,17 @@ public class ReadonlyRaceStateImpl implements ReadonlyRaceState, RaceLogChangedL
return racingProcedure;
}
@Override
public ReadonlyRacingProcedure getRacingProcedureNoFallback() {
final ReadonlyRacingProcedure result;
if (cachedRacingProcedureTypeNoFallback == null || cachedRacingProcedureTypeNoFallback == RacingProcedureType.UNKNOWN) {
result = null;
} else {
result = getRacingProcedure();
}
return result;
}
@SuppressWarnings("unchecked")
@Override
public <T extends ReadonlyRacingProcedure> T getTypedReadonlyRacingProcedure() {
@@ -11,6 +11,7 @@ import org.junit.Test;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.tractracadapter.DomainFactory;
import com.sap.sailing.domain.tractracadapter.LoadingQueueDoneCallBack;
import com.sap.sailing.domain.tractracadapter.Receiver;
import com.tractrac.model.lib.api.data.IPosition;
import com.tractrac.model.lib.api.route.IControl;
@@ -73,6 +74,10 @@ public class PositionConversionTest extends AbstractTracTracLiveTest {
}
});
}
@Override
public void callBackWhenLoadingQueueIsDone(LoadingQueueDoneCallBack callback) {
}
};
addListenersForStoredDataAndStartController(Collections.singleton(receiver));
synchronized (semaphor) {
@@ -21,6 +21,7 @@ import com.sap.sailing.domain.tracking.impl.DynamicTrackedRegattaImpl;
import com.sap.sailing.domain.tracking.impl.EmptyWindStore;
import com.sap.sailing.domain.tracking.impl.GPSFixMovingImpl;
import com.sap.sailing.domain.tractracadapter.DomainFactory;
import com.sap.sailing.domain.tractracadapter.LoadingQueueDoneCallBack;
import com.sap.sailing.domain.tractracadapter.Receiver;
import com.sap.sailing.domain.tractracadapter.ReceiverType;
import com.sap.sailing.domain.tractracadapter.impl.ControlPointAdapter;
@@ -88,6 +89,10 @@ public class ReceiveMarkPassingDataTest extends AbstractTracTracLiveTest {
}
});
}
@Override
public void callBackWhenLoadingQueueIsDone(LoadingQueueDoneCallBack callback) {
}
};
List<Receiver> receivers = new ArrayList<Receiver>();
receivers.add(receiver);
@@ -11,6 +11,7 @@ import org.junit.Test;
import com.sap.sailing.domain.base.Course;
import com.sap.sailing.domain.tractracadapter.DomainFactory;
import com.sap.sailing.domain.tractracadapter.LoadingQueueDoneCallBack;
import com.sap.sailing.domain.tractracadapter.Receiver;
import com.sap.sse.common.Util;
import com.tractrac.model.lib.api.route.IControlRoute;
@@ -67,6 +68,10 @@ public class RouteAssemblyTest extends AbstractTracTracLiveTest {
}
});
}
@Override
public void callBackWhenLoadingQueueIsDone(LoadingQueueDoneCallBack callback) {
}
};
addListenersForStoredDataAndStartController(Collections.singleton(receiver));
synchronized (semaphor) {
@@ -0,0 +1,28 @@
package com.sap.sailing.domain.tractracadapter;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tractracadapter.impl.AbstractReceiverWithQueue;
import com.sap.sailing.domain.tractracadapter.impl.TracTracRaceTrackerImpl;
/**
* Callback interface which is currently used, so that {@link Receiver}s can be told to callback when current queue
* content is worked through.
*
* When loading -> tracking or loading -> finished would happen, we want to wait for all the current events in the queue
* to be handled before notifying the {@link TrackedRace}. This will ensure that caches, polar miner, etc. will not be
* resumed/started, as long as loading events are handled.
*
* @author Frederik Petersen
*
*/
public interface LoadingQueueDoneCallBack {
/**
* Callback method
* See {@link AbstractReceiverWithQueue} and {@link TracTracRaceTrackerImpl} for example usage.
*
* @param receiver Receiver that handled the marked event.
*/
void loadingQueueDone(Receiver receiver);
}
@@ -1,5 +1,9 @@
package com.sap.sailing.domain.tractracadapter;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.TrackedRaceStatus;
import com.sap.sailing.domain.tractracadapter.impl.TracTracRaceTrackerImpl;
public interface Receiver {
/**
@@ -34,4 +38,19 @@ public interface Receiver {
* Waits until this received has stopped, but no longer than <code>timeout</code> milliseconds
*/
void join(long timeoutInMilliseconds) throws InterruptedException;
/**
* Allows to "mark" the currently last event in the queue and provides callback as soon as this event has been
* handled.
* <p>
*
* This is used in {@link TracTracRaceTrackerImpl} to ensure that events queued during loading phase will be
* processed before the new {@link TrackedRaceStatus} is propagated to the {@link TrackedRace}.
*
* @param callback
* {@link LoadingQueueDoneCallBack#loadingQueueDone(Receiver)} Will be called as soon as the event that
* was last in the queue at the time of calling
* {@link #callBackWhenLoadingQueueIsDone(LoadingQueueDoneCallBack)} has been handled.
*/
void callBackWhenLoadingQueueIsDone(LoadingQueueDoneCallBack callback);
}
@@ -0,0 +1,49 @@
package com.sap.sailing.domain.tractracadapter.impl;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import com.sap.sailing.domain.tractracadapter.LoadingQueueDoneCallBack;
import com.sap.sailing.domain.tractracadapter.Receiver;
/**
* Performs {@link #executeWhenAllReceiversAreDoneLoading()}, when all Receivers have called back.
*
* @author Frederik Petersen
*
*/
public abstract class AbstractLoadingQueueDoneCallBack implements LoadingQueueDoneCallBack {
/**
* Set keeping track of all receivers that still need to callback. When it's empty, desired
* action should be performed.
*/
private final Set<Receiver> receiversToCallback;
/**
*
* @param receivers
* All of these receivers will be queried to call back when they are done handling currently queued
* events
*/
public AbstractLoadingQueueDoneCallBack(Collection<Receiver> receivers) {
this.receiversToCallback = new HashSet<>(receivers);
for (Receiver receiver : receivers) {
receiver.callBackWhenLoadingQueueIsDone(this);
}
}
@Override
public void loadingQueueDone(Receiver receiver) {
synchronized (this.receiversToCallback) {
receiversToCallback.remove(receiver);
if (this.receiversToCallback.isEmpty()) {
executeWhenAllReceiversAreDoneLoading();
}
}
}
protected abstract void executeWhenAllReceiversAreDoneLoading();
}
@@ -1,6 +1,10 @@
package com.sap.sailing.domain.tractracadapter.impl;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
@@ -11,9 +15,11 @@ import com.sap.sailing.domain.tracking.RaceTracker;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.TrackedRegatta;
import com.sap.sailing.domain.tractracadapter.DomainFactory;
import com.sap.sailing.domain.tractracadapter.LoadingQueueDoneCallBack;
import com.sap.sailing.domain.tractracadapter.Receiver;
import com.tractrac.model.lib.api.event.IEvent;
import com.sap.sse.common.Util;
import com.sap.sse.common.Util.Triple;
import com.tractrac.model.lib.api.event.IEvent;
import com.tractrac.model.lib.api.event.IRace;
import com.tractrac.subscription.lib.api.IEventSubscriber;
import com.tractrac.subscription.lib.api.IRaceSubscriber;
@@ -29,7 +35,7 @@ import com.tractrac.subscription.lib.api.IRaceSubscriber;
public abstract class AbstractReceiverWithQueue<A, B, C> implements Runnable, Receiver {
private static Logger logger = Logger.getLogger(AbstractReceiverWithQueue.class.getName());
private final LinkedBlockingQueue<Util.Triple<A, B, C>> queue;
private final LinkedBlockingDeque<Util.Triple<A, B, C>> queue;
private final DomainFactory domainFactory;
private final IEvent tractracEvent;
private final IEventSubscriber eventSubscriber;
@@ -37,6 +43,7 @@ public abstract class AbstractReceiverWithQueue<A, B, C> implements Runnable, Re
private final DynamicTrackedRegatta trackedRegatta;
private final Simulator simulator;
private final Thread thread;
private final Map<Util.Triple<A, B, C>, Set<LoadingQueueDoneCallBack>> loadingQueueDoneCallBacks;
/**
* used by {@link #stopAfterNotReceivingEventsForSomeTime(long)} and {@link #run()} to check if an event was received
@@ -53,8 +60,9 @@ public abstract class AbstractReceiverWithQueue<A, B, C> implements Runnable, Re
this.trackedRegatta = trackedRegatta;
this.domainFactory = domainFactory;
this.simulator = simulator;
this.queue = new LinkedBlockingQueue<Util.Triple<A, B, C>>();
this.queue = new LinkedBlockingDeque<Util.Triple<A, B, C>>();
this.thread = new Thread(this, getClass().getName());
this.loadingQueueDoneCallBacks = new HashMap<>();
}
protected IEventSubscriber getEventSubscriber() {
@@ -136,6 +144,28 @@ public abstract class AbstractReceiverWithQueue<A, B, C> implements Runnable, Re
if (!isStopEvent(event)) {
handleEvent(event);
}
final Set<LoadingQueueDoneCallBack> callBacks;
synchronized (loadingQueueDoneCallBacks) {
if (getSimulator() != null) {
// when simulator is running, loading is considered finished and all callbacks will
// be satisfied instantly
callBacks = new HashSet<>();
for (Set<LoadingQueueDoneCallBack> set : loadingQueueDoneCallBacks.values()) {
callBacks.addAll(set);
}
loadingQueueDoneCallBacks.clear();
} else {
// otherwise, check only if there are callbacks that registered at the event
// currently consumed and notify if any are found
callBacks = loadingQueueDoneCallBacks.remove(event);
}
}
if (callBacks != null) {
for (LoadingQueueDoneCallBack callback : callBacks) {
callback.loadingQueueDone(this);
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
@@ -180,4 +210,22 @@ public abstract class AbstractReceiverWithQueue<A, B, C> implements Runnable, Re
}
return result;
}
@Override
public void callBackWhenLoadingQueueIsDone(LoadingQueueDoneCallBack callback) {
synchronized (loadingQueueDoneCallBacks) {
Triple<A, B, C> lastInQueue = queue.peekLast();
// when simulator is attached, consider loading already done; the simulator simulates "live" tracking
if (lastInQueue == null || getSimulator() != null) {
callback.loadingQueueDone(this);
} else {
Set<LoadingQueueDoneCallBack> set = loadingQueueDoneCallBacks.get(lastInQueue);
if (set == null) {
set = new HashSet<>();
loadingQueueDoneCallBacks.put(lastInQueue, set);
}
set.add(callback);
}
}
}
}
@@ -510,9 +510,21 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl implements
receiver.stopAfterProcessingQueuedEvents();
}
}
if (!stopReceiversPreemtively) {
// wait for their queues to be worked down before signalling the FINISHED state.
new AbstractLoadingQueueDoneCallBack(receivers) {
@Override
protected void executeWhenAllReceiversAreDoneLoading() {
lastStatus = new TrackedRaceStatusImpl(TrackedRaceStatusEnum.FINISHED, /* will be ignored */1.0);
updateStatusOfTrackedRaces();
}
};
} else {
// queues contents were cleared preemptively; this means we're done with loading immediately
lastStatus = new TrackedRaceStatusImpl(TrackedRaceStatusEnum.FINISHED, /* will be ignored */1.0);
updateStatusOfTrackedRaces();
}
}
}
/**
@@ -577,7 +589,16 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl implements
}
}
logger.info("Stored data progress in tracker "+getID()+" for race(s) "+getRaces()+": "+progress);
lastStatus = new TrackedRaceStatusImpl(progress==1.0 ? TrackedRaceStatusEnum.TRACKING : TrackedRaceStatusEnum.LOADING, progress);
lastStatus = new TrackedRaceStatusImpl(TrackedRaceStatusEnum.LOADING, progress);
if (progress==1.0) {
new AbstractLoadingQueueDoneCallBack(receivers) {
@Override
protected void executeWhenAllReceiversAreDoneLoading() {
lastStatus = new TrackedRaceStatusImpl(TrackedRaceStatusEnum.TRACKING, progress);
updateStatusOfTrackedRaces();
}
};
}
lastProgressPerID.put(getID(), new Util.Pair<Integer, Float>(counter, progress));
updateStatusOfTrackedRaces();
}
@@ -620,14 +641,20 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl implements
@Override
public void stopped(Object o) {
logger.info("stopped TracTrac tracking in tracker "+getID()+" for "+getRaces()+" while in status "+lastStatus);
logger.info("stopped TracTrac tracking in tracker " + getID() + " for " + getRaces() + " while in status "
+ lastStatus);
new AbstractLoadingQueueDoneCallBack(receivers) {
@Override
protected void executeWhenAllReceiversAreDoneLoading() {
lastStatus = new TrackedRaceStatusImpl(TrackedRaceStatusEnum.FINISHED, 1.0);
updateStatusOfTrackedRaces();
if (!stopped) {
try {
for (RaceDefinition race : getRaces()) {
// See also bug 1517; with TracAPI we assume that when stopped(IEvent) is called by the TracAPI then
// all subscriptions have received all their data and it's therefore safe to stop all subscriptions
// See also bug 1517; with TracAPI we assume that when stopped(IEvent) is called by the
// TracAPI then
// all subscriptions have received all their data and it's therefore safe to stop all
// subscriptions
// at this point without missing any data.
trackedRegattaRegistry.stopTracking(regatta, race);
}
@@ -636,6 +663,8 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl implements
}
}
}
};
}
@Override
public GPSFixStore getGPSFixStore() {
@@ -45,7 +45,7 @@ public class MarkPassingUpdateListener extends AbstractRaceChangeListener {
}
@Override
public void statusChanged(TrackedRaceStatus newStatus) {
public void statusChanged(TrackedRaceStatus newStatus, TrackedRaceStatus oldStatus) {
if (newStatus.getStatus() == TrackedRaceStatusEnum.FINISHED) {
queue.add(end);
}
@@ -50,5 +50,5 @@ public interface RaceChangeListener extends CourseListener {
void windSourcesToExcludeChanged(Iterable<? extends WindSource> windSourcesToExclude);
void statusChanged(TrackedRaceStatus newStatus);
void statusChanged(TrackedRaceStatus newStatus, TrackedRaceStatus oldStatus);
}
@@ -40,7 +40,7 @@ public abstract class AbstractRaceChangeListener implements RaceChangeListener {
}
@Override
public void statusChanged(TrackedRaceStatus newStatus) {
public void statusChanged(TrackedRaceStatus newStatus, TrackedRaceStatus oldStatus) {
defaultAction();
}
@@ -147,8 +147,9 @@ DynamicTrackedRace, GPSTrackListener<Competitor, GPSFixMoving> {
@Override
public void setStatus(TrackedRaceStatus newStatus) {
TrackedRaceStatus oldStatus = getStatus();
super.setStatus(newStatus);
notifyListeners(newStatus);
notifyListeners(newStatus, oldStatus);
}
@Override
@@ -189,7 +190,10 @@ DynamicTrackedRace, GPSTrackListener<Competitor, GPSFixMoving> {
@Override
public void setAndFixDelayToLiveInMillis(long delayToLiveInMillis) {
if (getDelayToLiveInMillis() != delayToLiveInMillis) {
super.setDelayToLiveInMillis(delayToLiveInMillis);
notifyListenersDelayToLiveChanged(delayToLiveInMillis);
}
delayToLiveInMillisFixed = true;
}
@@ -415,8 +419,8 @@ DynamicTrackedRace, GPSTrackListener<Competitor, GPSFixMoving> {
notifyListeners(listener -> listener.competitorPositionChanged(fix, competitor));
}
private void notifyListeners(TrackedRaceStatus status) {
notifyListeners(listener -> listener.statusChanged(status));
private void notifyListeners(TrackedRaceStatus status, TrackedRaceStatus oldStatus) {
notifyListeners(listener -> listener.statusChanged(status, oldStatus));
}
private void notifyListeners(Wind wind, WindSource windSource) {
@@ -667,7 +667,7 @@ public class TrackBasedEstimationWindTrackImpl extends VirtualWindTrackImpl {
}
@Override
public void statusChanged(TrackedRaceStatus newStatus) {
public void statusChanged(TrackedRaceStatus newStatus, TrackedRaceStatus oldStatus) {
// This virtual wind track's cache can cope with an empty cache after the LOADING phase and populates the
// cache
// upon request. Invalidation happens also during the LOADING phase, preserving the cache's invariant.
@@ -269,7 +269,7 @@ public class TrackedLegImpl implements TrackedLeg {
* no-op; the leg doesn't mind the tracked race's status being updated
*/
@Override
public void statusChanged(TrackedRaceStatus newStatus) {
public void statusChanged(TrackedRaceStatus newStatus, TrackedRaceStatus oldStatus) {
}
/**
@@ -22,6 +22,7 @@ import java.util.NavigableSet;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.WeakHashMap;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListSet;
@@ -251,6 +252,13 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
protected transient ConcurrentHashMap<Serializable, RaceLog> attachedRaceLogs;
/**
* Holds optional race states for the race logs in {@link #attachedRaceLogs}. By using a {@link WeakHashMap},
* these race states can be garbage-collected when the race log is no longer attached. The race states are created
* lazily, synchronizing on this weak hash map.
*/
protected final transient WeakHashMap<RaceLog, ReadonlyRaceState> raceStates;
protected transient ConcurrentHashMap<Serializable, RegattaLog> attachedRegattaLogs;
/**
@@ -304,6 +312,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
long delayToLiveInMillis, final long millisecondsOverWhichToAverageWind,
long millisecondsOverWhichToAverageSpeed, long delayForWindEstimationCacheInvalidation) {
super(race, trackedRegatta, windStore, millisecondsOverWhichToAverageWind);
raceStates = new WeakHashMap<>();
shortTimeWindCache = new ShortTimeWindCache(this, millisecondsOverWhichToAverageWind / 2);
locksForMarkPassings = new IdentityHashMap<>();
attachedRaceLogs = new ConcurrentHashMap<>();
@@ -2844,6 +2853,18 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
loadFixesForLog(raceLog, attachedRaceLogs);
}
private ReadonlyRaceState getRaceState(RaceLog raceLog) {
ReadonlyRaceState result;
synchronized (raceStates) {
result = raceStates.get(raceLog);
if (result == null) {
result = RaceStateImpl.create(raceLog);
raceStates.put(raceLog, result);
}
}
return result;
}
@Override
public void attachRegattaLog(RegattaLog regattaLog) {
loadFixesForLog(regattaLog, attachedRegattaLogs);
@@ -3312,8 +3333,8 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
public Boolean isGateStart() {
Boolean result = null;
for (RaceLog raceLog : attachedRaceLogs.values()) {
ReadonlyRaceState raceState = RaceStateImpl.create(raceLog);
ReadonlyRacingProcedure procedure = raceState.getRacingProcedure();
ReadonlyRaceState raceState = getRaceState(raceLog);
ReadonlyRacingProcedure procedure = raceState.getRacingProcedureNoFallback();
if (procedure != null && procedure.getType() != null) {
result = procedure.getType() == RacingProcedureType.GateStart;
break;
@@ -3327,7 +3348,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
final Distance result;
final Leg startLeg = getRace().getCourse().getFirstLeg();
final TrackedLegOfCompetitor competitorLeg;
if (startLeg != null && isGateStart() == true && (competitorLeg=getTrackedLeg(competitor, startLeg)).hasStartedLeg(timePoint)) {
if (startLeg != null && isGateStart() == Boolean.TRUE && (competitorLeg=getTrackedLeg(competitor, startLeg)).hasStartedLeg(timePoint)) {
TimePoint competitorLegStartTime = competitorLeg.getStartTime();
final Mark portMarkOfStartLine = getStartLine(competitorLegStartTime).getPortMarkWhileApproachingLine();
final Position portSideOfStartLinePosition = getOrCreateTrack(portMarkOfStartLine)
@@ -110,7 +110,7 @@ eventRegattaRacesLegendWind=Wind Data
eventRegattaRacesLegendVideo=Video Stream
eventRegattaRacesLegendAudio=Audio Stream
eventSponsorsTitle=Event Sponsorships
footerCopyright=\u00A9 2014 SAP Sailing Analytics
footerCopyright=\u00A9 2015 SAP Sailing Analytics
footerLanguage=SAP Sailing offered in
footerLegal=Legal Disclosure
footerPrivacy=Privacy
@@ -110,7 +110,7 @@ eventRegattaRacesLegendWind=Winddaten
eventRegattaRacesLegendVideo=Video Stream
eventRegattaRacesLegendAudio=Audio Stream
eventSponsorsTitle=Veranstaltungssponsoren
footerCopyright=\u00A9 2014 SAP Sailing Analytics
footerCopyright=\u00A9 2015 SAP Sailing Analytics
footerLanguage=SAP Sailing in
footerLegal=Impressum
footerPrivacy=Datenschutz
@@ -194,5 +194,5 @@ public interface PolarDataService {
*/
PolynomialFunction getAngleRegressionFunction(BoatClass boatClass, LegType legType, Tack tack) throws NotEnoughDataHasBeenAddedException;
void raceFinishedTracking(TrackedRace race);
void raceFinishedLoading(TrackedRace race);
}
@@ -148,7 +148,7 @@ public class PolarDataServiceImpl implements PolarDataService {
}
@Override
public void raceFinishedTracking(TrackedRace race) {
public void raceFinishedLoading(TrackedRace race) {
polarDataMiner.raceFinishedTracking(race);
}
}
@@ -43,7 +43,7 @@ import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.polars.impl.CubicEquation;
import com.sap.sailing.polars.regression.NotEnoughDataHasBeenAddedException;
import com.sap.sse.common.Util.Pair;
import com.sap.sse.common.impl.MillisecondsTimePoint;
import com.sap.sse.datamining.components.FilterCriterion;
import com.sap.sse.datamining.components.Processor;
import com.sap.sse.datamining.data.ClusterGroup;
import com.sap.sse.datamining.functions.Function;
@@ -58,7 +58,7 @@ public class PolarDataMiner {
private static final int THREAD_POOL_SIZE = Math.max((int) (Runtime.getRuntime().availableProcessors() * (3.0/4.0)), 3);
private final ThreadPoolExecutor executor = createExecutor();
private final PolarSheetGenerationSettings backendPolarSheetGenerationSettings;
private final Map<TrackedRace, Set<GPSFixMovingWithOriginInfo>> fixesForReplayRacesWhichAreStillLoading = new HashMap<>();
private final Map<TrackedRace, Set<GPSFixMovingWithOriginInfo>> fixesForRacesWhichAreStillLoading = new HashMap<>();
private final Queue<GPSFixMovingWithOriginInfo> fixQueue = new ConcurrentLinkedQueue<GPSFixMovingWithOriginInfo>();
@@ -79,14 +79,14 @@ public class PolarDataMiner {
synchronized (fixQueue) {
while (this.getQueue().size() < (EXECUTOR_QUEUE_SIZE / 10) && !fixQueue.isEmpty()) {
GPSFixMovingWithOriginInfo fix = fixQueue.poll();
enrichingProcessor.processElement(fix);
preFilteringProcessor.processElement(fix);
}
}
}
};
}
private AbstractEnrichingProcessor<GPSFixMovingWithOriginInfo, GPSFixMovingWithPolarContext> enrichingProcessor;
/**
* This processor keeps track of the moving average of the speed values and the average angle for each course (legtype tack combination)
@@ -100,6 +100,7 @@ public class PolarDataMiner {
private CubicRegressionPerCourseProcessor cubicRegressionPerCourseProcessor;
private SpeedRegressionPerAngleClusterProcessor speedRegressionPerAngleClusterProcessor;
private ParallelFilteringProcessor<GPSFixMovingWithOriginInfo> preFilteringProcessor;
public PolarDataMiner() {
this(PolarSheetGenerationSettingsImpl.createBackendPolarSettings());
@@ -170,15 +171,42 @@ public class PolarDataMiner {
.asList(filteringProcessor);
enrichingProcessor = new AbstractEnrichingProcessor<GPSFixMovingWithOriginInfo, GPSFixMovingWithPolarContext>(
GPSFixMovingWithOriginInfo.class, GPSFixMovingWithPolarContext.class, executor, enrichingResultReceivers) {
AbstractEnrichingProcessor<GPSFixMovingWithOriginInfo, GPSFixMovingWithPolarContext> enrichingProcessor = new AbstractEnrichingProcessor<GPSFixMovingWithOriginInfo, GPSFixMovingWithPolarContext>(
GPSFixMovingWithOriginInfo.class, GPSFixMovingWithPolarContext.class, executor,
enrichingResultReceivers) {
@Override
protected GPSFixMovingWithPolarContext enrich(GPSFixMovingWithOriginInfo element) {
return new GPSFixMovingWithPolarContext(element.getFix(), element.getTrackedRace(), element.getCompetitor(),
speedClusterGroup, angleClusterGroup);
GPSFixMovingWithPolarContext result = null;
result = new GPSFixMovingWithPolarContext(element.getFix(), element.getTrackedRace(),
element.getCompetitor(), speedClusterGroup, angleClusterGroup);
return result;
}
};
Collection<Processor<GPSFixMovingWithOriginInfo, ?>> preFilterResultReceivers = Arrays
.asList(enrichingProcessor);
preFilteringProcessor = new ParallelFilteringProcessor<GPSFixMovingWithOriginInfo>(
GPSFixMovingWithOriginInfo.class, executor, preFilterResultReceivers, new FilterCriterion<GPSFixMovingWithOriginInfo>() {
@Override
public boolean matches(GPSFixMovingWithOriginInfo element) {
boolean result = false;
if (PolarFixFilterCriteria.isInLeadingCompetitors(element.getTrackedRace(), element.getCompetitor(),
backendPolarSheetGenerationSettings.getPctOfLeadingCompetitorsToInclude())) {
result = true;
}
return result;
}
@Override
public Class<GPSFixMovingWithOriginInfo> getElementType() {
return GPSFixMovingWithOriginInfo.class;
}
});
}
@@ -188,48 +216,34 @@ public class PolarDataMiner {
public void addFix(GPSFixMoving fix, Competitor competitor, TrackedRace trackedRace) {
GPSFixMovingWithOriginInfo fixWithOriginInfo = new GPSFixMovingWithOriginInfo(fix, trackedRace, competitor);
if (isReplayAndFinishedOrLive(trackedRace)) {
processFix(trackedRace, fixWithOriginInfo);
} else {
if (trackedRace.getStatus().getStatus() == TrackedRaceStatusEnum.LOADING) {
/*
* logger.info("Received fix for replay race which has not finished loading. Queuing. " +
* (trackedRace.getRace() != null ? trackedRace.getRace().getName() : trackedRace.getRaceIdentifier()
* .getRaceName()));
*/
synchronized (fixesForReplayRacesWhichAreStillLoading) {
Set<GPSFixMovingWithOriginInfo> fixes = fixesForReplayRacesWhichAreStillLoading.get(trackedRace);
synchronized (fixesForRacesWhichAreStillLoading) {
Set<GPSFixMovingWithOriginInfo> fixes = fixesForRacesWhichAreStillLoading.get(trackedRace);
if (fixes == null) {
fixes = new HashSet<>();
fixesForReplayRacesWhichAreStillLoading.put(trackedRace, fixes);
fixesForRacesWhichAreStillLoading.put(trackedRace, fixes);
}
fixes.add(fixWithOriginInfo);
}
} else {
processFix(trackedRace, fixWithOriginInfo);
}
}
private void processFix(TrackedRace trackedRace, GPSFixMovingWithOriginInfo fixWithOriginInfo) {
// Pre Filter for performance. We don't need to run wind estimation for this filter
if (PolarFixFilterCriteria.isInLeadingCompetitors(trackedRace, fixWithOriginInfo.getCompetitor(),
backendPolarSheetGenerationSettings.getPctOfLeadingCompetitorsToInclude())) {
synchronized (fixQueue) {
if (executor.getQueue().size() >= EXECUTOR_QUEUE_SIZE / 10) {
fixQueue.add(fixWithOriginInfo);
} else {
enrichingProcessor.processElement(fixWithOriginInfo);
preFilteringProcessor.processElement(fixWithOriginInfo);
}
}
}
}
private boolean isReplayAndFinishedOrLive(TrackedRace trackedRace) {
boolean isLive = trackedRace.isLive(new MillisecondsTimePoint(System.currentTimeMillis()));
boolean loadingFinished = false;
if (trackedRace.getStatus().getStatus() == TrackedRaceStatusEnum.FINISHED) {
loadingFinished = true;
}
boolean replayRaceAndFinished = !isLive && loadingFinished;
return replayRaceAndFinished || isLive;
}
public boolean isCurrentlyActiveAndOrHasQueue() {
boolean isActive = executor.getActiveCount() > 0;
@@ -399,11 +413,11 @@ public class PolarDataMiner {
public void raceFinishedTracking(TrackedRace race) {
Set<GPSFixMovingWithOriginInfo> fixes = null;
synchronized (fixesForReplayRacesWhichAreStillLoading) {
fixes = fixesForReplayRacesWhichAreStillLoading.remove(race);
synchronized (fixesForRacesWhichAreStillLoading) {
fixes = fixesForRacesWhichAreStillLoading.remove(race);
}
if (fixes != null) {
logger.info("All queued fixes for newly completed race will process now. " + (race.getRace() != null ? race
logger.info("All queued fixes for newly loaded race will process now. " + (race.getRace() != null ? race
.getRace().getName() : race.getRaceIdentifier().getRaceName()));
for (GPSFixMovingWithOriginInfo fix : fixes) {
processFix(race, fix);
@@ -1540,9 +1540,10 @@ public class RacingEventServiceImpl implements RacingEventService, ClearStateTes
}
@Override
public void statusChanged(TrackedRaceStatus newStatus) {
if (newStatus.getStatus() == TrackedRaceStatusEnum.FINISHED) {
polarDataService.raceFinishedTracking(race);
public void statusChanged(TrackedRaceStatus newStatus, TrackedRaceStatus oldStatus) {
if (oldStatus.getStatus() == TrackedRaceStatusEnum.LOADING
&& newStatus.getStatus() != TrackedRaceStatusEnum.LOADING) {
polarDataService.raceFinishedLoading(race);
}
}
@@ -1617,7 +1618,7 @@ public class RacingEventServiceImpl implements RacingEventService, ClearStateTes
}
@Override
public void statusChanged(TrackedRaceStatus newStatus) {
public void statusChanged(TrackedRaceStatus newStatus, TrackedRaceStatus oldStatus) {
replicate(new UpdateTrackedRaceStatus(getRaceIdentifier(), newStatus));
}
@@ -2288,6 +2289,11 @@ public class RacingEventServiceImpl implements RacingEventService, ClearStateTes
return getBaseDomainFactory().createObjectInputStreamResolvingAgainstThisFactory(is);
}
@Override
public ClassLoader getDeserializationClassLoader() {
return getCombinedMasterDataClassLoader();
}
@Override
public Serializable getId() {
return getClass().getName();
@@ -338,7 +338,7 @@ public class Util {
}
public static String join(String separator, Iterable<? extends Named> nameds) {
return join(separator, toArray(nameds, new Named[size(nameds)]));
return join(separator, toArray(nameds, new Named[0]));
}
public static String join(String separator, Named... nameds) {
@@ -37,14 +37,6 @@ public class AbstractMailServiceReplicationTest extends AbstractServerWithSingle
}
public static class MailServerReplicationTestSetUp extends AbstractServerReplicationTestSetUp<MailService, MailServiceImpl> {
public MailServerReplicationTestSetUp() {
super();
}
public MailServerReplicationTestSetUp(int servletPort) {
super(servletPort);
}
@Override
protected MailServiceImpl createNewMaster() throws MailException {
return createMailCountingService(true);
@@ -45,7 +45,7 @@ public abstract class AbstractServerReplicationTestSetUp<ReplicableInterface ext
protected static final int DEFAULT_SERVLET_PORT = 9990;
private final int servletPort;
private int servletPort;
protected ReplicableImpl replica;
protected ReplicableImpl master;
protected ReplicationServiceTestImpl<ReplicableInterface> replicaReplicator;
@@ -54,7 +54,6 @@ public abstract class AbstractServerReplicationTestSetUp<ReplicableInterface ext
protected ReplicationMasterDescriptor masterDescriptor;
protected AbstractServerReplicationTestSetUp() {
servletPort = DEFAULT_SERVLET_PORT;
}
protected AbstractServerReplicationTestSetUp(int servletPort) {
@@ -127,9 +126,10 @@ public abstract class AbstractServerReplicationTestSetUp<ReplicableInterface ext
masterReplicator.registerReplica(replicaDescriptor);
// connect to exchange host and local server running as master
// master server and exchange host can be two different hosts
masterDescriptor = new ReplicationMasterDescriptorImpl(exchangeHost, exchangeName, 0, UUID.randomUUID().toString(), "localhost", servletPort);
ReplicationServiceTestImpl<ReplicableInterface> replicaReplicator = new ReplicationServiceTestImpl<ReplicableInterface>(exchangeName, exchangeHost, rim, replicaDescriptor,
this.replica, this.master, masterReplicator, masterDescriptor, servletPort);
this.replica, this.master, masterReplicator, masterDescriptor);
masterDescriptor = replicaReplicator.getMasterDescriptor();
servletPort = masterDescriptor.getServletPort();
Pair<ReplicationServiceTestImpl<ReplicableInterface>, ReplicationMasterDescriptor> result = new Pair<>(replicaReplicator, masterDescriptor);
logger.info("starting initial load transmission servlet for "+getClass().getName());
initialLoadTestServerThread = replicaReplicator.startInitialLoadTransmissionServlet();
@@ -197,28 +197,30 @@ public abstract class AbstractServerReplicationTestSetUp<ReplicableInterface ext
private final ReplicaDescriptor replicaDescriptor;
private final ReplicationService masterReplicationService;
private final ReplicationMasterDescriptor masterDescriptor;
private final int servletPort;
private final ServerSocket ss;
public ReplicationServiceTestImpl(String exchangeName, String exchangeHost, ReplicationInstancesManager replicationInstancesManager,
ReplicaDescriptor replicaDescriptor, ReplicableInterface replica,
ReplicableInterface master, ReplicationService masterReplicationService, ReplicationMasterDescriptor masterDescriptor,
int servletPort)
ReplicableInterface master, ReplicationService masterReplicationService, ReplicationMasterDescriptor masterDescriptor)
throws IOException {
super(exchangeName, exchangeHost, 0, replicationInstancesManager, new SingletonReplicablesProvider(replica));
this.replicaDescriptor = replicaDescriptor;
this.master = master;
ss = new ServerSocket(0); // bind to any free port
this.masterReplicationService = masterReplicationService;
this.masterDescriptor = masterDescriptor;
this.servletPort = servletPort;
this.masterDescriptor = new ReplicationMasterDescriptorImpl(exchangeHost, exchangeName, /* messagingPort */ 0,
UUID.randomUUID().toString(), "localhost", ss.getLocalPort());
}
ReplicationMasterDescriptor getMasterDescriptor() {
return masterDescriptor;
}
private Thread startInitialLoadTransmissionServlet() throws InterruptedException {
final boolean[] listening = new boolean[] { false };
Thread initialLoadTestServerThread = new Thread("Replication initial load test server") {
public void run() {
ServerSocket ss = null;
try {
ss = new ServerSocket(servletPort);
synchronized (listening) {
listening[0] = true;
listening.notifyAll();
@@ -70,7 +70,7 @@ public interface ReplicableWithObjectInputStream<S, O extends OperationWithResul
try {
final ObjectInputStream objectInputStream = createObjectInputStreamResolvingAgainstCache(is);
ClassLoader oldContextClassloader = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
Thread.currentThread().setContextClassLoader(getDeserializationClassLoader());
try {
initiallyFillFromInternal(objectInputStream);
} finally {
@@ -81,6 +81,13 @@ public interface ReplicableWithObjectInputStream<S, O extends OperationWithResul
}
}
/**
* The class loader to use for de-serializing objects. By default, this object's class's class loader is used.
*/
default ClassLoader getDeserializationClassLoader() {
return getClass().getClassLoader();
}
/**
* Wraps <code>os</code> by an {@link ObjectOutputStream} and invokes
* {@link #serializeForInitialReplicationInternal(ObjectOutputStream)}.
@@ -92,7 +99,13 @@ public interface ReplicableWithObjectInputStream<S, O extends OperationWithResul
@Override
default O readOperation(InputStream inputStream) throws IOException, ClassNotFoundException {
ClassLoader oldContextClassloader = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(getDeserializationClassLoader());
try {
return readOperationInternal(createObjectInputStreamResolvingAgainstCache(inputStream));
} finally {
Thread.currentThread().setContextClassLoader(oldContextClassloader);
}
}
@Override
@@ -67,12 +67,6 @@ public class SecurityReplicationLeadingToEmailReplicationTest extends AbstractSe
}
private class MailServerReplicationTestSetUp extends AbstractMailServiceReplicationTest.MailServerReplicationTestSetUp {
private static final int SERVLET_PORT = 9991;
public MailServerReplicationTestSetUp() {
super(SERVLET_PORT);
}
@Override
protected MailServiceImpl createNewMaster() throws MailException {
return masterMailService;