Merge branch 'master' of /home/trac/git

This commit is contained in:
Generic Wiki User
2018-03-05 13:00:01 +00:00
11 changed files with 721 additions and 189 deletions
@@ -42,6 +42,12 @@ public class LeaderboardDTO extends AbstractLeaderboardDTO implements Serializab
private Map<String, List<CompetitorDTO>> competitorOrderingPerRaceColumnName;
/**
* The validity time point for which this leaderboard DTO was requested (not to be confused with
* the time <em>when</em> the request was made).
*/
private Date timePoint;
private Date timePointOfLastCorrectionsValidity;
private String comment;
@@ -63,9 +69,10 @@ public class LeaderboardDTO extends AbstractLeaderboardDTO implements Serializab
* @param uuidGenerator used to provide the {@link #id ID} for this object (see also {@link #getId()}) and for any clones produced
* from it by the {@link #clone()} operation.
*/
public LeaderboardDTO(Date timePointOfLastCorrectionsValidity, String comment, ScoringSchemeType scoringScheme, boolean higherScoreIsBetter, UUIDGenerator uuidGenerator, boolean hasOverallDetails) {
public LeaderboardDTO(Date timepoint, Date timePointOfLastCorrectionsValidity, String comment, ScoringSchemeType scoringScheme, boolean higherScoreIsBetter, UUIDGenerator uuidGenerator, boolean hasOverallDetails) {
initCollections();
id = uuidGenerator.generateRandomUUID();
this.timePoint = timepoint;
this.timePointOfLastCorrectionsValidity = timePointOfLastCorrectionsValidity;
this.scoringScheme = scoringScheme;
this.comment = comment;
@@ -157,6 +164,14 @@ public class LeaderboardDTO extends AbstractLeaderboardDTO implements Serializab
return timePointOfLastCorrectionsValidity;
}
/**
* @return the validity time point for which this leaderboard DTO was requested (not to be confused with the time
* <em>when</em> the request was made).
*/
public Date getTimePoint() {
return timePoint;
}
@Override
public int hashCode() {
return super.hashCode() ^ ((competitors == null) ? 0 : competitors.hashCode());
@@ -352,7 +352,7 @@ public abstract class AbstractLeaderboardWithCache implements Leaderboard {
throws NoWindException {
long startOfRequestHandling = System.currentTimeMillis();
final LeaderboardDTOCalculationReuseCache cache = new LeaderboardDTOCalculationReuseCache(timePoint);
final LeaderboardDTO result = new LeaderboardDTO(this.getScoreCorrection().getTimePointOfLastCorrectionsValidity() == null ? null
final LeaderboardDTO result = new LeaderboardDTO(timePoint.asDate(), this.getScoreCorrection().getTimePointOfLastCorrectionsValidity() == null ? null
: this.getScoreCorrection().getTimePointOfLastCorrectionsValidity().asDate(),
this.getScoreCorrection() == null ? null : this.getScoreCorrection().getComment(),
this.getScoringScheme() == null ? null : this.getScoringScheme().getType(), this
@@ -55,7 +55,7 @@ public class TestColumnSwapping {
leaderboardCreationDate = new Date();
try {
// get Leaderboard with name and current date
leaderboardOriginalDTO = new LeaderboardDTO(null, null, ScoringSchemeType.LOW_POINT, /* higherScoreIsBetter */ false, new LeaderboardDTO.UUIDGenerator() {
leaderboardOriginalDTO = new LeaderboardDTO(new Date(), null, null, ScoringSchemeType.LOW_POINT, /* higherScoreIsBetter */ false, new LeaderboardDTO.UUIDGenerator() {
@Override
public String generateRandomUUID() {
return UUID.randomUUID().toString();
@@ -119,7 +119,7 @@ public class TestColumnSwapping {
@Test
public void testLeaderBoardDTOMethods() {
lb = new LeaderboardDTO(null, null, ScoringSchemeType.LOW_POINT, /* higherScoreIsBetter */ false, new LeaderboardDTO.UUIDGenerator() {
lb = new LeaderboardDTO(new Date(), null, null, ScoringSchemeType.LOW_POINT, /* higherScoreIsBetter */ false, new LeaderboardDTO.UUIDGenerator() {
@Override
public String generateRandomUUID() {
return UUID.randomUUID().toString();
@@ -0,0 +1,186 @@
package com.sap.sailing.server.gateway.jaxrs.api;
import java.util.Date;
import java.util.Iterator;
import java.util.logging.Logger;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.Fleet;
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.NoWindException;
import com.sap.sailing.domain.common.dto.CompetitorDTO;
import com.sap.sailing.domain.common.dto.LeaderboardDTO;
import com.sap.sailing.domain.common.dto.RaceColumnDTO;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.leaderboard.SettableScoreCorrection;
import com.sap.sailing.domain.regattalike.HasRegattaLike;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.server.gateway.jaxrs.AbstractSailingServerResource;
import com.sap.sse.common.TimePoint;
public abstract class AbstractLeaderboardsResource extends AbstractSailingServerResource {
private static final Logger logger = Logger.getLogger(AbstractLeaderboardsResource.class.getName());
public enum ResultStates {
Live, Final
}
protected boolean isValidLeaderboard(Leaderboard leaderboard) {
final boolean result;
if (leaderboard == null) {
logger.warning("Could not find a leaderboard with the given name");
result = false;
} else if (!(leaderboard instanceof HasRegattaLike)) {
logger.warning("Specified Leaderboard does not have a RegattaLike child (is not a RegattaLeaderboard/FlexibleLeaderboard)");
result = false;
} else {
result = true;
}
return result;
}
protected JSONObject createEmptyLeaderboardJson(Leaderboard leaderboard, ResultStates resultState,
TimePoint requestTimePoint, Integer maxCompetitorsCount) throws NoWindException {
JSONObject jsonLeaderboard = new JSONObject();
writeCommonLeaderboardData(jsonLeaderboard, leaderboard, resultState, null, maxCompetitorsCount);
JSONArray jsonCompetitorEntries = new JSONArray();
jsonLeaderboard.put("competitors", jsonCompetitorEntries);
for (Competitor competitor : leaderboard.getCompetitors()) {
JSONObject jsonCompetitor = new JSONObject();
jsonCompetitor.put("name", competitor.getName());
final String displayName = leaderboard.getDisplayName(competitor);
jsonCompetitor.put("displayName", displayName == null ? competitor.getName() : displayName);
jsonCompetitor.put("id", competitor.getId().toString());
jsonCompetitor.put("sailID", competitor.getBoat().getSailID());
Nationality nationality = competitor.getTeam().getNationality();
jsonCompetitor.put("nationality", nationality != null ? nationality.getThreeLetterIOCAcronym() : null);
jsonCompetitor.put("countryCode", nationality != null ? (nationality.getCountryCode() != null ? nationality
.getCountryCode().getTwoLetterISOCode() : null) : null);
jsonCompetitor.put("rank", 0);
jsonCompetitor.put("carriedPoints", null);
jsonCompetitor.put("netPoints", null);
jsonCompetitorEntries.add(jsonCompetitor);
JSONObject jsonRaceColumns = new JSONObject();
jsonCompetitor.put("raceScores", jsonRaceColumns);
for (RaceColumn raceColumn : leaderboard.getRaceColumns()) {
JSONObject jsonEntry = new JSONObject();
jsonRaceColumns.put(raceColumn.getName(), jsonEntry);
final Fleet fleetOfCompetitor = raceColumn.getFleetOfCompetitor(competitor);
jsonEntry.put("fleet", fleetOfCompetitor == null ? "" : fleetOfCompetitor.getName());
jsonEntry.put("totalPoints", null);
jsonEntry.put("netPoints", null);
jsonEntry.put("maxPointsReason", "");
jsonEntry.put("rank", 0);
jsonEntry.put("isDiscarded", false);
jsonEntry.put("isCorrected", false);
}
}
return jsonLeaderboard;
}
protected void writeCommonLeaderboardData(JSONObject jsonLeaderboard, LeaderboardDTO leaderboard,
ResultStates resultState, Integer maxCompetitorsCount) {
jsonLeaderboard.put("name", leaderboard.name);
final String displayName = leaderboard.getDisplayName();
jsonLeaderboard.put("displayName", displayName == null ? leaderboard.name : displayName);
jsonLeaderboard.put("resultTimepoint", leaderboard.getTimePoint() != null ? leaderboard.getTimePoint().getTime() : null);
jsonLeaderboard.put("resultState", resultState.name());
jsonLeaderboard.put("maxCompetitorsCount", maxCompetitorsCount);
jsonLeaderboard.put("higherScoreIsBetter", leaderboard.isHigherScoreBetter());
jsonLeaderboard.put("scoringComment", leaderboard.getComment());
Date lastUpdateTimepoint = leaderboard.getTimePointOfLastCorrectionsValidity();
jsonLeaderboard.put("lastScoringUpdate", lastUpdateTimepoint != null ? lastUpdateTimepoint.getTime() : null);
JSONArray jsonColumnNames = new JSONArray();
jsonLeaderboard.put("columnNames", jsonColumnNames);
for (RaceColumnDTO raceColumn : leaderboard.getRaceList()) {
jsonColumnNames.add(raceColumn.getName());
}
}
protected void writeCompetitorBaseData(JSONObject jsonCompetitor, CompetitorDTO competitor, LeaderboardDTO leaderboard) {
jsonCompetitor.put("name", competitor.getName());
final String displayName = leaderboard.getDisplayName(competitor);
jsonCompetitor.put("displayName", displayName == null ? competitor.getName() : displayName);
jsonCompetitor.put("id", competitor.getIdAsString());
jsonCompetitor.put("sailID", competitor.getSailID());
jsonCompetitor.put("nationality", competitor.getThreeLetterIocCountryCode());
jsonCompetitor.put("countryCode", competitor.getTwoLetterIsoCountryCode());
}
private void writeCommonLeaderboardData(JSONObject jsonLeaderboard, Leaderboard leaderboard,
ResultStates resultState, TimePoint resultTimePoint, Integer maxCompetitorsCount) {
jsonLeaderboard.put("name", leaderboard.getName());
final String displayName = leaderboard.getDisplayName();
jsonLeaderboard.put("displayName", displayName == null ? leaderboard.getName() : displayName);
jsonLeaderboard.put("resultTimepoint", resultTimePoint != null ? resultTimePoint.asMillis() : null);
jsonLeaderboard.put("resultState", resultState.name());
jsonLeaderboard.put("maxCompetitorsCount", maxCompetitorsCount);
SettableScoreCorrection scoreCorrection = leaderboard.getScoreCorrection();
if (scoreCorrection != null) {
jsonLeaderboard.put("scoringComment", scoreCorrection.getComment());
TimePoint lastUpdateTimepoint = scoreCorrection.getTimePointOfLastCorrectionsValidity();
jsonLeaderboard.put("lastScoringUpdate", lastUpdateTimepoint != null ? lastUpdateTimepoint.asMillis() : null);
} else {
jsonLeaderboard.put("scoringComment", null);
jsonLeaderboard.put("lastScoringUpdate", null);
}
JSONArray jsonColumnNames = new JSONArray();
jsonLeaderboard.put("columnNames", jsonColumnNames);
for (RaceColumn raceColumn : leaderboard.getRaceColumns()) {
jsonColumnNames.add(raceColumn.getName());
}
}
protected TimePoint calculateTimePointForResultState(Leaderboard leaderboard, ResultStates resultState) {
TimePoint result = null;
switch (resultState) {
case Live:
// the timepoint to be used for a live leaderboard is 'null'
result = null;
break;
case Final:
if (leaderboard.getScoreCorrection() != null
&& leaderboard.getScoreCorrection().getTimePointOfLastCorrectionsValidity() != null) {
result = leaderboard.getScoreCorrection().getTimePointOfLastCorrectionsValidity();
// As we don't have implemented bug 1246 (Define a clear result state for races and leaderboards) so far
// we need to make sure that the timpoint for the final state is not determined in the middle of a
// running race,
// because this would deliver not only final results but also some "mixed-in" live results.
// Therefore, if there is a race that hasn't finished yet and whose first start mark passing is before
// the current result, move result to before the start mark passing.
for (TrackedRace trackedRace : leaderboard.getTrackedRaces()) {
TimePoint endOfRace = trackedRace.getEndOfRace();
if (endOfRace == null) {
Waypoint firstWaypoint = trackedRace.getRace().getCourse().getFirstWaypoint();
if (firstWaypoint != null) {
Iterable<MarkPassing> markPassingsForFirstWaypoint = trackedRace
.getMarkPassingsInOrder(firstWaypoint);
if (markPassingsForFirstWaypoint != null) {
trackedRace.lockForRead(markPassingsForFirstWaypoint);
try {
Iterator<MarkPassing> i = markPassingsForFirstWaypoint.iterator();
if (i.hasNext()) {
TimePoint earliestMarkPassingTimePoint = i.next().getTimePoint();
if (result == null || earliestMarkPassingTimePoint.before(result)) {
result = earliestMarkPassingTimePoint.minus(1);
}
}
} finally {
trackedRace.unlockAfterRead(markPassingsForFirstWaypoint);
}
}
}
}
}
}
break;
}
return result;
}
}
@@ -7,9 +7,7 @@ import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -60,7 +58,6 @@ import com.sap.sailing.domain.base.Course;
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.RaceDefinition;
import com.sap.sailing.domain.base.Regatta;
@@ -94,7 +91,6 @@ import com.sap.sailing.domain.common.tracking.GPSFix;
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
import com.sap.sailing.domain.common.tracking.impl.GPSFixImpl;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.leaderboard.SettableScoreCorrection;
import com.sap.sailing.domain.racelogtracking.RaceLogTrackingAdapter;
import com.sap.sailing.domain.racelogtracking.RaceLogTrackingAdapterFactory;
import com.sap.sailing.domain.racelogtracking.impl.SmartphoneUUIDIdentifierImpl;
@@ -109,7 +105,6 @@ 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.ControlPointJsonSerializer;
import com.sap.sailing.server.gateway.serialization.coursedata.impl.CourseBaseJsonSerializer;
import com.sap.sailing.server.gateway.serialization.coursedata.impl.CourseJsonSerializer;
@@ -129,13 +124,9 @@ import com.sap.sse.common.Util.Pair;
import com.sap.sse.common.impl.MillisecondsTimePoint;
@Path("/v1/leaderboards")
public class LeaderboardsResource extends AbstractSailingServerResource {
public class LeaderboardsResource extends AbstractLeaderboardsResource {
private static final Logger logger = Logger.getLogger(LeaderboardsResource.class.getName());
public enum ResultStates {
Live, Preliminary, Final
};
@GET
@Produces("application/json;charset=UTF-8")
public Response getLeaderboards() {
@@ -164,12 +155,10 @@ public class LeaderboardsResource extends AbstractSailingServerResource {
.type(MediaType.TEXT_PLAIN).build();
} else {
try {
TimePoint resultTimePoint = calculateTimePointForResultState(leaderboard, resultState);
TimePoint timePoint = calculateTimePointForResultState(leaderboard, resultState);
JSONObject jsonLeaderboard;
if (resultTimePoint != null) {
Util.Triple<TimePoint, ResultStates, Integer> resultStateAndTimePoint = new Util.Triple<>(
resultTimePoint, resultState, maxCompetitorsCount);
jsonLeaderboard = getLeaderboardJson(leaderboard, resultStateAndTimePoint);
if (timePoint != null || resultState == ResultStates.Live) {
jsonLeaderboard = getLeaderboardJson(leaderboard, timePoint, resultState, maxCompetitorsCount);
} else {
jsonLeaderboard = createEmptyLeaderboardJson(leaderboard, resultState, requestTimePoint,
maxCompetitorsCount);
@@ -190,19 +179,16 @@ public class LeaderboardsResource extends AbstractSailingServerResource {
}
private JSONObject getLeaderboardJson(Leaderboard leaderboard,
Util.Triple<TimePoint, ResultStates, Integer> timePointAndResultStateAndMaxCompetitorsCount)
TimePoint resultTimePoint, ResultStates resultState, Integer maxCompetitorsCount)
throws NoWindException, InterruptedException, ExecutionException {
LeaderboardDTO leaderboardDTO = leaderboard.getLeaderboardDTO(
timePointAndResultStateAndMaxCompetitorsCount.getA(), Collections.<String> emptyList(), /* addOverallDetails */
resultTimePoint, Collections.<String> emptyList(), /* addOverallDetails */
false, getService(), getService().getBaseDomainFactory(),
/* fillTotalPointsUncorrected */false);
TimePoint resultTimePoint = timePointAndResultStateAndMaxCompetitorsCount.getA();
ResultStates resultState = timePointAndResultStateAndMaxCompetitorsCount.getB();
Integer maxCompetitorsCount = timePointAndResultStateAndMaxCompetitorsCount.getC();
JSONObject jsonLeaderboard = new JSONObject();
writeCommonLeaderboardData(jsonLeaderboard, leaderboardDTO, resultState, resultTimePoint, maxCompetitorsCount);
writeCommonLeaderboardData(jsonLeaderboard, leaderboardDTO, resultState, maxCompetitorsCount);
JSONArray jsonCompetitorEntries = new JSONArray();
jsonLeaderboard.put("competitors", jsonCompetitorEntries);
@@ -214,14 +200,7 @@ public class LeaderboardsResource extends AbstractSailingServerResource {
break;
}
JSONObject jsonCompetitor = new JSONObject();
jsonCompetitor.put("name", competitor.getName());
final String displayName = leaderboardDTO.getDisplayName(competitor);
jsonCompetitor.put("displayName", displayName == null ? competitor.getName() : displayName);
jsonCompetitor.put("id", competitor.getIdAsString());
jsonCompetitor.put("sailID", competitor.getSailID());
jsonCompetitor.put("nationality", competitor.getThreeLetterIocCountryCode());
jsonCompetitor.put("countryCode", competitor.getTwoLetterIsoCountryCode());
writeCompetitorBaseData(jsonCompetitor, competitor, leaderboardDTO);
jsonCompetitor.put("rank", counter);
jsonCompetitor.put("carriedPoints", leaderboardRowDTO.carriedPoints);
jsonCompetitor.put("netPoints", leaderboardRowDTO.netPoints);
@@ -255,151 +234,12 @@ public class LeaderboardsResource extends AbstractSailingServerResource {
return jsonLeaderboard;
}
private JSONObject createEmptyLeaderboardJson(Leaderboard leaderboard, ResultStates resultState,
TimePoint requestTimePoint, Integer maxCompetitorsCount) throws NoWindException {
JSONObject jsonLeaderboard = new JSONObject();
writeCommonLeaderboardData(jsonLeaderboard, leaderboard, resultState, null, maxCompetitorsCount);
JSONArray jsonCompetitorEntries = new JSONArray();
jsonLeaderboard.put("competitors", jsonCompetitorEntries);
for (Competitor competitor : leaderboard.getCompetitors()) {
JSONObject jsonCompetitor = new JSONObject();
jsonCompetitor.put("name", competitor.getName());
final String displayName = leaderboard.getDisplayName(competitor);
jsonCompetitor.put("displayName", displayName == null ? competitor.getName() : displayName);
jsonCompetitor.put("id", competitor.getId().toString());
jsonCompetitor.put("sailID", competitor.getBoat().getSailID());
Nationality nationality = competitor.getTeam().getNationality();
jsonCompetitor.put("nationality", nationality != null ? nationality.getThreeLetterIOCAcronym() : null);
jsonCompetitor.put("countryCode", nationality != null ? (nationality.getCountryCode() != null ? nationality
.getCountryCode().getTwoLetterISOCode() : null) : null);
jsonCompetitor.put("rank", 0);
jsonCompetitor.put("carriedPoints", null);
jsonCompetitor.put("netPoints", null);
jsonCompetitorEntries.add(jsonCompetitor);
JSONObject jsonRaceColumns = new JSONObject();
jsonCompetitor.put("raceScores", jsonRaceColumns);
for (RaceColumn raceColumn : leaderboard.getRaceColumns()) {
JSONObject jsonEntry = new JSONObject();
jsonRaceColumns.put(raceColumn.getName(), jsonEntry);
final Fleet fleetOfCompetitor = raceColumn.getFleetOfCompetitor(competitor);
jsonEntry.put("fleet", fleetOfCompetitor == null ? "" : fleetOfCompetitor.getName());
jsonEntry.put("totalPoints", null);
jsonEntry.put("netPoints", null);
jsonEntry.put("maxPointsReason", "");
jsonEntry.put("rank", 0);
jsonEntry.put("isDiscarded", false);
jsonEntry.put("isCorrected", false);
}
}
return jsonLeaderboard;
}
private void writeCommonLeaderboardData(JSONObject jsonLeaderboard, LeaderboardDTO leaderboard,
ResultStates resultState, TimePoint resultTimePoint, Integer maxCompetitorsCount) {
jsonLeaderboard.put("name", leaderboard.name);
final String displayName = leaderboard.getDisplayName();
jsonLeaderboard.put("displayName", displayName == null ? leaderboard.name : displayName);
jsonLeaderboard.put("resultTimepoint", resultTimePoint != null ? resultTimePoint.asMillis() : null);
jsonLeaderboard.put("resultState", resultState.name());
jsonLeaderboard.put("maxCompetitorsCount", maxCompetitorsCount);
jsonLeaderboard.put("higherScoreIsBetter", leaderboard.isHigherScoreBetter());
jsonLeaderboard.put("scoringComment", leaderboard.getComment());
Date lastUpdateTimepoint = leaderboard.getTimePointOfLastCorrectionsValidity();
jsonLeaderboard.put("lastScoringUpdate", lastUpdateTimepoint != null ? lastUpdateTimepoint.getTime() : null);
JSONArray jsonColumnNames = new JSONArray();
jsonLeaderboard.put("columnNames", jsonColumnNames);
for (RaceColumnDTO raceColumn : leaderboard.getRaceList()) {
jsonColumnNames.add(raceColumn.getName());
}
}
private void writeCommonLeaderboardData(JSONObject jsonLeaderboard, Leaderboard leaderboard,
ResultStates resultState, TimePoint resultTimePoint, Integer maxCompetitorsCount) {
jsonLeaderboard.put("name", leaderboard.getName());
final String displayName = leaderboard.getDisplayName();
jsonLeaderboard.put("displayName", displayName == null ? leaderboard.getName() : displayName);
jsonLeaderboard.put("resultTimepoint", resultTimePoint != null ? resultTimePoint.asMillis() : null);
jsonLeaderboard.put("resultState", resultState.name());
jsonLeaderboard.put("maxCompetitorsCount", maxCompetitorsCount);
SettableScoreCorrection scoreCorrection = leaderboard.getScoreCorrection();
if (scoreCorrection != null) {
jsonLeaderboard.put("scoringComment", scoreCorrection.getComment());
TimePoint lastUpdateTimepoint = scoreCorrection.getTimePointOfLastCorrectionsValidity();
jsonLeaderboard.put("lastScoringUpdate", lastUpdateTimepoint != null ? lastUpdateTimepoint.asMillis()
: null);
} else {
jsonLeaderboard.put("scoringComment", null);
jsonLeaderboard.put("lastScoringUpdate", null);
}
JSONArray jsonColumnNames = new JSONArray();
jsonLeaderboard.put("columnNames", jsonColumnNames);
for (RaceColumn raceColumn : leaderboard.getRaceColumns()) {
jsonColumnNames.add(raceColumn.getName());
}
}
private TimePoint calculateTimePointForResultState(Leaderboard leaderboard, ResultStates resultState) {
TimePoint result = null;
switch (resultState) {
case Live:
result = leaderboard.getTimePointOfLatestModification();
if (result == null) {
result = MillisecondsTimePoint.now();
}
break;
case Preliminary:
case Final:
if (leaderboard.getScoreCorrection() != null
&& leaderboard.getScoreCorrection().getTimePointOfLastCorrectionsValidity() != null) {
result = leaderboard.getScoreCorrection().getTimePointOfLastCorrectionsValidity();
// As we don't have implemented bug 1246 (Define a clear result state for races and leaderboards) so far
// we need to make sure that the timpoint for the final state is not determined in the middle of a
// running race,
// because this would deliver not only final results but also some "mixed-in" live results.
// Therefore, if there is a race that hasn't finished yet and whose first start mark passing is before
// the current result, move result to before the start mark passing.
for (TrackedRace trackedRace : leaderboard.getTrackedRaces()) {
TimePoint endOfRace = trackedRace.getEndOfRace();
if (endOfRace == null) {
Waypoint firstWaypoint = trackedRace.getRace().getCourse().getFirstWaypoint();
if (firstWaypoint != null) {
Iterable<MarkPassing> markPassingsForFirstWaypoint = trackedRace
.getMarkPassingsInOrder(firstWaypoint);
if (markPassingsForFirstWaypoint != null) {
trackedRace.lockForRead(markPassingsForFirstWaypoint);
try {
Iterator<MarkPassing> i = markPassingsForFirstWaypoint.iterator();
if (i.hasNext()) {
TimePoint earliestMarkPassingTimePoint = i.next().getTimePoint();
if (result == null || earliestMarkPassingTimePoint.before(result)) {
result = earliestMarkPassingTimePoint.minus(1);
}
}
} finally {
trackedRace.unlockAfterRead(markPassingsForFirstWaypoint);
}
}
}
}
}
}
break;
}
return result;
}
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Path("{name}/device_mappings/start")
public Response postCheckin(String checkinJson, @PathParam("name") String leaderboardName) {
Leaderboard leaderboard = getService().getLeaderboardByName(leaderboardName);
if (!leaderboardIsValid(leaderboard)) {
if (!isValidLeaderboard(leaderboard)) {
logger.warning("Leaderboard does not exist or does not hold a RegattaLog");
return Response.status(Status.INTERNAL_SERVER_ERROR)
.entity("Leaderboard does not exist or does not hold a RegattaLog").type(MediaType.TEXT_PLAIN)
@@ -476,26 +316,12 @@ public class LeaderboardsResource extends AbstractSailingServerResource {
return Response.status(Status.OK).build();
}
private boolean leaderboardIsValid(Leaderboard leaderboard) {
if (leaderboard == null) {
logger.warning("Could not find a leaderboard with the given name");
return false;
}
if (!(leaderboard instanceof HasRegattaLike)) {
logger.warning("Specified Leaderboard does not have a RegattaLike child (is not a RegattaLeaderboard/FlexibleLeaderboard)");
return false;
}
return true;
}
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Path("{name}/device_mappings/end")
public Response postCheckout(String json, @PathParam("name") String leaderboardName) {
Leaderboard leaderboard = getService().getLeaderboardByName(leaderboardName);
if (!leaderboardIsValid(leaderboard)) {
if (!isValidLeaderboard(leaderboard)) {
logger.warning("Leaderboard does not exist or does not hold a RegattaLog");
return Response.status(Status.INTERNAL_SERVER_ERROR)
.entity("Leaderboard does not exist or does not hold a RegattaLog").type(MediaType.TEXT_PLAIN)
@@ -0,0 +1,382 @@
package com.sap.sailing.server.gateway.jaxrs.api;
import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
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 org.apache.commons.lang.StringEscapeUtils;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.common.DetailType;
import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.ManeuverType;
import com.sap.sailing.domain.common.MaxPointsReason;
import com.sap.sailing.domain.common.NoWindException;
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.LegEntryDTO;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sse.InvalidDateException;
import com.sap.sse.common.Duration;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util.Pair;
import com.sap.sse.common.impl.MillisecondsTimePoint;
@Path("/v2/leaderboards")
public class LeaderboardsResourceV2 extends AbstractLeaderboardsResource {
@GET
@Produces("application/json;charset=UTF-8")
@Path("{name}")
public Response getLeaderboard(@PathParam("name") String leaderboardName,
@DefaultValue("Live") @QueryParam("resultState") ResultStates resultState,
@QueryParam("columnNames") final List<String> raceColumnNames,
@QueryParam("raceDetails") final List<String> raceDetails,
@QueryParam("time") String time, @QueryParam("timeasmillis") Long timeasmillis,
@QueryParam("maxCompetitorsCount") Integer maxCompetitorsCount) {
Response response;
TimePoint requestTimePoint = MillisecondsTimePoint.now();
Leaderboard leaderboard = getService().getLeaderboardByName(leaderboardName);
if (leaderboard == null) {
response = Response.status(Status.NOT_FOUND)
.entity("Could not find a leaderboard with name '" + StringEscapeUtils.escapeHtml(leaderboardName) + "'.")
.type(MediaType.TEXT_PLAIN).build();
} else {
try {
TimePoint timePoint;
try {
timePoint = parseTimePoint(time, timeasmillis, calculateTimePointForResultState(leaderboard, resultState));
} catch (InvalidDateException e1) {
return Response.status(Status.INTERNAL_SERVER_ERROR).entity("Could not parse the time.")
.type(MediaType.TEXT_PLAIN).build();
}
JSONObject jsonLeaderboard;
if (timePoint != null || resultState == ResultStates.Live) {
jsonLeaderboard = getLeaderboardJson(leaderboard, timePoint, resultState, maxCompetitorsCount, raceColumnNames, raceDetails);
} else {
jsonLeaderboard = createEmptyLeaderboardJson(leaderboard, resultState, requestTimePoint,
maxCompetitorsCount);
}
StringWriter sw = new StringWriter();
jsonLeaderboard.writeJSONString(sw);
String json = sw.getBuffer().toString();
response = Response.ok(json).header("Content-Type", MediaType.APPLICATION_JSON + ";charset=UTF-8").build();
} catch (NoWindException | InterruptedException | ExecutionException | IOException e) {
response = Response.status(Status.INTERNAL_SERVER_ERROR).entity(e.getMessage())
.type(MediaType.TEXT_PLAIN).build();
}
}
return response;
}
private JSONObject getLeaderboardJson(Leaderboard leaderboard,
TimePoint resultTimePoint, ResultStates resultState, Integer maxCompetitorsCount,
List<String> raceColumnNames, List<String> raceDetailNames)
throws NoWindException, InterruptedException, ExecutionException {
List<String> raceColumnsToShow = calculateRaceColumnsToShow(raceColumnNames, leaderboard.getRaceColumns());
List<DetailType> raceDetailsToShow = calculateRaceDetailTypesToShow(raceDetailNames);
LeaderboardDTO leaderboardDTO = leaderboard.getLeaderboardDTO(
resultTimePoint, raceColumnsToShow, /* addOverallDetails */
false, getService(), getService().getBaseDomainFactory(),
/* fillTotalPointsUncorrected */false);
JSONObject jsonLeaderboard = new JSONObject();
writeCommonLeaderboardData(jsonLeaderboard, leaderboardDTO, resultState, maxCompetitorsCount);
Map<String, Map<String, Map<CompetitorDTO, Integer>>> competitorRanksPerRaceColumnsAndFleets = new HashMap<>();
for (String raceColumnName : raceColumnsToShow) {
List<CompetitorDTO> competitorsFromBestToWorst = leaderboardDTO.getCompetitorsFromBestToWorst(raceColumnName);
Map<String, Map<CompetitorDTO, Integer>> competitorsOrderedByFleets = new HashMap<>();
for (CompetitorDTO competitor: competitorsFromBestToWorst) {
LeaderboardRowDTO row = leaderboardDTO.rows.get(competitor);
LeaderboardEntryDTO leaderboardEntry = row.fieldsByRaceColumnName.get(raceColumnName);
FleetDTO fleetOfCompetitor = leaderboardEntry.fleet;
if (fleetOfCompetitor != null && fleetOfCompetitor.getName() != null) {
Map<CompetitorDTO, Integer> competitorsOfFleet = competitorsOrderedByFleets.get(fleetOfCompetitor.getName());
if (competitorsOfFleet == null) {
competitorsOfFleet = new HashMap<>();
competitorsOrderedByFleets.put(fleetOfCompetitor.getName(), competitorsOfFleet);
}
competitorsOfFleet.put(competitor, competitorsOfFleet.size() + 1);
}
}
competitorRanksPerRaceColumnsAndFleets.put(raceColumnName, competitorsOrderedByFleets);
}
JSONArray jsonCompetitorEntries = new JSONArray();
jsonLeaderboard.put("competitors", jsonCompetitorEntries);
int competitorCounter = 1;
// Remark: leaderboardDTO.competitors are ordered by total rank
for (CompetitorDTO competitor : leaderboardDTO.competitors) {
LeaderboardRowDTO leaderboardRowDTO = leaderboardDTO.rows.get(competitor);
if (maxCompetitorsCount != null && competitorCounter > maxCompetitorsCount) {
break;
}
JSONObject jsonCompetitor = new JSONObject();
writeCompetitorBaseData(jsonCompetitor, competitor, leaderboardDTO);
jsonCompetitor.put("rank", competitorCounter);
jsonCompetitor.put("carriedPoints", leaderboardRowDTO.carriedPoints);
jsonCompetitor.put("netPoints", leaderboardRowDTO.netPoints);
jsonCompetitor.put("overallRank", leaderboardDTO.getTotalRank(competitor));
jsonCompetitorEntries.add(jsonCompetitor);
JSONObject jsonRaceColumns = new JSONObject();
jsonCompetitor.put("columns", jsonRaceColumns);
for (String raceColumnName : raceColumnsToShow) {
JSONObject jsonEntry = new JSONObject();
jsonRaceColumns.put(raceColumnName, jsonEntry);
LeaderboardEntryDTO leaderboardEntry = leaderboardRowDTO.fieldsByRaceColumnName.get(raceColumnName);
final FleetDTO fleetOfCompetitor = leaderboardEntry.fleet;
jsonEntry.put("fleet", fleetOfCompetitor == null ? "" : fleetOfCompetitor.getName());
jsonEntry.put("totalPoints", leaderboardEntry.totalPoints);
jsonEntry.put("uncorrectedTotalPoints", leaderboardEntry.totalPoints);
jsonEntry.put("netPoints", leaderboardEntry.netPoints);
MaxPointsReason maxPointsReason = leaderboardEntry.reasonForMaxPoints;
jsonEntry.put("maxPointsReason", maxPointsReason != null ? maxPointsReason.toString() : null);
jsonEntry.put("isDiscarded", leaderboardEntry.discarded);
jsonEntry.put("isCorrected", leaderboardEntry.hasScoreCorrection());
// if we have no fleet information there is no way to know in which fleet the competitor was racing
Integer rank = null;
if (fleetOfCompetitor != null && fleetOfCompetitor.getName() != null) {
Map<String, Map<CompetitorDTO, Integer>> rcMap = competitorRanksPerRaceColumnsAndFleets.get(raceColumnName);
if (rcMap != null && !rcMap.isEmpty()) {
Map<CompetitorDTO, Integer> rankMap = rcMap.get(fleetOfCompetitor.getName());
if (rankMap != null && !rankMap.isEmpty()) {
rank = rankMap.get(competitor);
}
}
}
jsonEntry.put("rank", rank);
LegEntryDTO detailsOfLastAvailableLeg = getDetailsOfLastAvailableLeg(leaderboardEntry);
jsonEntry.put("trackedRank", detailsOfLastAvailableLeg != null ? detailsOfLastAvailableLeg.rank : null);
boolean finished = false;
LegEntryDTO detailsOfLastCourseLeg = getDetailsOfLastCourseLeg(leaderboardEntry);
if (detailsOfLastCourseLeg != null) {
finished = detailsOfLastCourseLeg.finished;
}
jsonEntry.put("finished", finished);
if (!raceDetailsToShow.isEmpty() && leaderboardEntry.race != null) {
LegEntryDTO currentLegEntry = null;
int currentLegNumber = leaderboardEntry.getOneBasedCurrentLegNumber();
if (leaderboardEntry.legDetails != null && currentLegNumber > 0 && currentLegNumber <= leaderboardEntry.legDetails.size()) {
currentLegEntry = leaderboardEntry.legDetails.get(currentLegNumber-1);
if (currentLegEntry != null) {
jsonEntry.put("trackedRank", currentLegEntry.rank);
}
}
JSONObject jsonRaceDetails = new JSONObject();
jsonEntry.put("data", jsonRaceDetails);
for (DetailType type : raceDetailsToShow) {
Pair<String, Object> valueForRaceDetailType = getValueForRaceDetailType(type, leaderboardEntry, currentLegEntry);
if (valueForRaceDetailType != null && valueForRaceDetailType.getA() != null && valueForRaceDetailType.getB() != null) {
jsonRaceDetails.put(valueForRaceDetailType.getA(), valueForRaceDetailType.getB());
}
}
}
}
competitorCounter++;
}
return jsonLeaderboard;
}
private List<DetailType> calculateRaceDetailTypesToShow(List<String> raceDetailTypesNames) {
List<DetailType> result = new ArrayList<>();
if (raceDetailTypesNames.size() == 0) {
result = Arrays.asList(getDefaultRaceDetailColumnTypes());
} else if (raceDetailTypesNames.size() == 1 && raceDetailTypesNames.get(0).equals("ALL")) {
result = Arrays.asList(getAvailableRaceDetailColumnTypes());
} else {
Map<String, DetailType> typeMap = new HashMap<>();
for (DetailType detailType : getAvailableRaceDetailColumnTypes()) {
typeMap.put(detailType.name(), detailType);
}
for (String raceDetailTypeName : raceDetailTypesNames) {
if (typeMap.containsKey(raceDetailTypeName)) {
result.add(typeMap.get(raceDetailTypeName));
}
}
}
return result;
}
private DetailType[] getDefaultRaceDetailColumnTypes() {
return new DetailType[] { DetailType.RACE_GAP_TO_LEADER_IN_SECONDS,
DetailType.RACE_AVERAGE_SPEED_OVER_GROUND_IN_KNOTS,
DetailType.RACE_CURRENT_SPEED_OVER_GROUND_IN_KNOTS,
DetailType.RACE_DISTANCE_TO_COMPETITOR_FARTHEST_AHEAD_IN_METERS,
DetailType.CURRENT_LEG };
}
private DetailType[] getAvailableRaceDetailColumnTypes() {
return new DetailType[] { DetailType.RACE_GAP_TO_LEADER_IN_SECONDS,
DetailType.RACE_AVERAGE_SPEED_OVER_GROUND_IN_KNOTS,
DetailType.RACE_DISTANCE_TRAVELED,
DetailType.RACE_TIME_TRAVELED,
DetailType.RACE_CURRENT_SPEED_OVER_GROUND_IN_KNOTS,
DetailType.RACE_DISTANCE_TO_COMPETITOR_FARTHEST_AHEAD_IN_METERS,
DetailType.NUMBER_OF_MANEUVERS,
DetailType.CURRENT_LEG };
}
private Pair<String, Object> getValueForRaceDetailType(DetailType type, LeaderboardEntryDTO entry, LegEntryDTO currentLegEntry) {
String name;
Object value = null;
Pair<String, Object> result = null;
switch (type) {
case RACE_GAP_TO_LEADER_IN_SECONDS:
name = "gapToLeader-s";
if (entry.gapToLeaderInOwnTime != null) {
value = entry.gapToLeaderInOwnTime.asSeconds();
}
break;
case RACE_DISTANCE_TO_COMPETITOR_FARTHEST_AHEAD_IN_METERS:
name = "gapToLeader-m";
if (entry.windwardDistanceToCompetitorFarthestAheadInMeters != null) {
value = roundDouble(entry.windwardDistanceToCompetitorFarthestAheadInMeters, 2);
}
break;
case RACE_AVERAGE_SPEED_OVER_GROUND_IN_KNOTS:
name = "averageSpeedOverGround-kts";
Distance distance = entry.getDistanceTraveled();
Duration timeSailed = entry.getTimeSailed();
if (distance != null && timeSailed != null) {
value = roundDouble(distance.inTime(timeSailed).getKnots(), 2);
}
break;
case RACE_DISTANCE_TRAVELED:
name = "distanceTraveled-m";
Distance distanceTraveled = entry.getDistanceTraveled();
if (distanceTraveled != null) {
value = roundDouble(distanceTraveled.getMeters(), 2);
}
break;
case RACE_TIME_TRAVELED:
name = "timeTraveled-s";
Duration timeTraveled = entry.getTimeSailed();
if (timeTraveled != null) {
value = timeTraveled.asSeconds();
}
break;
case RACE_CURRENT_SPEED_OVER_GROUND_IN_KNOTS:
name = "currentSpeedOverGround-kts";
if (currentLegEntry != null) {
value = roundDouble(currentLegEntry.currentSpeedOverGroundInKnots, 2);
}
break;
case NUMBER_OF_MANEUVERS:
name = "numberOfManeuvers";
Integer numberOfManeuvers = null;
Map<ManeuverType, Integer> tacksJibesAndPenalties = getTotalNumberOfTacksJibesAndPenaltyCircles(entry);
for (Integer maneuverCount : tacksJibesAndPenalties.values()) {
if (maneuverCount != null) {
if (numberOfManeuvers == null) {
numberOfManeuvers = maneuverCount;
} else {
numberOfManeuvers += maneuverCount;
}
}
}
value = numberOfManeuvers;
break;
case CURRENT_LEG:
name = "currentLeg";
int currentLegNumber = entry.getOneBasedCurrentLegNumber();
if (currentLegNumber > 0) {
value = currentLegNumber;
}
break;
default:
name = null;
break;
}
if (name != null && value != null) {
result = new Pair<String, Object>(name, value);
}
return result;
}
private LegEntryDTO getDetailsOfLastCourseLeg(LeaderboardEntryDTO entry) {
LegEntryDTO lastLegDetail = null;
if (entry != null && entry.legDetails != null) {
int lastLegIndex = entry.legDetails.size() - 1;
if (lastLegIndex >= 0) {
lastLegDetail = entry.legDetails.get(lastLegIndex);
}
}
return lastLegDetail;
}
private LegEntryDTO getDetailsOfLastAvailableLeg(LeaderboardEntryDTO entry) {
LegEntryDTO lastAvailableLegDetail = null;
if (entry != null && entry.legDetails != null) {
for (int i = entry.legDetails.size() - 1; i >= 0; i--) {
lastAvailableLegDetail = entry.legDetails.get(i);
if (lastAvailableLegDetail != null) {
break;
}
}
}
return lastAvailableLegDetail;
}
private Map<ManeuverType, Integer> getTotalNumberOfTacksJibesAndPenaltyCircles(LeaderboardEntryDTO entry) {
Map<ManeuverType, Integer> totalNumberOfManeuvers = new HashMap<>();
for (ManeuverType maneuverType : new ManeuverType[] { ManeuverType.TACK, ManeuverType.JIBE, ManeuverType.PENALTY_CIRCLE }) {
totalNumberOfManeuvers.put(maneuverType, 0);
}
if (entry.legDetails != null) {
for (LegEntryDTO legDetail : entry.legDetails) {
if (legDetail != null) {
for (ManeuverType maneuverType : new ManeuverType[] { ManeuverType.TACK, ManeuverType.JIBE, ManeuverType.PENALTY_CIRCLE }) {
if (legDetail.numberOfManeuvers != null && legDetail.numberOfManeuvers.get(maneuverType) != null) {
totalNumberOfManeuvers.put(maneuverType,
totalNumberOfManeuvers.get(maneuverType) + legDetail.numberOfManeuvers.get(maneuverType));
}
}
}
}
}
return totalNumberOfManeuvers;
}
/**
* If {@code raceColumnNames} is empty or {@code null}, return the names of all {@link raceColumnsOfLeaderboard}; otherwise
* return those race column names from {@code raceColumnsOfLeaderboard} that are also in {@code raceColumnNames}.
*/
private List<String> calculateRaceColumnsToShow(List<String> raceColumnNames, Iterable<RaceColumn> raceColumnsOfLeaderboard) {
final Set<String> raceColumnNamesAsSet = new HashSet<>();
if (raceColumnNames != null) {
raceColumnNamesAsSet.addAll(raceColumnNames);
}
// Calculates the race columns to retrieve data for
final List<String> raceColumnsToShow = new ArrayList<>();
for (final RaceColumn raceColumn : raceColumnsOfLeaderboard) {
if (raceColumnNamesAsSet.isEmpty() || raceColumnNamesAsSet.contains(raceColumn.getName())) {
raceColumnsToShow.add(raceColumn.getName());
}
}
return raceColumnsToShow;
}
}
@@ -17,6 +17,7 @@ public class RestApiApplication extends Application {
classes.add(EventsResource.class);
classes.add(RegattasResource.class);
classes.add(LeaderboardsResource.class);
classes.add(LeaderboardsResourceV2.class);
classes.add(PolarResource.class);
classes.add(SearchResource.class);
classes.add(GPSFixesResource.class);
@@ -29,7 +29,7 @@
</tr>
<tr>
<td>Optional parameters:</td>
<td>resultState [Live = Default, Preliminary, Final] </td>
<td>resultState [Live = Default, Final] </td>
</tr>
<tr>
<td>&nbsp;</td>
@@ -0,0 +1,44 @@
<!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='../../../../sailing-fontface-1.0.cache.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 2.0</title>
</head>
<body>
<h1>SAP Sailing Analytics Webservices API Version 2.0 - Overview</h1>
<p>
A number of web services are available to query the state of a SAP Sailing Analytics server instance.
Note that different server instances exist, and not all servers hold the same data. Usually, events get
their own URL which is then made sure to point to a server instance holding the data for that event.
We use an Apache reverse proxy to map URLs (and in particular the sub-domain) to a particular server instance.
So, <a href="http://www.sapsailing.com">www.sapsailing.com</a> may end up on a different server instance than
<a href="http://tw2013.sapsailing.com">tw2013.sapsailing.com</a>
or <a href="http://ess40-2013.sapsailing.com">ess40-2013.sapsailing.com</a>. Usually, we hand out a per-event
or per-series URL, such as the ones above (except www.sapsailing.com which is the general landing page),
and you would only fetch the content for the event you're interested in, even if the server instance has more.
In all subsequent explanations, you may replace the "www.sapsailing.com" hostname by an according per-event host
name to make sure you get to the event you're interested in.
</p>
<div style="height: 1em;"></div>
<table>
<thead>
<tr>
<td>Webservice URL</td>
<td>Description</td>
</tr>
</thead>
<tbody>
<tr>
<td><a href="leaderboardGetDoc.html">/api/v2/leaderboards/{name}</a></td>
<td>Gets the details of a leaderboard</td>
</tr>
</tbody>
</table>
<div style="height: 1em;"></div>
<a href="../../index.html">Back to Web Services Home</a>
</body>
</html>
@@ -0,0 +1,77 @@
<!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='../../../../sailing-fontface-1.0.cache.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 2.0</title>
</head>
<body>
<h1>SAP Sailing Analytics Webservices API Version 2.0</h1>
<h2>URL: /api/v2/leaderboards/{name}</h2>
<b>Description:</b>
<p>The service gets the details of a leaderboard</p>
The service can be parameterized to return only the values of certain leaderboard columns.<br/>
Furthermore it's possible to control which calculated values should be returned on race level.<br/>
The supported race detail constants right now are:<br/>
- RACE_GAP_TO_LEADER_IN_SECONDS<br/>
- RACE_AVERAGE_SPEED_OVER_GROUND_IN_KNOTS<br/>
- RACE_DISTANCE_TRAVELED<br/>
- RACE_TIME_TRAVELED<br/>
- RACE_CURRENT_SPEED_OVER_GROUND_IN_KNOTS<br/>
- RACE_DISTANCE_TO_COMPETITOR_FARTHEST_AHEAD_IN_METERS<br/>
- NUMBER_OF_MANEUVERS<br/>
- CURRENT_LEG<br/>
There is also the 'ALL' shortcut constant which can be used to get all available race details without the need to enumerate them.<br/>
<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>resultState [Default = Live, Final] </td>
</tr>
<tr>
<td>&nbsp;</td>
<td>columnNames [Default = all columns]</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>raceDetails [Default = RACE_GAP_TO_LEADER_IN_SECONDS, RACE_AVERAGE_SPEED_OVER_GROUND_IN_KNOTS, RACE_CURRENT_SPEED_OVER_GROUND_IN_KNOTS, RACE_DISTANCE_TO_COMPETITOR_FARTHEST_AHEAD_IN_METERS, CURRENT_LEG]</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>
<div>time (ISO format, such as 2014-02-14T10:12:03Z)</div>
<div>timeasmillis</div>
</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>maxCompetitorsCount [Default = 1000]</td>
</tr>
<tr>
<td>Request method:</td>
<td>GET</td>
</tr>
<tr>
<td>Example:</td>
<td><a href="http://www.sapsailing.com/sailingserver/api/v2/leaderboards/505 Worlds 2013">http://www.sapsailing.com/sailingserver/api/v2/leaderboards/505 Worlds 2013</a></td>
</tr>
</table>
<div style="height: 1em;"></div>
<a href="index.html">Back to Web Service Overview</a>
</body>
</html>
@@ -28,6 +28,7 @@ name to make sure you get to the event you're interested in.
<b>Webservices API-Documention:</b><br/>
<ul style="margin-left: 20px;">
<li><a href="api/v1/index.html">SAP Sailing Analytics Webservices Version 1.0</a></li>
<li><a href="api/v2/index.html">SAP Sailing Analytics Webservices Version 2.0</a></li>
</ul>
</body>
</html>