mirror of
https://github.com/eclipse-sailing-analytics/sailing-analytics.git
synced 2026-09-21 05:05:31 +00:00
Merge branch 'master' of /home/trac/git
This commit is contained in:
+16
-1
@@ -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());
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
+2
-2
@@ -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();
|
||||
|
||||
+186
@@ -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;
|
||||
}
|
||||
}
|
||||
+10
-184
@@ -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)
|
||||
|
||||
+382
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+1
@@ -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> </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> </td>
|
||||
<td>columnNames [Default = all columns]</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> </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> </td>
|
||||
<td>
|
||||
<div>time (ISO format, such as 2014-02-14T10:12:03Z)</div>
|
||||
<div>timeasmillis</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> </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>
|
||||
Reference in New Issue
Block a user