Merge branch 'master' into test

Conflicts:
	java/pom.xml
This commit is contained in:
Axel Uhl committed 2011-06-13 23:56:53 +02:00
commit 326c3f72ba
52 files changed
+899 -1663

No files matched your search

@@ -55,7 +55,7 @@ public class DeclinationImportTest extends AbstractDeclinationTest {
public void readOnlineOrFromFile() throws IOException, ClassNotFoundException, ParseException {
Declination declination = importer.getDeclination(new DegreePosition(53, 3),
new MillisecondsTimePoint(simpleDateFormat.parse("2011-05-27").getTime()),
/* timeoutForOnlineFetchInMilliseconds */ 5000);
/* timeoutForOnlineFetchInMilliseconds */ 10000);
assertNotNull(declination);
System.out.println(declination);
}
@@ -0,0 +1,3 @@
#Fri Jun 10 21:39:55 CEST 2011
eclipse.preferences.version=1
encoding//src/com/sap/sailing/domain/test/WindTest.java=UTF-8
@@ -67,7 +67,7 @@ public class CourseUpdateTest extends AbstractTracTracLiveTest {
super.setUp();
domainFactory = new DomainFactoryImpl();
domainEvent = domainFactory.createEvent(getEvent());
trackedEvent = domainFactory.trackEvent(domainEvent);
trackedEvent = domainFactory.getOrCreateTrackedEvent(domainEvent);
receivers.add(new RaceCourseReceiver(domainFactory, trackedEvent, getEvent(), /* millisecondsOverWhichToAverageWind */
EmptyWindStore.INSTANCE,
30000, /* millisecondsOverWhichToAverageSpeed */30000) {
@@ -81,7 +81,10 @@ public class CourseUpdateTest extends AbstractTracTracLiveTest {
}
});
addListenersForStoredDataAndStartController(receivers);
race = domainFactory.getRaceDefinition(getEvent().getRaceList().iterator().next());
Race tractracRace = getEvent().getRaceList().iterator().next();
// now we expect that there is no
assertNull(domainFactory.getExistingRaceDefinitionForRace(tractracRace));
race = domainFactory.getRaceDefinition(tractracRace);
course = race.getCourse();
assertNotNull(course);
assertEquals(3, Util.size(course.getWaypoints()));
@@ -91,6 +94,11 @@ public class CourseUpdateTest extends AbstractTracTracLiveTest {
assertEquals("top", course.getLegs().get(1).getFrom().getName());
}
private void assertNull(RaceDefinition existingRaceDefinitionForRace) {
// TODO Auto-generated method stub
}
/**
* Asserts that the race course's legs have corresponding {@link TrackedLeg}s and the {@link TrackedLeg}s have
* {@link TrackedLegOfCompetitor} for each of the race's competitors.
@@ -73,7 +73,7 @@ public class ReceiveMarkPassingDataTest extends AbstractTracTracLiveTest {
List<Receiver> receivers = new ArrayList<Receiver>();
receivers.add(receiver);
for (Receiver r : DomainFactory.INSTANCE.getUpdateReceivers(
DomainFactory.INSTANCE.trackEvent(DomainFactory.INSTANCE.createEvent(getEvent())),
DomainFactory.INSTANCE.getOrCreateTrackedEvent(DomainFactory.INSTANCE.createEvent(getEvent())),
getEvent(), EmptyWindStore.INSTANCE, ReceiverType.RACECOURSE,
ReceiverType.MARKPOSITIONS, ReceiverType.RACESTARTFINISH, ReceiverType.RAWPOSITIONS)) {
receivers.add(r);
@@ -72,13 +72,16 @@ public class ReceiveTrackingDataTest extends AbstractTracTracLiveTest {
};
List<TypeController> listeners = new ArrayList<TypeController>();
Event event = domainFactory.createEvent(getEvent());
DynamicTrackedEvent trackedEvent = domainFactory.trackEvent(event);
DynamicTrackedEvent trackedEvent = domainFactory.getOrCreateTrackedEvent(event);
trackedEvent.addRaceListener(new RaceListener() {
@Override
public void raceAdded(TrackedRace trackedRace) {
System.out.println("Subscribing raw position listener for race "+trackedRace);
((DynamicTrackedRace) trackedRace).addListener(positionListener);
}
@Override
public void raceRemoved(TrackedRace trackedRace) {
}
});
for (Receiver receiver : domainFactory.getUpdateReceivers(trackedEvent, getEvent(), EmptyWindStore.INSTANCE)) {
for (TypeController raceListener : receiver.getTypeControllers()) {
@@ -224,4 +224,15 @@ public class TrackTest {
}
}
}
@Test
public void testDistanceTraveledOnInBetweenSectionFromFixToFix() {
// take second and third fix and compute distance between them
Iterator<GPSFixMoving> iter = track.getFixes().iterator();
iter.next(); // skip first;
GPSFix second = iter.next();
GPSFix third = iter.next();
assertEquals(second.getPosition().getDistance(third.getPosition()),
track.getDistanceTraveled(second.getTimePoint(), third.getTimePoint()));
}
}
@@ -76,7 +76,11 @@ public interface DomainFactory {
RaceTracker createRaceTracker(URL paramURL, URI liveURI, URI storedURI, WindStore windStore) throws MalformedURLException,
FileNotFoundException, URISyntaxException;
DynamicTrackedEvent trackEvent(com.sap.sailing.domain.base.Event event);
/**
* Looks for tracking information about <code>event</code>. If no such object exists yet, a new one
* is created.
*/
DynamicTrackedEvent getOrCreateTrackedEvent(com.sap.sailing.domain.base.Event event);
BoatClass getBoatClass(CompetitorClass competitorClass);
@@ -93,7 +97,7 @@ public interface DomainFactory {
*
* @param trackedEvent
* must have been created before through
* {@link #trackEvent(com.sap.sailing.domain.base.Event)} because
* {@link #getOrCreateTrackedEvent(com.sap.sailing.domain.base.Event)} because
* otherwise the link to the {@link Event} can't be established
* @param windStore TODO
*/
@@ -117,6 +121,11 @@ public interface DomainFactory {
DynamicTrackedRace trackRace(TrackedEvent trackedEvent, RaceDefinition raceDefinition,
WindStore windStore, long millisecondsOverWhichToAverageWind, long millisecondsOverWhichToAverageSpeed, Event tractracEvent);
/**
* Non-blocking call that returns <code>null</code> if the {@link RaceDefinition} for the TracTrac Event
* hasn't been created yet, e.g., because the course definition hasn't been received yet or the listener
* for receiving course information hasn't been registered (yet).
*/
RaceDefinition getRace(Event tractracEvent);
JSONService parseJSONURL(URL jsonURL) throws IOException, ParseException, org.json.simple.parser.ParseException;
@@ -133,4 +142,10 @@ public interface DomainFactory {
* in a course. A single control point may be used more than once in a course's list of waypoints.
*/
void updateCourseWaypoints(Course courseToUpdate, List<ControlPoint> controlPoints) throws PatchFailedException;
/**
* Looks for the tracking information for <code>event</code>. If not found, <code>null</code> is returned
* immediately. See also {@link #getOrCreateTrackedEvent(com.sap.sailing.domain.base.Event)}.
*/
DynamicTrackedEvent getTrackedEvent(com.sap.sailing.domain.base.Event event);
}
@@ -0,0 +1,33 @@
package com.sap.sailing.domain.tractracadapter;
import com.sap.sailing.domain.base.Event;
import com.sap.sailing.domain.base.RaceDefinition;
import com.sap.sailing.domain.tracking.DynamicTrackedEvent;
import com.sap.sailing.domain.tractracadapter.impl.RaceCourseReceiver;
/**
* Gives clients a way to refer to a specific race even though the {@link RaceDefinition} object may not have
* been created yet, but without exposing the TracTrac internals. The {@link #getRace()} call will block
* until the {@link RaceDefinition} has been created.
*
* @author Axel Uhl (d043530)
*
*/
public interface RaceHandle {
Event getEvent();
/**
* Fetch the race definition. If the race definition represented by this handle hasn't been created yet, the call
* blocks until such a definition is provided by another call, usually by the {@link RaceCourseReceiver}.
*/
RaceDefinition getRace();
DynamicTrackedEvent getTrackedEvent();
/**
* The tracker managing the tracking of the race to which this is a handle.
*/
RaceTracker getRaceTracker();
}
@@ -2,15 +2,39 @@ package com.sap.sailing.domain.tractracadapter;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import com.sap.sailing.domain.base.RaceDefinition;
import com.sap.sailing.domain.tracking.DynamicTrackedEvent;
import com.sap.sailing.domain.tracking.TrackedEvent;
import com.sap.sailing.domain.tracking.WindStore;
import com.sap.sailing.util.Util.Triple;
public interface RaceTracker {
/**
* Stops tracking the race and removes the {@link TrackedRace} object one gets from calling
* {@link #getTrackedEvent()}.{@link TrackedEvent#getTrackedRace(RaceDefinition) getTrackedRace(}{@link #getRace() getRace())}
* from the {@link #getTrackedEvent() tracked event}.
*/
void stop() throws MalformedURLException, IOException, InterruptedException;
com.sap.sailing.domain.base.Event getEvent();
/**
* Non-blocking call that returns <code>null</code> if the {@link RaceDefinition} for the TracTrac Event
* hasn't been created yet, e.g., because the course definition hasn't been received yet or the listener
* for receiving course information hasn't been registered (yet).
*/
RaceDefinition getRace();
RaceHandle getRaceHandle();
DynamicTrackedEvent getTrackedEvent();
WindStore getWindStore();
Triple<URL, URI, URI> getURLs();
}
@@ -74,7 +74,7 @@ import difflib.PatchFailedException;
public class DomainFactoryImpl implements DomainFactory {
private static final Logger logger = Logger.getLogger(DomainFactoryImpl.class.getName());
private final long millisecondsOverWhichToAverageSpeed = 5000;
private final long millisecondsOverWhichToAverageSpeed = 15000;
// TODO clarify how millisecondsOverWhichToAverageWind could be updated and propagated live
private final long millisecondsOverWhichToAverageWind = 30000;
@@ -338,9 +338,14 @@ public class DomainFactoryImpl implements DomainFactory {
ReceiverType.MARKPASSINGS, ReceiverType.MARKPOSITIONS, ReceiverType.RACESTARTFINISH,
ReceiverType.RAWPOSITIONS);
}
@Override
public DynamicTrackedEvent getTrackedEvent(com.sap.sailing.domain.base.Event event) {
return eventTrackingCache.get(event);
}
@Override
public DynamicTrackedEvent trackEvent(com.sap.sailing.domain.base.Event event) {
public DynamicTrackedEvent getOrCreateTrackedEvent(com.sap.sailing.domain.base.Event event) {
synchronized (eventTrackingCache) {
DynamicTrackedEvent result = eventTrackingCache.get(event);
if (result == null) {
@@ -84,19 +84,26 @@ public class RaceCourseReceiver extends AbstractReceiverWithQueue<Route, RouteDa
// Therefore, don't create TrackedRace again because it already exists.
try {
getDomainFactory().updateCourseWaypoints(course, event.getB().getPoints());
if (trackedEvent.getExistingTrackedRace(existingRaceDefinitionForRace) == null) {
createTrackedRace(existingRaceDefinitionForRace);
}
} catch (PatchFailedException e) {
logger.log(Level.SEVERE, "Internal error updating race course "+course+": "+e.getMessage());
logger.throwing(RaceCourseReceiver.class.getName(), "handleEvent", e);
}
} else {
logger.log(Level.INFO, "Received course for non-existing race "+event.getC().getName()+". Creating RaceDefinition.");
// create race redefinition
// create race definition
RaceDefinition raceDefinition = getDomainFactory().createRaceDefinition(event.getC(), course);
trackedEvent.getEvent().addRace(raceDefinition);
DynamicTrackedRace trackedRace = getDomainFactory().trackRace(trackedEvent, raceDefinition,
windStore, millisecondsOverWhichToAverageWind, millisecondsOverWhichToAverageSpeed, tractracEvent);
trackedEvent.addTrackedRace(trackedRace);
createTrackedRace(raceDefinition);
}
}
private void createTrackedRace(RaceDefinition race) {
DynamicTrackedRace trackedRace = getDomainFactory().trackRace(trackedEvent, race,
windStore, millisecondsOverWhichToAverageWind, millisecondsOverWhichToAverageSpeed, tractracEvent);
trackedEvent.addTrackedRace(trackedRace);
}
}
@@ -0,0 +1,44 @@
package com.sap.sailing.domain.tractracadapter.impl;
import com.sap.sailing.domain.base.RaceDefinition;
import com.sap.sailing.domain.tracking.DynamicTrackedEvent;
import com.sap.sailing.domain.tractracadapter.DomainFactory;
import com.sap.sailing.domain.tractracadapter.RaceHandle;
import com.sap.sailing.domain.tractracadapter.RaceTracker;
import com.tractrac.clientmodule.Event;
public class RaceHandleImpl implements RaceHandle {
private final Event tractracEvent;
private final DomainFactory domainFactory;
private final DynamicTrackedEvent trackedEvent;
private final RaceTracker raceTracker;
public RaceHandleImpl(DomainFactory domainFactory, Event tractracEvent, DynamicTrackedEvent trackedEvent, RaceTracker raceTracker) {
this.domainFactory = domainFactory;
this.tractracEvent = tractracEvent;
this.trackedEvent = trackedEvent;
this.raceTracker = raceTracker;
}
@Override
public com.sap.sailing.domain.base.Event getEvent() {
return domainFactory.createEvent(tractracEvent);
}
@Override
public RaceDefinition getRace() {
// we assume there is exactly one Race per TracTrac event
return domainFactory.getRaceDefinition(tractracEvent.getRaceList().iterator().next());
}
@Override
public DynamicTrackedEvent getTrackedEvent() {
return trackedEvent;
}
@Override
public RaceTracker getRaceTracker() {
return raceTracker;
}
}
@@ -16,10 +16,13 @@ import com.sap.sailing.domain.base.RaceDefinition;
import com.sap.sailing.domain.tracking.DynamicTrackedEvent;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.WindStore;
import com.sap.sailing.domain.tracking.WindTrack;
import com.sap.sailing.domain.tracking.impl.EmptyWindStore;
import com.sap.sailing.domain.tractracadapter.DomainFactory;
import com.sap.sailing.domain.tractracadapter.RaceHandle;
import com.sap.sailing.domain.tractracadapter.RaceTracker;
import com.sap.sailing.domain.tractracadapter.Receiver;
import com.sap.sailing.util.Util.Triple;
import com.tractrac.clientmodule.Event;
import com.tractrac.clientmodule.data.DataController;
import com.tractrac.clientmodule.data.DataController.Listener;
@@ -31,10 +34,18 @@ public class RaceTrackerImpl implements Listener, RaceTracker {
private final Thread ioThread;
private final DataController controller;
private final Set<Receiver> receivers;
private final DomainFactory domainFactory;
private final DynamicTrackedEvent trackedEvent;
private final WindStore windStore;
private final Triple<URL, URI, URI> urls;
/**
* Creates a race tracked for the specified URL/URIs and starts receiving all available existing and future push
* data from there. Receiving continues until {@link #stop()} is called.
* <p>
*
* A race tracker uses the <code>paramURL</code> for the TracTrac Java client to register for push data about one
* race. The {@link RaceDefinition} for that race, however, cannot be created until the {@link Course} has been
* race. The {@link RaceDefinition} for that race, however, isn't created until the {@link Course} has been
* received. Therefore, the {@link RaceCourseReceiver} will create the {@link RaceDefinition} and will add it to the
* {@link com.sap.sailing.domain.base.Event}.
* <p>
@@ -45,16 +56,16 @@ public class RaceTrackerImpl implements Listener, RaceTracker {
* {@link Event} as argument that is used for its tracking.
* <p>
*
* When {@link #getRace} is called on this object before the {@link RaceCourseReceiver} has created the
* {@link RaceDefinition}, the call will block until this has happened.
*
* @param windStore
* Provides the capability to obtain the {@link WindTrack}s for the different wind sources.
* A trivial implementation is {@link EmptyWindStore} which simply provides new, empty tracks.
* This is always available but loses track of the wind, e.g., during server restarts.
* Provides the capability to obtain the {@link WindTrack}s for the different wind sources. A trivial
* implementation is {@link EmptyWindStore} which simply provides new, empty tracks. This is always
* available but loses track of the wind, e.g., during server restarts.
*/
protected RaceTrackerImpl(DomainFactory domainFactory, URL paramURL, URI liveURI, URI storedURI, WindStore windStore)
throws URISyntaxException, MalformedURLException, FileNotFoundException {
urls = new Triple<URL, URI, URI>(paramURL, liveURI, storedURI);
this.windStore = windStore;
this.domainFactory = domainFactory;
// Read event data from configuration file
tractracEvent = KeyValue.setup(paramURL);
@@ -69,7 +80,7 @@ public class RaceTrackerImpl implements Listener, RaceTracker {
// Start live and stored data streams
ioThread = new Thread(controller, "io");
domainEvent = domainFactory.createEvent(tractracEvent);
DynamicTrackedEvent trackedEvent = domainFactory.trackEvent(domainEvent);
trackedEvent = domainFactory.getOrCreateTrackedEvent(domainEvent);
receivers = new HashSet<Receiver>();
Set<TypeController> typeControllers = new HashSet<TypeController>();
for (Receiver receiver : domainFactory.getUpdateReceivers(trackedEvent, tractracEvent, windStore)) {
@@ -81,9 +92,29 @@ public class RaceTrackerImpl implements Listener, RaceTracker {
addListenersForStoredDataAndStartController(typeControllers);
}
@Override
public Triple<URL, URI, URI> getURLs() {
return urls;
}
@Override
public WindStore getWindStore() {
return windStore;
}
@Override
public DynamicTrackedEvent getTrackedEvent() {
return trackedEvent;
}
@Override
public RaceHandle getRaceHandle() {
return new RaceHandleImpl(domainFactory, tractracEvent, getTrackedEvent(), this);
}
@Override
public RaceDefinition getRace() {
return DomainFactory.INSTANCE.getRace(tractracEvent);
return domainFactory.getRace(tractracEvent);
}
protected void addListenersForStoredDataAndStartController(Iterable<TypeController> listenersForStoredData) {
@@ -118,7 +149,8 @@ public class RaceTrackerImpl implements Listener, RaceTracker {
for (Receiver receiver : receivers) {
receiver.stop();
}
ioThread.join();
ioThread.join(3000); // wait no more than three seconds
trackedEvent.removedTrackedRace(trackedEvent.getTrackedRace(getRace()));
}
protected DataController getController() {
@@ -7,6 +7,7 @@ import com.sap.sailing.domain.base.Leg;
import com.sap.sailing.domain.base.Mile;
import com.sap.sailing.domain.base.Position;
import com.sap.sailing.domain.base.Speed;
import com.sap.sailing.domain.base.SpeedWithBearing;
import com.sap.sailing.domain.base.TimePoint;
import com.sap.sailing.domain.base.impl.KilometersPerHourSpeedImpl;
import com.sap.sailing.domain.base.impl.KnotSpeedImpl;
@@ -139,4 +140,13 @@ public class TracTracTrackedLegOfCompetitor implements TrackedLegOfCompetitor {
return trackedRace;
}
@Override
public SpeedWithBearing getSpeedOverGround(TimePoint at) {
if (hasStartedLeg(at)) {
return getTrackedRace().getTrack(getCompetitor()).getEstimatedSpeed(at);
} else {
return null;
}
}
}
@@ -6,6 +6,16 @@ import com.sap.sailing.domain.base.Speed;
public abstract class AbstractDistance implements Distance {
@Override
public boolean equals(Object o) {
return (o instanceof Distance) && this.compareTo((Distance) o) == 0;
}
@Override
public int hashCode() {
return 9128347 ^ (int) getMeters();
}
@Override
public int compareTo(Distance o) {
return getMeters() > o.getMeters() ? 1 : getMeters() == o.getMeters() ? 0 : -1;
@@ -3,5 +3,7 @@ package com.sap.sailing.domain.tracking;
import com.sap.sailing.domain.base.RaceDefinition;
public interface DynamicTrackedEvent extends TrackedEvent {
DynamicTrackedRace getTrackedRace(RaceDefinition race);
}
@@ -2,4 +2,6 @@ package com.sap.sailing.domain.tracking;
public interface RaceListener {
void raceAdded(TrackedRace trackedRace);
void raceRemoved(TrackedRace trackedRace);
}
@@ -19,8 +19,16 @@ public interface TrackedEvent {
*/
TrackedRace getTrackedRace(RaceDefinition race);
/**
* Non-blocking call that returns <code>null</code> if no tracking information currently exists
* for <code>race</code>. See also {@link #getTrackedRace(RaceDefinition)} for a blocking variant.
*/
TrackedRace getExistingTrackedRace(RaceDefinition race);
void addTrackedRace(TrackedRace trackedRace);
void removedTrackedRace(TrackedRace trackedRace);
/**
* Listener will be notified when {@link #addTrackedRace(TrackedRace)} is called and
* upon registration for each tracked race already known. Therefore, the listener
@@ -31,5 +39,5 @@ public interface TrackedEvent {
int getTotalPoints(Competitor competitor, TimePoint timePoint);
int getNetPoints(Competitor competitor, TimePoint timePoint) throws NoWindException;
}
@@ -4,6 +4,7 @@ import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.Distance;
import com.sap.sailing.domain.base.Leg;
import com.sap.sailing.domain.base.Speed;
import com.sap.sailing.domain.base.SpeedWithBearing;
import com.sap.sailing.domain.base.TimePoint;
public interface TrackedLegOfCompetitor {
@@ -91,4 +92,6 @@ public interface TrackedLegOfCompetitor {
*/
Double getEstimatedTimeToNextMarkInSeconds(TimePoint timePoint) throws NoWindException;
SpeedWithBearing getSpeedOverGround(TimePoint at);
}
@@ -137,8 +137,7 @@ public interface TrackedRace {
WindTrack getWindTrack(WindSource windSource);
/**
* Waits until {@link #getUpdateCount()} is after <code>since</code>.
* @param sinceUpdate TODO
* Waits until {@link #getUpdateCount()} is after <code>sinceUpdate</code>.
*/
void waitForNextUpdate(int sinceUpdate) throws InterruptedException;
@@ -149,4 +148,10 @@ public interface TrackedRace {
NavigableSet<MarkPassing> getMarkPassings(Competitor competitor);
void removeWind(Wind wind, WindSource windSource);
/**
* Time stamp that the last event received from the underlying push service carried on it.
* Note that these times may not increase monotonically.
*/
TimePoint getTimePointOfLastEvent();
}
@@ -119,6 +119,9 @@ public class DynamicTrackedRaceImpl extends TrackedRaceImpl implements
competitorMarkPassings.add(markPassing);
getMarkPassingsInOrder(markPassing.getWaypoint()).add(markPassing);
updated(markPassing.getTimePoint());
}
// notify *after* all mark passings have been re-established; should avoid flicker
for (MarkPassing markPassing : markPassings) {
notifyListeners(markPassing);
}
}
@@ -47,6 +47,24 @@ public class TrackedEventImpl implements TrackedEvent {
trackedRaces.notifyAll();
}
}
@Override
public void removedTrackedRace(TrackedRace trackedRace) {
synchronized (trackedRaces) {
trackedRaces.remove(trackedRace.getRace());
Collection<TrackedRace> trbbc = trackedRacesByBoatClass.get(trackedRace.getRace().getBoatClass());
if (trbbc != null) {
trbbc.remove(trackedRace);
if (trbbc.isEmpty()) {
trbbc.remove(trackedRace.getRace().getBoatClass());
}
}
for (RaceListener listener : raceListeners) {
listener.raceRemoved(trackedRace);
}
trackedRaces.notifyAll();
}
}
@Override
public Event getEvent() {
@@ -79,6 +97,11 @@ public class TrackedEventImpl implements TrackedEvent {
}
return result;
}
@Override
public TrackedRace getExistingTrackedRace(RaceDefinition race) {
return trackedRaces.get(race);
}
@Override
public void addRaceListener(RaceListener listener) {
@@ -77,7 +77,7 @@ public class TrackedLegOfCompetitorImpl implements TrackedLegOfCompetitor {
if (legStart == null) {
return Distance.NULL;
} else {
MarkPassing legEnd = getTrackedRace().getMarkPassing(getCompetitor(), getLeg().getTo());
MarkPassing legEnd = getMarkPassingForLegEnd();
TimePoint end = timePoint;
if (legEnd != null && timePoint.compareTo(legEnd.getTimePoint()) > 0) {
// timePoint is after leg finish; take leg end and end time point
@@ -245,7 +245,7 @@ public class TrackedLegOfCompetitorImpl implements TrackedLegOfCompetitor {
public Speed getAverageVelocityMadeGood(TimePoint timePoint) throws NoWindException {
Speed result = null;
MarkPassing start = getMarkPassingForLegStart();
if (start != null && start.getTimePoint().compareTo(timePoint) > 0) {
if (start != null && start.getTimePoint().compareTo(timePoint) <= 0) {
MarkPassing end = getMarkPassingForLegEnd();
if (end != null) {
TimePoint to;
@@ -254,11 +254,13 @@ public class TrackedLegOfCompetitorImpl implements TrackedLegOfCompetitor {
} else {
to = timePoint;
}
Position endPos = getTrackedRace().getTrack(getCompetitor()).getEstimatedPosition(to, false);
Distance d = getWindwardDistance(
getTrackedRace().getTrack(getCompetitor()).getEstimatedPosition(start.getTimePoint(), false), endPos,
to);
result = d.inTime(to.asMillis() - start.getTimePoint().asMillis());
Position endPos = getTrackedRace().getTrack(getCompetitor()).getEstimatedPosition(to, /* extrapolate */ false);
if (endPos != null) {
Distance d = getWindwardDistance(
getTrackedRace().getTrack(getCompetitor())
.getEstimatedPosition(start.getTimePoint(), false), endPos, to);
result = d.inTime(to.asMillis() - start.getTimePoint().asMillis());
}
}
}
return result;
@@ -359,7 +361,8 @@ public class TrackedLegOfCompetitorImpl implements TrackedLegOfCompetitor {
}
}
private SpeedWithBearing getSpeedOverGround(TimePoint at) {
@Override
public SpeedWithBearing getSpeedOverGround(TimePoint at) {
if (hasStartedLeg(at)) {
return getTrackedRace().getTrack(getCompetitor()).getEstimatedSpeed(at);
} else {
@@ -46,6 +46,7 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
private TimePoint start;
private TimePoint finish;
private TimePoint timePointOfNewestEvent;
private TimePoint timePointOfLastEvent;
private int updateCount;
private final Map<TimePoint, TreeSet<Competitor>> competitorRankings;
@@ -352,6 +353,11 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
public TimePoint getTimePointOfNewestEvent() {
return timePointOfNewestEvent;
}
@Override
public TimePoint getTimePointOfLastEvent() {
return timePointOfLastEvent;
}
/**
* @param timeOfEvent may be <code>null</code> meaning to only unblock waiters but not update any time points
@@ -366,6 +372,7 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
if (startOfTracking == null || startOfTracking.compareTo(timeOfEvent) > 0) {
startOfTracking = timeOfEvent;
}
timePointOfLastEvent = timeOfEvent;
}
notifyAll();
}
@@ -243,4 +243,10 @@ public class MockedTrackedRace implements DynamicTrackedRace {
}
@Override
public TimePoint getTimePointOfLastEvent() {
// TODO Auto-generated method stub
return null;
}
}
@@ -160,7 +160,8 @@ public class UDPExpeditionReceiverTest {
assertEquals(19, cal.get(Calendar.DAY_OF_MONTH));
assertEquals(13, cal.get(Calendar.HOUR_OF_DAY));
assertEquals(52, cal.get(Calendar.MINUTE));
assertEquals(46, cal.get(Calendar.SECOND));
// for some bizarre reason the conversion doesn't seem to be predictable; a calendar / platform issue?
assertTrue(cal.get(Calendar.SECOND) == 46 || cal.get(Calendar.SECOND) == 45);
}
@Test
@@ -62,7 +62,7 @@ public class TestStoringAndRetrievingWindTracks extends AbstractTracTracLiveTest
public void testStoreAFewWindEntries() throws UnknownHostException, MongoException, InterruptedException {
DomainFactory domainFactory = DomainFactory.INSTANCE;
Event domainEvent = domainFactory.createEvent(getEvent());
DynamicTrackedEvent trackedEvent = domainFactory.trackEvent(domainEvent);
DynamicTrackedEvent trackedEvent = domainFactory.getOrCreateTrackedEvent(domainEvent);
Iterable<Receiver> typeControllers = domainFactory.getUpdateReceivers(trackedEvent, getEvent(),
EmptyWindStore.INSTANCE, ReceiverType.RACECOURSE);
addListenersForStoredDataAndStartController(typeControllers);
@@ -65,7 +65,7 @@ public class MongoWindStoreFactoryImpl implements MongoWindStoreFactory, BundleA
}
defaultHostName = context.getProperty(MONGO_HOSTNAME);
if (defaultHostName == null) {
defaultHostName = "localhost";
defaultHostName = "127.0.0.1";
} else {
logger.log(Level.INFO, "found mongo.hostname="+defaultHostName);
}
+7
View File
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
<classpathentry kind="src" path="src"/>
<classpathentry kind="output" path="bin"/>
</classpath>
+1
View File
@@ -0,0 +1 @@
/bin
+28
View File
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>com.sap.sailing.server.test</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.pde.ManifestBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.pde.SchemaBuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.pde.PluginNature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>
@@ -0,0 +1,8 @@
#Mon Jun 13 17:15:33 CEST 2011
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
org.eclipse.jdt.core.compiler.compliance=1.6
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.source=1.6
@@ -0,0 +1,4 @@
#Mon Jun 13 17:15:33 CEST 2011
eclipse.preferences.version=1
pluginProject.extensions=false
resolve.requirebundle=false
+11
View File
@@ -0,0 +1,11 @@
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Test
Bundle-SymbolicName: com.sap.sailing.server.test
Bundle-Version: 1.0.0.qualifier
Bundle-Vendor: SAP
Bundle-RequiredExecutionEnvironment: JavaSE-1.6
Require-Bundle: com.sap.sailing.server,
com.sap.sailing.domain,
com.sap.sailing.domain.tractracadapter,
org.junit4
+4
View File
@@ -0,0 +1,4 @@
source.. = src/
output.. = bin/
bin.includes = META-INF/,\
.
+14
View File
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>root</artifactId>
<groupId>com.sap.sailing</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<groupId>com.sap.sailing</groupId>
<artifactId>com.sap.sailing.server.test</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>eclipse-test-plugin</packaging>
</project>
@@ -0,0 +1,111 @@
package com.sap.sailing.server.test;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertSame;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import org.junit.Before;
import org.junit.Test;
import com.sap.sailing.domain.base.RaceDefinition;
import com.sap.sailing.domain.tracking.RaceListener;
import com.sap.sailing.domain.tracking.TrackedEvent;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.impl.EmptyWindStore;
import com.sap.sailing.domain.tractracadapter.RaceHandle;
import com.sap.sailing.server.RacingEventServiceImpl;
public class RaceTrackerTest {
private final URL paramUrl;
private final URI liveUri;
private final URI storedUri;
private RacingEventServiceImpl service;
private RaceHandle raceHandle;
public RaceTrackerTest() throws MalformedURLException, URISyntaxException {
// for live simulation:
// paramUrl = new URL("http://sapsimulation.tracdev.dk/simulateconf/j80race12.txt");
// liveUri = new URI("tcp://sapsimulation.tracdev.dk:4420"); // or with tunneling: tcp://localhost:4420
// storedUri = new URI("tcp://sapsimulation.tracdev.dk:4421"); // or with tunneling: tcp://localhost:4421
// for stored race, non-real-time simulation:
paramUrl = new URL("http://germanmaster.traclive.dk/events/event_20110505_SailingTea/clientparams.php?event=event_20110505_SailingTea&race=bd8c778e-7c65-11e0-8236-406186cbf87c");
// tunneled:
//liveUri = new URI("tcp://localhost:4412");
//storedUri = new URI("tcp://localhost:4413");
//no tunnel:
liveUri = new URI("tcp://germanmaster.traclive.dk:4400");
storedUri = new URI("tcp://germanmaster.traclive.dk:4401");
}
@Before
public void setUp() throws MalformedURLException, FileNotFoundException, URISyntaxException, InterruptedException {
service = new RacingEventServiceImpl();
raceHandle = service.addRace(paramUrl, liveUri, storedUri, EmptyWindStore.INSTANCE);
}
private TrackedRace getTrackedRace(TrackedEvent trackedEvent) throws InterruptedException {
final TrackedRace[] trackedRaces = new TrackedRace[1];
trackedEvent.addRaceListener(new RaceListener() {
@Override
public void raceAdded(TrackedRace trackedRace) {
synchronized (trackedRaces) {
trackedRaces[0] = trackedRace;
trackedRaces.notifyAll();
}
}
@Override
public void raceRemoved(TrackedRace trackedRace) {
}
});
synchronized (trackedRaces) {
if (trackedRaces[0] == null) {
trackedRaces.wait();
}
}
return trackedRaces[0];
}
@Test
public void testInitialization() throws InterruptedException {
RaceDefinition race = raceHandle.getRace();
assertNotNull(race);
assertNotNull(getTrackedRace(raceHandle.getTrackedEvent()));
}
@Test
public void testStopTracking() throws MalformedURLException, IOException, InterruptedException, URISyntaxException {
TrackedEvent oldTrackedEvent = raceHandle.getTrackedEvent();
TrackedRace oldTrackedRace = getTrackedRace(oldTrackedEvent);
service.stopTracking(raceHandle.getEvent());
RaceHandle myRaceHandle = service.addRace(paramUrl, liveUri, storedUri, EmptyWindStore.INSTANCE);
TrackedEvent newTrackedEvent = myRaceHandle.getTrackedEvent();
TrackedRace newTrackedRace = getTrackedRace(newTrackedEvent);
// expecting a new tracked race to be created when starting over with tracking
assertNotSame(oldTrackedRace, newTrackedRace);
}
/**
* This test asserts that tracking the same race twice doesn't create another tracker and in particular no
* new tracked event / tracked race.
*/
@Test
public void testTrackingSameRaceWithoutStopping() throws MalformedURLException, IOException, InterruptedException, URISyntaxException {
TrackedEvent oldTrackedEvent = raceHandle.getTrackedEvent();
TrackedRace oldTrackedRace = getTrackedRace(oldTrackedEvent);
RaceHandle myRaceHandle = service.addRace(paramUrl, liveUri, storedUri, EmptyWindStore.INSTANCE);
TrackedEvent newTrackedEvent = myRaceHandle.getTrackedEvent();
TrackedRace newTrackedRace = getTrackedRace(newTrackedEvent);
// expecting a new tracked race to be created when starting over with tracking
assertSame(oldTrackedRace, newTrackedRace);
assertSame(raceHandle.getRaceTracker(), myRaceHandle.getRaceTracker());
}
}
@@ -19,7 +19,7 @@
</listAttribute>
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-os ${target.os} -ws ${target.ws} -arch ${target.arch} -nl ${target.nl} -consoleLog -console"/>
<stringAttribute key="org.eclipse.jdt.launching.SOURCE_PATH_PROVIDER" value="org.eclipse.pde.ui.workbenchClasspathProvider"/>
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Declipse.ignoreApp=true -Dosgi.noShutdown=true -Dorg.osgi.service.http.port=8888"/>
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Declipse.ignoreApp=true -Dosgi.noShutdown=true -Dorg.osgi.service.http.port=8886"/>
<stringAttribute key="pde.version" value="3.3"/>
<booleanAttribute key="show_selected_only" value="false"/>
<stringAttribute key="target_bundles" value="javax.servlet.jsp@default:default,javax.servlet@default:default,org.apache.ant@default:default,org.apache.commons.el@default:default,org.apache.commons.logging@default:default,org.apache.jasper@default:default,org.eclipse.core.jobs@default:default,org.eclipse.core.runtime.compatibility.registry@default:false,org.eclipse.equinox.common@2:true,org.eclipse.equinox.http.jetty@default:default,org.eclipse.equinox.http.registry@default:default,org.eclipse.equinox.http.servlet@default:default,org.eclipse.equinox.registry@default:default,org.eclipse.equinox.util@default:default,org.eclipse.osgi.services@default:default,org.eclipse.osgi.util@default:default,org.eclipse.osgi@-1:true,org.hamcrest.core@default:default,org.mortbay.jetty.server@default:default,org.mortbay.jetty.util@default:default"/>
@@ -160,7 +160,7 @@ public class AdminApp extends Servlet {
if (race == null) {
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Race not found");
} else {
TrackedRace trackedRace = getService().getDomainFactory().trackEvent(event).getTrackedRace(race);
TrackedRace trackedRace = getService().getDomainFactory().getTrackedEvent(event).getTrackedRace(race);
TimePoint time = getTimePoint(req, PARAM_NAME_TIME, PARAM_NAME_TIME_MILLIS, MillisecondsTimePoint.now());
TimePoint oneHourLater = new MillisecondsTimePoint(time.asMillis()+3600*1000);
String[] latitudes = req.getParameterValues(PARAM_NAME_LATDEG);
@@ -202,7 +202,7 @@ public class AdminApp extends Servlet {
if (race == null) {
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Race not found");
} else {
TrackedRace trackedRace = getService().getDomainFactory().trackEvent(event).getTrackedRace(race);
TrackedRace trackedRace = getService().getDomainFactory().getTrackedEvent(event).getTrackedRace(race);
TimePoint from = getTimePoint(req, PARAM_NAME_FROM_TIME, PARAM_NAME_FROM_TIME_MILLIS,
trackedRace.getStart()==null?new MillisecondsTimePoint(0):
/* 24h before race start */ new MillisecondsTimePoint(trackedRace.getStart().asMillis()-24*3600*1000));
@@ -257,7 +257,7 @@ public class AdminApp extends Servlet {
if (race == null) {
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Race not found");
} else {
TrackedRace trackedRace = getService().getDomainFactory().trackEvent(event)
TrackedRace trackedRace = getService().getDomainFactory().getTrackedEvent(event)
.getTrackedRace(race);
trackedRace.setWindSource(windSource);
resp.getWriter().println(
@@ -313,7 +313,7 @@ public class AdminApp extends Servlet {
try {
TimePoint timePoint = getTimePoint(req, PARAM_NAME_TIME, PARAM_NAME_TIME_MILLIS, MillisecondsTimePoint.now());
Wind wind = new WindImpl(p, timePoint, speed);
getService().getDomainFactory().trackEvent(event).getTrackedRace(race).recordWind(wind, WindSource.WEB);
getService().getDomainFactory().getTrackedEvent(event).getTrackedRace(race).recordWind(wind, WindSource.WEB);
} catch (InvalidDateException e) {
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Couldn't parse time specification " + e.getMessage());
}
@@ -342,7 +342,7 @@ public class AdminApp extends Servlet {
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Wind source name " + sourceName + " unknown");
} else {
try {
WindTrack windTrack = getService().getDomainFactory().trackEvent(event)
WindTrack windTrack = getService().getDomainFactory().getTrackedEvent(event)
.getTrackedRace(race).getWindTrack(windSource);
TimePoint timePoint = getTimePoint(req, PARAM_NAME_TIME, PARAM_NAME_TIME_MILLIS,
MillisecondsTimePoint.now());
@@ -169,7 +169,7 @@ public class ModeratorApp extends Servlet {
RaceDefinition race = getRaceDefinition(req);
TrackedRace trackedRace = null;
if (event != null && race != null) {
trackedRace = getService().getDomainFactory().trackEvent(event).getTrackedRace(race);
trackedRace = getService().getDomainFactory().getTrackedEvent(event).getTrackedRace(race);
}
return trackedRace;
}
@@ -183,7 +183,7 @@ public class ModeratorApp extends Servlet {
} else {
try {
TimePoint timePoint = getTimePoint(req, PARAM_NAME_TIME, PARAM_NAME_TIME_MILLIS,
trackedRace.getTimePointOfNewestEvent()==null?MillisecondsTimePoint.now():trackedRace.getTimePointOfNewestEvent());
trackedRace.getTimePointOfLastEvent()==null?MillisecondsTimePoint.now():trackedRace.getTimePointOfLastEvent());
String sinceUpdateString = req.getParameter(PARAM_NAME_SINCE_UPDATE);
if (sinceUpdateString != null) {
System.out.println("Blocking...");
@@ -200,6 +200,8 @@ public class ModeratorApp extends Servlet {
.asMillis());
jsonRace.put("timeofnewestevent", trackedRace.getTimePointOfNewestEvent() == null ? 0l : trackedRace
.getTimePointOfNewestEvent().asMillis());
jsonRace.put("timeoflastevent", trackedRace.getTimePointOfLastEvent() == null ? 0l : trackedRace
.getTimePointOfLastEvent().asMillis());
jsonRace.put("updatecount", trackedRace.getUpdateCount());
Position positionForWind = null;
TrackedLeg currentLeg = trackedRace.getCurrentLeg(timePoint);
@@ -234,6 +236,11 @@ public class ModeratorApp extends Servlet {
TrackedLegOfCompetitor trackedLegOfCompetitor = leg.getTrackedLeg(competitor);
if (trackedLegOfCompetitor != null) {
jsonCompetitorInLeg.put("name", competitor.getName());
Speed currentSpeedOverGround = trackedLegOfCompetitor.getSpeedOverGround(timePoint);
if (currentSpeedOverGround != null) {
jsonCompetitorInLeg.put("currentSpeedOverGroundInKnots",
currentSpeedOverGround == null ? null : currentSpeedOverGround.getKnots());
}
Speed averageSpeedOverGround = trackedLegOfCompetitor.getAverageSpeedOverGround(timePoint);
if (averageSpeedOverGround != null) {
jsonCompetitorInLeg.put("averageSpeedOverGroundInKnots",
@@ -347,7 +354,7 @@ public class ModeratorApp extends Servlet {
JSONObject jsonRace = new JSONObject();
jsonRace.put("name", race.getName());
jsonRace.put("boatclass", race.getBoatClass()==null?"":race.getBoatClass().getName());
TimePoint start = getService().getDomainFactory().trackEvent(event).getTrackedRace(race).getStart();
TimePoint start = getService().getDomainFactory().getTrackedEvent(event).getTrackedRace(race).getStart();
jsonRace.put("start", start==null?Long.MAX_VALUE:start.asMillis());
JSONArray jsonLegs = new JSONArray();
for (Leg leg : race.getCourse().getLegs()) {
@@ -356,7 +363,7 @@ public class ModeratorApp extends Servlet {
jsonLeg.put("end", leg.getTo().getName());
jsonLegs.add(jsonLeg);
}
TrackedRace trackedRace = getService().getDomainFactory().trackEvent(event).getTrackedRace(race);
TrackedRace trackedRace = getService().getDomainFactory().getOrCreateTrackedEvent(event).getTrackedRace(race);
if (trackedRace.getStart() != null) {
jsonRace.put("start", trackedRace.getStart().asMillis());
}
@@ -12,11 +12,32 @@ import java.text.ParseException;
import com.sap.sailing.declination.DeclinationService;
import com.sap.sailing.domain.base.Event;
import com.sap.sailing.domain.base.RaceDefinition;
import com.sap.sailing.domain.tracking.TrackedEvent;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.WindStore;
import com.sap.sailing.domain.tractracadapter.DomainFactory;
import com.sap.sailing.domain.tractracadapter.RaceHandle;
import com.sap.sailing.domain.tractracadapter.RaceTracker;
import com.sap.sailing.util.Util.Triple;
/**
* An OSGi service that can be used to track boat races using a TracTrac connector that pushes
* live GPS boat location, waypoint, coarse and mark passing data.<p>
*
* If a race/event is already being tracked, another {@link #addRace(URL, URI, URI, WindStore)} or
* {@link #addEvent(URL, URI, URI, WindStore)} call will have no effect, even if a different
* {@link WindStore} is requested.<p>
*
* TODO When the tracking of a race/event is {@link #stopTracking(Event, RaceDefinition) stopped}, the next
* time it's started to be tracked, a new {@link TrackedRace} at least will be constructed. This also
* means that when a {@link TrackedEvent} exists that still holds other {@link TrackedRace}s, the
* no longer tracked {@link TrackedRace} will be removed from the {@link TrackedEvent}.
* corresponding information is removed also from the {@link DomainFactory}'s caches to ensure that
* clean, fresh data is received should another tracking request be issued later.
*
* @author Axel Uhl (d043530)
*
*/
public interface RacingEventService {
Iterable<Event> getAllEvents();
@@ -25,38 +46,46 @@ public interface RacingEventService {
DomainFactory getDomainFactory();
/**
* Defines the event and for each race listed in the JSON document creates a {@link RaceTracker} that
* tracks the respective race. The {@link RaceDefinition}s obtained this way are all grouped into
* the single {@link Event} produced for the event listed in the JSON response. Note that the
* many race trackers will have their TracTrac <code>Event</code> each, all with the same name,
* meaning the same event but being distinct.
* Defines the event and for each race listed in the JSON document that is not already being tracked by this service
* creates a {@link RaceTracker} that starts tracking the respective race. The {@link RaceDefinition}s obtained this
* way are all grouped into the single {@link Event} produced for the event listed in the JSON response. Note that
* the many race trackers will have their TracTrac <code>Event</code> each, all with the same name, meaning the same
* event but being distinct.
*
* @param jsonURL
* URL of a JSON response that contains an "event" object telling the event's name and ID, as well as a
* JSON array named "races" which tells ID and replay URL for the race. From those replay URLs the
* paramURL for the Java client can be derived.
* @param windStore TODO
*/
void addEvent(URL jsonURL, URI liveURI, URI storedURI, WindStore windStore) throws MalformedURLException, FileNotFoundException,
Event addEvent(URL jsonURL, URI liveURI, URI storedURI, WindStore windStore) throws MalformedURLException, FileNotFoundException,
URISyntaxException, IOException, ParseException, org.json.simple.parser.ParseException;
/**
* Adds a single race tracker, using the race's parameter URL which delivers the single configuration
* text file for that race. While the result of passing this URL to the TracTrac <code>KeyValue.setup</code>
* is a TracTrac <code>Event</code>, those events only manage a single race. In our domain model, we group
* those races into a single instance of our {@link Event} class.<p>
* If not already tracking the URL/URI/URI combination, adds a single race tracker and starts tracking the race,
* using the race's parameter URL which delivers the single configuration text file for that race. While the result
* of passing this URL to the TracTrac <code>KeyValue.setup</code> is a TracTrac <code>Event</code>, those events
* only manage a single race. In our domain model, we group those races into a single instance of our {@link Event}
* class.
* <p>
*
* If this is the first race of an event, the {@link Event} is created as well. If the {@link RaceDefinition} for
* the race already exists, it isn't created again. Also, if a {@link RaceTracker} for the given race already
* exists, it is not added again.
* @param windStore TODO
* exists, it is not added again.<p>
*
* Note that when the race identified by <code>paramURL</code>, <code>liveURI</code> and <code>storedURI</code> is
* already being tracked, then regardless of the <code>windStore</code> selection the existing tracker will be used
* and its race handle will be returned. A log message will indicate a potential wind store mismatch (based on
* {@link WindStore#equals(Object)}).
*/
void addRace(URL paramURL, URI liveURI, URI storedURI, WindStore windStore) throws MalformedURLException, FileNotFoundException,
RaceHandle addRace(URL paramURL, URI liveURI, URI storedURI, WindStore windStore) throws MalformedURLException, FileNotFoundException,
URISyntaxException;
/**
* Stops tracking all races of the event specified. This will also stop tracking wind for all races of this event.
* See {@link #stopTrackingWind(Event, RaceDefinition)}.
* See {@link #stopTrackingWind(Event, RaceDefinition)}. If there were multiple calls to
* {@link #addRace(URL, URI, URI, WindStore)} with an equal combination of URLs/URIs, the {@link RaceTracker}
* already tracking the race was re-used. The trackers will be stopped by this call regardless of how many calls
* were made that ensured they were tracking.
*/
void stopTracking(Event event) throws MalformedURLException, IOException, InterruptedException;
@@ -16,6 +16,7 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import com.sap.sailing.declination.DeclinationService;
import com.sap.sailing.domain.base.Event;
@@ -25,6 +26,7 @@ import com.sap.sailing.domain.tracking.DynamicTrackedRace;
import com.sap.sailing.domain.tracking.WindStore;
import com.sap.sailing.domain.tractracadapter.DomainFactory;
import com.sap.sailing.domain.tractracadapter.JSONService;
import com.sap.sailing.domain.tractracadapter.RaceHandle;
import com.sap.sailing.domain.tractracadapter.RaceRecord;
import com.sap.sailing.domain.tractracadapter.RaceTracker;
import com.sap.sailing.expeditionconnector.UDPExpeditionReceiver;
@@ -33,11 +35,13 @@ import com.sap.sailing.util.Util.Pair;
import com.sap.sailing.util.Util.Triple;
public class RacingEventServiceImpl implements RacingEventService {
private static final Logger logger = Logger.getLogger(RacingEventServiceImpl.class.getName());
private final DomainFactory domainFactory;
private final Map<String, Event> eventsByName;
private final Map<Event, Set<RaceTracker>> raceTrackers;
private final Map<Event, Set<RaceTracker>> raceTrackersByEvent;
/**
* Remembers the wind tracker and the port on which the UDP receiver with which the wind tracker is
@@ -45,13 +49,19 @@ public class RacingEventServiceImpl implements RacingEventService {
*/
private final Map<RaceDefinition, Pair<WindTracker, Integer>> windTrackers;
/**
* Remembers the trackers by paramURL/liveURI/storedURI to avoid duplication
*/
private final Map<Triple<URL, URI, URI>, RaceTracker> raceTrackersByURLs;
private final Map<Integer, UDPExpeditionReceiver> windReceivers;
public RacingEventServiceImpl() {
domainFactory = DomainFactory.INSTANCE;
eventsByName = new HashMap<String, Event>();
raceTrackers = new HashMap<Event, Set<RaceTracker>>();
raceTrackersByEvent = new HashMap<Event, Set<RaceTracker>>();
windTrackers = new HashMap<RaceDefinition, Pair<WindTracker, Integer>>();
raceTrackersByURLs = new HashMap<Triple<URL, URI, URI>, RaceTracker>();
windReceivers = new HashMap<Integer, UDPExpeditionReceiver>();
}
@@ -71,42 +81,57 @@ public class RacingEventServiceImpl implements RacingEventService {
}
@Override
public void addEvent(URL jsonURL, URI liveURI, URI storedURI, WindStore windStore) throws URISyntaxException, IOException, ParseException, org.json.simple.parser.ParseException {
public Event addEvent(URL jsonURL, URI liveURI, URI storedURI, WindStore windStore) throws URISyntaxException, IOException, ParseException, org.json.simple.parser.ParseException {
JSONService jsonService = getDomainFactory().parseJSONURL(jsonURL);
Event event = null;
for (RaceRecord rr : jsonService.getRaceRecords()) {
URL paramURL = rr.getParamURL();
addRace(paramURL, liveURI, storedURI, windStore);
event = addRace(paramURL, liveURI, storedURI, windStore).getEvent();
}
return event;
}
@Override
public void addRace(URL paramURL, URI liveURI, URI storedURI, WindStore windStore) throws MalformedURLException, FileNotFoundException,
public RaceHandle addRace(URL paramURL, URI liveURI, URI storedURI, WindStore windStore) throws MalformedURLException, FileNotFoundException,
URISyntaxException {
RaceTracker tracker = getDomainFactory().createRaceTracker(paramURL, liveURI, storedURI, windStore);
Set<RaceTracker> trackers = raceTrackers.get(tracker.getEvent());
if (trackers == null) {
trackers = new HashSet<RaceTracker>();
raceTrackers.put(tracker.getEvent(), trackers);
}
trackers.add(tracker);
String eventName = tracker.getEvent().getName();
Event eventWithName = eventsByName.get(eventName);
if (eventWithName != null) {
if (eventWithName != tracker.getEvent()) {
throw new RuntimeException("Internal error. Two Event objects with equal name "+eventName);
Triple<URL, URI, URI> key = new Triple<URL, URI, URI>(paramURL, liveURI, storedURI);
RaceTracker tracker = raceTrackersByURLs.get(key);
if (tracker == null) {
tracker = getDomainFactory().createRaceTracker(paramURL, liveURI, storedURI, windStore);
raceTrackersByURLs.put(key, tracker);
Set<RaceTracker> trackers = raceTrackersByEvent.get(tracker.getEvent());
if (trackers == null) {
trackers = new HashSet<RaceTracker>();
raceTrackersByEvent.put(tracker.getEvent(), trackers);
}
trackers.add(tracker);
String eventName = tracker.getEvent().getName();
Event eventWithName = eventsByName.get(eventName);
if (eventWithName != null) {
if (eventWithName != tracker.getEvent()) {
throw new RuntimeException("Internal error. Two Event objects with equal name "+eventName);
}
} else {
eventsByName.put(eventName, tracker.getEvent());
}
} else {
eventsByName.put(eventName, tracker.getEvent());
WindStore existingTrackersWindStore = tracker.getWindStore();
if (!existingTrackersWindStore.equals(windStore)) {
logger.warning("Wind store mismatch. Requested wind store: "+windStore+
". Wind store in use by existing tracker: "+existingTrackersWindStore);
}
}
return tracker.getRaceHandle();
}
@Override
public void stopTracking(Event event) throws MalformedURLException, IOException, InterruptedException {
if (raceTrackers.containsKey(event)) {
for (RaceTracker raceTracker : raceTrackers.get(event)) {
raceTracker.stop();
if (raceTrackersByEvent.containsKey(event)) {
for (RaceTracker raceTracker : raceTrackersByEvent.get(event)) {
raceTracker.stop(); // this also removes the TrackedRace from trackedEvent
raceTrackersByURLs.remove(raceTracker.getURLs());
}
raceTrackers.remove(event);
raceTrackersByEvent.remove(event);
}
if (event != null && event.getName() != null) {
eventsByName.remove(event.getName());
@@ -118,18 +143,19 @@ public class RacingEventServiceImpl implements RacingEventService {
@Override
public void stopTracking(Event event, RaceDefinition race) throws MalformedURLException, IOException, InterruptedException {
if (raceTrackers.containsKey(event)) {
Iterator<RaceTracker> trackerIter = raceTrackers.get(event).iterator();
if (raceTrackersByEvent.containsKey(event)) {
Iterator<RaceTracker> trackerIter = raceTrackersByEvent.get(event).iterator();
while (trackerIter.hasNext()) {
RaceTracker raceTracker = trackerIter.next();
if (raceTracker.getRace() == race) {
raceTracker.stop();
raceTracker.stop(); // this also removes the TrackedRace from trackedEvent
trackerIter.remove();
raceTrackersByURLs.remove(raceTracker.getURLs());
}
}
}
// if the last tracked race was removed, remove the entire event
if (raceTrackers.get(event).isEmpty()) {
if (raceTrackersByEvent.get(event).isEmpty()) {
stopTracking(event);
}
}
@@ -138,7 +164,7 @@ public class RacingEventServiceImpl implements RacingEventService {
public synchronized void startTrackingWind(Event event, RaceDefinition race, int port,
DeclinationService declinationService) throws SocketException {
if (!windTrackers.containsKey(race)) {
DynamicTrackedEvent trackedEvent = getDomainFactory().trackEvent(event);
DynamicTrackedEvent trackedEvent = getDomainFactory().getOrCreateTrackedEvent(event);
DynamicTrackedRace trackedRace = trackedEvent.getTrackedRace(race);
WindTracker windTracker = new WindTracker(trackedRace, declinationService);
UDPExpeditionReceiver receiver = getOrCreateWindReceiverForPort(port);