Merge remote-tracking branch 'origin/master'

This commit is contained in:
Alexander Ries committed 2015-03-25 00:27:41 +01:00
commit 523d85d61e
67 files changed
+1407 -342

No files matched your search

+1
View File
@@ -232,6 +232,7 @@ if [[ "$@" == "release" ]]; then
fi
mkdir -p $PROJECT_HOME/dist
rm -rf $PROJECT_HOME/dist/*
mkdir -p $PROJECT_HOME/build
RELEASE_NOTES="Release $VERSION_INFO\n$RELEASE_NOTES"
View File
File mode changed.
@@ -1,7 +1,9 @@
package com.sap.sailing.domain.common;
import com.sap.sailing.domain.common.impl.NauticalMileDistance;
import com.sap.sse.common.Duration;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.impl.MillisecondsDurationImpl;
public abstract class AbstractSpeedImpl implements Speed {
@@ -17,6 +19,11 @@ public abstract class AbstractSpeedImpl implements Speed {
return new NauticalMileDistance((t2.asMillis() - t1.asMillis()) / 1000. / 3600. * getKnots());
}
@Override
public Duration getDuration(Distance distance) {
return new MillisecondsDurationImpl((long) (1000 * distance.getMeters() / getMetersPerSecond()));
}
@Override
public double getMetersPerSecond() {
return getKnots() * Mile.METERS_PER_SEA_MILE / 3600;
@@ -2,6 +2,7 @@ package com.sap.sailing.domain.common;
import java.io.Serializable;
import com.sap.sse.common.Duration;
import com.sap.sse.common.TimePoint;
import com.sap.sse.datamining.shared.Unit;
import com.sap.sse.datamining.shared.annotations.Statistic;
@@ -46,6 +47,11 @@ public interface Speed extends Comparable<Speed>, Serializable {
return Distance.NULL;
}
@Override
public Duration getDuration(Distance distance) {
throw new ArithmeticException("Cannot determine duration for any distance with zero speed");
}
@Override
public String toString() {
return "0kn";
@@ -68,4 +74,10 @@ public interface Speed extends Comparable<Speed>, Serializable {
*/
Distance travel(TimePoint from, TimePoint to);
/**
* The duration it takes to travel the <code>distance</code> specified on a great circle (the "straight
* line" on a sphere) with this speed.
*/
Duration getDuration(Distance distance);
}
@@ -8,7 +8,7 @@ package com.sap.sailing.domain.common.racelog;
*/
public interface RaceLogServletConstants {
static final String PARAMS_LEADERBOARD_NAME = "leaderboard";
static final String PARAMS_RACE_COLUMN_NAME = "raceColumn";
static final String PARAMS_RACE_COLUMN_NAME = "race_column";
static final String PARAMS_RACE_FLEET_NAME = "fleet";
static final String PARAMS_CLIENT_UUID = "clientuuid";
static final String PARAM_COURSE_AREA_FILTER = "courseArea";
@@ -21,7 +21,8 @@ public class DeviceMappingConstants {
// schema and host are case insensitive.
// But to adhere to best practices, and due to the deficiencies of some web servers and clients,
// all URL components should be treated as case insensitive, preferring _underscores_ to CamelCase.
public static final String URL_BASE = "/tracking/checkin";
public static final String TRACKING_URL_BASE = "/tracking/checkin";
public static final String BUOY_TENDER_URL_BASE = "/buoy-tender/checkin";
public static final String URL_EVENT_ID = "event_id";
public static final String URL_LEADERBOARD_NAME = "leaderboard_name";
public static final String URL_COMPETITOR_ID_AS_STRING = "competitor_id";
@@ -43,11 +44,16 @@ public class DeviceMappingConstants {
public static String getDeviceMappingForRegattaLogUrl(String serverUrlWithoutTrailingSlash, String eventId,
String leaderboardName, String mappedItemType, String mappedItemId, UrlHelper helper) {
return helper.encodeUrl(serverUrlWithoutTrailingSlash + URL_BASE + "?" + URL_EVENT_ID + "=" + eventId + "&"
return helper.encodeUrl(serverUrlWithoutTrailingSlash + TRACKING_URL_BASE + "?" + URL_EVENT_ID + "=" + eventId + "&"
+ URL_LEADERBOARD_NAME + "=" + leaderboardName + "&" + mappedItemType + "="
+ mappedItemId);
}
public static String getBuoyTenderInvitationUrl(String serverUrlWithoutTrailingSlash,
String leaderboardName, UrlHelper helper) {
return helper.encodeUrl(serverUrlWithoutTrailingSlash + BUOY_TENDER_URL_BASE + "?" + URL_LEADERBOARD_NAME + "=" + leaderboardName);
}
@Deprecated
public static String getDeviceMappingForRaceLogUrl(String serverUrlWithoutTrailingSlash, String leaderboardName,
String raceColumnName, String fleetName, String mappedItemType, String mappedItemId, long fromMillis,
@@ -120,7 +120,6 @@ public class MongoGPSFixStoreImpl implements MongoGPSFixStore {
public void loadMarkTrack(DynamicGPSFixTrack<Mark, GPSFix> track, AbstractLog<?, ?> log, Mark mark)
throws NoCorrespondingServiceRegisteredException, TransformationException{
List<DeviceMapping<Mark>> mappings = new DeviceMarkMappingFinder<>(log).analyze().get(mark);
if (mappings != null) {
for (DeviceMapping<Mark> mapping : mappings) {
loadTrack(track, mapping.getDevice(), mapping.getTimeRange().from(), mapping.getTimeRange().to(), true /*inclusive*/);
@@ -2,3 +2,4 @@ trackingInvitationFor=Einladung zum Tracken für
scanQRCodeOrVisitUrlToRegisterAs=Scanne den QRCode oder klicke den Link mit deinem Smartphone an um dich zu registrieren als
welcomeTo=Willkommen zu
alternativelyVisitThisLink=Alternativ kannst du diesen Link anklicken
buoyTender=Bojenleger
@@ -2,3 +2,4 @@ trackingInvitationFor=Tracking Invitation for
scanQRCodeOrVisitUrlToRegisterAs=Scan the following QRCode or click the link using your smartphone to register as
welcomeTo=Welcome to
alternativelyVisitThisLink=Alternatively, you can click this Link
buoyTender=buoy tender
@@ -21,9 +21,9 @@ import com.sap.sailing.domain.common.racelog.tracking.NotDenotedForRaceLogTracki
import com.sap.sailing.domain.common.racelog.tracking.RaceLogTrackingState;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.leaderboard.RegattaLeaderboard;
import com.sap.sailing.domain.racelog.tracking.GPSFixStore;
import com.sap.sailing.domain.racelogtracking.impl.RaceLogRaceTracker;
import com.sap.sailing.domain.tracking.GPSFix;
import com.sap.sailing.domain.tracking.RaceHandle;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.server.RacingEventService;
import com.sap.sse.common.mail.MailException;
@@ -40,7 +40,7 @@ public interface RaceLogTrackingAdapter {
* <li>Is a {@link RaceLogStartTrackingEvent} present in the racelog? If not, add one</li>
* </ul>
*/
void startTracking(RacingEventService service, Leaderboard leaderboard, RaceColumn raceColumn, Fleet fleet)
RaceHandle startTracking(RacingEventService service, Leaderboard leaderboard, RaceColumn raceColumn, Fleet fleet)
throws NotDenotedForRaceLogTrackingException, Exception;
RaceLogTrackingState getRaceLogTrackingState(RacingEventService service, RaceColumn raceColumn, Fleet fleet);
@@ -76,10 +76,15 @@ public interface RaceLogTrackingAdapter {
throws NotDenotableForRaceLogTrackingException;
/**
* Add a fix to the {@link GPSFixStore}, and create a mapping with a virtual device for exactly that timepoint.
* @see #pingMark(RaceLog, Mark, GPSFix, RacingEventService) using a random {@link PingDeviceIdentifier}
*/
void pingMark(RaceLog raceLogToAddTo, Mark mark, GPSFix gpsFix, RacingEventService service);
/**
* @see #pingMark(RaceLog, Mark, GPSFix, RacingEventService)
*/
void pingMark(RegattaLog regattaLogToAddTo, Mark mark, GPSFix gpsFix, RacingEventService service);
/**
* Duplicate the course and competitor registrations in the newest {@link RaceLogCourseDesignChangedEvent} in
* {@code from} race log to the {@code to} race logs. The {@link Mark}s and {@link ControlPoint}s are duplicated and
@@ -102,7 +107,15 @@ public interface RaceLogTrackingAdapter {
/**
* Invite competitors for tracking via the Tracking App by sending out emails.
* @throws MailException
*/
void inviteCompetitorsForTrackingViaEmail(Event event, Leaderboard leaderboard,
String serverUrlWithoutTrailingSlash, Set<Competitor> competitors, Locale locale) throws MailException;
/**
* Invite buoy tenders for buoy pinging via the Buoy Tender App by sending out emails.
* @throws MailException
*/
void inviteBuoyTenderViaEmail(Event event, Leaderboard leaderboard, String serverUrlWithoutTrailingSlash,
String emails, Locale locale) throws MailException;
}
@@ -2,11 +2,9 @@ package com.sap.sailing.domain.racelogtracking.impl;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.function.Function;
@@ -38,12 +36,12 @@ import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogDenoteForTracking
import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogStartTrackingEvent;
import com.sap.sailing.domain.abstractlog.race.tracking.analyzing.impl.RaceLogTrackingStateAnalyzer;
import com.sap.sailing.domain.abstractlog.regatta.RegattaLog;
import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogDeviceMarkMappingEventImpl;
import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogRegisterCompetitorEventImpl;
import com.sap.sailing.domain.abstractlog.shared.analyzing.RegisteredCompetitorsAnalyzer;
import com.sap.sailing.domain.abstractlog.shared.events.RegisterCompetitorEvent;
import com.sap.sailing.domain.base.BoatClass;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.ControlPoint;
import com.sap.sailing.domain.base.CourseBase;
import com.sap.sailing.domain.base.DomainFactory;
import com.sap.sailing.domain.base.Event;
@@ -54,7 +52,6 @@ import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.base.SharedDomainFactory;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.base.impl.CourseDataImpl;
import com.sap.sailing.domain.common.PassingInstruction;
import com.sap.sailing.domain.common.RegattaIdentifier;
import com.sap.sailing.domain.common.abstractlog.NotRevokableException;
import com.sap.sailing.domain.common.racelog.tracking.DeviceMappingConstants;
@@ -94,36 +91,39 @@ public class RaceLogTrackingAdapterImpl implements RaceLogTrackingAdapter {
}
@Override
public void startTracking(RacingEventService service, Leaderboard leaderboard, RaceColumn raceColumn, Fleet fleet)
public RaceHandle startTracking(RacingEventService service, Leaderboard leaderboard, RaceColumn raceColumn, Fleet fleet)
throws NotDenotedForRaceLogTrackingException, Exception {
RaceLog raceLog = raceColumn.getRaceLog(fleet);
RaceLogTrackingState raceLogTrackingState = new RaceLogTrackingStateAnalyzer(raceLog).analyze();
if (! raceLogTrackingState.isForTracking()) {
if (!raceLogTrackingState.isForTracking()) {
throw new NotDenotedForRaceLogTrackingException();
}
RegattaIdentifier regatta = ((RegattaLeaderboard) leaderboard).getRegatta().getRegattaIdentifier();
if (raceLogTrackingState != RaceLogTrackingState.TRACKING) {
RaceLogEvent event = RaceLogEventFactory.INSTANCE.createStartTrackingEvent(MillisecondsTimePoint.now(),
service.getServerAuthor(), raceLog.getCurrentPassId());
raceLog.add(event);
}
if (! isRaceLogRaceTrackerAttached(service, raceLog)) {
addTracker(service, regatta, leaderboard, raceColumn, fleet, -1);
final RaceHandle result;
if (!isRaceLogRaceTrackerAttached(service, raceLog)) {
result = addTracker(service, regatta, leaderboard, raceColumn, fleet, -1);
} else {
result = null;
}
return result;
}
/**
* Adds a {@link RaceLogRaceTracker}. If a {@link RaceLogStartTrackingEvent} is already present in the {@code RaceLog}
* linked to the {@code raceColumn} and {@code fleet}, a {@code TrackedRace} is created immediately and tracking begins.
* Otherwise, the {@code RaceLogRaceTracker} waits until a {@code StartTrackingEvent} is added to perform these actions.
* The race first has to be denoted for racelog tracking.
* Adds a {@link RaceLogRaceTracker}. If a {@link RaceLogStartTrackingEvent} is already present in the
* {@code RaceLog} linked to the {@code raceColumn} and {@code fleet}, a {@code TrackedRace} is created immediately
* and tracking begins. Otherwise, the {@code RaceLogRaceTracker} waits until a {@code StartTrackingEvent} is added
* to perform these actions. The race first has to be denoted for racelog tracking.
*/
private RaceHandle addTracker(RacingEventService service, RegattaIdentifier regattaToAddTo, Leaderboard leaderboard,
RaceColumn raceColumn, Fleet fleet, long timeoutInMilliseconds) throws RaceLogRaceTrackerExistsException, Exception {
private RaceHandle addTracker(RacingEventService service, RegattaIdentifier regattaToAddTo,
Leaderboard leaderboard, RaceColumn raceColumn, Fleet fleet, long timeoutInMilliseconds)
throws RaceLogRaceTrackerExistsException, Exception {
RaceLog raceLog = raceColumn.getRaceLog(fleet);
assert ! isRaceLogRaceTrackerAttached(service, raceLog) : new RaceLogRaceTrackerExistsException(
assert !isRaceLogRaceTrackerAttached(service, raceLog) : new RaceLogRaceTrackerExistsException(
leaderboard.getName() + " - " + raceColumn.getName() + " - " + fleet.getName());
Regatta regatta = regattaToAddTo == null ? null : service.getRegatta(regattaToAddTo);
@@ -133,15 +133,16 @@ public class RaceLogTrackingAdapterImpl implements RaceLogTrackingAdapter {
}
@Override
public void denoteRaceForRaceLogTracking(RacingEventService service, Leaderboard leaderboard, RaceColumn raceColumn,
Fleet fleet, String raceName) throws NotDenotableForRaceLogTrackingException {
public void denoteRaceForRaceLogTracking(RacingEventService service, Leaderboard leaderboard,
RaceColumn raceColumn, Fleet fleet, String raceName) throws NotDenotableForRaceLogTrackingException {
BoatClass boatClass = null;
if (leaderboard instanceof RegattaLeaderboard) {
RegattaLeaderboard rLeaderboard = (RegattaLeaderboard) leaderboard;
boatClass = rLeaderboard.getRegatta().getBoatClass();
} else {
throw new NotDenotableForRaceLogTrackingException("Can only denote races in RegattaLeaderboards for RaceLog-tracking");
throw new NotDenotableForRaceLogTrackingException(
"Can only denote races in RegattaLeaderboards for RaceLog-tracking");
}
if (raceName == null) {
@@ -170,7 +171,8 @@ public class RaceLogTrackingAdapterImpl implements RaceLogTrackingAdapter {
for (Fleet fleet : column.getFleets()) {
try {
denoteRaceForRaceLogTracking(service, leaderboard, column, fleet, null);
} catch (NotDenotableForRaceLogTrackingException e) {}
} catch (NotDenotableForRaceLogTrackingException e) {
}
}
}
}
@@ -185,46 +187,9 @@ public class RaceLogTrackingAdapterImpl implements RaceLogTrackingAdapter {
return new RaceLogTrackingStateAnalyzer(raceColumn.getRaceLog(fleet)).analyze();
}
private Waypoint duplicateWaypoint(Waypoint waypoint, Map<ControlPoint, ControlPoint> controlPointDuplicationCache,
Map<Mark, Mark> markDuplicationCache, SharedDomainFactory baseDomainFactory) {
ControlPoint oldCP = waypoint.getControlPoint();
ControlPoint newCP = null;
PassingInstruction pi = waypoint.getPassingInstructions();
if (controlPointDuplicationCache.get(oldCP) != null) {
newCP = controlPointDuplicationCache.get(oldCP);
} else {
Mark[] newMarks = new Mark[Util.size(oldCP.getMarks())];
int i = 0;
for (Mark oldMark : oldCP.getMarks()) {
newMarks[i] = null;
if (markDuplicationCache.get(oldMark) != null) {
newMarks[i] = markDuplicationCache.get(oldMark);
} else {
newMarks[i] = baseDomainFactory.getOrCreateMark(UUID.randomUUID(), oldMark.getName(), oldMark.getType(),
oldMark.getColor(), oldMark.getShape(), oldMark.getPattern());
markDuplicationCache.put(oldMark, newMarks[i]);
}
i++;
}
switch (newMarks.length) {
case 1:
newCP = newMarks[0];
break;
case 2:
newCP = baseDomainFactory.createControlPointWithTwoMarks(newMarks[0], newMarks[1], oldCP.getName());
break;
default:
logger.log(Level.WARNING, "Don't know how to duplicate CP with more than 2 marks");
throw new RuntimeException("Don't know how to duplicate CP with more than 2 marks");
}
controlPointDuplicationCache.put(oldCP, newCP);
}
return baseDomainFactory.createWaypoint(newCP, pi);
}
private void revokeAlreadyDefinedMarks(RaceLog raceLog, AbstractLogEventAuthor author) {
List<RaceLogEvent> markEvents = new AllEventsOfTypeFinder<>(raceLog, true, RaceLogDefineMarkEvent.class).analyze();
List<RaceLogEvent> markEvents = new AllEventsOfTypeFinder<>(raceLog, /* only unrevoked */ true, RaceLogDefineMarkEvent.class)
.analyze();
for (RaceLogEvent event : markEvents) {
try {
raceLog.revokeEvent(author, event, "removing mark that was already defined");
@@ -235,70 +200,90 @@ public class RaceLogTrackingAdapterImpl implements RaceLogTrackingAdapter {
}
@Override
public void copyCourseAndCompetitors(RaceLog fromRaceLog, Set<RaceLog> toRaceLogs, SharedDomainFactory baseDomainFactory,
RacingEventService service) {
public void copyCourseAndCompetitors(RaceLog fromRaceLog, Set<RaceLog> toRaceLogs,
SharedDomainFactory baseDomainFactory, RacingEventService service) {
CourseBase course = new LastPublishedCourseDesignFinder(fromRaceLog).analyze();
final Set<Mark> marks = new HashSet<>();
course.getWaypoints().forEach(wp->Util.addAll(wp.getMarks(), marks));
Set<Competitor> competitors = new RegisteredCompetitorsAnalyzer<>(fromRaceLog).analyze();
for (RaceLog toRaceLog : toRaceLogs) {
if (course == null || ! new RaceLogTrackingStateAnalyzer(toRaceLog).analyze().isForTracking()) {
if (course == null || !new RaceLogTrackingStateAnalyzer(toRaceLog).analyze().isForTracking()) {
continue;
}
CourseBase to = new CourseDataImpl("Copy of \"" + course.getName());
TimePoint now = MillisecondsTimePoint.now();
int i = 0;
Map<ControlPoint, ControlPoint> controlPointDuplicationCache = new HashMap<ControlPoint, ControlPoint>();
revokeAlreadyDefinedMarks(toRaceLog, service.getServerAuthor());
Map<Mark, Mark> markDuplicationCache = new HashMap<Mark, Mark>();
for (Waypoint oldWaypoint : course.getWaypoints()) {
to.addWaypoint(i++, duplicateWaypoint(oldWaypoint, controlPointDuplicationCache, markDuplicationCache, baseDomainFactory));
to.addWaypoint(i++, oldWaypoint);
}
for (Mark mark : markDuplicationCache.values()) {
RaceLogEvent event = RaceLogEventFactory.INSTANCE.createDefineMarkEvent(now, service.getServerAuthor(),
toRaceLog.getCurrentPassId(), mark);
for (Mark mark : marks) {
RaceLogEvent event = RaceLogEventFactory.INSTANCE.createDefineMarkEvent(now,
service.getServerAuthor(), toRaceLog.getCurrentPassId(), mark);
toRaceLog.add(event);
}
int passId = toRaceLog.getCurrentPassId();
RaceLogEvent newCourseEvent = RaceLogEventFactory.INSTANCE.createCourseDesignChangedEvent(
now, service.getServerAuthor(), passId, to);
RaceLogEvent newCourseEvent = RaceLogEventFactory.INSTANCE.createCourseDesignChangedEvent(now,
service.getServerAuthor(), passId, to);
toRaceLog.add(newCourseEvent);
registerCompetitors(service, toRaceLog, competitors);
}
}
@FunctionalInterface
private static interface DeviceMarkMappingEventFactory<VisitorT, EventT extends AbstractLogEvent<VisitorT>> {
EventT createDeviceMarkMapping(DeviceIdentifier device, TimePoint timePoint);
}
private <VisitorT, EventT extends AbstractLogEvent<VisitorT>, LogT extends AbstractLog<EventT, VisitorT>> void pingMark(
LogT log, Mark mark, GPSFix gpsFix, RacingEventService service,
DeviceMarkMappingEventFactory<VisitorT, EventT> factory, DeviceIdentifier device) {
TimePoint time = gpsFix.getTimePoint();
EventT mapping = factory.createDeviceMarkMapping(device, time);
log.add(mapping);
try {
service.getGPSFixStore().storeFix(device, gpsFix);
} catch (TransformationException | NoCorrespondingServiceRegisteredException e) {
logger.log(Level.WARNING, "Could not ping mark " + mark);
}
}
@Override
public void pingMark(RaceLog raceLog, Mark mark, GPSFix gpsFix, RacingEventService service) {
DeviceIdentifier device = new PingDeviceIdentifierImpl();
TimePoint time = gpsFix.getTimePoint();
RaceLogEvent mapping = RaceLogEventFactory.INSTANCE.createDeviceMarkMappingEvent(time,
service.getServerAuthor(), device, mark, raceLog.getCurrentPassId(), time, time);
raceLog.add(mapping);
try {
service.getGPSFixStore().storeFix(device, gpsFix);
} catch (TransformationException | NoCorrespondingServiceRegisteredException e) {
logger.log(Level.WARNING, "Could not pint mark " + mark);
pingMark(raceLog, mark, gpsFix, service,
(DeviceIdentifier dev, TimePoint timePoint) -> RaceLogEventFactory.INSTANCE
.createDeviceMarkMappingEvent(timePoint, service.getServerAuthor(), dev, mark,
raceLog.getCurrentPassId(), timePoint, timePoint), new PingDeviceIdentifierImpl());
}
@Override
public void pingMark(RegattaLog regattaLog, Mark mark, GPSFix gpsFix, RacingEventService service) {
pingMark(
regattaLog,
mark,
gpsFix,
service,
(DeviceIdentifier dev, TimePoint timePoint) -> new RegattaLogDeviceMarkMappingEventImpl(timePoint, service
.getServerAuthor(), timePoint, UUID.randomUUID(), mark, dev, timePoint, timePoint), new PingDeviceIdentifierImpl());
}
@Override
public void removeDenotationForRaceLogTracking(RacingEventService service, RaceLog raceLog) {
RaceLogEvent denoteForTrackingEvent = new LastEventOfTypeFinder<>(raceLog, true, RaceLogDenoteForTrackingEvent.class).analyze();
RaceLogEvent startTrackingEvent = new LastEventOfTypeFinder<>(raceLog, true, RaceLogStartTrackingEvent.class).analyze();
RaceLogEvent denoteForTrackingEvent = new LastEventOfTypeFinder<>(raceLog, true,
RaceLogDenoteForTrackingEvent.class).analyze();
RaceLogEvent startTrackingEvent = new LastEventOfTypeFinder<>(raceLog, true, RaceLogStartTrackingEvent.class)
.analyze();
try {
raceLog.revokeEvent(service.getServerAuthor(), denoteForTrackingEvent, "remove denotation");
raceLog.revokeEvent(service.getServerAuthor(), startTrackingEvent, "reset start time upon removing denotation");
raceLog.revokeEvent(service.getServerAuthor(), startTrackingEvent,
"reset start time upon removing denotation");
} catch (NotRevokableException e) {
logger.log(Level.WARNING, "could not remove denotation by adding RevokeEvents", e);
}
}
private <LogT extends AbstractLog<EventT, VisitorT>, EventT extends AbstractLogEvent<VisitorT>, VisitorT>
void registerCompetitors(AbstractLogEventAuthor author, LogT log, Set<Competitor> competitors,
private <LogT extends AbstractLog<EventT, VisitorT>, EventT extends AbstractLogEvent<VisitorT>, VisitorT> void registerCompetitors(
AbstractLogEventAuthor author, LogT log, Set<Competitor> competitors,
Function<Competitor, EventT> registerEventFactory) {
Set<Competitor> alreadyRegistered = new HashSet<Competitor>(new RegisteredCompetitorsAnalyzer<>(log).analyze());
Set<Competitor> toBeRegistered = new HashSet<Competitor>();
@@ -311,12 +296,12 @@ public class RaceLogTrackingAdapterImpl implements RaceLogTrackingAdapter {
toBeRemoved.removeAll(toBeRegistered);
toBeRegistered.removeAll(alreadyRegistered);
//register
// register
for (Competitor c : toBeRegistered) {
log.add(registerEventFactory.apply(c));
}
//unregister
// unregister
for (EventT event : log.getUnrevokedEventsDescending()) {
if (event instanceof RegisterCompetitorEvent) {
RegisterCompetitorEvent<?> registerEvent = (RegisterCompetitorEvent<?>) event;
@@ -334,7 +319,10 @@ public class RaceLogTrackingAdapterImpl implements RaceLogTrackingAdapter {
@Override
public void registerCompetitors(RacingEventService service, RaceLog raceLog, Set<Competitor> competitors) {
registerCompetitors(service.getServerAuthor(), raceLog, competitors,
registerCompetitors(
service.getServerAuthor(),
raceLog,
competitors,
c -> RaceLogEventFactory.INSTANCE.createRegisterCompetitorEvent(MillisecondsTimePoint.now(),
service.getServerAuthor(), raceLog.getCurrentPassId(), c));
}
@@ -347,8 +335,7 @@ public class RaceLogTrackingAdapterImpl implements RaceLogTrackingAdapter {
}
private MailService getMailService() {
ServiceReference<MailService> ref = Activator.getContext()
.getServiceReference(MailService.class);
ServiceReference<MailService> ref = Activator.getContext().getServiceReference(MailService.class);
if (ref == null) {
logger.warning("No file storage management service registered");
return null;
@@ -361,7 +348,7 @@ public class RaceLogTrackingAdapterImpl implements RaceLogTrackingAdapter {
String serverUrlWithoutTrailingSlash, Set<Competitor> competitors, Locale locale) throws MailException {
StringBuilder occuredExceptions = new StringBuilder();
for (Competitor competitor : competitors){
for (Competitor competitor : competitors) {
final String toAddress = competitor.getEmail();
if (toAddress != null) {
String leaderboardName = leaderboard.getName();
@@ -370,17 +357,30 @@ public class RaceLogTrackingAdapterImpl implements RaceLogTrackingAdapter {
String url = DeviceMappingConstants.getDeviceMappingForRegattaLogUrl(serverUrlWithoutTrailingSlash,
event.getId().toString(), leaderboardName, DeviceMappingConstants.URL_COMPETITOR_ID_AS_STRING,
competitor.getId().toString(), NonGwtUrlHelper.INSTANCE);
try {
sendInvitationEmail(locale, toAddress, leaderboardName, competitorName, url);
} catch (MailException e) {
occuredExceptions.append(e.getMessage() + "\r\n");
}
}
}
if (!(occuredExceptions.length() == 0)) {
throw new MailException(occuredExceptions.toString());
}
}
private void sendInvitationEmail(Locale locale, final String toAddress, String leaderboardName, String invitee,
String url) throws MailException {
String subject = String.format("%s %s",
RaceLogTrackingI18n.STRING_MESSAGES.get(locale, "trackingInvitationFor"), competitorName);
RaceLogTrackingI18n.STRING_MESSAGES.get(locale, "trackingInvitationFor"), invitee);
// taken from http://www.tutorialspoint.com/javamail_api/javamail_api_send_inlineimage_in_email.htm
BodyPart messageTextPart = new MimeBodyPart();
String htmlText = String.format("<h1>%s %s</h1>" + "<p>%s <b>%s</b></p>"
+ "<img src=\"cid:image\" title=\"%s\"><br/>"
+ "<a href=\"%s\">%s</a>",
+ "<img src=\"cid:image\" title=\"%s\"><br/>" + "<a href=\"%s\">%s</a>",
RaceLogTrackingI18n.STRING_MESSAGES.get(locale, "welcomeTo"), leaderboardName,
RaceLogTrackingI18n.STRING_MESSAGES.get(locale, "scanQRCodeOrVisitUrlToRegisterAs"), competitorName,
url, url, RaceLogTrackingI18n.STRING_MESSAGES.get(locale, "alternativelyVisitThisLink"));
RaceLogTrackingI18n.STRING_MESSAGES.get(locale, "scanQRCodeOrVisitUrlToRegisterAs"), invitee, url, url,
RaceLogTrackingI18n.STRING_MESSAGES.get(locale, "alternativelyVisitThisLink"));
try {
messageTextPart.setContent(htmlText, "text/html");
@@ -397,13 +397,33 @@ public class RaceLogTrackingAdapterImpl implements RaceLogTrackingAdapter {
getMailService().sendMail(toAddress, subject, multipart);
} catch (MessagingException | MailException | WriterException | IOException e) {
logger.log(Level.SEVERE, "Error trying to send mail to " + competitor.getName()
+ " with e-mail address " + toAddress, e);
occuredExceptions.append(e.getMessage()+"\r\n");
logger.log(Level.SEVERE, "Error trying to send mail to " + invitee + " with e-mail address " + toAddress, e);
throw new MailException(e.getMessage());
}
}
@Override
public void inviteBuoyTenderViaEmail(Event event, Leaderboard leaderboard, String serverUrlWithoutTrailingSlash,
String emails, Locale locale) throws MailException {
StringBuilder occuredExceptions = new StringBuilder();
String[] emailArray = emails.split(",");
String leaderboardName = leaderboard.getName();
// http://<host>/buoy-tender/checkin&leaderboard_name=<leaderboard-name>
String url = DeviceMappingConstants.getBuoyTenderInvitationUrl(serverUrlWithoutTrailingSlash, leaderboardName,
NonGwtUrlHelper.INSTANCE);
for (String toAddress : emailArray) {
try {
sendInvitationEmail(locale, toAddress, leaderboardName,
RaceLogTrackingI18n.STRING_MESSAGES.get(locale, "buoyTender"), url);
} catch (MailException e) {
occuredExceptions.append(e.getMessage() + "\r\n");
}
if (!(occuredExceptions.length() == 0)){
}
if (!(occuredExceptions.length() == 0)) {
throw new MailException(occuredExceptions.toString());
}
}
@@ -6,6 +6,7 @@ import com.sap.sailing.domain.base.Mark;
/**
* Defines a mark, that can then be used to build a course.
*
* @author Fredrik Teschke
*
*/
@@ -1,7 +1,6 @@
package com.sap.sailing.domain.abstractlog.race.tracking.analyzing.impl;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import com.sap.sailing.domain.abstractlog.race.RaceLog;
@@ -10,13 +9,13 @@ import com.sap.sailing.domain.abstractlog.race.analyzing.impl.RaceLogAnalyzer;
import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogDefineMarkEvent;
import com.sap.sailing.domain.base.Mark;
public class DefinedMarkFinder extends RaceLogAnalyzer<Collection<Mark>> {
public class DefinedMarkFinder extends RaceLogAnalyzer<Iterable<Mark>> {
public DefinedMarkFinder(RaceLog raceLog) {
super(raceLog);
}
@Override
protected Collection<Mark> performAnalysis() {
protected Iterable<Mark> performAnalysis() {
List< Mark> result = new ArrayList<Mark>();
for (RaceLogEvent event : getLog().getUnrevokedEvents()) {
if (event instanceof RaceLogDefineMarkEvent) {
@@ -48,6 +48,7 @@ import com.sap.sailing.domain.tracking.DynamicTrackedRace;
import com.sap.sailing.domain.tracking.DynamicTrackedRegatta;
import com.sap.sailing.domain.tracking.GPSFix;
import com.sap.sailing.domain.tracking.GPSFixMoving;
import com.sap.sailing.domain.tracking.GPSFixTrack;
import com.sap.sailing.domain.tracking.LineDetails;
import com.sap.sailing.domain.tracking.Maneuver;
import com.sap.sailing.domain.tracking.MarkPassing;
@@ -893,4 +894,9 @@ public class MockedTrackedRace implements DynamicTrackedRace {
public Distance getAdditionalGateStartDistance(Competitor competitor, TimePoint timePoint) {
return null;
}
@Override
public GPSFixTrack<Mark, GPSFix> getTrack(Mark mark) {
return null;
}
}
@@ -587,4 +587,9 @@ public class MockedTrackedRaceWithStartTimeAndRanks implements TrackedRace {
return null;
}
@Override
public GPSFixTrack<Mark, GPSFix> getTrack(Mark mark) {
return null;
}
}
@@ -6,8 +6,8 @@ import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.base.Series;
import com.sap.sailing.domain.common.Renamable;
import com.sap.sailing.domain.regattalike.HasRegattaLike;
import com.sap.sailing.domain.regattalike.IsRegattaLike;
import com.sap.sailing.domain.regattalike.LeaderboardThatHasRegattaLike;
import com.sap.sailing.domain.tracking.TrackedRace;
/**
@@ -23,7 +23,7 @@ import com.sap.sailing.domain.tracking.TrackedRace;
* @author Axel Uhl (D043530)
*
*/
public interface FlexibleLeaderboard extends Leaderboard, Renamable, HasRegattaLike, IsRegattaLike {
public interface FlexibleLeaderboard extends LeaderboardThatHasRegattaLike, Renamable, IsRegattaLike {
/**
* Moves the column with the name <code>name</code> up.
* @param name The name of the column to move.
@@ -2,8 +2,8 @@ package com.sap.sailing.domain.leaderboard;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.common.Renamable;
import com.sap.sailing.domain.regattalike.HasRegattaLike;
import com.sap.sailing.domain.regattalike.LeaderboardThatHasRegattaLike;
public interface RegattaLeaderboard extends Leaderboard, Renamable, HasRegattaLike {
public interface RegattaLeaderboard extends Renamable, LeaderboardThatHasRegattaLike {
Regatta getRegatta();
}
@@ -0,0 +1,6 @@
package com.sap.sailing.domain.regattalike;
import com.sap.sailing.domain.leaderboard.Leaderboard;
public interface LeaderboardThatHasRegattaLike extends Leaderboard, HasRegattaLike {
}
@@ -221,9 +221,19 @@ public interface TrackedRace extends Serializable {
/**
* Yields the track describing <code>mark</code>'s movement over time; never <code>null</code> because a new track
* will be created in case no track was present for <code>mark</code> so far.
*
* @see #getTrack(Mark)
*/
GPSFixTrack<Mark, GPSFix> getOrCreateTrack(Mark mark);
/**
* Yields the track describing <code>mark</code>'s movement over time; <code>null</code> if no track exists for
* <code>mark</code> so far.
*
* @see #getOrCreateTrack(Mark)
*/
GPSFixTrack<Mark, GPSFix> getTrack(Mark mark);
/**
* Retrieves all marks assigned to the race. They are not necessarily part of the race course.
*/
@@ -871,4 +871,9 @@ DynamicTrackedRace, GPSTrackListener<Competitor, GPSFixMoving> {
}
return new com.sap.sse.common.Util.Pair<Mark, Mark>(portMarkWhileApproachingLine, starboardMarkWhileApproachingLine);
}
@Override
public DynamicGPSFixTrack<Mark, GPSFix> getTrack(Mark mark) {
return (DynamicGPSFixTrack<Mark, GPSFix>) super.getTrack(mark);
}
}
@@ -1092,4 +1092,8 @@ public class GPSFixTrackImpl<ItemType, FixType extends GPSFix> extends TrackImpl
listener.speedAveragingChanged(oldMillis, millisecondsOverWhichToAverage);
}
}
public static Speed getDefaultMaxSpeedForSmoothing() {
return DEFAULT_MAX_SPEED_FOR_SMOOTHING;
}
}
@@ -1280,6 +1280,15 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
*/
@Override
public GPSFixTrack<Mark, GPSFix> getOrCreateTrack(Mark mark) {
return getOrCreateTrack(mark, true);
}
@Override
public GPSFixTrack<Mark, GPSFix> getTrack(Mark mark) {
return getOrCreateTrack(mark, false);
}
private GPSFixTrack<Mark, GPSFix> getOrCreateTrack(Mark mark, boolean createIfNotExistent){
GPSFixTrack<Mark, GPSFix> result = markTracks.get(mark);
if (result == null) {
// try again, this time with more expensive synchronization
@@ -1287,7 +1296,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
LockUtil.lockForRead(getSerializationLock());
try {
result = markTracks.get(mark);
if (result == null) {
if (result == null && createIfNotExistent) {
result = createMarkTrack(mark);
markTracks.put(mark, result);
}
@@ -357,6 +357,10 @@ public class WindTrackImpl extends TrackImpl<Wind> implements WindTrack {
return null;
}
@Override
public Duration getDuration(Distance distance) {
return null;
}
@Override
public SpeedWithBearing add(SpeedWithBearing other) {
return null;
}
@@ -46,6 +46,7 @@ import com.sap.sailing.domain.tracking.DynamicTrackedRace;
import com.sap.sailing.domain.tracking.DynamicTrackedRegatta;
import com.sap.sailing.domain.tracking.GPSFix;
import com.sap.sailing.domain.tracking.GPSFixMoving;
import com.sap.sailing.domain.tracking.GPSFixTrack;
import com.sap.sailing.domain.tracking.LineDetails;
import com.sap.sailing.domain.tracking.Maneuver;
import com.sap.sailing.domain.tracking.MarkPassing;
@@ -888,4 +889,9 @@ public class MockedTrackedRace implements DynamicTrackedRace {
return null;
}
@Override
public GPSFixTrack<Mark, GPSFix> getTrack(Mark mark) {
return null;
}
}
@@ -1,4 +1,4 @@
eclipse.preferences.version=1
entryPointModules=
filesCopiedToWebInfLib=gwt-servlet.jar
gwtCompileSettings=PGd3dC1jb21waWxlLXNldHRpbmdzPjxsb2ctbGV2ZWw+SU5GTzwvbG9nLWxldmVsPjxvdXRwdXQtc3R5bGU+T0JGVVNDQVRFRDwvb3V0cHV0LXN0eWxlPjxleHRyYS1hcmdzPjwhW0NEQVRBWy13YXIgLiAtbG9jYWxXb3JrZXJzIDQgLXN0cmljdCAtbG9nTGV2ZWwgVFJBQ0VdXT48L2V4dHJhLWFyZ3M+PHZtLWFyZ3M+PCFbQ0RBVEFbLVhteDIwMjRtIC1EZ3d0LnVzZWFyY2hpdmVzPWZhbHNlXV0+PC92bS1hcmdzPjxlbnRyeS1wb2ludC1tb2R1bGU+Y29tLnNhcC5zYWlsaW5nLmd3dC5BdXRvUGxheTwvZW50cnktcG9pbnQtbW9kdWxlPjwvZ3d0LWNvbXBpbGUtc2V0dGluZ3M+
gwtCompileSettings=PGd3dC1jb21waWxlLXNldHRpbmdzPjxsb2ctbGV2ZWw+SU5GTzwvbG9nLWxldmVsPjxvdXRwdXQtc3R5bGU+T0JGVVNDQVRFRDwvb3V0cHV0LXN0eWxlPjxleHRyYS1hcmdzPjwhW0NEQVRBWy13YXIgLiAtbG9jYWxXb3JrZXJzIDQgLXN0cmljdCAtbG9nTGV2ZWwgVFJBQ0VdXT48L2V4dHJhLWFyZ3M+PHZtLWFyZ3M+PCFbQ0RBVEFbLVhteDIwMjRtIC1EZ3d0LnVzZWFyY2hpdmVzPWZhbHNlXV0+PC92bS1hcmdzPjxlbnRyeS1wb2ludC1tb2R1bGU+Y29tLnNhcC5zYWlsaW5nLmd3dC51aS5BZG1pbkNvbnNvbGU8L2VudHJ5LXBvaW50LW1vZHVsZT48ZW50cnktcG9pbnQtbW9kdWxlPmNvbS5zYXAuc2FpbGluZy5nd3QudWkuTGVhZGVyYm9hcmQ8L2VudHJ5LXBvaW50LW1vZHVsZT48ZW50cnktcG9pbnQtbW9kdWxlPmNvbS5zYXAuc2FpbGluZy5nd3QudWkuTGVhZGVyYm9hcmRFZGl0aW5nPC9lbnRyeS1wb2ludC1tb2R1bGU+PGVudHJ5LXBvaW50LW1vZHVsZT5jb20uc2FwLnNhaWxpbmcuZ3d0LnVpLlJhY2VCb2FyZDwvZW50cnktcG9pbnQtbW9kdWxlPjxlbnRyeS1wb2ludC1tb2R1bGU+Y29tLnNhcC5zYWlsaW5nLmd3dC51aS5TcGVjdGF0b3I8L2VudHJ5LXBvaW50LW1vZHVsZT48L2d3dC1jb21waWxlLXNldHRpbmdzPg\=\=
Binary file not shown.
@@ -263,11 +263,6 @@ public abstract class AbstractLeaderboardConfigPanel extends FormPanel implement
protected ListDataProvider<StrippedLeaderboardDTO> getListDataProvider() {
return leaderboardList;
}
@Override
public Boolean getValue(StrippedLeaderboardDTO row) {
return leaderboardTable.getSelectionModel().isSelected(row);
}
};
selectionCheckboxColumn.setSortable(true);
leaderboardColumnListHandler.setComparator(selectionCheckboxColumn, new Comparator<StrippedLeaderboardDTO>() {
@@ -88,8 +88,10 @@ public class AddDeviceMappingToRegattaLogDialog extends AbstractCancelableDialog
// load table content
sailingService.getCompetitorRegistrations(leaderboardName, itemSelectionPanel.getSetCompetitorsCallback());
// TODO marks from RegattaLog also?
// fetching marks definitions from the race logs; note that currently there are no mark *definitions*
// on regatta logs although there can be mark/device assignments on a regatta log which then gives the
// mark a position that applies across all races in the regatta-like thing
sailingService.getMarksInRaceLogsAndTrackedRaces(leaderboardName, itemSelectionPanel.getSetMarksCallback());
qrWidget = new DeviceMappingQRCodeWidget(stringMessages, new DeviceMappingQRCodeWidget.URLFactory() {
@Override
public String createURL(String baseUrlWithoutTrailingSlash, String mappedItemType, String mappedItemId)
@@ -94,4 +94,7 @@ interface AdminConsoleResources extends ClientBundle {
@Source("com/sap/sailing/gwt/ui/client/images/close_time_range.png")
ImageResource closeTimeRange();
@Source("com/sap/sailing/gwt/ui/client/images/compose_mail_small.png")
ImageResource inviteBuoyTenders();
}
@@ -84,9 +84,9 @@ public class ImportFixesAndAddMappingsDialog extends DataEntryDialog<Collection<
errorReporter.reportError("Could not load competitors: " + caught.getMessage());
}
});
sailingService.getMarksInRaceLog(leaderboardName, raceColumnName, fleetName, new AsyncCallback<Collection<MarkDTO>>() {
sailingService.getMarksInRaceLog(leaderboardName, raceColumnName, fleetName, new AsyncCallback<Iterable<MarkDTO>>() {
@Override
public void onSuccess(Collection<MarkDTO> result) {
public void onSuccess(Iterable<MarkDTO> result) {
markTable.refresh(result);
}
@@ -0,0 +1,98 @@
package com.sap.sailing.gwt.ui.adminconsole;
import java.util.Collection;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.shared.EventDTO;
import com.sap.sse.common.Util.Triple;
import com.sap.sse.gwt.client.ErrorReporter;
import com.sap.sse.gwt.client.controls.GenericListBox;
import com.sap.sse.gwt.client.controls.GenericListBox.ValueBuilder;
import com.sap.sse.gwt.client.dialog.DataEntryDialog;
public class InviteBuoyTenderDialog extends DataEntryDialog<Triple<EventDTO, String, String>> {
private GenericListBox<EventDTO> events;
private TextBox serverUrl;
private TextBox emails;
private StringMessages stringMessages;
private SailingServiceAsync sailingService;
private String leaderboardName;
private ErrorReporter errorReporter;
public InviteBuoyTenderDialog(final StringMessages stringMessages, SailingServiceAsync sailingService, String leaderboardName, ErrorReporter errorReporter, DialogCallback<Triple<EventDTO, String, String>> callback) {
super(stringMessages.selectEventForInvitation(), null, stringMessages.inviteCompetitors(), stringMessages
.cancel(), new Validator<Triple<EventDTO, String, String>>() {
@Override
public String getErrorMessage(Triple<EventDTO, String, String> valueToValidate) {
if (valueToValidate.getA() == null) {
return stringMessages.pleaseSelectAnEvent();
} else if (valueToValidate.getB() == null || valueToValidate.getB().isEmpty()) {
return stringMessages.pleaseEnterA(stringMessages.hostname());
} else if (valueToValidate.getC() == null || valueToValidate.getC().isEmpty()) {
return stringMessages.pleaseEnterA(stringMessages.email())+stringMessages.orMultipleEmails() ;
}
return null;
}
}, false, callback);
this.stringMessages = stringMessages;
this.sailingService = sailingService;
this.leaderboardName = leaderboardName;
this.errorReporter = errorReporter;
}
@Override
protected Triple<EventDTO, String, String> getResult() {
EventDTO event = events.getValue();
String serverUrlString = serverUrl.getValue();
String emailsString = emails.getValue();
if (serverUrlString.endsWith("/")) {
serverUrlString = serverUrlString.substring(0, serverUrlString.length() - 1);
}
return new Triple<>(event, serverUrlString, emailsString);
}
@Override
protected Widget getAdditionalWidget() {
Grid grid = new Grid(3, 3);
grid.setWidget(0, 0, createLabel(stringMessages.hostname()));
serverUrl = createTextBox(Window.Location.getProtocol() + "//" + Window.Location.getHost());
emails = createTextBox("test@example.com,test2@example.com");
grid.setWidget(0, 1, serverUrl);
grid.setWidget(1, 0, createLabel(stringMessages.event()));
events = createGenericListBox(new ValueBuilder<EventDTO>() {
@Override
public String getValue(EventDTO item) {
if (item == null) {
return "";
}
return item.getName();
}
}, false);
grid.setWidget(1, 1, events);
events.addItem((EventDTO) null);
sailingService.getEventsForLeaderboard(leaderboardName, new AsyncCallback<Collection<EventDTO>>() {
@Override
public void onSuccess(Collection<EventDTO> result) {
events.addItems(result);
}
@Override
public void onFailure(Throwable caught) {
errorReporter.reportError("Could not load events: " + caught.getMessage());
}
});
grid.setWidget(2, 0, createLabel(stringMessages.email()));
grid.setWidget(2, 1, emails);
return grid;
}
}
@@ -107,10 +107,10 @@ public class ItemToMapToDeviceSelectionPanel implements IsWidget {
};
}
public AsyncCallback<Collection<MarkDTO>> getSetMarksCallback() {
return new AsyncCallback<Collection<MarkDTO>>() {
public AsyncCallback<Iterable<MarkDTO>> getSetMarksCallback() {
return new AsyncCallback<Iterable<MarkDTO>>() {
@Override
public void onSuccess(Collection<MarkDTO> result) {
public void onSuccess(Iterable<MarkDTO> result) {
markTable.refresh(result);
select(result, markTable.getSelectionModel());
}
@@ -1,6 +1,5 @@
package com.sap.sailing.gwt.ui.adminconsole;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
@@ -93,10 +92,8 @@ public class MarkTableWrapper<S extends SelectionModel<MarkDTO>> extends TableWr
table.addColumn(markUUIDColumn, "UUID");
}
public void refresh(Collection<MarkDTO> marks) {
dataProvider.getList().clear();
dataProvider.getList().addAll(marks);
dataProvider.flush();
public void refresh(Iterable<MarkDTO> marks) {
super.refresh(marks);
Collections.sort(dataProvider.getList(), new Comparator<MarkDTO>() {
@Override
public int compare(MarkDTO o1, MarkDTO o2) {
@@ -106,9 +103,9 @@ public class MarkTableWrapper<S extends SelectionModel<MarkDTO>> extends TableWr
}
public void refresh(String leaderboardName, String raceColumnName, String fleetName) {
sailingService.getMarksInRaceLog(leaderboardName, raceColumnName, fleetName, new AsyncCallback<Collection<MarkDTO>>() {
sailingService.getMarksInRaceLog(leaderboardName, raceColumnName, fleetName, new AsyncCallback<Iterable<MarkDTO>>() {
@Override
public void onSuccess(Collection<MarkDTO> result) {
public void onSuccess(Iterable<MarkDTO> result) {
refresh(result);
}
@@ -1,7 +1,5 @@
package com.sap.sailing.gwt.ui.adminconsole;
import java.util.Collection;
import com.google.gwt.cell.client.FieldUpdater;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
@@ -125,9 +123,9 @@ public class RaceLogTrackingCourseDefinitionDialog extends RaceLogTrackingDialog
}
});
sailingService.getMarksInRaceLog(leaderboardName, raceColumnName, fleetName, new AsyncCallback<Collection<MarkDTO>>() {
sailingService.getMarksInRaceLog(leaderboardName, raceColumnName, fleetName, new AsyncCallback<Iterable<MarkDTO>>() {
@Override
public void onSuccess(Collection<MarkDTO> result) {
public void onSuccess(Iterable<MarkDTO> result) {
marks.refresh(result);
}
@@ -11,6 +11,7 @@ public class RaceLogTrackingEventManagementImagesBarCell extends ImagesBarCell {
public static final String ACTION_DENOTE_FOR_RACELOG_TRACKING = "ACTION_DENOTE_FOR_RACELOG_TRACKING";
public final static String ACTION_COMPETITOR_REGISTRATIONS = "ACTION_COMPETITOR_REGISTRATIONS";
public final static String ACTION_MAP_DEVICES = "ACTION_MAP_DEVICES";
public final static String ACTION_INVITE_BUOY_TENDERS = "ACTION_INVITE_BUOY_TENDERS";
private static AdminConsoleResources resources = GWT.create(AdminConsoleResources.class);
private final StringMessages stringMessages;
@@ -34,6 +35,8 @@ public class RaceLogTrackingEventManagementImagesBarCell extends ImagesBarCell {
makeImagePrototype(resources.competitorRegistrations())));
result.add(new ImageSpec(ACTION_MAP_DEVICES, stringMessages.mapDevices(),
makeImagePrototype(resources.mapDevices())));
result.add(new ImageSpec(ACTION_INVITE_BUOY_TENDERS, stringMessages.inviteBuoyTenders(),
makeImagePrototype(resources.inviteBuoyTenders())));
return result;
}
@@ -9,6 +9,7 @@ import com.google.gwt.cell.client.FieldUpdater;
import com.google.gwt.core.client.Callback;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.i18n.client.LocaleInfo;
import com.google.gwt.user.cellview.client.CellTable;
import com.google.gwt.user.cellview.client.ColumnSortEvent.ListHandler;
import com.google.gwt.user.cellview.client.TextColumn;
@@ -16,6 +17,7 @@ import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.CaptionPanel;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.Panel;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.view.client.SelectionChangeEvent;
@@ -31,9 +33,11 @@ import com.sap.sailing.gwt.ui.client.RegattaRefresher;
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.client.shared.controls.SelectionCheckboxColumn;
import com.sap.sailing.gwt.ui.shared.EventDTO;
import com.sap.sailing.gwt.ui.shared.RaceLogSetStartTimeAndProcedureDTO;
import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO;
import com.sap.sse.common.Util;
import com.sap.sse.common.Util.Triple;
import com.sap.sse.gwt.client.ErrorReporter;
import com.sap.sse.gwt.client.dialog.DataEntryDialog;
import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback;
@@ -44,6 +48,8 @@ import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback;
public class RaceLogTrackingEventManagementPanel extends AbstractLeaderboardConfigPanel implements LeaderboardsDisplayer {
private Button startTrackingButton;
private TrackFileImportDeviceIdentifierTableWrapper deviceIdentifierTable;
private CheckBox correctWindDirectionForDeclination;
private CheckBox trackWind;
public RaceLogTrackingEventManagementPanel(SailingServiceAsync sailingService,
RegattaRefresher regattaRefresher, LeaderboardsRefresher leaderboardsRefresher,
@@ -132,6 +138,8 @@ public class RaceLogTrackingEventManagementPanel extends AbstractLeaderboardConf
} else if (RaceLogTrackingEventManagementImagesBarCell.ACTION_MAP_DEVICES.equals(value)) {
new AddDeviceMappingToRegattaLogDialog(sailingService, errorReporter, stringMessages,
leaderboardName).show();
} else if (RaceLogTrackingEventManagementImagesBarCell.ACTION_INVITE_BUOY_TENDERS.equals(value)) {
openChooseEventDialogAndSendMails(leaderboardName);
}
}
});
@@ -287,13 +295,17 @@ public class RaceLogTrackingEventManagementPanel extends AbstractLeaderboardConf
@Override
protected void addSelectedLeaderboardRacesControls(Panel racesPanel) {
trackWind = new CheckBox(stringMessages.trackWind());
correctWindDirectionForDeclination = new CheckBox(stringMessages.declinationCheckbox());
startTrackingButton = new Button(stringMessages.startTracking());
startTrackingButton.setEnabled(false);
racesPanel.add(trackWind);
racesPanel.add(correctWindDirectionForDeclination);
racesPanel.add(startTrackingButton);
startTrackingButton.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
startTracking(raceColumnTableSelectionModel.getSelectedSet());
startTracking(raceColumnTableSelectionModel.getSelectedSet(), trackWind.getValue(), correctWindDirectionForDeclination.getValue());
}
});
@@ -393,13 +405,12 @@ public class RaceLogTrackingEventManagementPanel extends AbstractLeaderboardConf
});
}
private void startTracking(Set<RaceColumnDTOAndFleetDTOWithNameBasedEquality> races) {
private void startTracking(Set<RaceColumnDTOAndFleetDTOWithNameBasedEquality> races, boolean trackWind, boolean correctWindByDeclination) {
final StrippedLeaderboardDTO leaderboard = getSelectedLeaderboard();
//prompt user if competitor registrations are missing for same races
String namesOfRacesMissingRegistrations = "";
for (RaceColumnDTOAndFleetDTOWithNameBasedEquality race : races) {
if (! doCompetitorResgistrationsExist(race)) {
if (!doCompetitorResgistrationsExist(race)) {
namesOfRacesMissingRegistrations += race.getA().getName() + "/" + race.getB().getName() + " ";
}
}
@@ -410,12 +421,11 @@ public class RaceLogTrackingEventManagementPanel extends AbstractLeaderboardConf
return;
}
}
for (RaceColumnDTOAndFleetDTOWithNameBasedEquality race : races) {
final RaceColumnDTO raceColumn = race.getA();
final FleetDTO fleet = race.getB();
sailingService.startRaceLogTracking(leaderboard.name, raceColumn.getName(), fleet.getName(),
trackWind, correctWindByDeclination,
new AsyncCallback<Void>() {
@Override
public void onSuccess(Void result) {
@@ -458,4 +468,33 @@ public class RaceLogTrackingEventManagementPanel extends AbstractLeaderboardConf
public void cancel() { }
}).show();
}
private String getLocaleInfo() {
return LocaleInfo.getCurrentLocale().getLocaleName();
}
private void openChooseEventDialogAndSendMails(final String leaderBoardName) {
new InviteBuoyTenderDialog(stringMessages, sailingService, leaderBoardName, errorReporter, new DialogCallback<Triple<EventDTO, String, String>>() {
@Override
public void ok(Triple<EventDTO, String, String> result) {
sailingService.inviteBuoyTenderViaEmail(result.getB(), result.getA(), leaderBoardName, result.getC(), getLocaleInfo(), new AsyncCallback<Void>() {
@Override
public void onFailure(Throwable caught) {
Window.alert(stringMessages.sendingMailsFailed() + caught.getMessage());
}
@Override
public void onSuccess(Void result) {
Window.alert(stringMessages.sendingMailsSuccessfull());
}
});
}
@Override
public void cancel() {
}
}).show();
}
}
@@ -1,7 +1,5 @@
package com.sap.sailing.gwt.ui.adminconsole;
import java.util.Collection;
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.cellview.client.CellTable;
import com.google.gwt.user.cellview.client.ColumnSortEvent.ListHandler;
@@ -15,6 +13,7 @@ import com.google.gwt.view.client.SingleSelectionModel;
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.client.shared.controls.SelectionCheckboxColumn;
import com.sap.sse.common.Util;
import com.sap.sse.gwt.client.ErrorReporter;
/**
@@ -95,9 +94,9 @@ public abstract class TableWrapper<T, S extends SelectionModel<T>> implements Is
return dataProvider;
}
public void refresh(Collection<T> newItems) {
public void refresh(Iterable<T> newItems) {
dataProvider.getList().clear();
dataProvider.getList().addAll(newItems);
Util.addAll(newItems, dataProvider.getList());
dataProvider.flush();
}
}
@@ -458,7 +458,7 @@ public interface SailingService extends RemoteService, FileStorageManagementGwtS
*
* @see RaceLogTrackingAdapter#startTracking
*/
void startRaceLogTracking(String leaderboardName, String raceColumnName, String fleetName)
void startRaceLogTracking(String leaderboardName, String raceColumnName, String fleetName, boolean trackWind, boolean correctWindByDeclination)
throws NotDenotedForRaceLogTrackingException, Exception;
void setCompetitorRegistrations(String leaderboardName, String raceColumnName, String fleetName, Set<CompetitorDTO> competitors);
@@ -477,7 +477,7 @@ public interface SailingService extends RemoteService, FileStorageManagementGwtS
void addMarkToRaceLog(String leaderboardName, String raceColumnName, String fleetName, MarkDTO markDTO);
Collection<MarkDTO> getMarksInRaceLog(String leaderboardName, String raceColumnName, String fleetName);
Iterable<MarkDTO> getMarksInRaceLog(String leaderboardName, String raceColumnName, String fleetName);
/**
* Adds the course definition to the racelog, while trying to reuse existing marks, controlpoints and waypoints
@@ -492,6 +492,10 @@ public interface SailingService extends RemoteService, FileStorageManagementGwtS
*/
void pingMarkViaRaceLogTracking(String leaderboardName, String raceColumnName, String fleetName, MarkDTO mark, PositionDTO position);
/**
* @param raceLogFrom identifies the race log to copy from by its leaderboard name, race column name and fleet name
* @param raceLogsTo identifies the race log to copy from by their leaderboard name, race column name and fleet name
*/
void copyCourseAndCompetitorsToOtherRaceLogs(Util.Triple<String, String, String> raceLogFrom,
Set<Util.Triple<String, String, String>> raceLogsTo);
@@ -577,4 +581,9 @@ public interface SailingService extends RemoteService, FileStorageManagementGwtS
void inviteCompetitorsForTrackingViaEmail(String serverUrlWithoutTrailingSlash, EventDTO event,
String leaderboardName, Set<CompetitorDTO> competitors, String localeInfo) throws MailException;
Iterable<MarkDTO> getMarksInRaceLogsAndTrackedRaces(String leaderboardName);
void inviteBuoyTenderViaEmail(String serverUrlWithoutTrailingSlash, EventDTO eventDto, String leaderboardName,
String emails, String localeInfoName) throws MailException;
}
@@ -571,8 +571,8 @@ public interface SailingServiceAsync extends BuildVersionRetriever, FileStorageM
void denoteForRaceLogTracking(String leaderboardName, AsyncCallback<Void> callback);
void startRaceLogTracking(String leaderboardName, String raceColumnName, String fleetName,
AsyncCallback<Void> callback);
void startRaceLogTracking(String leaderboardName, String raceColumnName, String fleetName, boolean trackWind,
boolean correctWindByDeclination, AsyncCallback<Void> callback);
/**
* Set the competitor registrations in the racelog. Unregisters formerly registered competitors
@@ -595,7 +595,7 @@ public interface SailingServiceAsync extends BuildVersionRetriever, FileStorageM
AsyncCallback<Void> callback);
void getMarksInRaceLog(String leaderboardName, String raceColumnName, String fleetName,
AsyncCallback<Collection<MarkDTO>> callback);
AsyncCallback<Iterable<MarkDTO>> callback);
void addCourseDefinitionToRaceLog(String leaderboardName, String raceColumnName, String fleetName,
List<Util.Pair<ControlPointDTO, PassingInstruction>> course, AsyncCallback<Void> callback);
@@ -688,4 +688,9 @@ public interface SailingServiceAsync extends BuildVersionRetriever, FileStorageM
void inviteCompetitorsForTrackingViaEmail(String serverUrlWithoutTrailingSlash, EventDTO event,
String leaderboardName, Set<CompetitorDTO> competitors, String localeInfo, AsyncCallback<Void> callback);
void getMarksInRaceLogsAndTrackedRaces(String leaderboardName, AsyncCallback<Iterable<MarkDTO>> callback);
void inviteBuoyTenderViaEmail(String serverUrlWithoutTrailingSlash, EventDTO eventDto, String leaderboardName,
String emails, String localeInfoName, AsyncCallback<Void> callback);
}
@@ -1162,6 +1162,8 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages, c
String showCompetitorFullNameColumn();
String loadingDimensionValues();
String runningQuery();
String inviteBuoyTenders();
String orMultipleEmails();
String courseOverGroundTrueDegreesTooltip();
String courseOverGroundTrueDegrees();
String distanceIncludingGateStartInMeters();
@@ -1142,6 +1142,8 @@ showCompetitorSailIdColumn=Show Competitor Sail Id
showCompetitorFullNameColumn=Show Competitor Full Name
loadingDimensionValues=Loading dimension values
runningQuery=Running Query
inviteBuoyTenders=Invite Buoy Tenders
orMultipleEmails=or multiple E-Mails separated by comma
courseOverGroundTrueDegreesTooltip=True course over ground in degrees
courseOverGroundTrueDegrees=COG
distanceIncludingGateStartInMeters=Distance (w/ Gate Start)
@@ -1132,6 +1132,8 @@ showCompetitorSailIdColumn=Sail Id anzeigen
showCompetitorFullNameColumn=Vollen Namen anzeigen
loadingDimensionValues=Lade Dimensionswerte
runningQuery=Datenabfrage läuft
inviteBuoyTenders=Tonnenleger einladen
orMultipleEmails=oder mehrere E-Mails (Komma-separiert)
courseOverGroundTrueDegreesTooltip=Rechtweisender Kurs über Grund in Grad
courseOverGroundTrueDegrees=KüG
distanceIncludingGateStartInMeters=Distanz (m. Torstart)
@@ -1081,6 +1081,8 @@ showCompetitorSailIdColumn=Show Competitor Sail Id
showCompetitorFullNameColumn=Show Competitor Full Name
loadingDimensionValues=Loading dimension values
runningQuery=Running Query
inviteBuoyTenders=Invite Buoy Tenders
orMultipleEmails=or multiple E-Mails separated by comma
courseOverGroundTrueDegreesTooltip=True course over ground in degrees
courseOverGroundTrueDegrees=COG
distanceIncludingGateStartInMeters=Distance (w/ Gate Start)
@@ -71,14 +71,12 @@ import com.sap.sailing.domain.abstractlog.race.state.racingprocedure.gate.Readon
import com.sap.sailing.domain.abstractlog.race.state.racingprocedure.rrs26.ReadonlyRRS26RacingProcedure;
import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogCloseOpenEndedDeviceMappingEvent;
import com.sap.sailing.domain.abstractlog.race.tracking.analyzing.impl.DefinedMarkFinder;
import com.sap.sailing.domain.abstractlog.race.tracking.analyzing.impl.RaceLogLastPingMappingEventFinder;
import com.sap.sailing.domain.abstractlog.race.tracking.analyzing.impl.RaceLogOpenEndedDeviceMappingCloser;
import com.sap.sailing.domain.abstractlog.race.tracking.analyzing.impl.RaceLogTrackingStateAnalyzer;
import com.sap.sailing.domain.abstractlog.regatta.RegattaLog;
import com.sap.sailing.domain.abstractlog.shared.analyzing.DeviceCompetitorMappingFinder;
import com.sap.sailing.domain.abstractlog.shared.analyzing.DeviceMarkMappingFinder;
import com.sap.sailing.domain.abstractlog.shared.analyzing.RegisteredCompetitorsAnalyzer;
import com.sap.sailing.domain.abstractlog.shared.events.DeviceMappingEvent;
import com.sap.sailing.domain.base.BoatClass;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.ControlPoint;
@@ -220,6 +218,7 @@ import com.sap.sailing.domain.racelogtracking.RaceLogTrackingAdapter;
import com.sap.sailing.domain.racelogtracking.RaceLogTrackingAdapterFactory;
import com.sap.sailing.domain.racelogtracking.impl.DeviceMappingImpl;
import com.sap.sailing.domain.regattalike.HasRegattaLike;
import com.sap.sailing.domain.regattalike.LeaderboardThatHasRegattaLike;
import com.sap.sailing.domain.regattalog.RegattaLogStore;
import com.sap.sailing.domain.swisstimingadapter.StartList;
import com.sap.sailing.domain.swisstimingadapter.SwissTimingAdapter;
@@ -253,7 +252,6 @@ import com.sap.sailing.domain.tracking.WindLegTypeAndLegBearingCache;
import com.sap.sailing.domain.tracking.WindPositionMode;
import com.sap.sailing.domain.tracking.WindTrack;
import com.sap.sailing.domain.tracking.WindWithConfidence;
import com.sap.sailing.domain.tracking.impl.DynamicGPSFixTrackImpl;
import com.sap.sailing.domain.tracking.impl.GPSFixImpl;
import com.sap.sailing.domain.tracking.impl.GPSFixMovingImpl;
import com.sap.sailing.domain.tracking.impl.WindImpl;
@@ -4884,28 +4882,18 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
return existing;
}
}
Serializable id = UUID.randomUUID();
return baseDomainFactory.getOrCreateMark(id, dto.getName(), dto.type, dto.color, dto.shape, dto.pattern);
}
/**
* Also finds the last position of the marks, if set by pinging them
*/
private Collection<MarkDTO> convertToMarkDTOs(RaceLog raceLog, Iterable<Mark> marks) {
private Collection<MarkDTO> convertToMarkDTOs(LeaderboardThatHasRegattaLike leaderboard, RaceLog raceLog, Iterable<Mark> marks) {
List<MarkDTO> dtos = new ArrayList<MarkDTO>();
final TimePoint now = MillisecondsTimePoint.now();
for (Mark mark : marks) {
DynamicGPSFixTrackImpl<Mark> track = new DynamicGPSFixTrackImpl<Mark>(mark, 0);
DeviceMappingEvent<?, Mark> lastPingMappingEvent = new RaceLogLastPingMappingEventFinder<Mark>(raceLog, mark).analyze();
if (lastPingMappingEvent != null) {
try {
getService().getGPSFixStore().loadMarkTrack(track, DeviceMappingImpl.convertToDeviceMapping(lastPingMappingEvent));
} catch (TransformationException | NoCorrespondingServiceRegisteredException e) {
logger.log(Level.WARNING, "Could not convert to Mark DTO: " + mark);
}
}
Position lastPos = track.getLastRawFix() == null ? null : track.getLastRawFix().getPosition();
final Position lastPos = getService().getMarkPosition(mark, leaderboard, now, raceLog);
dtos.add(convertToMarkDTO(mark, lastPos));
}
return dtos;
@@ -4921,10 +4909,39 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
}
@Override
public Collection<MarkDTO> getMarksInRaceLog(String leaderboardName, String raceColumnName, String fleetName) {
public Iterable<MarkDTO> getMarksInRaceLog(String leaderboardName, String raceColumnName, String fleetName) {
RaceLog raceLog = getRaceLog(leaderboardName, raceColumnName, fleetName);
Collection<Mark> marks = new DefinedMarkFinder(raceLog).analyze();
return convertToMarkDTOs(raceLog, marks);
Iterable<Mark> marks = new DefinedMarkFinder(raceLog).analyze();
return convertToMarkDTOs((LeaderboardThatHasRegattaLike) getService().getLeaderboardByName(leaderboardName), raceLog, marks);
}
@Override
public Iterable<MarkDTO> getMarksInRaceLogsAndTrackedRaces(String leaderboardName) {
Leaderboard leaderboard = getService().getLeaderboardByName(leaderboardName);
if (leaderboard == null) {
// TODO: implement proper Exception Handling
return null;
}
Set<MarkDTO> markDTOs = new HashSet<>();
Map<Serializable, Mark> marksById = new HashMap<>();
for (RaceColumn raceColumn : leaderboard.getRaceColumns()) {
for (Fleet fleet : raceColumn.getFleets()) {
RaceLog raceLog = raceColumn.getRaceLog(fleet);
TrackedRace trackedRace = raceColumn.getTrackedRace(fleet); //might not yet be attached
for (Mark markDefinitionFromRaceLog : new DefinedMarkFinder(raceLog).analyze()) {
marksById.put(markDefinitionFromRaceLog.getId(), markDefinitionFromRaceLog);
}
if (trackedRace != null) {
for (Mark markFromTrackedRace : trackedRace.getMarks()) {
marksById.put(markFromTrackedRace.getId(), markFromTrackedRace);
}
}
Util.addAll(convertToMarkDTOs(
(LeaderboardThatHasRegattaLike) getService().getLeaderboardByName(leaderboardName),
raceLog, marksById.values()), markDTOs);
}
}
return markDTOs;
}
@Override
@@ -5011,7 +5028,6 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
for (com.sap.sse.common.Util.Triple<String, String, String> toTriple : toTriples) {
toRaceLogs.add(getRaceLog(toTriple));
}
getRaceLogTrackingAdapter().copyCourseAndCompetitors(fromRaceLog, toRaceLogs, baseDomainFactory, getService());
}
@@ -5195,13 +5211,24 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
}
@Override
public void startRaceLogTracking(String leaderboardName, String raceColumnName, String fleetName)
public void startRaceLogTracking(String leaderboardName, String raceColumnName, String fleetName, final boolean trackWind, final boolean correctWindByDeclination)
throws NotDenotedForRaceLogTrackingException, Exception {
Leaderboard leaderboard = getService().getLeaderboardByName(leaderboardName);
RaceColumn raceColumn = leaderboard.getRaceColumnByName(raceColumnName);
Fleet fleet = raceColumn.getFleetByName(fleetName);
getRaceLogTrackingAdapter().startTracking(getService(), leaderboard, raceColumn, fleet);
final RaceHandle raceHandle = getRaceLogTrackingAdapter().startTracking(getService(), leaderboard, raceColumn, fleet);
if (raceHandle != null && trackWind) {
new Thread("Wind tracking starter for race " + leaderboardName + "/" + raceColumnName + "/" + fleetName) {
public void run() {
try {
startTrackingWind(raceHandle, correctWindByDeclination,
RaceTracker.TIMEOUT_FOR_RECEIVING_RACE_DEFINITION_IN_MILLISECONDS);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}.start();
}
}
@Override
@@ -5435,4 +5462,13 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
getRaceLogTrackingAdapter().inviteCompetitorsForTrackingViaEmail(event, leaderboard, serverUrlWithoutTrailingSlash,
competitors, getLocale(localeInfoName));
}
@Override
public void inviteBuoyTenderViaEmail(String serverUrlWithoutTrailingSlash, EventDTO eventDto,
String leaderboardName, String emails, String localeInfoName) throws MailException {
Event event = getService().getEvent(eventDto.id);
Leaderboard leaderboard = getService().getLeaderboardByName(leaderboardName);
getRaceLogTrackingAdapter().inviteBuoyTenderViaEmail(event, leaderboard, serverUrlWithoutTrailingSlash,
emails, getLocale(localeInfoName));
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 270 B

@@ -0,0 +1,25 @@
package com.sap.sailing.server.gateway.deserialization.impl;
import org.json.simple.JSONObject;
import com.sap.sailing.domain.tracking.GPSFix;
import com.sap.sailing.domain.tracking.impl.GPSFixImpl;
import com.sap.sailing.server.gateway.deserialization.JsonDeserializationException;
import com.sap.sailing.server.gateway.deserialization.JsonDeserializer;
import com.sap.sailing.server.gateway.serialization.impl.FlatSmartphoneUuidAndGPSFixMovingJsonSerializer;
public class FlatGPSFixJsonDeserializer implements
JsonDeserializer<GPSFix> {
@Override
public GPSFix deserialize(JSONObject object) throws JsonDeserializationException {
try {
double lonDeg = Double.parseDouble(object.get(FlatSmartphoneUuidAndGPSFixMovingJsonSerializer.LON_DEG).toString());
double latDeg = Double.parseDouble(object.get(FlatSmartphoneUuidAndGPSFixMovingJsonSerializer.LAT_DEG).toString());
long timeMillis = Long.parseLong(object.get(FlatSmartphoneUuidAndGPSFixMovingJsonSerializer.TIME_MILLIS).toString());
return GPSFixImpl.create(lonDeg, latDeg, timeMillis);
} catch (NumberFormatException e){
throw new JsonDeserializationException(e);
}
}
}
@@ -0,0 +1,24 @@
package com.sap.sailing.server.gateway.serialization.impl;
import org.json.simple.JSONObject;
import com.sap.sailing.domain.tracking.GPSFix;
import com.sap.sailing.server.gateway.serialization.JsonSerializer;
public class FlatGPSFixJsonSerializer implements JsonSerializer<GPSFix> {
public static final String FIELD_LON_DEG = "longitude";
public static final String FIELD_LAT_DEG = "latitude";
public static final String FIELD_TIME_MILLIS = "timestamp";
public static final String FIELD_ACCURACY = "accuracy";
@Override
public JSONObject serialize(GPSFix object) {
JSONObject result = new JSONObject();
result.put(FIELD_LON_DEG, object.getPosition().getLngDeg());
result.put(FIELD_LAT_DEG, object.getPosition().getLatDeg());
result.put(FIELD_TIME_MILLIS, object.getTimePoint().asMillis());
// TODO the default accuracy is 1.0; when fixes ever have an accuracy, report it here
result.put(FIELD_ACCURACY, 1.0);
return result;
}
}
@@ -0,0 +1,32 @@
package com.sap.sailing.server.gateway.serialization.impl;
import org.json.simple.JSONObject;
import com.sap.sailing.domain.base.ControlPoint;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.tracking.impl.GPSFixImpl;
import com.sap.sailing.server.gateway.serialization.JsonSerializer;
import com.sap.sailing.server.gateway.serialization.coursedata.impl.MarkJsonSerializer;
import com.sap.sse.common.Util.Pair;
import com.sap.sse.common.impl.MillisecondsTimePoint;
public class MarkJsonSerializerWithPosition implements JsonSerializer<Pair<ControlPoint, Position>> {
public static final String FIELD_POSITION = "position";
private final FlatGPSFixJsonSerializer gpsFixSerializer;
private final MarkJsonSerializer markSerializer;
public MarkJsonSerializerWithPosition(MarkJsonSerializer markSerializer, FlatGPSFixJsonSerializer gpsFixSerializer) {
this.markSerializer = markSerializer;
this.gpsFixSerializer = gpsFixSerializer;
}
@Override
public JSONObject serialize(Pair<ControlPoint, Position> object) {
final JSONObject result = markSerializer.serialize(object.getA());
result.put(FIELD_POSITION,
object.getB() == null ? null : gpsFixSerializer.serialize(new GPSFixImpl(object.getB(),
MillisecondsTimePoint.now())));
return result;
}
}
@@ -0,0 +1,106 @@
package com.sap.sailing.server.gateway.jaxrs.api;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.mockito.Mockito.doReturn;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import javax.ws.rs.core.Response;
import org.junit.Before;
import org.junit.Test;
import com.sap.sailing.domain.abstractlog.regatta.RegattaLog;
import com.sap.sailing.domain.abstractlog.shared.analyzing.DeviceMarkMappingFinder;
import com.sap.sailing.domain.base.Mark;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.base.impl.BoatClassImpl;
import com.sap.sailing.domain.base.impl.FleetImpl;
import com.sap.sailing.domain.base.impl.RegattaImpl;
import com.sap.sailing.domain.base.impl.SeriesImpl;
import com.sap.sailing.domain.common.racelog.tracking.TransformationException;
import com.sap.sailing.domain.leaderboard.RegattaLeaderboard;
import com.sap.sailing.domain.leaderboard.impl.HighPoint;
import com.sap.sailing.domain.racelogtracking.DeviceIdentifier;
import com.sap.sailing.domain.racelogtracking.DeviceMapping;
import com.sap.sailing.domain.racelogtracking.RaceLogTrackingAdapterFactory;
import com.sap.sailing.server.gateway.test.jaxrs.AbstractJaxRsApiTest;
import com.sap.sse.common.NoCorrespondingServiceRegisteredException;
import com.sap.sse.common.impl.MillisecondsTimePoint;
public class LeaderboardsResourcePingMarkTest extends AbstractJaxRsApiTest {
private Mark mark;
private RegattaLog log;
private RegattaLeaderboard leaderboard;
public static final String PING_MARK_JSON =
" {\n" +
" \"timestamp\" : 1427142552000,\n" +
" \"latitude\" : 54.325246,\n" +
" \"longitude\" : 10.148556,\n" +
" \"speed\" : 3.61,\n" +
" \"course\" : 258.11,\n" +
" }";
public static final String PING2_MARK_JSON =
" {\n" +
" \"timestamp\" : 1427142562000,\n" +
" \"latitude\" : 54.425246,\n" +
" \"longitude\" : 10.248556,\n" +
" \"speed\" : 3.61,\n" +
" \"course\" : 258.11,\n" +
" }";
@Before
public void setUp() {
super.setUp();
mark = racingEventService.getBaseDomainFactory().getOrCreateMark("id", "name");
Regatta regatta = new RegattaImpl("regatta", new BoatClassImpl("49er", false), MillisecondsTimePoint.now(),
MillisecondsTimePoint.now(), Collections.singleton(new SeriesImpl("series", false, Collections
.singleton(new FleetImpl("fleet")), Arrays.asList("column"), racingEventService)), false,
new HighPoint(), 0, null);
racingEventService.addRegattaWithoutReplication(regatta);
leaderboard = racingEventService.addRegattaLeaderboard(regatta.getRegattaIdentifier(), "regatta", new int[] {});
log = leaderboard.getRegattaLike().getRegattaLog();
}
@Test
public void testCheckinAndCheckout() throws Exception {
LeaderboardsResource resource = spyResource(new LeaderboardsResource());
doReturn(RaceLogTrackingAdapterFactory.INSTANCE.getAdapter(racingEventService.getBaseDomainFactory())).when(resource).getRaceLogTrackingAdapter();
{
Response response = resource.pingMark(PING_MARK_JSON, leaderboard.getName(), mark.getId().toString());
assertThat("response is ok", response.getStatus(), equalTo(Response.Status.OK.getStatusCode()));
Map<Mark, List<DeviceMapping<Mark>>> mappings = new DeviceMarkMappingFinder<>(log).analyze();
List<DeviceMapping<Mark>> mappingsForMark = mappings.get(mark);
assertThat("one mapping was created for the one ping", mappingsForMark.size(), equalTo(1));
assertOneFixPerMapping(mappingsForMark);
}
{
// now produce a second ping; this should produce two fixes for the mark: one repeating the last known
// position, and another one with the new position.
Response response = resource.pingMark(PING2_MARK_JSON, leaderboard.getName(), mark.getId().toString());
assertThat("response is ok", response.getStatus(), equalTo(Response.Status.OK.getStatusCode()));
Map<Mark, List<DeviceMapping<Mark>>> mappings = new DeviceMarkMappingFinder<>(log).analyze();
List<DeviceMapping<Mark>> mappingsForMark = mappings.get(mark);
assertThat("two additional mappings were created for the second ping", mappingsForMark.size(), equalTo(3));
assertOneFixPerMapping(mappingsForMark);
}
}
private void assertOneFixPerMapping(List<DeviceMapping<Mark>> mappingsForMark) throws TransformationException,
NoCorrespondingServiceRegisteredException {
for (DeviceMapping<Mark> i : mappingsForMark) {
DeviceIdentifier device = i.getDevice();
// first ping produces exactly one fix:
assertThat("all fixes stored", racingEventService.getGPSFixStore().getNumberOfFixes(device), equalTo(1L));
}
}
}
@@ -21,6 +21,7 @@ import com.sap.sailing.domain.base.impl.CompetitorImpl;
import com.sap.sailing.domain.base.impl.NationalityImpl;
import com.sap.sailing.domain.base.impl.PersonImpl;
import com.sap.sailing.domain.base.impl.TeamImpl;
import com.sap.sailing.domain.racelog.tracking.test.mock.MockSmartphoneUuidServiceFinderFactory;
import com.sap.sailing.server.RacingEventService;
import com.sap.sailing.server.gateway.jaxrs.AbstractSailingServerResource;
import com.sap.sailing.server.impl.RacingEventServiceImpl;
@@ -40,7 +41,7 @@ public abstract class AbstractJaxRsApiTest {
service = MongoDBConfiguration.getDefaultTestConfiguration().getService();
service.getDB().dropDatabase();
racingEventService = new RacingEventServiceImpl();
racingEventService = new RacingEventServiceImpl(true, new MockSmartphoneUuidServiceFinderFactory());
}
protected <T extends AbstractSailingServerResource> T spyResource(T resource) {
@@ -21,16 +21,7 @@ import com.sap.sailing.server.impl.RacingEventServiceImpl;
import com.sap.sse.common.NoCorrespondingServiceRegisteredException;
public class GPSFixesResourceTest {
private RacingEventService service;
@Before
public void setup() {
service = new RacingEventServiceImpl(true, new MockSmartphoneUuidServiceFinderFactory());
service.getMongoObjectFactory().getDatabase().dropDatabase();
}
@Test
public void deserialize() throws ParseException, TransformationException, NoCorrespondingServiceRegisteredException {
String json = "{\n" +
public static final String FIXES_JSON = "{\n" +
" \"deviceUuid\" : \"af855a56-9726-4a9c-a77e-da955bd289bf\",\n" +
" \"fixes\" : [\n" +
" {\n" +
@@ -49,12 +40,24 @@ public class GPSFixesResourceTest {
" }\n" +
" ]\n" +
"}";
private RacingEventService service;
@Before
public void setup() {
service = new RacingEventServiceImpl(true, new MockSmartphoneUuidServiceFinderFactory());
service.getMongoObjectFactory().getDatabase().dropDatabase();
}
@Test
public void deserialize() throws ParseException, TransformationException, NoCorrespondingServiceRegisteredException {
GPSFixesResource resource = new GPSFixesResource() {
public RacingEventService getService() {
return service;
};
};
Response response = resource.postFixes(json);
Response response = resource.postFixes(FIXES_JSON);
assertThat("response is ok", response.getStatus(), equalTo(Response.Status.OK.getStatusCode()));
DeviceIdentifier device = new SmartphoneUUIDIdentifierImpl(UUID.fromString("af855a56-9726-4a9c-a77e-da955bd289bf"));
@@ -24,6 +24,7 @@ Require-Bundle: com.sap.sailing.domain,
com.sap.sailing.domain.tractracadapter,
com.sap.sailing.domain.persistence,
com.sap.sse.mongodb,
com.sap.sailing.domain.racelogtrackingadapter,
org.mongodb.mongo-java-driver;bundle-version="2.13.0",
javax.servlet;bundle-version="2.6.0",
com.sap.sailing.domain.common,
@@ -6,6 +6,7 @@ import java.math.RoundingMode;
import javax.servlet.ServletContext;
import javax.ws.rs.core.Context;
import org.osgi.framework.BundleContext;
import org.osgi.util.tracker.ServiceTracker;
import com.sap.sailing.domain.base.RaceDefinition;
@@ -21,6 +22,16 @@ import com.sap.sse.util.DateParser;
public abstract class AbstractSailingServerResource {
@Context ServletContext servletContext;
protected <T> T getService(Class<T> clazz) {
BundleContext context = (BundleContext) servletContext
.getAttribute(RestServletContainer.OSGI_RFC66_WEBBUNDLE_BUNDLECONTEXT_NAME);
ServiceTracker<T, T> tracker = new ServiceTracker<T, T>(context, clazz, null);
tracker.open();
T service = tracker.getService();
tracker.close();
return service;
}
public RacingEventService getService() {
@SuppressWarnings("unchecked")
ServiceTracker<RacingEventService, RacingEventService> tracker = (ServiceTracker<RacingEventService, RacingEventService>) servletContext.getAttribute(RestServletContainer.RACING_EVENT_SERVICE_TRACKER_NAME);
@@ -13,7 +13,7 @@ import com.sun.jersey.spi.container.servlet.ServletContainer;
public class RestServletContainer extends ServletContainer {
private static final long serialVersionUID = -3132655822281021061L;
private static final String OSGI_RFC66_WEBBUNDLE_BUNDLECONTEXT_NAME = "osgi-bundlecontext";
public static final String OSGI_RFC66_WEBBUNDLE_BUNDLECONTEXT_NAME = "osgi-bundlecontext";
public static final String RACING_EVENT_SERVICE_TRACKER_NAME = "racingEventServiceTracker";
@@ -5,6 +5,7 @@ import java.io.Serializable;
import java.io.StringWriter;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -25,6 +26,7 @@ import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.xml.ws.http.HTTPException;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
@@ -33,6 +35,9 @@ import org.json.simple.parser.ParseException;
import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor;
import com.sap.sailing.domain.abstractlog.impl.LogEventAuthorImpl;
import com.sap.sailing.domain.abstractlog.race.RaceLog;
import com.sap.sailing.domain.abstractlog.race.tracking.analyzing.impl.DefinedMarkFinder;
import com.sap.sailing.domain.abstractlog.regatta.RegattaLog;
import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogCloseOpenEndedDeviceMappingEventImpl;
import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogDeviceCompetitorMappingEventImpl;
import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogRegisterCompetitorEventImpl;
@@ -41,32 +46,50 @@ import com.sap.sailing.domain.abstractlog.shared.analyzing.RegisteredCompetitors
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.DomainFactory;
import com.sap.sailing.domain.base.Fleet;
import com.sap.sailing.domain.base.Mark;
import com.sap.sailing.domain.base.Nationality;
import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.LeaderboardNameConstants;
import com.sap.sailing.domain.common.MaxPointsReason;
import com.sap.sailing.domain.common.NoWindException;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.dto.CompetitorDTO;
import com.sap.sailing.domain.common.dto.FleetDTO;
import com.sap.sailing.domain.common.dto.LeaderboardDTO;
import com.sap.sailing.domain.common.dto.LeaderboardEntryDTO;
import com.sap.sailing.domain.common.dto.LeaderboardRowDTO;
import com.sap.sailing.domain.common.dto.RaceColumnDTO;
import com.sap.sailing.domain.common.racelog.RaceLogServletConstants;
import com.sap.sailing.domain.common.racelog.tracking.DeviceMappingConstants;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.leaderboard.SettableScoreCorrection;
import com.sap.sailing.domain.racelogtracking.DeviceIdentifier;
import com.sap.sailing.domain.racelogtracking.RaceLogTrackingAdapter;
import com.sap.sailing.domain.racelogtracking.RaceLogTrackingAdapterFactory;
import com.sap.sailing.domain.racelogtracking.impl.SmartphoneUUIDIdentifierImpl;
import com.sap.sailing.domain.regattalike.HasRegattaLike;
import com.sap.sailing.domain.regattalike.IsRegattaLike;
import com.sap.sailing.domain.regattalike.LeaderboardThatHasRegattaLike;
import com.sap.sailing.domain.tracking.GPSFix;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.impl.GPSFixImpl;
import com.sap.sailing.domain.tracking.impl.GPSFixTrackImpl;
import com.sap.sailing.server.RacingEventService;
import com.sap.sailing.server.gateway.deserialization.JsonDeserializationException;
import com.sap.sailing.server.gateway.deserialization.impl.FlatGPSFixJsonDeserializer;
import com.sap.sailing.server.gateway.deserialization.impl.Helpers;
import com.sap.sailing.server.gateway.jaxrs.AbstractSailingServerResource;
import com.sap.sailing.server.gateway.serialization.coursedata.impl.MarkJsonSerializer;
import com.sap.sailing.server.gateway.serialization.impl.FlatGPSFixJsonSerializer;
import com.sap.sailing.server.gateway.serialization.impl.MarkJsonSerializerWithPosition;
import com.sap.sse.common.Duration;
import com.sap.sse.common.NoCorrespondingServiceRegisteredException;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util;
import com.sap.sse.common.Util.Pair;
import com.sap.sse.common.impl.MillisecondsTimePoint;
@Path("/v1/leaderboards")
@@ -368,9 +391,9 @@ public class LeaderboardsResource extends AbstractSailingServerResource {
String deviceUuid = (String) requestObject.get(DeviceMappingConstants.JSON_DEVICE_UUID);
Long fromMillis = (Long) requestObject.get(DeviceMappingConstants.JSON_FROM_MILLIS);
//don't need the device type and push ID yet - important once we start add support for push notifications
//String deviceType = (String) requestObject.get(DeviceMappingConstants.JSON_DEVICE_TYPE);
//String pushDeviceId = (String) requestObject.get(DeviceMappingConstants.JSON_PUSH_DEVICE_ID);
// don't need the device type and push ID yet - important once we start add support for push notifications
// String deviceType = (String) requestObject.get(DeviceMappingConstants.JSON_DEVICE_TYPE);
// String pushDeviceId = (String) requestObject.get(DeviceMappingConstants.JSON_PUSH_DEVICE_ID);
if (competitorId == null || deviceUuid == null || fromMillis == null) {
// || deviceType == null
@@ -385,7 +408,7 @@ public class LeaderboardsResource extends AbstractSailingServerResource {
return Response.status(Status.BAD_REQUEST).entity("No competitor found for id " + competitorId)
.type(MediaType.TEXT_PLAIN).build();
}
//add registration if necessary
// add registration if necessary
Set<Competitor> registered = new RegisteredCompetitorsAnalyzer<>(isRegattaLike.getRegattaLog()).analyze();
if (!registered.contains(mappedTo)) {
isRegattaLike.getRegattaLog().add(
@@ -393,7 +416,8 @@ public class LeaderboardsResource extends AbstractSailingServerResource {
}
DeviceIdentifier device = new SmartphoneUUIDIdentifierImpl(UUID.fromString(deviceUuid));
TimePoint from = new MillisecondsTimePoint(fromMillis);
event = new RegattaLogDeviceCompetitorMappingEventImpl(now, author, now, UUID.randomUUID(), mappedTo, device, from, null);
event = new RegattaLogDeviceCompetitorMappingEventImpl(now, author, now, UUID.randomUUID(), mappedTo, device,
from, null);
isRegattaLike.getRegattaLog().add(event);
logger.fine("Successfully checked in competitor " + mappedTo.getName());
return Response.status(Status.OK).build();
@@ -451,7 +475,7 @@ public class LeaderboardsResource extends AbstractSailingServerResource {
String deviceUuid = (String) requestObject.get("deviceUuid");
TimePoint closingTimePoint = new MillisecondsTimePoint(toMillis);
if (toMillis == null || deviceUuid == null || closingTimePoint == null){
if (toMillis == null || deviceUuid == null || closingTimePoint == null) {
logger.warning("Invalid JSON body in request");
return Response.status(Status.BAD_REQUEST).entity("Invalid JSON body in request")
.type(MediaType.TEXT_PLAIN).build();
@@ -464,8 +488,8 @@ public class LeaderboardsResource extends AbstractSailingServerResource {
.type(MediaType.TEXT_PLAIN).build();
}
OpenEndedDeviceMappingFinder finder = new OpenEndedDeviceMappingFinder(isRegattaLike.getRegattaLog(),
mappedTo, deviceUuid);
OpenEndedDeviceMappingFinder finder = new OpenEndedDeviceMappingFinder(isRegattaLike.getRegattaLog(), mappedTo,
deviceUuid);
Serializable deviceMappingEventId = finder.analyze();
if (deviceMappingEventId == null) {
@@ -486,7 +510,8 @@ public class LeaderboardsResource extends AbstractSailingServerResource {
@GET
@Produces("application/json;charset=UTF-8")
@Path("{leaderboardName}/competitors/{competitorId}")
public Response getCompetitor(@PathParam("leaderboardName") String leaderboardName, @PathParam("competitorId") String competitorIdAsString) {
public Response getCompetitor(@PathParam("leaderboardName") String leaderboardName,
@PathParam("competitorId") String competitorIdAsString) {
Response response;
Leaderboard leaderboard = getService().getLeaderboardByName(leaderboardName);
Competitor competitor = getService().getCompetitorStore().getExistingCompetitorByIdAsString(
@@ -494,8 +519,8 @@ public class LeaderboardsResource extends AbstractSailingServerResource {
if (competitor == null) {
response = Response.status(Status.NOT_FOUND)
.entity("Could not find a competitor with id '" + competitorIdAsString + "'.").type(MediaType.TEXT_PLAIN)
.build();
.entity("Could not find a competitor with id '" + competitorIdAsString + "'.")
.type(MediaType.TEXT_PLAIN).build();
} else if (leaderboard == null) {
response = Response.status(Status.NOT_FOUND)
.entity("Could not find a leaderboard with name '" + leaderboardName + "'.")
@@ -507,4 +532,174 @@ public class LeaderboardsResource extends AbstractSailingServerResource {
}
return response;
}
@GET
@Produces(MediaType.APPLICATION_JSON)
@Path("{leaderboardName}/marks")
public Response getMarksForRace(@PathParam("leaderboardName") String leaderboardName,
@QueryParam(RaceLogServletConstants.PARAMS_RACE_COLUMN_NAME) String raceColumnName,
@QueryParam(RaceLogServletConstants.PARAMS_RACE_FLEET_NAME) String fleetName) {
// TODO also look for defined marks in RegattaLog?
Leaderboard leaderboard = getService().getLeaderboardByName(leaderboardName);
if (leaderboard == null) {
return Response.status(Status.NOT_FOUND)
.entity("Could not find a leaderboard with name '" + leaderboardName + "'.")
.type(MediaType.TEXT_PLAIN).build();
}
final Set<Mark> marks;
if (raceColumnName == null) {
if (fleetName != null) {
return Response
.status(Status.BAD_REQUEST)
.entity("Either specify neither raceColumnName nor fleetName, only raceColumnName, or raceColumnName and fleetName but not only fleetName")
.type(MediaType.TEXT_PLAIN).build();
} else {
marks = new HashSet<Mark>();
for (RaceColumn raceColumn : leaderboard.getRaceColumns()) {
for (Fleet fleet : raceColumn.getFleets()) {
RaceLog raceLog = raceColumn.getRaceLog(fleet);
TrackedRace trackedRace = raceColumn.getTrackedRace(fleet); // might not yet be attached
Util.addAll(new DefinedMarkFinder(raceLog).analyze(), marks);
if (trackedRace != null) {
Util.addAll(trackedRace.getMarks(), marks);
}
}
}
}
} else {
RaceColumn raceColumn = leaderboard.getRaceColumnByName(raceColumnName);
if (raceColumn == null) {
return Response
.status(Status.NOT_FOUND)
.entity("Could not find a race column '" + raceColumnName + "' in leaderboard '"
+ leaderboardName + "'.").type(MediaType.TEXT_PLAIN).build();
} else if (fleetName != null) {
Fleet fleet = raceColumn.getFleetByName(fleetName);
if (fleet == null) {
return Response
.status(Status.NOT_FOUND)
.entity("Could not find fleet '" + fleetName + "' in leaderboard '" + leaderboardName
+ "'.").type(MediaType.TEXT_PLAIN).build();
} else {
marks = getMarksForFleet(raceColumn, fleet);
}
} else {
// Return all marks for a certain race column
marks = new HashSet<Mark>();
for (Fleet fleet : raceColumn.getFleets()) {
Util.addAll(getMarksForFleet(raceColumn, fleet), marks);
}
}
}
JSONArray array = new JSONArray();
for (Mark mark : marks) {
final MillisecondsTimePoint now = MillisecondsTimePoint.now();
Position lastKnownPosition = getService().getMarkPosition(mark,
(LeaderboardThatHasRegattaLike) leaderboard, now,
/* raceLog==null means use all race logs */ null);
array.add(markWithPositionSerializer.serialize(new Pair<>(mark, lastKnownPosition)));
}
JSONObject result = new JSONObject();
result.put("marks", array);
return Response.ok(result.toJSONString(), MediaType.APPLICATION_JSON).build();
}
private Set<Mark> getMarksForFleet(RaceColumn raceColumn, Fleet fleet) {
RaceLog raceLog = raceColumn.getRaceLog(fleet);
TrackedRace trackedRace = raceColumn.getTrackedRace(fleet); // might not yet be attached
Set<Mark> marks = new HashSet<>();
Util.addAll(new DefinedMarkFinder(raceLog).analyze(), marks);
if (trackedRace != null) {
Util.addAll(trackedRace.getMarks(), marks);
}
return marks;
}
private final MarkJsonSerializer markSerializer = new MarkJsonSerializer();
private final MarkJsonSerializerWithPosition markWithPositionSerializer = new MarkJsonSerializerWithPosition(markSerializer, new FlatGPSFixJsonSerializer());
private final FlatGPSFixJsonDeserializer fixDeserializer = new FlatGPSFixJsonDeserializer();
private final FlatGPSFixJsonSerializer fixSerializer = new FlatGPSFixJsonSerializer();
/**
* Mockito requires this to be public in order to be able to mock it :-(
*/
public RaceLogTrackingAdapter getRaceLogTrackingAdapter() {
return getService(RaceLogTrackingAdapterFactory.class).getAdapter(getService().getBaseDomainFactory());
}
@POST
@Path("{leaderboardName}/marks/{markId}/gps_fixes")
@Consumes(MediaType.APPLICATION_JSON)
/**
* Add the fixes to the GPSFixStore and create mappings for each fix in the RegattaLog.
* @param json
* @param leaderboardName
* @param markId
* @return
* @throws HTTPException
*/
public Response pingMark(String json, @PathParam("leaderboardName") String leaderboardName,
@PathParam("markId") String markId) throws HTTPException {
logger.fine("Post issued to " + this.getClass().getName());
Leaderboard leaderboard = getService().getLeaderboardByName(leaderboardName);
if (leaderboard == null) {
return Response.status(Status.NOT_FOUND)
.entity("Could not find a leaderboard with name '" + leaderboardName + "'.")
.type(MediaType.TEXT_PLAIN).build();
}
RegattaLog regattaLog = null;
if (leaderboard instanceof HasRegattaLike) {
regattaLog = ((HasRegattaLike) leaderboard).getRegattaLike().getRegattaLog();
} else {
return Response.status(Status.BAD_REQUEST)
.entity("Leaderboard '" + leaderboardName + "' does not have an attached RegattaLog.")
.type(MediaType.TEXT_PLAIN).build();
}
Mark mark = getService().getBaseDomainFactory().getExistingMarkByIdAsString(markId);
if (mark == null) {
return Response.status(Status.NOT_FOUND).entity("Could not find a mark with ID '" + markId + "'.")
.type(MediaType.TEXT_PLAIN).build();
}
MillisecondsTimePoint now = MillisecondsTimePoint.now();
Position lastKnownPosition = getService().getMarkPosition(mark,
(LeaderboardThatHasRegattaLike) leaderboard, now,
/* raceLog==null means use all race logs */ null);
GPSFix fix = null;
try {
Object requestBody = JSONValue.parseWithException(json);
JSONObject requestObject = Helpers.toJSONObjectSafe(requestBody);
logger.fine("JSON requestObject is: " + requestObject.toString());
fix = fixDeserializer.deserialize(requestObject);
} catch (ParseException | JsonDeserializationException | NumberFormatException e) {
logger.warning(String.format("Exception while parsing post request:\n%s", e.toString()));
return Response.status(Status.BAD_REQUEST).entity("Invalid JSON body in request")
.type(MediaType.TEXT_PLAIN).build();
}
RaceLogTrackingAdapter adapter = getRaceLogTrackingAdapter();
RacingEventService service = getService();
try {
if (lastKnownPosition != null) {
// ping again to avoid interpolation, avoid filtering by paying attention to maxSpeedForSmoothing
final Distance distance = lastKnownPosition.getDistance(fix.getPosition());
final Duration minDuration = GPSFixTrackImpl.getDefaultMaxSpeedForSmoothing().getDuration(distance);
final GPSFix repeatedFixAvoidingCreepingMark = new GPSFixImpl(lastKnownPosition, fix.getTimePoint().minus(minDuration));
adapter.pingMark(regattaLog, mark, repeatedFixAvoidingCreepingMark, service);
}
adapter.pingMark(regattaLog, mark, fix, service);
logger.log(Level.INFO, "Pinged mark " + mark.getName());
} catch (NoCorrespondingServiceRegisteredException e) {
logger.log(Level.WARNING, "Could not ping mark " + mark.getName());
}
if (lastKnownPosition != null) {
JSONObject lastKnownFixJson = fixSerializer.serialize(new GPSFixImpl(lastKnownPosition, now));
String fixJson = lastKnownFixJson.toJSONString();
return Response.ok(fixJson, MediaType.APPLICATION_JSON).build();
} else {
return Response.ok().build();
}
}
}
@@ -70,11 +70,14 @@ public class RegattasResource extends AbstractSailingServerResource {
private static final Logger logger = Logger.getLogger(RegattasResource.class.getName());
private Response getBadRegattaErrorResponse(String regattaName) {
return Response.status(Status.NOT_FOUND).entity("Could not find a regatta with name '" + regattaName + "'.").type(MediaType.TEXT_PLAIN).build();
return Response.status(Status.NOT_FOUND).entity("Could not find a regatta with name '" + regattaName + "'.")
.type(MediaType.TEXT_PLAIN).build();
}
private Response getBadRaceErrorResponse(String regattaName, String raceName) {
return Response.status(Status.NOT_FOUND).entity("Could not find a race with name '" + raceName + "' in regatta '" + regattaName + "'.").type(MediaType.TEXT_PLAIN).build();
return Response.status(Status.NOT_FOUND)
.entity("Could not find a race with name '" + raceName + "' in regatta '" + regattaName + "'.")
.type(MediaType.TEXT_PLAIN).build();
}
@GET
@@ -99,7 +102,8 @@ public class RegattasResource extends AbstractSailingServerResource {
if (regatta == null) {
response = getBadRegattaErrorResponse(regattaName);
} else {
SeriesJsonSerializer seriesJsonSerializer = new SeriesJsonSerializer(new FleetJsonSerializer(new ColorJsonSerializer()));
SeriesJsonSerializer seriesJsonSerializer = new SeriesJsonSerializer(new FleetJsonSerializer(
new ColorJsonSerializer()));
JsonSerializer<Regatta> regattaSerializer = new RegattaJsonSerializer(seriesJsonSerializer, null);
JSONObject serializedRegatta = regattaSerializer.serialize(regatta);
@@ -111,7 +115,9 @@ public class RegattasResource extends AbstractSailingServerResource {
/**
* Gets all entries for a regatta.
* @param regattaName the name of the regatta
*
* @param regattaName
* the name of the regatta
* @return
*/
@GET
@@ -150,9 +156,10 @@ public class RegattasResource extends AbstractSailingServerResource {
@GET
@Produces("application/json;charset=UTF-8")
@Path("{regattaname}/races/{racename}/competitors/positions")
public Response getCompetitorPositions(@PathParam("regattaname") String regattaName, @PathParam("racename") String raceName,
@QueryParam("fromtime") String fromtime, @QueryParam("fromtimeasmillis") Long fromtimeasmillis,
@QueryParam("totime") String totime, @QueryParam("totimeasmillis") Long totimeasmillis, @QueryParam("withtack") Boolean withTack,
public Response getCompetitorPositions(@PathParam("regattaname") String regattaName,
@PathParam("racename") String raceName, @QueryParam("fromtime") String fromtime,
@QueryParam("fromtimeasmillis") Long fromtimeasmillis, @QueryParam("totime") String totime,
@QueryParam("totimeasmillis") Long totimeasmillis, @QueryParam("withtack") Boolean withTack,
@QueryParam("competitorId") Set<String> competitorIds) {
Response response;
Regatta regatta = findRegattaByName(regattaName);
@@ -170,14 +177,17 @@ public class RegattasResource extends AbstractSailingServerResource {
try {
from = parseTimePoint(fromtime, fromtimeasmillis,
trackedRace.getStartOfRace() == null ? new MillisecondsTimePoint(0) :
/* 24h before race start */ new MillisecondsTimePoint(trackedRace.getStartOfRace().asMillis()-24*3600*1000));
/* 24h before race start */new MillisecondsTimePoint(trackedRace.getStartOfRace()
.asMillis() - 24 * 3600 * 1000));
} catch (InvalidDateException e1) {
return Response.status(Status.INTERNAL_SERVER_ERROR).entity("Could not parse the 'from' time.").type(MediaType.TEXT_PLAIN).build();
return Response.status(Status.INTERNAL_SERVER_ERROR).entity("Could not parse the 'from' time.")
.type(MediaType.TEXT_PLAIN).build();
}
try {
to = parseTimePoint(totime, totimeasmillis, MillisecondsTimePoint.now());
} catch (InvalidDateException e1) {
return Response.status(Status.INTERNAL_SERVER_ERROR).entity("Could not parse the 'to' time.").type(MediaType.TEXT_PLAIN).build();
return Response.status(Status.INTERNAL_SERVER_ERROR).entity("Could not parse the 'to' time.")
.type(MediaType.TEXT_PLAIN).build();
}
JSONObject jsonRace = new JSONObject();
@@ -185,12 +195,14 @@ public class RegattasResource extends AbstractSailingServerResource {
jsonRace.put("regatta", regatta.getName());
JSONArray jsonCompetitors = new JSONArray();
for (Competitor competitor : trackedRace.getRace().getCompetitors()) {
if (competitorIds == null || competitorIds.isEmpty() || competitorIds.contains(competitor.getId().toString())) {
if (competitorIds == null || competitorIds.isEmpty()
|| competitorIds.contains(competitor.getId().toString())) {
JSONObject jsonCompetitor = new JSONObject();
jsonCompetitor.put("id", competitor.getId() != null ? competitor.getId().toString() : null);
jsonCompetitor.put("name", competitor.getName());
jsonCompetitor.put("sailNumber", competitor.getBoat().getSailID());
jsonCompetitor.put("color", competitor.getColor() != null ? competitor.getColor().getAsHtml() : null);
jsonCompetitor.put("color", competitor.getColor() != null ? competitor.getColor().getAsHtml()
: null);
GPSFixTrack<Competitor, GPSFixMoving> track = trackedRace.getTrack(competitor);
JSONArray jsonFixes = new JSONArray();
track.lockForRead();
@@ -247,24 +259,29 @@ public class RegattasResource extends AbstractSailingServerResource {
/**
* Gets all GPS positions of the course marks for a given race.
* @param regattaName the name of the regatta
*
* @param regattaName
* the name of the regatta
* @return
*/
@GET
@Produces("application/json;charset=UTF-8")
@Path("{regattaname}/races/{racename}/marks/positions")
public Response getMarkPositions(@PathParam("regattaname") String regattaName, @PathParam("racename") String raceName,
@QueryParam("fromtime") String fromtime, @QueryParam("fromtimeasmillis") Long fromtimeasmillis,
@QueryParam("totime") String totime, @QueryParam("totimeasmillis") Long totimeasmillis) {
Response response;
public Response getMarkPositions(@PathParam("regattaname") String regattaName,
@PathParam("racename") String raceName, @QueryParam("fromtime") String fromtime,
@QueryParam("fromtimeasmillis") Long fromtimeasmillis, @QueryParam("totime") String totime,
@QueryParam("totimeasmillis") Long totimeasmillis,
@DefaultValue("false") @QueryParam("lastknown") boolean addLastKnown) {
Regatta regatta = findRegattaByName(regattaName);
if (regatta == null) {
response = getBadRegattaErrorResponse(regattaName);
} else {
return getBadRegattaErrorResponse(regattaName);
}
RaceDefinition race = findRaceByName(regatta, raceName);
if (race == null) {
response = getBadRaceErrorResponse(regattaName, raceName);
} else {
return getBadRaceErrorResponse(regattaName, raceName);
}
TrackedRace trackedRace = findTrackedRace(regattaName, raceName);
TimePoint from;
@@ -272,14 +289,17 @@ public class RegattasResource extends AbstractSailingServerResource {
try {
from = parseTimePoint(fromtime, fromtimeasmillis,
trackedRace.getStartOfRace() == null ? new MillisecondsTimePoint(0) :
/* 24h before race start */ new MillisecondsTimePoint(trackedRace.getStartOfRace().asMillis()-24*3600*1000));
/* 24h before race start */new MillisecondsTimePoint(
trackedRace.getStartOfRace().asMillis() - 24 * 3600 * 1000));
} catch (InvalidDateException e1) {
return Response.status(Status.INTERNAL_SERVER_ERROR).entity("Could not parse the 'from' time.").type(MediaType.TEXT_PLAIN).build();
return Response.status(Status.INTERNAL_SERVER_ERROR).entity("Could not parse the 'from' time.")
.type(MediaType.TEXT_PLAIN).build();
}
try {
to = parseTimePoint(totime, totimeasmillis, MillisecondsTimePoint.now());
} catch (InvalidDateException e1) {
return Response.status(Status.INTERNAL_SERVER_ERROR).entity("Could not parse the 'to' time.").type(MediaType.TEXT_PLAIN).build();
return Response.status(Status.INTERNAL_SERVER_ERROR).entity("Could not parse the 'to' time.")
.type(MediaType.TEXT_PLAIN).build();
}
JSONObject jsonRace = new JSONObject();
@@ -308,17 +328,24 @@ public class RegattasResource extends AbstractSailingServerResource {
} else {
fixIter = track.getFixesIterator(from, /* inclusive */true);
}
GPSFix fix = null;
boolean lastAdded = false;
while (fixIter.hasNext()) {
GPSFix fix = fixIter.next();
fix = fixIter.next();
if (to != null && fix.getTimePoint() != null && to.compareTo(fix.getTimePoint()) < 0) {
lastAdded = false;
break;
}
JSONObject jsonFix = new JSONObject();
jsonFix.put("timepoint-ms", fix.getTimePoint().asMillis());
jsonFix.put("lat-deg", UnitSerializationUtil.latLngDecimalFormatter.format(fix.getPosition().getLatDeg()));
jsonFix.put("lng-deg", UnitSerializationUtil.latLngDecimalFormatter.format(fix.getPosition().getLngDeg()));
JSONObject jsonFix = constructFixJson(fix);
jsonFixes.add(jsonFix);
lastAdded = true;
}
if (addLastKnown && fix != null && !lastAdded){
JSONObject jsonFix = constructFixJson(fix);
jsonFixes.add(jsonFix);
}
} finally {
track.unlockAfterRead();
}
@@ -328,15 +355,25 @@ public class RegattasResource extends AbstractSailingServerResource {
jsonRace.put("marks", jsonMarks);
String json = jsonRace.toJSONString();
response = Response.ok(json, MediaType.APPLICATION_JSON).build();
return Response.ok(json, MediaType.APPLICATION_JSON).build();
}
}
return response;
private JSONObject constructFixJson(GPSFix fix) {
JSONObject jsonFix = new JSONObject();
jsonFix.put("timepoint-ms", fix.getTimePoint().asMillis());
jsonFix.put("lat-deg",
UnitSerializationUtil.latLngDecimalFormatter.format(fix.getPosition().getLatDeg()));
jsonFix.put("lng-deg",
UnitSerializationUtil.latLngDecimalFormatter.format(fix.getPosition().getLngDeg()));
return jsonFix;
}
/**
* Gets the course of the race.
* @param regattaName the name of the regatta
*
* @param regattaName
* the name of the regatta
* @return
*/
@GET
@@ -353,11 +390,9 @@ public class RegattasResource extends AbstractSailingServerResource {
response = getBadRaceErrorResponse(regattaName, raceName);
} else {
Course course = race.getCourse();
CourseBaseJsonSerializer serializer = new CourseBaseJsonSerializer(
new WaypointJsonSerializer(
new ControlPointJsonSerializer(
new MarkJsonSerializer(),
new GateJsonSerializer(new MarkJsonSerializer()))));
CourseBaseJsonSerializer serializer = new CourseBaseJsonSerializer(new WaypointJsonSerializer(
new ControlPointJsonSerializer(new MarkJsonSerializer(), new GateJsonSerializer(
new MarkJsonSerializer()))));
JSONObject jsonCourse = serializer.serialize(course);
String json = jsonCourse.toJSONString();
@@ -369,7 +404,9 @@ public class RegattasResource extends AbstractSailingServerResource {
/**
* Gets the relevant times of the race.
* @param regattaName the name of the regatta
*
* @param regattaName
* the name of the regatta
* @return
*/
@GET
@@ -391,16 +428,22 @@ public class RegattasResource extends AbstractSailingServerResource {
jsonRaceTimes.put("name", trackedRace.getRace().getName());
jsonRaceTimes.put("regatta", regatta.getName());
jsonRaceTimes.put("startOfRace-ms", trackedRace.getStartOfRace() == null ? null : trackedRace.getStartOfRace().asMillis());
jsonRaceTimes.put("startOfTracking-ms", trackedRace.getStartOfTracking() == null ? null : trackedRace.getStartOfTracking().asMillis());
jsonRaceTimes.put("newestTrackingEvent-ms", trackedRace.getTimePointOfNewestEvent() == null ? null : trackedRace.getTimePointOfNewestEvent().asMillis());
jsonRaceTimes.put("endOfTracking-ms", trackedRace.getEndOfTracking() == null ? null : trackedRace.getEndOfTracking().asMillis());
jsonRaceTimes.put("endOfRace-ms", trackedRace.getEndOfRace() == null ? null : trackedRace.getEndOfRace().asMillis());
jsonRaceTimes.put("startOfRace-ms", trackedRace.getStartOfRace() == null ? null : trackedRace
.getStartOfRace().asMillis());
jsonRaceTimes.put("startOfTracking-ms", trackedRace.getStartOfTracking() == null ? null : trackedRace
.getStartOfTracking().asMillis());
jsonRaceTimes.put("newestTrackingEvent-ms", trackedRace.getTimePointOfNewestEvent() == null ? null
: trackedRace.getTimePointOfNewestEvent().asMillis());
jsonRaceTimes.put("endOfTracking-ms", trackedRace.getEndOfTracking() == null ? null : trackedRace
.getEndOfTracking().asMillis());
jsonRaceTimes.put("endOfRace-ms", trackedRace.getEndOfRace() == null ? null : trackedRace
.getEndOfRace().asMillis());
jsonRaceTimes.put("delayToLive-ms", trackedRace.getDelayToLiveInMillis());
JSONArray jsonMarkPassingTimes = new JSONArray();
List<TimePoint> firstPassingTimepoints = new ArrayList<>();
Iterable<com.sap.sse.common.Util.Pair<Waypoint, com.sap.sse.common.Util.Pair<TimePoint, TimePoint>>> markPassingsTimes = trackedRace.getMarkPassingsTimes();
Iterable<com.sap.sse.common.Util.Pair<Waypoint, com.sap.sse.common.Util.Pair<TimePoint, TimePoint>>> markPassingsTimes = trackedRace
.getMarkPassingsTimes();
synchronized (markPassingsTimes) {
int numberOfWaypoints = Util.size(markPassingsTimes);
int wayPointNumber = 1;
@@ -414,8 +457,10 @@ public class RegattasResource extends AbstractSailingServerResource {
com.sap.sse.common.Util.Pair<TimePoint, TimePoint> timesPair = markPassingTimes.getB();
TimePoint firstPassingTime = timesPair.getA();
TimePoint lastPassingTime = timesPair.getB();
jsonMarkPassing.put("firstPassing-ms", firstPassingTime == null ? null : firstPassingTime.asMillis());
jsonMarkPassing.put("lastPassing-ms", lastPassingTime == null ? null : lastPassingTime.asMillis());
jsonMarkPassing.put("firstPassing-ms",
firstPassingTime == null ? null : firstPassingTime.asMillis());
jsonMarkPassing.put("lastPassing-ms",
lastPassingTime == null ? null : lastPassingTime.asMillis());
firstPassingTimepoints.add(firstPassingTime);
@@ -438,7 +483,10 @@ public class RegattasResource extends AbstractSailingServerResource {
TimePoint firstPassingTime = firstPassingTimepoints.get(legNumber - 1);
if (firstPassingTime != null) {
jsonLegInfo.put("type", trackedLeg.getLegType(firstPassingTime));
jsonLegInfo.put("bearing-deg", UnitSerializationUtil.bearingDecimalFormatter.format(trackedLeg.getLegBearing(firstPassingTime).getDegrees()));
jsonLegInfo.put(
"bearing-deg",
UnitSerializationUtil.bearingDecimalFormatter.format(trackedLeg.getLegBearing(
firstPassingTime).getDegrees()));
}
} catch (NoWindException e) {
// do nothing
@@ -469,11 +517,15 @@ public class RegattasResource extends AbstractSailingServerResource {
Response response;
Regatta regatta = findRegattaByName(regattaName);
if (regatta == null) {
response = Response.status(Status.NOT_FOUND).entity("Could not find a regatta with name '" + regattaName + "'.").type(MediaType.TEXT_PLAIN).build();
response = Response.status(Status.NOT_FOUND)
.entity("Could not find a regatta with name '" + regattaName + "'.").type(MediaType.TEXT_PLAIN)
.build();
} else {
RaceDefinition race = findRaceByName(regatta, raceName);
if (race == null) {
response = Response.status(Status.NOT_FOUND).entity("Could not find a race with name '" + raceName + "'.").type(MediaType.TEXT_PLAIN).build();
response = Response.status(Status.NOT_FOUND)
.entity("Could not find a race with name '" + raceName + "'.").type(MediaType.TEXT_PLAIN)
.build();
} else {
TrackedRace trackedRace = findTrackedRace(regattaName, raceName);
JSONArray windSourcesAvailable = new JSONArray();
@@ -496,17 +548,21 @@ public class RegattasResource extends AbstractSailingServerResource {
@Path("{regattaname}/races/{racename}/wind")
public Response getWind(@PathParam("regattaname") String regattaName, @PathParam("racename") String raceName,
@DefaultValue("COMBINED") @QueryParam("windsource") String windSource,
@QueryParam("windsourceid") String windSourceId,
@QueryParam("fromtime") String fromtime, @QueryParam("fromtimeasmillis") Long fromtimeasmillis,
@QueryParam("totime") String totime, @QueryParam("totimeasmillis") Long totimeasmillis) {
@QueryParam("windsourceid") String windSourceId, @QueryParam("fromtime") String fromtime,
@QueryParam("fromtimeasmillis") Long fromtimeasmillis, @QueryParam("totime") String totime,
@QueryParam("totimeasmillis") Long totimeasmillis) {
Response response;
Regatta regatta = findRegattaByName(regattaName);
if (regatta == null) {
response = Response.status(Status.NOT_FOUND).entity("Could not find a regatta with name '" + regattaName + "'.").type(MediaType.TEXT_PLAIN).build();
response = Response.status(Status.NOT_FOUND)
.entity("Could not find a regatta with name '" + regattaName + "'.").type(MediaType.TEXT_PLAIN)
.build();
} else {
RaceDefinition race = findRaceByName(regatta, raceName);
if (race == null) {
response = Response.status(Status.NOT_FOUND).entity("Could not find a race with name '" + raceName + "'.").type(MediaType.TEXT_PLAIN).build();
response = Response.status(Status.NOT_FOUND)
.entity("Could not find a race with name '" + raceName + "'.").type(MediaType.TEXT_PLAIN)
.build();
} else {
TrackedRace trackedRace = findTrackedRace(regattaName, raceName);
@@ -515,17 +571,21 @@ public class RegattasResource extends AbstractSailingServerResource {
try {
from = parseTimePoint(fromtime, fromtimeasmillis,
trackedRace.getStartOfRace() == null ? new MillisecondsTimePoint(0) :
/* 24h before race start */ new MillisecondsTimePoint(trackedRace.getStartOfRace().asMillis()-24*3600*1000));
/* 24h before race start */new MillisecondsTimePoint(trackedRace.getStartOfRace()
.asMillis() - 24 * 3600 * 1000));
} catch (InvalidDateException e1) {
return Response.status(Status.INTERNAL_SERVER_ERROR).entity("Could not parse the 'from' time.").type(MediaType.TEXT_PLAIN).build();
return Response.status(Status.INTERNAL_SERVER_ERROR).entity("Could not parse the 'from' time.")
.type(MediaType.TEXT_PLAIN).build();
}
try {
to = parseTimePoint(totime, totimeasmillis, MillisecondsTimePoint.now());
} catch (InvalidDateException e1) {
return Response.status(Status.INTERNAL_SERVER_ERROR).entity("Could not parse the 'to' time.").type(MediaType.TEXT_PLAIN).build();
return Response.status(Status.INTERNAL_SERVER_ERROR).entity("Could not parse the 'to' time.")
.type(MediaType.TEXT_PLAIN).build();
}
TrackedRaceJsonSerializer serializer = new TrackedRaceJsonSerializer(new DefaultWindTrackJsonSerializer());
TrackedRaceJsonSerializer serializer = new TrackedRaceJsonSerializer(
new DefaultWindTrackJsonSerializer());
serializer.setWindSource(windSource);
serializer.setWindSourceId(windSourceId);
serializer.setFromTime(from);
@@ -542,29 +602,38 @@ public class RegattasResource extends AbstractSailingServerResource {
@GET
@Produces("application/json;charset=UTF-8")
@Path("{regattaname}/races/{racename}/firstlegbearing")
public Response getFirstLegBearing(@PathParam("regattaname") String regattaName, @PathParam("racename") String raceName,
@QueryParam("time") String time, @QueryParam("timeasmillis") Long timeasmillis) {
public Response getFirstLegBearing(@PathParam("regattaname") String regattaName,
@PathParam("racename") String raceName, @QueryParam("time") String time,
@QueryParam("timeasmillis") Long timeasmillis) {
Response response;
Regatta regatta = findRegattaByName(regattaName);
if (regatta == null) {
response = Response.status(Status.NOT_FOUND).entity("Could not find a regatta with name '" + regattaName + "'.").type(MediaType.TEXT_PLAIN).build();
response = Response.status(Status.NOT_FOUND)
.entity("Could not find a regatta with name '" + regattaName + "'.").type(MediaType.TEXT_PLAIN)
.build();
} else {
RaceDefinition race = findRaceByName(regatta, raceName);
if (race == null) {
response = Response.status(Status.NOT_FOUND).entity("Could not find a race with name '" + raceName + "'.").type(MediaType.TEXT_PLAIN).build();
response = Response.status(Status.NOT_FOUND)
.entity("Could not find a race with name '" + raceName + "'.").type(MediaType.TEXT_PLAIN)
.build();
} else {
TrackedRace trackedRace = findTrackedRace(regattaName, raceName);
final TimePoint timePoint;
try {
timePoint = parseTimePoint(time, timeasmillis,
trackedRace.getStartOfRace() == null ? new MillisecondsTimePoint(0) :
trackedRace.getStartOfRace());
timePoint = parseTimePoint(
time,
timeasmillis,
trackedRace.getStartOfRace() == null ? new MillisecondsTimePoint(0) : trackedRace
.getStartOfRace());
} catch (InvalidDateException e1) {
return Response.status(Status.INTERNAL_SERVER_ERROR).entity("Could not parse the 'from' time.").type(MediaType.TEXT_PLAIN).build();
return Response.status(Status.INTERNAL_SERVER_ERROR).entity("Could not parse the 'from' time.")
.type(MediaType.TEXT_PLAIN).build();
}
BearingJsonSerializer serializer = new BearingJsonSerializer();
JSONObject jsonBearing = serializer.serialize(trackedRace.getDirectionFromStartToNextMark(timePoint).getFrom());
JSONObject jsonBearing = serializer.serialize(trackedRace.getDirectionFromStartToNextMark(timePoint)
.getFrom());
String json = jsonBearing.toJSONString();
return Response.ok(json, MediaType.APPLICATION_JSON).build();
}
@@ -579,11 +648,15 @@ public class RegattasResource extends AbstractSailingServerResource {
Response response;
Regatta regatta = findRegattaByName(regattaName);
if (regatta == null) {
response = Response.status(Status.NOT_FOUND).entity("Could not find a regatta with name '" + regattaName + "'.").type(MediaType.TEXT_PLAIN).build();
response = Response.status(Status.NOT_FOUND)
.entity("Could not find a regatta with name '" + regattaName + "'.").type(MediaType.TEXT_PLAIN)
.build();
} else {
RaceDefinition race = findRaceByName(regatta, raceName);
if (race == null) {
response = Response.status(Status.NOT_FOUND).entity("Could not find a race with name '" + raceName + "'.").type(MediaType.TEXT_PLAIN).build();
response = Response.status(Status.NOT_FOUND)
.entity("Could not find a race with name '" + raceName + "'.").type(MediaType.TEXT_PLAIN)
.build();
} else {
TrackedRace trackedRace = findTrackedRace(regattaName, raceName);
MarkPassingsJsonSerializer serializer = new MarkPassingsJsonSerializer();
@@ -625,15 +698,20 @@ public class RegattasResource extends AbstractSailingServerResource {
@GET
@Produces("application/json;charset=UTF-8")
@Path("{regattaname}/races/{racename}/competitors/legs")
public Response getCompetitorRanks(@PathParam("regattaname") String regattaName, @PathParam("racename") String raceName) {
public Response getCompetitorRanks(@PathParam("regattaname") String regattaName,
@PathParam("racename") String raceName) {
Response response;
Regatta regatta = findRegattaByName(regattaName);
if (regatta == null) {
response = Response.status(Status.NOT_FOUND).entity("Could not find a regatta with name '" + regattaName + "'.").type(MediaType.TEXT_PLAIN).build();
response = Response.status(Status.NOT_FOUND)
.entity("Could not find a regatta with name '" + regattaName + "'.").type(MediaType.TEXT_PLAIN)
.build();
} else {
RaceDefinition race = findRaceByName(regatta, raceName);
if (race == null) {
response = Response.status(Status.NOT_FOUND).entity("Could not find a race with name '" + raceName + "'.").type(MediaType.TEXT_PLAIN).build();
response = Response.status(Status.NOT_FOUND)
.entity("Could not find a race with name '" + raceName + "'.").type(MediaType.TEXT_PLAIN)
.build();
} else {
TrackedRace trackedRace = findTrackedRace(regattaName, raceName);
TimePoint timePoint = trackedRace.getTimePointOfNewestEvent() == null ? MillisecondsTimePoint.now()
@@ -642,7 +720,8 @@ public class RegattasResource extends AbstractSailingServerResource {
JSONObject jsonRaceResults = new JSONObject();
jsonRaceResults.put("name", trackedRace.getRace().getName());
jsonRaceResults.put("regatta", regatta.getName());
jsonRaceResults.put("startOfRace-ms", trackedRace.getStartOfRace() == null ? null : trackedRace.getStartOfRace().asMillis());
jsonRaceResults.put("startOfRace-ms", trackedRace.getStartOfRace() == null ? null : trackedRace
.getStartOfRace().asMillis());
JSONArray jsonLegs = new JSONArray();
for (TrackedLeg leg : trackedRace.getTrackedLegs()) {
@@ -663,20 +742,25 @@ public class RegattasResource extends AbstractSailingServerResource {
JSONObject jsonCompetitorInLeg = new JSONObject();
TrackedLegOfCompetitor trackedLegOfCompetitor = leg.getTrackedLeg(competitor);
if (trackedLegOfCompetitor != null) {
jsonCompetitorInLeg.put("id", competitor.getId() != null ? competitor.getId().toString() : null);
jsonCompetitorInLeg.put("id", competitor.getId() != null ? competitor.getId().toString()
: null);
jsonCompetitorInLeg.put("name", competitor.getName());
jsonCompetitorInLeg.put("sailNumber", competitor.getBoat().getSailID());
jsonCompetitorInLeg.put("color", competitor.getColor() != null ? competitor.getColor().getAsHtml() : null);
jsonCompetitorInLeg.put("color", competitor.getColor() != null ? competitor.getColor()
.getAsHtml() : null);
Speed averageSpeedOverGround = trackedLegOfCompetitor.getAverageSpeedOverGround(timePoint);
if(averageSpeedOverGround != null) {
jsonCompetitorInLeg.put("averageSOG-kts", UnitSerializationUtil.knotsDecimalFormatter.format(averageSpeedOverGround.getKnots()));
if (averageSpeedOverGround != null) {
jsonCompetitorInLeg.put("averageSOG-kts", UnitSerializationUtil.knotsDecimalFormatter
.format(averageSpeedOverGround.getKnots()));
}
try {
Integer numberOfTacks = trackedLegOfCompetitor.getNumberOfTacks(timePoint, /* waitForLatest */ false);
Integer numberOfJibes = trackedLegOfCompetitor.getNumberOfJibes(timePoint, /* waitForLatest */ false);
Integer numberOfPenaltyCircles = trackedLegOfCompetitor
.getNumberOfPenaltyCircles(timePoint, /* waitForLatest */ false);
Integer numberOfTacks = trackedLegOfCompetitor.getNumberOfTacks(timePoint, /* waitForLatest */
false);
Integer numberOfJibes = trackedLegOfCompetitor.getNumberOfJibes(timePoint, /* waitForLatest */
false);
Integer numberOfPenaltyCircles = trackedLegOfCompetitor.getNumberOfPenaltyCircles(
timePoint, /* waitForLatest */false);
jsonCompetitorInLeg.put("tacks", numberOfTacks);
jsonCompetitorInLeg.put("jibes", numberOfJibes);
jsonCompetitorInLeg.put("penaltyCircles", numberOfPenaltyCircles);
@@ -701,8 +785,8 @@ public class RegattasResource extends AbstractSailingServerResource {
if (timeSinceGun > 0) {
jsonCompetitorInLeg.put("timeSinceGun-ms", timeSinceGun);
}
Distance distanceSinceGun = trackedRace.getTrack(competitor).getDistanceTraveled(startOfRace,
finishTime != null ? finishTime : timePoint);
Distance distanceSinceGun = trackedRace.getTrack(competitor).getDistanceTraveled(
startOfRace, finishTime != null ? finishTime : timePoint);
if (distanceSinceGun != null) {
jsonCompetitorInLeg.put("distanceSinceGun-m", distanceSinceGun.getMeters());
}
@@ -710,7 +794,9 @@ public class RegattasResource extends AbstractSailingServerResource {
Distance distanceTraveled = trackedLegOfCompetitor.getDistanceTraveled(timePoint);
if (distanceTraveled != null) {
jsonCompetitorInLeg.put("distanceTraveled-m", UnitSerializationUtil.distanceDecimalFormatter.format(distanceTraveled.getMeters()));
jsonCompetitorInLeg.put("distanceTraveled-m",
UnitSerializationUtil.distanceDecimalFormatter.format(distanceTraveled
.getMeters()));
}
Distance distanceTraveledIncludingGateStart = trackedLegOfCompetitor.getDistanceTraveledConsideringGateStart(timePoint);
if (distanceTraveledIncludingGateStart != null) {
@@ -729,8 +815,8 @@ public class RegattasResource extends AbstractSailingServerResource {
}
}
try {
jsonCompetitorInLeg.put("gapToLeader-s",
trackedLegOfCompetitor.getGapToLeaderInSeconds(timePoint, WindPositionMode.LEG_MIDDLE));
jsonCompetitorInLeg.put("gapToLeader-s", trackedLegOfCompetitor
.getGapToLeaderInSeconds(timePoint, WindPositionMode.LEG_MIDDLE));
} catch (NoWindException e1) {
// well, we don't know the wind direction... then no gap to leader will be shown...
}
@@ -754,16 +840,20 @@ public class RegattasResource extends AbstractSailingServerResource {
@GET
@Produces("application/json;charset=UTF-8")
@Path("{regattaname}/races/{racename}/competitors/live")
public Response getCompetitorLiveRanks(@PathParam("regattaname") String regattaName, @PathParam("racename") String raceName,
@DefaultValue("-1") @QueryParam("topN") Integer topN) {
public Response getCompetitorLiveRanks(@PathParam("regattaname") String regattaName,
@PathParam("racename") String raceName, @DefaultValue("-1") @QueryParam("topN") Integer topN) {
Response response;
Regatta regatta = findRegattaByName(regattaName);
if (regatta == null) {
response = Response.status(Status.NOT_FOUND).entity("Could not find a regatta with name '" + regattaName + "'.").type(MediaType.TEXT_PLAIN).build();
response = Response.status(Status.NOT_FOUND)
.entity("Could not find a regatta with name '" + regattaName + "'.").type(MediaType.TEXT_PLAIN)
.build();
} else {
RaceDefinition race = findRaceByName(regatta, raceName);
if (race == null) {
response = Response.status(Status.NOT_FOUND).entity("Could not find a race with name '" + raceName + "'.").type(MediaType.TEXT_PLAIN).build();
response = Response.status(Status.NOT_FOUND)
.entity("Could not find a race with name '" + raceName + "'.").type(MediaType.TEXT_PLAIN)
.build();
} else {
TrackedRace trackedRace = findTrackedRace(regattaName, raceName);
Course course = trackedRace.getRace().getCourse();
@@ -777,12 +867,12 @@ public class RegattasResource extends AbstractSailingServerResource {
jsonLiveData.put("name", trackedRace.getRace().getName());
jsonLiveData.put("regatta", regatta.getName());
if(trackedRace.getStartOfRace() != null) {
if (trackedRace.getStartOfRace() != null) {
TimePoint startOfRace = trackedRace.getStartOfRace();
TimePoint now = MillisecondsTimePoint.now();
jsonLiveData.put("startTime", startOfRace.asMillis());
jsonLiveData.put("liveTime", now.asMillis());
if(startOfRace.before(now)) {
if (startOfRace.before(now)) {
jsonLiveData.put("timeSinceStart-s", (now.asMillis() - startOfRace.asMillis()) / 1000.0);
} else {
jsonLiveData.put("timeToStart-s", (startOfRace.asMillis() - now.asMillis()) / 1000.0);
@@ -796,47 +886,56 @@ public class RegattasResource extends AbstractSailingServerResource {
for (Competitor competitor : competitorsFromBestToWorst) {
JSONObject jsonCompetitorInLeg = new JSONObject();
if(topN != null && topN > 0 && rank > topN) {
if (topN != null && topN > 0 && rank > topN) {
break;
}
jsonCompetitorInLeg.put("id", competitor.getId() != null ? competitor.getId().toString() : null);
jsonCompetitorInLeg
.put("id", competitor.getId() != null ? competitor.getId().toString() : null);
jsonCompetitorInLeg.put("name", competitor.getName());
jsonCompetitorInLeg.put("sailNumber", competitor.getBoat().getSailID());
jsonCompetitorInLeg.put("color", competitor.getColor() != null ? competitor.getColor().getAsHtml() : null);
jsonCompetitorInLeg.put("color", competitor.getColor() != null ? competitor.getColor()
.getAsHtml() : null);
jsonCompetitorInLeg.put("rank", rank++);
TrackedLegOfCompetitor currentLegOfCompetitor = trackedRace.getCurrentLeg(competitor, timePoint);
TrackedLegOfCompetitor currentLegOfCompetitor = trackedRace
.getCurrentLeg(competitor, timePoint);
if (currentLegOfCompetitor != null) {
int indexOfWaypoint = course.getIndexOfWaypoint(currentLegOfCompetitor.getLeg().getFrom());
jsonCompetitorInLeg.put("leg", indexOfWaypoint+1);
jsonCompetitorInLeg.put("leg", indexOfWaypoint + 1);
Speed speedOverGround = currentLegOfCompetitor.getSpeedOverGround(timePoint);
if(speedOverGround != null) {
jsonCompetitorInLeg.put("speedOverGround-kts", roundDouble(speedOverGround.getKnots(), 2));
if (speedOverGround != null) {
jsonCompetitorInLeg.put("speedOverGround-kts",
roundDouble(speedOverGround.getKnots(), 2));
}
Distance distanceTraveled = currentLegOfCompetitor.getDistanceTraveled(timePoint);
if (distanceTraveled != null) {
jsonCompetitorInLeg.put("distanceTraveled-m", roundDouble(distanceTraveled.getMeters(), 2));
jsonCompetitorInLeg.put("distanceTraveled-m",
roundDouble(distanceTraveled.getMeters(), 2));
}
Distance distanceTraveledConsideringGateStart = currentLegOfCompetitor.getDistanceTraveledConsideringGateStart(timePoint);
if (distanceTraveledConsideringGateStart != null) {
jsonCompetitorInLeg.put("distanceTraveledConsideringGateStart-m", roundDouble(distanceTraveledConsideringGateStart.getMeters(), 2));
}
Double gapToLeaderInSeconds = currentLegOfCompetitor.getGapToLeaderInSeconds(timePoint, WindPositionMode.LEG_MIDDLE);
if(gapToLeaderInSeconds != null) {
Double gapToLeaderInSeconds = currentLegOfCompetitor.getGapToLeaderInSeconds(timePoint,
WindPositionMode.LEG_MIDDLE);
if (gapToLeaderInSeconds != null) {
jsonCompetitorInLeg.put("gapToLeader-s", roundDouble(gapToLeaderInSeconds, 2));
}
Distance windwardDistanceToOverallLeader = currentLegOfCompetitor.getWindwardDistanceToOverallLeader(timePoint, WindPositionMode.LEG_MIDDLE);
if(windwardDistanceToOverallLeader != null) {
jsonCompetitorInLeg.put("gapToLeader-m", roundDouble(windwardDistanceToOverallLeader.getMeters(), 2));
Distance windwardDistanceToOverallLeader = currentLegOfCompetitor
.getWindwardDistanceToOverallLeader(timePoint, WindPositionMode.LEG_MIDDLE);
if (windwardDistanceToOverallLeader != null) {
jsonCompetitorInLeg.put("gapToLeader-m",
roundDouble(windwardDistanceToOverallLeader.getMeters(), 2));
}
jsonCompetitorInLeg.put("finished", false);
} else {
// we need to distinguish between competitors which did not start and competitors which already finished
if(trackedRace.getMarkPassing(competitor, lastWaypoint) != null) {
// we need to distinguish between competitors which did not start and competitors which
// already finished
if (trackedRace.getMarkPassing(competitor, lastWaypoint) != null) {
jsonCompetitorInLeg.put("finished", true);
} else {
jsonCompetitorInLeg.put("finished", false);
@@ -856,4 +955,3 @@ public class RegattasResource extends AbstractSailingServerResource {
return response;
}
}
@@ -19,6 +19,10 @@
<td>Webservice Type:</td>
<td>REST</td>
</tr>
<tr>
<td>Input format:</td>
<td>binary (not multipart)</td>
</tr>
<tr>
<td>Output format:</td>
<td>JSON</td>
@@ -41,18 +45,26 @@
</tr>
<tr>
<td>Request method:</td>
<td>POST (Binary data, not multipart)</td>
<td>POST</td>
</tr>
<tr>
<td>Request example:</td>
<td><pre>curl -H "Content-Type:image/jpeg" --data-binary @{image-path} \
http://sapsailing.com/sailingserver/api/v1/competitors/{competitor-id}/team/image</pre></td>
<td>
<pre><code>
curl -H "Content-Type:image/jpeg" --data-binary @{image-path} \
http://sapsailing.com/sailingserver/api/v1/competitors/{competitor-id}/team/image
</code></pre>
</td>
</tr>
<tr>
<td>Response example:</td>
<td><pre>{
<td>
<pre><code>
{
"teamImageUri" : "http://training.sapsailing.com/team_images/9871d3a2c554b27151cacf1422eec048.jpeg"
}</pre></td>
}
</code></pre>
</td>
</tr>
</table>
<div style="height: 1em;"></div>
@@ -21,8 +21,8 @@ These can be mapped to a competitor or a mark via the DeviceMappings in the Race
<td>REST</td>
</tr>
<tr>
<td>Post data input format:</td>
<td>JSON</td>
<td>Input format:</td>
<td>JSON (application/json)</td>
</tr>
<tr>
<td>Output format:</td>
@@ -42,8 +42,7 @@ These can be mapped to a competitor or a mark via the DeviceMappings in the Race
</tr>
<tr>
<td>Example:</td>
<td>
POST the following JSON data to <a href="http://www.sapsailing.com/sailingserver/api/v1/gps_fixes">http://www.sapsailing.com/sailingserver/api/v1/gps_fixes</a>
<td>POST <a href="http://www.sapsailing.com/sailingserver/api/v1/gps_fixes">http://www.sapsailing.com/sailingserver/api/v1/gps_fixes</a>
<pre><code>
{
"deviceUuid" : "af855a56-9726-4a9c-a77e-da955bd289bf",
@@ -64,7 +63,8 @@ These can be mapped to a competitor or a mark via the DeviceMappings in the Race
}
]
}
</code></pre></td>
</code></pre>
</td>
</tr>
</table>
<div style="height: 1em;"></div>
@@ -116,6 +116,14 @@ name to make sure you get to the event you're interested in.
<td><a href="leaderboardCompetitorGetDoc.html">/api/v1/leaderboards/{name}/competitors/{id}</a></td>
<td>Gets the details of a competitor in the context of a leaderboard</td>
</tr>
<tr>
<td><a href="leaderboardRaceMarksGetDoc.html">/api/v1/leaderboards/{name}/marks?race_column={name}&amp;fleet={name}</a></td>
<td>Gets the marks of a race (from the RaceLog and if possible the TrackedRace)</td>
</tr>
<tr>
<td><a href="leaderboardMarkGpsFixesPostDoc.html">/api/v1/leaderboards/{name}/marks/{id}/gps_fixes</a></td>
<td>Pings the location of a mark via the RegattaLog</td>
</tr>
<tr>
<td><a href="gpsFixesPostDoc.html">/api/v1/gps_fixes</a></td>
<td>Adds GPS-fixes that originated from a device with a unique identifier</td>
@@ -19,9 +19,13 @@
<td>Webservice Type:</td>
<td>REST</td>
</tr>
<tr>
<td>Input format:</td>
<td>JSON (application/json)</td>
</tr>
<tr>
<td>Output format:</td>
<td>HTTP 200 (no body)</td>
<td>HTTP 200</td>
</tr>
<tr>
<td>Mandatory parameters:</td>
@@ -36,18 +40,18 @@
<td>POST</td>
</tr>
<tr>
<td>Mandatory input:</td>
<td>JSON</td>
</tr>
<tr>
<td>&nbsp;</td>
<td><pre>{
<td>Example:</td>
<td>POST <a href=http://sapsailing.com/api/v1/leaderboards/{name}/device_mappings/start>http://sapsailing.com/api/v1/leaderboards/{name}/device_mappings/start</a>
<pre><code>
{
"competitorId" : "af855a56-9726-4a9c-a77e-da955bd289bg",
"deviceType" : "ios",
"deviceUuid" : "af855a56-9726-4a9c-a77e-da955bd289bf",
"pushDeviceId" : "0f744707bebcf74f9b7c25d48e3358945f6aa01da5ddb387462c7eaf61bbad78",
"fromMillis" : 1415624564718
}</pre></td>
}
</code></pre>
</td>
</tr>
</table>
<div style="height: 1em;"></div>
@@ -19,9 +19,13 @@
<td>Webservice Type:</td>
<td>REST</td>
</tr>
<tr>
<td>Input format:</td>
<td>JSON (application/json)</td>
</tr>
<tr>
<td>Output format:</td>
<td>HTTP 200 (no body)</td>
<td>HTTP 200</td>
</tr>
<tr>
<td>Mandatory parameters:</td>
@@ -36,16 +40,16 @@
<td>POST</td>
</tr>
<tr>
<td>Mandatory input:</td>
<td>JSON</td>
</tr>
<tr>
<td>&nbsp;</td>
<td><pre>{
<td>Example:</td>
<td>POST <a href=http://sapsailing.com/api/v1/leaderboards/{name}/device_mappings/end>http://sapsailing.com/api/v1/leaderboards/{name}/device_mappings/end</a>
<pre><code>
{
"competitorId" : "af855a56-9726-4a9c-a77e-da955bd289bg",
"deviceUuid" : "af855a56-9726-4a9c-a77e-da955bd289bf",
"toMillis" : 1415624564719
}</pre></td>
}
</code></pre>
</td>
</tr>
</table>
<div style="height: 1em;"></div>
@@ -0,0 +1,73 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<link href='../../../../fontface.css' media='screen' rel='stylesheet' title='Default stylesheet' type='text/css' />
<link href='../../../main.css' media='screen' rel='stylesheet' title='Default stylesheet' type='text/css' />
<link rel='icon' href='../../../../sap.ico' type='image/x-icon'>
<title>SAP Sailing Analytics Webservices API Version 1.0 Beta</title>
</head>
<body>
<h1>SAP Sailing Analytics Webservices API Version 1.0 Beta</h1>
<h2>URL: /api/v1/leaderboards/{name}/marks/{id}/gps_fixes</h2>
<b>Description:</b>
<p>Adds an arbitrary number GPS-fixes that originated from a device with a unique identifier as fixes for the mark.
This is done by adding the fixes to the GPSFixStore, and creating device-mappings in the RegattaLog attached to the Leaderboard.</p>
<br/>
<table>
<tr>
<td>Webservice Type:</td>
<td>REST</td>
</tr>
<tr>
<td>Post data input format:</td>
<td>JSON</td>
</tr>
<tr>
<td>Output format:</td>
<td>HTTP 200</td>
</tr>
<tr>
<td>Mandatory parameters:</td>
<td>None</td>
</tr>
<tr>
<td>Optional parameters:</td>
<td>None</td>
</tr>
<tr>
<td>Request method:</td>
<td>POST</td>
</tr>
<tr>
<td>Example:</td>
<td>POST <a href="http://www.sapsailing.com/sailingserver/api/v1/leaderboards/{name}/marks/{id}/gps_fixes">http://www.sapsailing.com/sailingserver/api/v1/leaderboards/{name}/marks/{id}/gps_fixes</a>
<pre><code>
{
"deviceUuid" : "af855a56-9726-4a9c-a77e-da955bd289bf",
"fixes" : [
{
"timestamp" : 14144160080000,
"latitude" : 54.325246,
"longitude" : 10.148556,
"speed" : 3.61,
"course" : 258.11,
},
{
"timestamp" : 14144168490000,
"latitude" : 55.12456,
"longitude" : 8.03456,
"speed" : 5.1,
"course" : 14.2,
}
]
}
</code></pre>
</td>
</tr>
</table>
<div style="height: 1em;"></div>
<a href="index.html">Back to Web Service Overview</a>
</body>
</html>
@@ -0,0 +1,50 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<link href='../../../../fontface.css' media='screen' rel='stylesheet' title='Default stylesheet' type='text/css' />
<link href='../../../main.css' media='screen' rel='stylesheet' title='Default stylesheet' type='text/css' />
<link rel='icon' href='../../../../sap.ico' type='image/x-icon'>
<title>SAP Sailing Analytics Webservices API Version 1.0 Beta</title>
</head>
<body>
<h1>SAP Sailing Analytics Webservices API Version 1.0 Beta</h1>
<h2>URL: /api/v1/leaderboards/{name}/marks?race_column={name}&amp;fleet={name}</h2>
<b>Description:</b>
<p>Gets the marks of a race (from the RaceLog and if possible the TrackedRace). If the "fleet"
parameter is specified then so must the race_column parameter. If no fleet is specified,
the marks for all fleets of the specified race column are returned. If no race column and
no fleet are specified, all marks of all races in the leaderboard are returned.</p>
<br/>
<table>
<tr>
<td>Webservice Type:</td>
<td>REST</td>
</tr>
<tr>
<td>Output format:</td>
<td>Json</td>
</tr>
<tr>
<td>Mandatory parameters:</td>
<td>None</td>
</tr>
<tr>
<td>Optional parameters:</td>
<td>race_column
<br/>fleet</td>
</tr>
<tr>
<td>Request method:</td>
<td>GET</td>
</tr>
<tr>
<td>Example:</td>
<td><a href="http://www.sapsailing.com/sailingserver/api/v1/leaderboards/{name}/marks?race_column={name}&fleet={name}">http://www.sapsailing.com/sailingserver/api/v1/leaderboards/{name}/marks?race_column={name}&amp;fleet={name}</a></td>
</tr>
</table>
<div style="height: 1em;"></div>
<a href="index.html">Back to Web Service Overview</a>
</body>
</html>
@@ -34,6 +34,8 @@
<div>fromtimeasmillis</div>
<div>totime (ISO format, such as 2014-02-14T10:12:03Z)</div>
<div>totimeasmillis</div>
<div>lastknown (boolean, false or true; defaults to false; true will return the last known fix even
if there is no fix for the mark within the interval requested)</div>
</td>
</tr>
<tr>
@@ -24,6 +24,7 @@ import com.sap.sailing.domain.base.EventBase;
import com.sap.sailing.domain.base.Fleet;
import com.sap.sailing.domain.base.LeaderboardSearchResult;
import com.sap.sailing.domain.base.LeaderboardSearchResultBase;
import com.sap.sailing.domain.base.Mark;
import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.base.RaceDefinition;
import com.sap.sailing.domain.base.Regatta;
@@ -35,6 +36,7 @@ import com.sap.sailing.domain.base.configuration.DeviceConfigurationIdentifier;
import com.sap.sailing.domain.base.configuration.DeviceConfigurationMatcher;
import com.sap.sailing.domain.base.configuration.RegattaConfiguration;
import com.sap.sailing.domain.common.DataImportProgress;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.RaceFetcher;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.RegattaFetcher;
@@ -54,6 +56,7 @@ import com.sap.sailing.domain.leaderboard.ScoringScheme;
import com.sap.sailing.domain.persistence.DomainObjectFactory;
import com.sap.sailing.domain.persistence.MongoObjectFactory;
import com.sap.sailing.domain.racelog.tracking.GPSFixStore;
import com.sap.sailing.domain.regattalike.LeaderboardThatHasRegattaLike;
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
import com.sap.sailing.domain.tracking.RaceListener;
import com.sap.sailing.domain.tracking.RaceTracker;
@@ -124,6 +127,22 @@ public interface RacingEventService extends TrackedRegattaRegistry, RegattaFetch
*/
Leaderboard getLeaderboardByName(String name);
/**
* Looks at the mark tracks in the tracked races attached to the <code>leaderboard</code>. If it doesn't find a
* track for the <code>mark</code> requested there which has fixes before and after <code>timePoint</code> (to
* ensure that no track cropping has taken place, removing the fixes for the interesting time period), looks in the
* leaderboard's regatta log and the specific <code>raceLog</code> (if provided) or all race logs attached to the
* leaderboard (if not provided) for device mappings for the mark and tries to load fixes from the
* {@link GPSFixStore}. The latter is only necessary if the mark isn't found in any tracked race with fixes
* surrounding <code>timePoint</code> because should there be a tracked race in the leaderboard that has the mark
* then it will also have received the fixes from the {@link GPSFixStore} through the regatta log mapping.
* <p>
*
* @return the position obtained by interpolation but never extrapolation from the track identified as described
* above
*/
Position getMarkPosition(Mark mark, LeaderboardThatHasRegattaLike leaderboard, TimePoint timePoint, RaceLog raceLog);
/**
* Stops tracking all races of the regatta specified. This will also stop tracking wind for all races of this regatta.
* See {@link #stopTrackingWind(Regatta, RaceDefinition)}. If there were multiple calls to
@@ -581,4 +600,5 @@ public interface RacingEventService extends TrackedRegattaRegistry, RegattaFetch
FileStorageManagementService getFileStorageManagementService();
ClassLoader getCombinedMasterDataClassLoader();
}
@@ -13,8 +13,10 @@ import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
@@ -43,6 +45,7 @@ import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.osgi.util.tracker.ServiceTracker;
import com.sap.sailing.domain.abstractlog.AbstractLog;
import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor;
import com.sap.sailing.domain.abstractlog.impl.LogEventAuthorImpl;
import com.sap.sailing.domain.abstractlog.race.RaceLog;
@@ -85,6 +88,7 @@ import com.sap.sailing.domain.base.impl.RegattaImpl;
import com.sap.sailing.domain.base.impl.RemoteSailingServerReferenceImpl;
import com.sap.sailing.domain.common.DataImportProgress;
import com.sap.sailing.domain.common.LeaderboardNameConstants;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.RegattaIdentifier;
import com.sap.sailing.domain.common.RegattaName;
@@ -125,11 +129,13 @@ import com.sap.sailing.domain.racelog.RaceLogStore;
import com.sap.sailing.domain.racelog.tracking.GPSFixStore;
import com.sap.sailing.domain.racelogtracking.DeviceIdentifier;
import com.sap.sailing.domain.regattalike.IsRegattaLike;
import com.sap.sailing.domain.regattalike.LeaderboardThatHasRegattaLike;
import com.sap.sailing.domain.regattalog.RegattaLogStore;
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
import com.sap.sailing.domain.tracking.DynamicTrackedRegatta;
import com.sap.sailing.domain.tracking.GPSFix;
import com.sap.sailing.domain.tracking.GPSFixMoving;
import com.sap.sailing.domain.tracking.GPSFixTrack;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.RaceChangeListener;
import com.sap.sailing.domain.tracking.RaceHandle;
@@ -144,6 +150,7 @@ import com.sap.sailing.domain.tracking.WindStore;
import com.sap.sailing.domain.tracking.WindTracker;
import com.sap.sailing.domain.tracking.WindTrackerFactory;
import com.sap.sailing.domain.tracking.impl.AbstractRaceChangeListener;
import com.sap.sailing.domain.tracking.impl.DynamicGPSFixTrackImpl;
import com.sap.sailing.domain.tracking.impl.DynamicTrackedRegattaImpl;
import com.sap.sailing.expeditionconnector.ExpeditionWindTrackerFactory;
import com.sap.sailing.polars.PolarDataService;
@@ -970,6 +977,83 @@ public class RacingEventServiceImpl implements RacingEventService, ClearStateTes
return leaderboardsByName.get(name);
}
@Override
public Position getMarkPosition(Mark mark, LeaderboardThatHasRegattaLike leaderboard, TimePoint timePoint, RaceLog raceLog) {
GPSFixTrack<Mark, GPSFix> track = null;
// If no spanning track is found, the fix closest to the time point requested is used instead
GPSFix nonSpanningFallback = null;
for (TrackedRace trackedRace : leaderboard.getTrackedRaces()) {
GPSFixTrack<Mark, GPSFix> trackCandidate = trackedRace.getTrack(mark);
if (trackCandidate != null) {
if (spansTimePoint(trackCandidate, timePoint)) {
track = trackCandidate;
break;
} else {
nonSpanningFallback = improveTimewiseClosestFix(nonSpanningFallback, trackCandidate, timePoint);
}
}
}
if (track == null) { // no spanning track found in any tracked race, or no tracked races found
// try to load from store
DynamicGPSFixTrackImpl<Mark> loadedTrack = new DynamicGPSFixTrackImpl<Mark>(mark, 0);
track = loadedTrack;
Set<AbstractLog<?, ?>> logs = new HashSet<>();
logs.add(leaderboard.getRegattaLike().getRegattaLog());
if (raceLog == null) { // no race log explicitly provided --> use all race logs
for (RaceColumn raceColumn : leaderboard.getRaceColumns()) {
for (Fleet fleet : raceColumn.getFleets()) {
logs.add(raceColumn.getRaceLog(fleet));
}
}
} else {
logs.add(raceLog);
}
for (AbstractLog<?, ?> log : logs) {
try {
getGPSFixStore().loadMarkTrack(loadedTrack, log, mark);
} catch (Exception e) {
logger.log(Level.WARNING, "Couldn't load mark track for mark " + mark + " from log " + log, e);
}
}
}
Position result = track.getEstimatedPosition(timePoint, /* extrapolate */ false);
if (result == null) {
result = nonSpanningFallback == null ? null : nonSpanningFallback.getPosition();
}
return result;
}
private GPSFix improveTimewiseClosestFix(GPSFix nonSpanningFallback, GPSFixTrack<Mark, GPSFix> track, final TimePoint timePoint) {
GPSFix lastAtOrBefore = track.getLastFixAtOrBefore(timePoint);
GPSFix firstAtOrAfter = track.getFirstFixAtOrAfter(timePoint);
// find the fix closes to timePoint, sorting null values to the end and fixes near timePoint to the beginning
final List<GPSFix> list = Arrays.asList(nonSpanningFallback, lastAtOrBefore, firstAtOrAfter);
list.sort(new Comparator<GPSFix>() {
@Override
public int compare(GPSFix o1, GPSFix o2) {
final int result;
if (o1 == null) {
if (o2 == null) {
result = 0;
} else {
result = 1;
}
} else if (o2 == null) {
result = -1;
} else {
result = new Long(Math.abs(o1.getTimePoint().until(timePoint).asMillis())).compareTo(
Math.abs(o2.getTimePoint().until(timePoint).asMillis()));
}
return result;
}
});
return list.get(0);
}
private boolean spansTimePoint(GPSFixTrack<Mark, GPSFix> track, TimePoint timePoint) {
return track.getLastFixAtOrBefore(timePoint) != null && track.getFirstFixAtOrAfter(timePoint) != null;
}
@Override
public Map<String, Leaderboard> getLeaderboards() {
return Collections.unmodifiableMap(new HashMap<String, Leaderboard>(leaderboardsByName));
@@ -562,4 +562,9 @@ public class DummyTrackedRace extends TrackedRaceWithWindEssentials {
return null;
}
@Override
public GPSFixTrack<Mark, GPSFix> getTrack(Mark mark) {
return null;
}
}