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(); 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(); 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 * Gets the currently attached {@link ReadonlyRacingProcedure}. Use this method to avoid casting in cases you are
* sure about the {@link ReadonlyRacingProcedure}'s type. * sure about the {@link ReadonlyRacingProcedure}'s type.
@@ -92,7 +92,18 @@ public class ReadonlyRaceStateImpl implements ReadonlyRaceState, RaceLogChangedL
private final LastPublishedCourseDesignFinder courseDesignerAnalyzer; private final LastPublishedCourseDesignFinder courseDesignerAnalyzer;
private final LastWindFixFinder lastWindFixAnalyzer; 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; 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 RaceLogRaceStatus cachedRaceStatus;
private int cachedPassId; private int cachedPassId;
private TimePoint cachedStartTime; private TimePoint cachedStartTime;
@@ -130,7 +141,12 @@ public class ReadonlyRaceStateImpl implements ReadonlyRaceState, RaceLogChangedL
this.courseDesignerAnalyzer = new LastPublishedCourseDesignFinder(raceLog); this.courseDesignerAnalyzer = new LastPublishedCourseDesignFinder(raceLog);
this.lastWindFixAnalyzer = new LastWindFixFinder(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.cachedRaceStatus = RaceLogRaceStatus.UNKNOWN;
this.cachedPassId = raceLog.getCurrentPassId(); this.cachedPassId = raceLog.getCurrentPassId();
// see resolved bug 2083: make sure the listener registration is "weak" in the sense that it is removed when // 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(); RacingProcedureType inRaceLogType = racingProcedureAnalyzer.analyze();
if (inRaceLogType != RacingProcedureType.UNKNOWN) { if (inRaceLogType != RacingProcedureType.UNKNOWN) {
return inRaceLogType; return inRaceLogType;
} else if (configuration.getDefaultRacingProcedureType() != null
&& configuration.getDefaultRacingProcedureType() != RacingProcedureType.UNKNOWN) {
return configuration.getDefaultRacingProcedureType();
} else { } else {
return fallbackInitialProcedureType; return configuration.getDefaultRacingProcedureType();
} }
} }
@@ -166,6 +179,17 @@ public class ReadonlyRaceStateImpl implements ReadonlyRaceState, RaceLogChangedL
return racingProcedure; return racingProcedure;
} }
@Override
public ReadonlyRacingProcedure getRacingProcedureNoFallback() {
final ReadonlyRacingProcedure result;
if (cachedRacingProcedureTypeNoFallback == null || cachedRacingProcedureTypeNoFallback == RacingProcedureType.UNKNOWN) {
result = null;
} else {
result = getRacingProcedure();
}
return result;
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Override @Override
public <T extends ReadonlyRacingProcedure> T getTypedReadonlyRacingProcedure() { 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.common.Position;
import com.sap.sailing.domain.tractracadapter.DomainFactory; 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.Receiver;
import com.tractrac.model.lib.api.data.IPosition; import com.tractrac.model.lib.api.data.IPosition;
import com.tractrac.model.lib.api.route.IControl; 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)); addListenersForStoredDataAndStartController(Collections.singleton(receiver));
synchronized (semaphor) { 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.EmptyWindStore;
import com.sap.sailing.domain.tracking.impl.GPSFixMovingImpl; import com.sap.sailing.domain.tracking.impl.GPSFixMovingImpl;
import com.sap.sailing.domain.tractracadapter.DomainFactory; 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.Receiver;
import com.sap.sailing.domain.tractracadapter.ReceiverType; import com.sap.sailing.domain.tractracadapter.ReceiverType;
import com.sap.sailing.domain.tractracadapter.impl.ControlPointAdapter; 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>(); List<Receiver> receivers = new ArrayList<Receiver>();
receivers.add(receiver); receivers.add(receiver);
@@ -11,6 +11,7 @@ import org.junit.Test;
import com.sap.sailing.domain.base.Course; import com.sap.sailing.domain.base.Course;
import com.sap.sailing.domain.tractracadapter.DomainFactory; 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.Receiver;
import com.sap.sse.common.Util; import com.sap.sse.common.Util;
import com.tractrac.model.lib.api.route.IControlRoute; 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)); addListenersForStoredDataAndStartController(Collections.singleton(receiver));
synchronized (semaphor) { 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; 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 { public interface Receiver {
/** /**
@@ -34,4 +38,19 @@ public interface Receiver {
* Waits until this received has stopped, but no longer than <code>timeout</code> milliseconds * Waits until this received has stopped, but no longer than <code>timeout</code> milliseconds
*/ */
void join(long timeoutInMilliseconds) throws InterruptedException; 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; 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.concurrent.TimeUnit;
import java.util.logging.Logger; 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.TrackedRace;
import com.sap.sailing.domain.tracking.TrackedRegatta; import com.sap.sailing.domain.tracking.TrackedRegatta;
import com.sap.sailing.domain.tractracadapter.DomainFactory; 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.Receiver;
import com.tractrac.model.lib.api.event.IEvent;
import com.sap.sse.common.Util; 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.model.lib.api.event.IRace;
import com.tractrac.subscription.lib.api.IEventSubscriber; import com.tractrac.subscription.lib.api.IEventSubscriber;
import com.tractrac.subscription.lib.api.IRaceSubscriber; 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 { public abstract class AbstractReceiverWithQueue<A, B, C> implements Runnable, Receiver {
private static Logger logger = Logger.getLogger(AbstractReceiverWithQueue.class.getName()); 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 DomainFactory domainFactory;
private final IEvent tractracEvent; private final IEvent tractracEvent;
private final IEventSubscriber eventSubscriber; private final IEventSubscriber eventSubscriber;
@@ -37,6 +43,7 @@ public abstract class AbstractReceiverWithQueue<A, B, C> implements Runnable, Re
private final DynamicTrackedRegatta trackedRegatta; private final DynamicTrackedRegatta trackedRegatta;
private final Simulator simulator; private final Simulator simulator;
private final Thread thread; 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 * 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.trackedRegatta = trackedRegatta;
this.domainFactory = domainFactory; this.domainFactory = domainFactory;
this.simulator = simulator; 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.thread = new Thread(this, getClass().getName());
this.loadingQueueDoneCallBacks = new HashMap<>();
} }
protected IEventSubscriber getEventSubscriber() { protected IEventSubscriber getEventSubscriber() {
@@ -136,6 +144,28 @@ public abstract class AbstractReceiverWithQueue<A, B, C> implements Runnable, Re
if (!isStopEvent(event)) { if (!isStopEvent(event)) {
handleEvent(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) { } catch (InterruptedException e) {
e.printStackTrace(); e.printStackTrace();
} }
@@ -180,4 +210,22 @@ public abstract class AbstractReceiverWithQueue<A, B, C> implements Runnable, Re
} }
return result; 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(); 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); lastStatus = new TrackedRaceStatusImpl(TrackedRaceStatusEnum.FINISHED, /* will be ignored */1.0);
updateStatusOfTrackedRaces(); 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); 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)); lastProgressPerID.put(getID(), new Util.Pair<Integer, Float>(counter, progress));
updateStatusOfTrackedRaces(); updateStatusOfTrackedRaces();
} }
@@ -620,14 +641,20 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl implements
@Override @Override
public void stopped(Object o) { 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); lastStatus = new TrackedRaceStatusImpl(TrackedRaceStatusEnum.FINISHED, 1.0);
updateStatusOfTrackedRaces(); updateStatusOfTrackedRaces();
if (!stopped) { if (!stopped) {
try { try {
for (RaceDefinition race : getRaces()) { for (RaceDefinition race : getRaces()) {
// See also bug 1517; with TracAPI we assume that when stopped(IEvent) is called by the TracAPI then // See also bug 1517; with TracAPI we assume that when stopped(IEvent) is called by the
// all subscriptions have received all their data and it's therefore safe to stop all subscriptions // 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. // at this point without missing any data.
trackedRegattaRegistry.stopTracking(regatta, race); trackedRegattaRegistry.stopTracking(regatta, race);
} }
@@ -636,6 +663,8 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl implements
} }
} }
} }
};
}
@Override @Override
public GPSFixStore getGPSFixStore() { public GPSFixStore getGPSFixStore() {
@@ -45,7 +45,7 @@ public class MarkPassingUpdateListener extends AbstractRaceChangeListener {
} }
@Override @Override
public void statusChanged(TrackedRaceStatus newStatus) { public void statusChanged(TrackedRaceStatus newStatus, TrackedRaceStatus oldStatus) {
if (newStatus.getStatus() == TrackedRaceStatusEnum.FINISHED) { if (newStatus.getStatus() == TrackedRaceStatusEnum.FINISHED) {
queue.add(end); queue.add(end);
} }
@@ -50,5 +50,5 @@ public interface RaceChangeListener extends CourseListener {
void windSourcesToExcludeChanged(Iterable<? extends WindSource> windSourcesToExclude); 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 @Override
public void statusChanged(TrackedRaceStatus newStatus) { public void statusChanged(TrackedRaceStatus newStatus, TrackedRaceStatus oldStatus) {
defaultAction(); defaultAction();
} }
@@ -147,8 +147,9 @@ DynamicTrackedRace, GPSTrackListener<Competitor, GPSFixMoving> {
@Override @Override
public void setStatus(TrackedRaceStatus newStatus) { public void setStatus(TrackedRaceStatus newStatus) {
TrackedRaceStatus oldStatus = getStatus();
super.setStatus(newStatus); super.setStatus(newStatus);
notifyListeners(newStatus); notifyListeners(newStatus, oldStatus);
} }
@Override @Override
@@ -189,7 +190,10 @@ DynamicTrackedRace, GPSTrackListener<Competitor, GPSFixMoving> {
@Override @Override
public void setAndFixDelayToLiveInMillis(long delayToLiveInMillis) { public void setAndFixDelayToLiveInMillis(long delayToLiveInMillis) {
if (getDelayToLiveInMillis() != delayToLiveInMillis) {
super.setDelayToLiveInMillis(delayToLiveInMillis); super.setDelayToLiveInMillis(delayToLiveInMillis);
notifyListenersDelayToLiveChanged(delayToLiveInMillis);
}
delayToLiveInMillisFixed = true; delayToLiveInMillisFixed = true;
} }
@@ -415,8 +419,8 @@ DynamicTrackedRace, GPSTrackListener<Competitor, GPSFixMoving> {
notifyListeners(listener -> listener.competitorPositionChanged(fix, competitor)); notifyListeners(listener -> listener.competitorPositionChanged(fix, competitor));
} }
private void notifyListeners(TrackedRaceStatus status) { private void notifyListeners(TrackedRaceStatus status, TrackedRaceStatus oldStatus) {
notifyListeners(listener -> listener.statusChanged(status)); notifyListeners(listener -> listener.statusChanged(status, oldStatus));
} }
private void notifyListeners(Wind wind, WindSource windSource) { private void notifyListeners(Wind wind, WindSource windSource) {
@@ -667,7 +667,7 @@ public class TrackBasedEstimationWindTrackImpl extends VirtualWindTrackImpl {
} }
@Override @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 // This virtual wind track's cache can cope with an empty cache after the LOADING phase and populates the
// cache // cache
// upon request. Invalidation happens also during the LOADING phase, preserving the cache's invariant. // 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 * no-op; the leg doesn't mind the tracked race's status being updated
*/ */
@Override @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.Set;
import java.util.Timer; import java.util.Timer;
import java.util.TimerTask; import java.util.TimerTask;
import java.util.WeakHashMap;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListSet; import java.util.concurrent.ConcurrentSkipListSet;
@@ -251,6 +252,13 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
protected transient ConcurrentHashMap<Serializable, RaceLog> attachedRaceLogs; 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; protected transient ConcurrentHashMap<Serializable, RegattaLog> attachedRegattaLogs;
/** /**
@@ -304,6 +312,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
long delayToLiveInMillis, final long millisecondsOverWhichToAverageWind, long delayToLiveInMillis, final long millisecondsOverWhichToAverageWind,
long millisecondsOverWhichToAverageSpeed, long delayForWindEstimationCacheInvalidation) { long millisecondsOverWhichToAverageSpeed, long delayForWindEstimationCacheInvalidation) {
super(race, trackedRegatta, windStore, millisecondsOverWhichToAverageWind); super(race, trackedRegatta, windStore, millisecondsOverWhichToAverageWind);
raceStates = new WeakHashMap<>();
shortTimeWindCache = new ShortTimeWindCache(this, millisecondsOverWhichToAverageWind / 2); shortTimeWindCache = new ShortTimeWindCache(this, millisecondsOverWhichToAverageWind / 2);
locksForMarkPassings = new IdentityHashMap<>(); locksForMarkPassings = new IdentityHashMap<>();
attachedRaceLogs = new ConcurrentHashMap<>(); attachedRaceLogs = new ConcurrentHashMap<>();
@@ -2844,6 +2853,18 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
loadFixesForLog(raceLog, attachedRaceLogs); 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 @Override
public void attachRegattaLog(RegattaLog regattaLog) { public void attachRegattaLog(RegattaLog regattaLog) {
loadFixesForLog(regattaLog, attachedRegattaLogs); loadFixesForLog(regattaLog, attachedRegattaLogs);
@@ -3312,8 +3333,8 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
public Boolean isGateStart() { public Boolean isGateStart() {
Boolean result = null; Boolean result = null;
for (RaceLog raceLog : attachedRaceLogs.values()) { for (RaceLog raceLog : attachedRaceLogs.values()) {
ReadonlyRaceState raceState = RaceStateImpl.create(raceLog); ReadonlyRaceState raceState = getRaceState(raceLog);
ReadonlyRacingProcedure procedure = raceState.getRacingProcedure(); ReadonlyRacingProcedure procedure = raceState.getRacingProcedureNoFallback();
if (procedure != null && procedure.getType() != null) { if (procedure != null && procedure.getType() != null) {
result = procedure.getType() == RacingProcedureType.GateStart; result = procedure.getType() == RacingProcedureType.GateStart;
break; break;
@@ -3327,7 +3348,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
final Distance result; final Distance result;
final Leg startLeg = getRace().getCourse().getFirstLeg(); final Leg startLeg = getRace().getCourse().getFirstLeg();
final TrackedLegOfCompetitor competitorLeg; 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(); TimePoint competitorLegStartTime = competitorLeg.getStartTime();
final Mark portMarkOfStartLine = getStartLine(competitorLegStartTime).getPortMarkWhileApproachingLine(); final Mark portMarkOfStartLine = getStartLine(competitorLegStartTime).getPortMarkWhileApproachingLine();
final Position portSideOfStartLinePosition = getOrCreateTrack(portMarkOfStartLine) final Position portSideOfStartLinePosition = getOrCreateTrack(portMarkOfStartLine)
@@ -110,7 +110,7 @@ eventRegattaRacesLegendWind=Wind Data
eventRegattaRacesLegendVideo=Video Stream eventRegattaRacesLegendVideo=Video Stream
eventRegattaRacesLegendAudio=Audio Stream eventRegattaRacesLegendAudio=Audio Stream
eventSponsorsTitle=Event Sponsorships eventSponsorsTitle=Event Sponsorships
footerCopyright=\u00A9 2014 SAP Sailing Analytics footerCopyright=\u00A9 2015 SAP Sailing Analytics
footerLanguage=SAP Sailing offered in footerLanguage=SAP Sailing offered in
footerLegal=Legal Disclosure footerLegal=Legal Disclosure
footerPrivacy=Privacy footerPrivacy=Privacy
@@ -110,7 +110,7 @@ eventRegattaRacesLegendWind=Winddaten
eventRegattaRacesLegendVideo=Video Stream eventRegattaRacesLegendVideo=Video Stream
eventRegattaRacesLegendAudio=Audio Stream eventRegattaRacesLegendAudio=Audio Stream
eventSponsorsTitle=Veranstaltungssponsoren eventSponsorsTitle=Veranstaltungssponsoren
footerCopyright=\u00A9 2014 SAP Sailing Analytics footerCopyright=\u00A9 2015 SAP Sailing Analytics
footerLanguage=SAP Sailing in footerLanguage=SAP Sailing in
footerLegal=Impressum footerLegal=Impressum
footerPrivacy=Datenschutz footerPrivacy=Datenschutz
@@ -194,5 +194,5 @@ public interface PolarDataService {
*/ */
PolynomialFunction getAngleRegressionFunction(BoatClass boatClass, LegType legType, Tack tack) throws NotEnoughDataHasBeenAddedException; 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 @Override
public void raceFinishedTracking(TrackedRace race) { public void raceFinishedLoading(TrackedRace race) {
polarDataMiner.raceFinishedTracking(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.impl.CubicEquation;
import com.sap.sailing.polars.regression.NotEnoughDataHasBeenAddedException; import com.sap.sailing.polars.regression.NotEnoughDataHasBeenAddedException;
import com.sap.sse.common.Util.Pair; 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.components.Processor;
import com.sap.sse.datamining.data.ClusterGroup; import com.sap.sse.datamining.data.ClusterGroup;
import com.sap.sse.datamining.functions.Function; 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 static final int THREAD_POOL_SIZE = Math.max((int) (Runtime.getRuntime().availableProcessors() * (3.0/4.0)), 3);
private final ThreadPoolExecutor executor = createExecutor(); private final ThreadPoolExecutor executor = createExecutor();
private final PolarSheetGenerationSettings backendPolarSheetGenerationSettings; 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>(); private final Queue<GPSFixMovingWithOriginInfo> fixQueue = new ConcurrentLinkedQueue<GPSFixMovingWithOriginInfo>();
@@ -79,14 +79,14 @@ public class PolarDataMiner {
synchronized (fixQueue) { synchronized (fixQueue) {
while (this.getQueue().size() < (EXECUTOR_QUEUE_SIZE / 10) && !fixQueue.isEmpty()) { while (this.getQueue().size() < (EXECUTOR_QUEUE_SIZE / 10) && !fixQueue.isEmpty()) {
GPSFixMovingWithOriginInfo fix = fixQueue.poll(); 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) * 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 CubicRegressionPerCourseProcessor cubicRegressionPerCourseProcessor;
private SpeedRegressionPerAngleClusterProcessor speedRegressionPerAngleClusterProcessor; private SpeedRegressionPerAngleClusterProcessor speedRegressionPerAngleClusterProcessor;
private ParallelFilteringProcessor<GPSFixMovingWithOriginInfo> preFilteringProcessor;
public PolarDataMiner() { public PolarDataMiner() {
this(PolarSheetGenerationSettingsImpl.createBackendPolarSettings()); this(PolarSheetGenerationSettingsImpl.createBackendPolarSettings());
@@ -170,15 +171,42 @@ public class PolarDataMiner {
.asList(filteringProcessor); .asList(filteringProcessor);
enrichingProcessor = new AbstractEnrichingProcessor<GPSFixMovingWithOriginInfo, GPSFixMovingWithPolarContext>( AbstractEnrichingProcessor<GPSFixMovingWithOriginInfo, GPSFixMovingWithPolarContext> enrichingProcessor = new AbstractEnrichingProcessor<GPSFixMovingWithOriginInfo, GPSFixMovingWithPolarContext>(
GPSFixMovingWithOriginInfo.class, GPSFixMovingWithPolarContext.class, executor, enrichingResultReceivers) { GPSFixMovingWithOriginInfo.class, GPSFixMovingWithPolarContext.class, executor,
enrichingResultReceivers) {
@Override @Override
protected GPSFixMovingWithPolarContext enrich(GPSFixMovingWithOriginInfo element) { protected GPSFixMovingWithPolarContext enrich(GPSFixMovingWithOriginInfo element) {
return new GPSFixMovingWithPolarContext(element.getFix(), element.getTrackedRace(), element.getCompetitor(), GPSFixMovingWithPolarContext result = null;
speedClusterGroup, angleClusterGroup); 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) { public void addFix(GPSFixMoving fix, Competitor competitor, TrackedRace trackedRace) {
GPSFixMovingWithOriginInfo fixWithOriginInfo = new GPSFixMovingWithOriginInfo(fix, trackedRace, competitor); GPSFixMovingWithOriginInfo fixWithOriginInfo = new GPSFixMovingWithOriginInfo(fix, trackedRace, competitor);
if (isReplayAndFinishedOrLive(trackedRace)) { if (trackedRace.getStatus().getStatus() == TrackedRaceStatusEnum.LOADING) {
processFix(trackedRace, fixWithOriginInfo);
} else {
/* /*
* logger.info("Received fix for replay race which has not finished loading. Queuing. " + * logger.info("Received fix for replay race which has not finished loading. Queuing. " +
* (trackedRace.getRace() != null ? trackedRace.getRace().getName() : trackedRace.getRaceIdentifier() * (trackedRace.getRace() != null ? trackedRace.getRace().getName() : trackedRace.getRaceIdentifier()
* .getRaceName())); * .getRaceName()));
*/ */
synchronized (fixesForReplayRacesWhichAreStillLoading) { synchronized (fixesForRacesWhichAreStillLoading) {
Set<GPSFixMovingWithOriginInfo> fixes = fixesForReplayRacesWhichAreStillLoading.get(trackedRace); Set<GPSFixMovingWithOriginInfo> fixes = fixesForRacesWhichAreStillLoading.get(trackedRace);
if (fixes == null) { if (fixes == null) {
fixes = new HashSet<>(); fixes = new HashSet<>();
fixesForReplayRacesWhichAreStillLoading.put(trackedRace, fixes); fixesForRacesWhichAreStillLoading.put(trackedRace, fixes);
} }
fixes.add(fixWithOriginInfo); fixes.add(fixWithOriginInfo);
} }
} else {
processFix(trackedRace, fixWithOriginInfo);
} }
} }
private void processFix(TrackedRace trackedRace, GPSFixMovingWithOriginInfo 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) { synchronized (fixQueue) {
if (executor.getQueue().size() >= EXECUTOR_QUEUE_SIZE / 10) { if (executor.getQueue().size() >= EXECUTOR_QUEUE_SIZE / 10) {
fixQueue.add(fixWithOriginInfo); fixQueue.add(fixWithOriginInfo);
} else { } 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() { public boolean isCurrentlyActiveAndOrHasQueue() {
boolean isActive = executor.getActiveCount() > 0; boolean isActive = executor.getActiveCount() > 0;
@@ -399,11 +413,11 @@ public class PolarDataMiner {
public void raceFinishedTracking(TrackedRace race) { public void raceFinishedTracking(TrackedRace race) {
Set<GPSFixMovingWithOriginInfo> fixes = null; Set<GPSFixMovingWithOriginInfo> fixes = null;
synchronized (fixesForReplayRacesWhichAreStillLoading) { synchronized (fixesForRacesWhichAreStillLoading) {
fixes = fixesForReplayRacesWhichAreStillLoading.remove(race); fixes = fixesForRacesWhichAreStillLoading.remove(race);
} }
if (fixes != null) { 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())); .getRace().getName() : race.getRaceIdentifier().getRaceName()));
for (GPSFixMovingWithOriginInfo fix : fixes) { for (GPSFixMovingWithOriginInfo fix : fixes) {
processFix(race, fix); processFix(race, fix);
@@ -1540,9 +1540,10 @@ public class RacingEventServiceImpl implements RacingEventService, ClearStateTes
} }
@Override @Override
public void statusChanged(TrackedRaceStatus newStatus) { public void statusChanged(TrackedRaceStatus newStatus, TrackedRaceStatus oldStatus) {
if (newStatus.getStatus() == TrackedRaceStatusEnum.FINISHED) { if (oldStatus.getStatus() == TrackedRaceStatusEnum.LOADING
polarDataService.raceFinishedTracking(race); && newStatus.getStatus() != TrackedRaceStatusEnum.LOADING) {
polarDataService.raceFinishedLoading(race);
} }
} }
@@ -1617,7 +1618,7 @@ public class RacingEventServiceImpl implements RacingEventService, ClearStateTes
} }
@Override @Override
public void statusChanged(TrackedRaceStatus newStatus) { public void statusChanged(TrackedRaceStatus newStatus, TrackedRaceStatus oldStatus) {
replicate(new UpdateTrackedRaceStatus(getRaceIdentifier(), newStatus)); replicate(new UpdateTrackedRaceStatus(getRaceIdentifier(), newStatus));
} }
@@ -2288,6 +2289,11 @@ public class RacingEventServiceImpl implements RacingEventService, ClearStateTes
return getBaseDomainFactory().createObjectInputStreamResolvingAgainstThisFactory(is); return getBaseDomainFactory().createObjectInputStreamResolvingAgainstThisFactory(is);
} }
@Override
public ClassLoader getDeserializationClassLoader() {
return getCombinedMasterDataClassLoader();
}
@Override @Override
public Serializable getId() { public Serializable getId() {
return getClass().getName(); return getClass().getName();
@@ -338,7 +338,7 @@ public class Util {
} }
public static String join(String separator, Iterable<? extends Named> nameds) { 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) { 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 static class MailServerReplicationTestSetUp extends AbstractServerReplicationTestSetUp<MailService, MailServiceImpl> {
public MailServerReplicationTestSetUp() {
super();
}
public MailServerReplicationTestSetUp(int servletPort) {
super(servletPort);
}
@Override @Override
protected MailServiceImpl createNewMaster() throws MailException { protected MailServiceImpl createNewMaster() throws MailException {
return createMailCountingService(true); return createMailCountingService(true);
@@ -45,7 +45,7 @@ public abstract class AbstractServerReplicationTestSetUp<ReplicableInterface ext
protected static final int DEFAULT_SERVLET_PORT = 9990; protected static final int DEFAULT_SERVLET_PORT = 9990;
private final int servletPort; private int servletPort;
protected ReplicableImpl replica; protected ReplicableImpl replica;
protected ReplicableImpl master; protected ReplicableImpl master;
protected ReplicationServiceTestImpl<ReplicableInterface> replicaReplicator; protected ReplicationServiceTestImpl<ReplicableInterface> replicaReplicator;
@@ -54,7 +54,6 @@ public abstract class AbstractServerReplicationTestSetUp<ReplicableInterface ext
protected ReplicationMasterDescriptor masterDescriptor; protected ReplicationMasterDescriptor masterDescriptor;
protected AbstractServerReplicationTestSetUp() { protected AbstractServerReplicationTestSetUp() {
servletPort = DEFAULT_SERVLET_PORT;
} }
protected AbstractServerReplicationTestSetUp(int servletPort) { protected AbstractServerReplicationTestSetUp(int servletPort) {
@@ -127,9 +126,10 @@ public abstract class AbstractServerReplicationTestSetUp<ReplicableInterface ext
masterReplicator.registerReplica(replicaDescriptor); masterReplicator.registerReplica(replicaDescriptor);
// connect to exchange host and local server running as master // connect to exchange host and local server running as master
// master server and exchange host can be two different hosts // 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, 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); Pair<ReplicationServiceTestImpl<ReplicableInterface>, ReplicationMasterDescriptor> result = new Pair<>(replicaReplicator, masterDescriptor);
logger.info("starting initial load transmission servlet for "+getClass().getName()); logger.info("starting initial load transmission servlet for "+getClass().getName());
initialLoadTestServerThread = replicaReplicator.startInitialLoadTransmissionServlet(); initialLoadTestServerThread = replicaReplicator.startInitialLoadTransmissionServlet();
@@ -197,28 +197,30 @@ public abstract class AbstractServerReplicationTestSetUp<ReplicableInterface ext
private final ReplicaDescriptor replicaDescriptor; private final ReplicaDescriptor replicaDescriptor;
private final ReplicationService masterReplicationService; private final ReplicationService masterReplicationService;
private final ReplicationMasterDescriptor masterDescriptor; private final ReplicationMasterDescriptor masterDescriptor;
private final int servletPort; private final ServerSocket ss;
public ReplicationServiceTestImpl(String exchangeName, String exchangeHost, ReplicationInstancesManager replicationInstancesManager, public ReplicationServiceTestImpl(String exchangeName, String exchangeHost, ReplicationInstancesManager replicationInstancesManager,
ReplicaDescriptor replicaDescriptor, ReplicableInterface replica, ReplicaDescriptor replicaDescriptor, ReplicableInterface replica,
ReplicableInterface master, ReplicationService masterReplicationService, ReplicationMasterDescriptor masterDescriptor, ReplicableInterface master, ReplicationService masterReplicationService, ReplicationMasterDescriptor masterDescriptor)
int servletPort)
throws IOException { throws IOException {
super(exchangeName, exchangeHost, 0, replicationInstancesManager, new SingletonReplicablesProvider(replica)); super(exchangeName, exchangeHost, 0, replicationInstancesManager, new SingletonReplicablesProvider(replica));
this.replicaDescriptor = replicaDescriptor; this.replicaDescriptor = replicaDescriptor;
this.master = master; this.master = master;
ss = new ServerSocket(0); // bind to any free port
this.masterReplicationService = masterReplicationService; this.masterReplicationService = masterReplicationService;
this.masterDescriptor = masterDescriptor; this.masterDescriptor = new ReplicationMasterDescriptorImpl(exchangeHost, exchangeName, /* messagingPort */ 0,
this.servletPort = servletPort; UUID.randomUUID().toString(), "localhost", ss.getLocalPort());
}
ReplicationMasterDescriptor getMasterDescriptor() {
return masterDescriptor;
} }
private Thread startInitialLoadTransmissionServlet() throws InterruptedException { private Thread startInitialLoadTransmissionServlet() throws InterruptedException {
final boolean[] listening = new boolean[] { false }; final boolean[] listening = new boolean[] { false };
Thread initialLoadTestServerThread = new Thread("Replication initial load test server") { Thread initialLoadTestServerThread = new Thread("Replication initial load test server") {
public void run() { public void run() {
ServerSocket ss = null;
try { try {
ss = new ServerSocket(servletPort);
synchronized (listening) { synchronized (listening) {
listening[0] = true; listening[0] = true;
listening.notifyAll(); listening.notifyAll();
@@ -70,7 +70,7 @@ public interface ReplicableWithObjectInputStream<S, O extends OperationWithResul
try { try {
final ObjectInputStream objectInputStream = createObjectInputStreamResolvingAgainstCache(is); final ObjectInputStream objectInputStream = createObjectInputStreamResolvingAgainstCache(is);
ClassLoader oldContextClassloader = Thread.currentThread().getContextClassLoader(); ClassLoader oldContextClassloader = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(getClass().getClassLoader()); Thread.currentThread().setContextClassLoader(getDeserializationClassLoader());
try { try {
initiallyFillFromInternal(objectInputStream); initiallyFillFromInternal(objectInputStream);
} finally { } 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 * Wraps <code>os</code> by an {@link ObjectOutputStream} and invokes
* {@link #serializeForInitialReplicationInternal(ObjectOutputStream)}. * {@link #serializeForInitialReplicationInternal(ObjectOutputStream)}.
@@ -92,7 +99,13 @@ public interface ReplicableWithObjectInputStream<S, O extends OperationWithResul
@Override @Override
default O readOperation(InputStream inputStream) throws IOException, ClassNotFoundException { default O readOperation(InputStream inputStream) throws IOException, ClassNotFoundException {
ClassLoader oldContextClassloader = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(getDeserializationClassLoader());
try {
return readOperationInternal(createObjectInputStreamResolvingAgainstCache(inputStream)); return readOperationInternal(createObjectInputStreamResolvingAgainstCache(inputStream));
} finally {
Thread.currentThread().setContextClassLoader(oldContextClassloader);
}
} }
@Override @Override
@@ -67,12 +67,6 @@ public class SecurityReplicationLeadingToEmailReplicationTest extends AbstractSe
} }
private class MailServerReplicationTestSetUp extends AbstractMailServiceReplicationTest.MailServerReplicationTestSetUp { private class MailServerReplicationTestSetUp extends AbstractMailServiceReplicationTest.MailServerReplicationTestSetUp {
private static final int SERVLET_PORT = 9991;
public MailServerReplicationTestSetUp() {
super(SERVLET_PORT);
}
@Override @Override
protected MailServiceImpl createNewMaster() throws MailException { protected MailServiceImpl createNewMaster() throws MailException {
return masterMailService; return masterMailService;