Merge branch 'bug4757_2'

This commit is contained in:
Kai Börnert committed 2018-10-24 08:55:07 +02:00
commit 7d417feb22
16 files changed
+600 -210

No files matched your search

@@ -1,65 +1,37 @@
package com.sap.sailing.server.gateway.serialization.impl;
import java.util.Map.Entry;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import com.sap.sailing.domain.base.Boat;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.tracking.Maneuver;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sse.common.TimePoint;
import com.sap.sailing.server.gateway.serialization.JsonSerializer;
import com.sap.sse.common.Util.Pair;
import com.sap.sse.common.impl.MillisecondsTimePoint;
public class ManeuversJsonSerializer extends AbstractTrackedRaceDataJsonSerializer {
public class ManeuversJsonSerializer implements JsonSerializer<Iterable<Pair<Competitor, Iterable<Maneuver>>>> {
public final static String MANEUVERS = "maneuvers";
private final CompetitorAndBoatJsonSerializer competitorAndBoatSerializer;
private final ManeuverJsonSerializer maneuverSerializer;
public ManeuversJsonSerializer(CompetitorAndBoatJsonSerializer competitorSerializer, ManeuverJsonSerializer maneuverSerializer) {
super();
this.competitorAndBoatSerializer = competitorSerializer;
public ManeuversJsonSerializer(ManeuverJsonSerializer maneuverSerializer) {
this.maneuverSerializer = maneuverSerializer;
}
@Override
public JSONObject serialize(TrackedRace trackedRace) {
public JSONObject serialize(Iterable<Pair<Competitor, Iterable<Maneuver>>> data) {
final JSONObject result = new JSONObject();
JSONArray byCompetitorJson = new JSONArray();
result.put(BYCOMPETITOR, byCompetitorJson);
for (Entry<Competitor, Boat> competitorAndBoatEntry : trackedRace.getRace().getCompetitorsAndTheirBoats().entrySet()) {
Competitor competitor = competitorAndBoatEntry.getKey();
Boat boat = competitorAndBoatEntry.getValue();
result.put(AbstractTrackedRaceDataJsonSerializer.BYCOMPETITOR, byCompetitorJson);
for (Pair<Competitor, Iterable<Maneuver>> singleCompetitorData : data) {
final JSONObject forCompetitorJson = new JSONObject();
byCompetitorJson.add(forCompetitorJson);
forCompetitorJson.put(COMPETITOR, competitorAndBoatSerializer.serialize(new Pair<>(competitor, boat)));
final Competitor competitor = singleCompetitorData.getA();
forCompetitorJson.put(AbstractTrackedRaceDataJsonSerializer.COMPETITOR, String.valueOf(competitor.getId()));
final JSONArray maneuvers = new JSONArray();
forCompetitorJson.put(MANEUVERS, maneuvers);
for (final Maneuver maneuver : getManeuversDuringRace(trackedRace, competitor)) {
for (final Maneuver maneuver : singleCompetitorData.getB()) {
maneuvers.add(maneuverSerializer.serialize(maneuver));
}
}
return result;
}
private Iterable<Maneuver> getManeuversDuringRace(TrackedRace trackedRace, Competitor competitor) {
final TimePoint startTime = trackedRace.getStartOfRace();
final TimePoint endOfRace = trackedRace.getEndOfRace();
final TimePoint endTime;
if (endOfRace != null) {
endTime = endOfRace;
} else {
final TimePoint endOfTracking = trackedRace.getEndOfTracking();
if (endOfTracking == null || endOfTracking.after(MillisecondsTimePoint.now())) {
endTime = MillisecondsTimePoint.now();
} else {
endTime = endOfTracking;
}
}
return trackedRace.getManeuvers(competitor, startTime, endTime, /* waitForLatest */ false);
}
}
@@ -4,6 +4,7 @@ import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.UUID;
@@ -16,12 +17,17 @@ import org.json.simple.JSONValue;
import org.junit.Before;
import org.junit.Test;
import com.sap.sailing.domain.base.BoatClass;
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.Series;
import com.sap.sailing.domain.base.impl.CourseImpl;
import com.sap.sailing.domain.base.impl.FleetImpl;
import com.sap.sailing.domain.base.impl.RaceDefinitionImpl;
import com.sap.sailing.domain.base.impl.RegattaImpl;
import com.sap.sailing.domain.base.impl.SeriesImpl;
import com.sap.sailing.domain.common.RegattaName;
import com.sap.sailing.domain.common.ScoringSchemeType;
import com.sap.sailing.domain.ranking.OneDesignRankingMetric;
import com.sap.sailing.server.gateway.jaxrs.api.RegattasResource;
@@ -30,7 +36,8 @@ import com.sap.sse.common.impl.MillisecondsTimePoint;
public class RegattasResourceTest extends AbstractJaxRsApiTest {
private String boatClassName = "49er";
private String regattaName = "TestRegatta";
private String regattaNamePart = "TestRegatta";
private String regattaName = RegattaImpl.getDefaultName(regattaNamePart, boatClassName);
@Before
public void setUp() throws Exception {
@@ -45,34 +52,52 @@ public class RegattasResourceTest extends AbstractJaxRsApiTest {
final TimePoint startDate = new MillisecondsTimePoint(cal.getTime());
cal.set(2014, 5, 8, 16, 00);
final TimePoint endDate = new MillisecondsTimePoint(cal.getTime());
Series testSeries = new SeriesImpl("TestSeries", /* isMedal */false, /* isFleetsCanRunInParallel */ true, fleets, raceColumnNames, /* trackedRegattaRegistry */null);
Series testSeries = new SeriesImpl("TestSeries", /* isMedal */false, /* isFleetsCanRunInParallel */ true,
fleets, raceColumnNames, /* trackedRegattaRegistry */null);
series.add(testSeries);
racingEventService.createRegatta(RegattaImpl.getDefaultName(regattaName, boatClassName), boatClassName,
/* canBoatsOfCompetitorsChangePerRace */ true, startDate, endDate, UUID.randomUUID(), series, /*persistent*/ true,
DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null, /*buoyZoneRadiusInHullLengths*/2.0, /* useStartTimeInference */ true, /* controlTrackingFromStartAndFinishTimes */ false, OneDesignRankingMetric::new);
racingEventService.createRegatta(regattaName, boatClassName, /* canBoatsOfCompetitorsChangePerRace */ true,
startDate, endDate, UUID.randomUUID(), series, /* persistent */ true,
DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null,
/* buoyZoneRadiusInHullLengths */2.0, /* useStartTimeInference */ true,
/* controlTrackingFromStartAndFinishTimes */ false, OneDesignRankingMetric::new);
testSeries.addRaceColumn("R1", /* trackedRegattaRegistry */ null);
testSeries.addRaceColumn("R2", /* trackedRegattaRegistry */ null);
Course course = new CourseImpl("emptyCourse", Collections.emptySet());
// get the same instance! of the boat class object, as else addRace will fail
BoatClass boatClass = racingEventService.getBaseDomainFactory().getOrCreateBoatClass(boatClassName);
racingEventService.addRace(new RegattaName(regattaName), new RaceDefinitionImpl("Race 1", course, boatClass));
}
@Test
public void testGetRegattas() throws Exception {
public void testGetRegattas() throws Exception {
RegattasResource resource = new RegattasResource();
RegattasResource spyResource = spyResource(resource);
Response regattasResponse = spyResource.getRegattas();
String jsonString = (String) regattasResponse.getEntity();
Object obj= JSONValue.parse(jsonString);
JSONArray array= (JSONArray) obj;
Object obj = JSONValue.parse(jsonString);
JSONArray array = (JSONArray) obj;
assertTrue(array.size() == 1);
JSONObject firstElement = (JSONObject) array.get(0);
JSONObject firstElement = (JSONObject) array.get(0);
String jsonName = (String) firstElement.get("name");
String jsonBoatClass = (String) firstElement.get("boatclass");
assertTrue(RegattaImpl.getDefaultName(regattaName, boatClassName).equals(jsonName));
assertTrue(RegattaImpl.getDefaultName(regattaNamePart, boatClassName).equals(jsonName));
assertTrue(boatClassName.equals(jsonBoatClass));
}
@Test
public void testNullCheckForTrackedRaceInGetManeuvers() throws Exception {
RegattasResource resource = new RegattasResource();
RegattasResource spyResource = spyResource(resource);
Response response = spyResource.getManeuvers(regattaName, "Race 1", null, null);
// the current race is not tracked, expect an error
assertTrue(response.getStatus() != 200);
}
}
@@ -82,7 +82,7 @@ bearerToken = com.sap.sailing.server.security.BearerTokenOrBasicOrFormAuthentica
# Secure the access (in particular the POST/write access) to posting race log events
/rc/racelog = bearerToken
# Secure access to additional apis used for SailRacer
# Secure access to additional apis used for SailRacer and SailInsight 2.0
/api/v1/leaderboards/*/enableRaceLogForCompetitorRegistration/** = bearerToken
/api/v1/leaderboards/*/denoteForTracking/** = bearerToken
/api/v1/leaderboards/*/removeTrackedRace/** = bearerToken
@@ -91,3 +91,6 @@ bearerToken = com.sap.sailing.server.security.BearerTokenOrBasicOrFormAuthentica
/api/v1/mark/addMarkFix/** = bearerToken
/api/v1/mark/addCourseDefinitionToRaceLog/** = bearerToken
/api/v1/wind/putWind/** = bearerToken
#Secure access for preferences api, used for SailInsight 2.0
/api/v1/preferences/** = bearerToken
@@ -196,15 +196,14 @@ public abstract class AbstractLeaderboardsResource extends AbstractSailingServer
protected JSONObject getLeaderboardJson(ResultStates resultState, Integer maxCompetitorsCount,
TimePoint requestTimePoint, Leaderboard leaderboard, TimePoint timePoint, List<String> raceColumnNames,
List<String> raceDetailNames)
throws NoWindException, InterruptedException, ExecutionException {
final JSONObject jsonLeaderboard;
if (timePoint != null || resultState == ResultStates.Live) {
jsonLeaderboard = getLeaderboardJson(leaderboard, timePoint, resultState, maxCompetitorsCount, raceColumnNames, raceDetailNames);
} else {
jsonLeaderboard = createEmptyLeaderboardJson(leaderboard, resultState, maxCompetitorsCount);
}
return jsonLeaderboard;
}
List<String> raceDetailNames) throws NoWindException, InterruptedException, ExecutionException {
final JSONObject jsonLeaderboard;
if (timePoint != null || resultState == ResultStates.Live) {
jsonLeaderboard = getLeaderboardJson(leaderboard, timePoint, resultState, maxCompetitorsCount,
raceColumnNames, raceDetailNames);
} else {
jsonLeaderboard = createEmptyLeaderboardJson(leaderboard, resultState, maxCompetitorsCount);
}
return jsonLeaderboard;
}
}
@@ -10,6 +10,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
@@ -105,7 +106,8 @@ public class LeaderboardsResourceV2 extends AbstractLeaderboardsResource {
List<DetailType> raceDetailsToShow = calculateRaceDetailTypesToShow(raceDetailNames);
LeaderboardDTO leaderboardDTO = leaderboard.getLeaderboardDTO(
resultTimePoint, raceColumnsToShow, /* addOverallDetails */
false, getService(), getService().getBaseDomainFactory(),
hasOverallDetail(raceDetailNames),
getService(), getService().getBaseDomainFactory(),
/* fillTotalPointsUncorrected */false);
JSONObject jsonLeaderboard = new JSONObject();
writeCommonLeaderboardData(jsonLeaderboard, leaderboard, resultState, leaderboardDTO.getTimePoint(), maxCompetitorsCount);
@@ -204,9 +206,9 @@ public class LeaderboardsResourceV2 extends AbstractLeaderboardsResource {
JSONObject jsonRaceDetails = new JSONObject();
jsonEntry.put("data", jsonRaceDetails);
for (DetailType type : raceDetailsToShow) {
Pair<String, Object> valueForRaceDetailType = getValueForRaceDetailType(type, leaderboardEntry, currentLegEntry);
Pair<String, Object> valueForRaceDetailType = getValueForRaceDetailType(type, leaderboardRowDTO, leaderboardEntry, currentLegEntry);
if (valueForRaceDetailType != null && valueForRaceDetailType.getA() != null && valueForRaceDetailType.getB() != null) {
jsonRaceDetails.put(valueForRaceDetailType.getA(), valueForRaceDetailType.getB());
jsonRaceDetails.put(valueForRaceDetailType.getA(), valueForRaceDetailType.getB());
}
}
}
@@ -216,6 +218,13 @@ public class LeaderboardsResourceV2 extends AbstractLeaderboardsResource {
return jsonLeaderboard;
}
private boolean hasOverallDetail(List<String> raceDetailNames) {
final HashSet<String> availableOverallDetailTypeNames = new HashSet<>(
Arrays.asList(getAvailableOverallDetailColumnTypes()).stream().map(dt->dt.name()).collect(Collectors.toSet()));
// returns true if the set changed, meaning there was an overall detail type in the raceDetailNames
return availableOverallDetailTypeNames.removeAll(raceDetailNames);
}
private List<DetailType> calculateRaceDetailTypesToShow(List<String> raceDetailTypesNames) {
List<DetailType> result = new ArrayList<>();
if (raceDetailTypesNames.size() == 0) {
@@ -252,10 +261,15 @@ public class LeaderboardsResourceV2 extends AbstractLeaderboardsResource {
DetailType.RACE_CURRENT_SPEED_OVER_GROUND_IN_KNOTS,
DetailType.RACE_DISTANCE_TO_COMPETITOR_FARTHEST_AHEAD_IN_METERS,
DetailType.NUMBER_OF_MANEUVERS,
DetailType.RACE_CURRENT_LEG };
DetailType.RACE_CURRENT_LEG,
DetailType.OVERALL_MAXIMUM_SPEED_OVER_GROUND_IN_KNOTS };
}
private Pair<String, Object> getValueForRaceDetailType(DetailType type, LeaderboardEntryDTO entry, LegEntryDTO currentLegEntry) {
private DetailType[] getAvailableOverallDetailColumnTypes() {
return new DetailType[] { DetailType.OVERALL_MAXIMUM_SPEED_OVER_GROUND_IN_KNOTS };
}
private Pair<String, Object> getValueForRaceDetailType(DetailType type, LeaderboardRowDTO leaderboardRowDTO, LeaderboardEntryDTO entry, LegEntryDTO currentLegEntry) {
String name;
Object value = null;
Pair<String, Object> result = null;
@@ -322,6 +336,10 @@ public class LeaderboardsResourceV2 extends AbstractLeaderboardsResource {
value = currentLegNumber;
}
break;
case OVERALL_MAXIMUM_SPEED_OVER_GROUND_IN_KNOTS:
name = "maxSpeedOverGroundInKnots";
value = leaderboardRowDTO.maximumSpeedOverGroundInKnots==null?null:roundDouble(leaderboardRowDTO.maximumSpeedOverGroundInKnots, 2);
break;
default:
name = null;
break;
@@ -0,0 +1,76 @@
package com.sap.sailing.server.gateway.jaxrs.api;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.apache.shiro.SecurityUtils;
import org.json.simple.parser.ParseException;
import com.sap.sailing.server.gateway.deserialization.JsonDeserializationException;
import com.sap.sailing.server.gateway.jaxrs.AbstractSailingServerResource;
import com.sap.sse.security.UserStore;
@Path("/v1/preferences")
public class PreferencesResource extends AbstractSailingServerResource {
@GET
@Produces("application/json;charset=UTF-8")
@Path("{settingsKey}")
public Response getPreference(@PathParam("settingsKey") String settingsKey)
throws ParseException, JsonDeserializationException {
Response response = null;
if (SecurityUtils.getSubject().isAuthenticated()) {
String username = SecurityUtils.getSubject().getPrincipal().toString();
UserStore userStore = getService(UserStore.class);
String settings = userStore.getPreference(username, settingsKey);
if (settings == null) {
response = Response.noContent().build();
} else {
response = Response.ok(settings).build();
}
} else {
response = Response.status(401).build();
}
return response;
}
@PUT
@Consumes(MediaType.APPLICATION_JSON)
@Path("{settingsKey}")
public Response putPreference(@PathParam("settingsKey") String settingsKey, String json)
throws ParseException, JsonDeserializationException {
Response response = null;
if (SecurityUtils.getSubject().isAuthenticated()) {
String username = SecurityUtils.getSubject().getPrincipal().toString();
UserStore userStore = getService(UserStore.class);
userStore.setPreference(username, settingsKey, json);
response = Response.ok().build();
} else {
response = Response.status(401).build();
}
return response;
}
@DELETE
@Produces("application/json;charset=UTF-8")
@Path("{settingsKey}")
public Response deletePreference(@PathParam("settingsKey") String settingsKey)
throws ParseException, JsonDeserializationException {
Response response = null;
if (SecurityUtils.getSubject().isAuthenticated()) {
String username = SecurityUtils.getSubject().getPrincipal().toString();
UserStore userStore = getService(UserStore.class);
userStore.unsetPreference(username, settingsKey);
response = Response.ok().build();
} else {
response = Response.status(401).build();
}
return response;
}
}
@@ -11,6 +11,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.function.Function;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -35,7 +36,6 @@ import org.json.simple.JSONValue;
import org.json.simple.parser.ParseException;
import com.sap.sailing.datamining.SailingPredefinedQueries;
import com.sap.sailing.domain.base.Boat;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.CompetitorWithBoat;
import com.sap.sailing.domain.base.Course;
@@ -65,6 +65,7 @@ import com.sap.sailing.domain.polars.NotEnoughDataHasBeenAddedException;
import com.sap.sailing.domain.ranking.RankingMetric.RankingInfo;
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
import com.sap.sailing.domain.tracking.GPSFixTrack;
import com.sap.sailing.domain.tracking.Maneuver;
import com.sap.sailing.domain.tracking.TrackedLeg;
import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor;
import com.sap.sailing.domain.tracking.TrackedRace;
@@ -118,6 +119,7 @@ import com.sap.sse.common.Duration;
import com.sap.sse.common.Speed;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util;
import com.sap.sse.common.Util.Pair;
import com.sap.sse.common.impl.MillisecondsDurationImpl;
import com.sap.sse.common.impl.MillisecondsTimePoint;
import com.sap.sse.common.impl.RGBColor;
@@ -318,24 +320,16 @@ public class RegattasResource extends AbstractSailingServerResource {
return response;
}
@POST
@Produces("application/json;charset=UTF-8")
@Path("{regattaname}/competitors/createandadd")
public Response createAndAddCompetitor(@PathParam("regattaname") String regattaName,
@QueryParam("boatclass") String boatClassName, @QueryParam("sailid") String sailId,
@QueryParam("nationalityIOC") String nationalityThreeLetterIOCCode,
@QueryParam("timeontimefactor") Double timeOnTimeFactor,
@QueryParam("timeondistanceallowancepernauticalmileasmillis") Long timeOnDistanceAllowancePerNauticalMileAsMillis,
@QueryParam("searchtag") String searchTag, @QueryParam("competitorName") String competitorName,
@QueryParam("competitorEmail") String competitorEmail) {
private Response createAndAddCompetitor(String regattaName,
String nationalityThreeLetterIOCCode, Double timeOnTimeFactor,
Long timeOnDistanceAllowancePerNauticalMileAsMillis, String searchTag, String competitorName,
String competitorEmail, Function<String, DynamicBoat> boatObtainer) {
final Subject subject = SecurityUtils.getSubject();
subject.checkPermission(Permission.REGATTA.getStringPermissionForObjects(Mode.UPDATE, regattaName));
Response response;
Regatta regatta = findRegattaByName(regattaName);
if (regatta == null) {
response = getBadRegattaErrorResponse(regattaName);
} else if (boatClassName == null) {
response = getBadBoatClassResponse(boatClassName);
} else {
final User user = getService(SecurityService.class).getCurrentUser();
final String shortName;
@@ -353,27 +347,65 @@ public class RegattasResource extends AbstractSailingServerResource {
} else {
email = user.getEmail();
}
final Boat boat = new BoatImpl(UUID.randomUUID(), shortName, getService().getBaseDomainFactory()
.getOrCreateBoatClass(boatClassName, /* typicallyStartsUpwind */ true), sailId);
DynamicBoat boat = boatObtainer.apply(shortName);
final TeamImpl team = new TeamImpl(shortName,
Collections.singleton(new PersonImpl(name,
getService().getBaseDomainFactory()
.getOrCreateNationality(nationalityThreeLetterIOCCode),
getService().getBaseDomainFactory().getOrCreateNationality(nationalityThreeLetterIOCCode),
/* dateOfBirth */ null, /* description */ null)),
/* coach */ null);
final CompetitorWithBoat competitor = getService().getCompetitorAndBoatStore()
.getOrCreateCompetitorWithBoat(UUID.randomUUID(),
name, /* shortName */ null, /* displayColor */ null, email, /* flagImageURI */ null,
team,
timeOnTimeFactor,
.getOrCreateCompetitorWithBoat(UUID.randomUUID(), name, /* shortName */ null,
/* displayColor */ null, email, /* flagImageURI */ null, team, timeOnTimeFactor,
timeOnDistanceAllowancePerNauticalMileAsMillis == null ? null
: new MillisecondsDurationImpl(timeOnDistanceAllowancePerNauticalMileAsMillis),
searchTag, (DynamicBoat) boat);
searchTag, boat);
regatta.registerCompetitor(competitor);
response = Response.ok(CompetitorJsonSerializer.create().serialize(competitor).toJSONString()).
header("Content-Type", MediaType.APPLICATION_JSON + ";charset=UTF-8").build();
response = Response.ok(CompetitorJsonSerializer.create().serialize(competitor).toJSONString())
.header("Content-Type", MediaType.APPLICATION_JSON + ";charset=UTF-8").build();
}
return response;
}
@POST
@Produces("application/json;charset=UTF-8")
@Path("{regattaname}/competitors/createandadd")
public Response createAndAddCompetitor(@PathParam("regattaname") String regattaName,
@QueryParam("boatclass") String boatClassName, @QueryParam("sailid") String sailId,
@QueryParam("nationalityIOC") String nationalityThreeLetterIOCCode,
@QueryParam("timeontimefactor") Double timeOnTimeFactor,
@QueryParam("timeondistanceallowancepernauticalmileasmillis") Long timeOnDistanceAllowancePerNauticalMileAsMillis,
@QueryParam("searchtag") String searchTag, @QueryParam("competitorName") String competitorName,
@QueryParam("competitorEmail") String competitorEmail) {
Response response;
if (boatClassName == null) {
response = getBadBoatClassResponse(boatClassName);
} else {
response = createAndAddCompetitor(regattaName, nationalityThreeLetterIOCCode, timeOnTimeFactor,
timeOnDistanceAllowancePerNauticalMileAsMillis, searchTag, competitorName, competitorEmail,
shortName -> new BoatImpl(UUID.randomUUID(), shortName, getService().getBaseDomainFactory()
.getOrCreateBoatClass(boatClassName, /* typicallyStartsUpwind */ true), sailId));
}
return response;
}
@POST
@Produces("application/json;charset=UTF-8")
@Path("{regattaname}/competitors/createandaddwithboat")
public Response createAndAddCompetitorWithBoat(@PathParam("regattaname") String regattaName,
@QueryParam("boatId") String boatId, @QueryParam("sailid") String sailId,
@QueryParam("nationalityIOC") String nationalityThreeLetterIOCCode,
@QueryParam("timeontimefactor") Double timeOnTimeFactor,
@QueryParam("timeondistanceallowancepernauticalmileasmillis") Long timeOnDistanceAllowancePerNauticalMileAsMillis,
@QueryParam("searchtag") String searchTag, @QueryParam("competitorName") String competitorName,
@QueryParam("competitorEmail") String competitorEmail) {
Response response;
DynamicBoat boat = getService().getCompetitorAndBoatStore().getExistingBoatByIdAsString(boatId);
if (boat == null) {
response = Response.status(Status.NOT_FOUND).entity("Boat is not valid").type(MediaType.TEXT_PLAIN).build();
} else {
response = createAndAddCompetitor(regattaName, nationalityThreeLetterIOCCode, timeOnTimeFactor,
timeOnDistanceAllowancePerNauticalMileAsMillis, searchTag, competitorName, competitorEmail,
t -> boat);
}
return response;
}
@@ -397,7 +429,8 @@ public class RegattasResource extends AbstractSailingServerResource {
competitorId = competitorIdAsString;
}
final Competitor competitor = getService().getCompetitorAndBoatStore().getExistingCompetitorById(competitorId);
final Competitor competitor = getService().getCompetitorAndBoatStore()
.getExistingCompetitorById(competitorId);
if (competitor == null) {
response = getBadCompetitorIdResponse(competitorId);
} else {
@@ -1029,29 +1062,76 @@ public class RegattasResource extends AbstractSailingServerResource {
return response;
}
private TimePoint determineEndTimeForManeuverDetection(TrackedRace trackedRace) {
final TimePoint endOfRace = trackedRace.getEndOfRace();
final TimePoint endTime;
if (endOfRace != null) {
endTime = endOfRace;
} else {
final TimePoint endOfTracking = trackedRace.getEndOfTracking();
if (endOfTracking == null || endOfTracking.after(MillisecondsTimePoint.now())) {
endTime = MillisecondsTimePoint.now();
} else {
endTime = endOfTracking;
}
}
return endTime;
}
@GET
@Produces("application/json;charset=UTF-8")
@Path("{regattaname}/races/{racename}/maneuvers")
public Response getManeuvers(@PathParam("regattaname") String regattaName, @PathParam("racename") String raceName) {
public Response getManeuvers(@PathParam("regattaname") String regattaName, @PathParam("racename") String raceName,
@QueryParam("competitorId") String competitorId, @QueryParam("fromTime") String fromTime) {
Response response;
Regatta regatta = findRegattaByName(regattaName);
if (regatta == null) {
response = Response.status(Status.NOT_FOUND)
.entity("Could not find a regatta with name '" + StringEscapeUtils.escapeHtml(regattaName) + "'.").type(MediaType.TEXT_PLAIN)
.build();
.entity("Could not find a regatta with name '" + StringEscapeUtils.escapeHtml(regattaName) + "'.")
.type(MediaType.TEXT_PLAIN).build();
} else {
RaceDefinition race = findRaceByName(regatta, raceName);
if (race == null) {
response = Response.status(Status.NOT_FOUND)
.entity("Could not find a race with name '" + StringEscapeUtils.escapeHtml(raceName) + "'.").type(MediaType.TEXT_PLAIN)
.build();
.entity("Could not find a race with name '" + StringEscapeUtils.escapeHtml(raceName) + "'.")
.type(MediaType.TEXT_PLAIN).build();
} else {
TrackedRace trackedRace = findTrackedRace(regattaName, raceName);
ManeuversJsonSerializer serializer = new ManeuversJsonSerializer(CompetitorAndBoatJsonSerializer.create(),
new ManeuverJsonSerializer(new GPSFixJsonSerializer(), new DistanceJsonSerializer()));
JSONObject jsonMarkPassings = serializer.serialize(trackedRace);
String json = jsonMarkPassings.toJSONString();
return Response.ok(json).header("Content-Type", MediaType.APPLICATION_JSON + ";charset=UTF-8").build();
if (trackedRace == null) {
response = Response.status(Status.NOT_FOUND).entity(
"Could not find a trackedrace with name '" + StringEscapeUtils.escapeHtml(raceName) + "'.")
.type(MediaType.TEXT_PLAIN).build();
} else {
List<Pair<Competitor, Iterable<Maneuver>>> data = new ArrayList<>();
Iterable<Competitor> competitors = trackedRace.getRace().getCompetitors();
UUID competitorFilter = null;
if (competitorId != null) {
competitorFilter = UUID.fromString(competitorId);
}
final TimePoint endTime = determineEndTimeForManeuverDetection(trackedRace);
final TimePoint startTime;
if (fromTime != null) {
startTime = new MillisecondsTimePoint(Long.parseLong(fromTime));
} else {
startTime = trackedRace.getStartOfRace();
}
for (Competitor competitor : competitors) {
if (competitorFilter == null || competitor.getId().equals(competitorFilter)) {
Iterable<Maneuver> maneuversForCompetitor = trackedRace.getManeuvers(competitor, startTime,
endTime, false);
data.add(new Pair<Competitor, Iterable<Maneuver>>(competitor, maneuversForCompetitor));
}
}
ManeuversJsonSerializer serializer = new ManeuversJsonSerializer(
new ManeuverJsonSerializer(new GPSFixJsonSerializer(), new DistanceJsonSerializer()));
JSONObject jsonMarkPassings = serializer.serialize(data);
String json = jsonMarkPassings.toJSONString();
return Response.ok(json).header("Content-Type", MediaType.APPLICATION_JSON + ";charset=UTF-8")
.build();
}
}
}
return response;
@@ -8,7 +8,6 @@ import javax.ws.rs.core.Application;
import com.sap.sailing.server.gateway.jaxrs.exceptions.ExceptionManager;
import com.sap.sse.security.jaxrs.ShiroAuthorizationExceptionTo401ResponseMapper;
public class RestApiApplication extends Application {
public Set<Class<?>> getClasses() {
HashSet<Class<?>> classes = new HashSet<>();
@@ -30,6 +29,7 @@ public class RestApiApplication extends Application {
classes.add(StatisticsResource.class);
classes.add(WindResource.class);
classes.add(MarkRessource.class);
classes.add(PreferencesResource.class);
classes.add(TagsResource.class);
// Exception Mappers
@@ -137,10 +137,14 @@ name to make sure you get to the event you're interested in.
<td><a href="regattaUpdateOrCreateSeries.html">/api/v1/regattas/updateOrCreateSeries</a></td>
<td>Creates a Series in an regatta, and configures the Series it contains</td>
</tr>
<tr>
<td><a href="regattasCompetitorsCreateAndAdd.html">/api/v1/regattas/{regattaname}/competitors/createandadd</a></td>
<td>Creates a competitor and adds it to the regatta</td>
</tr>
<tr>
<td><a href="regattasCompetitorsCreateAndAddWithBoat.html">/api/v1/regattas/{regattaname}/competitors/createandaddwithboat</a></td>
<td>Creates a competitor using an already exising boat and adds it to the regatta</td>
</tr>
<tr>
<td><a href="regattasCompetitorsCreateAndAdd.html">/api/v1/regattas/{regattaname}/competitors/createandadd</a></td>
<td>Creates a competitor and adds it to the regatta</td>
</tr>
<tr>
<td>/api/v1/regattas/{regattaname}/competitors/{competitorId}/add</td>
<td>Registers a competitor with a regatta (POST)</td>
@@ -288,6 +292,20 @@ name to make sure you get to the event you're interested in.
<td><a href="tagsCRUDDoc.html">/api/v1/{leaderboard}/{racecolumn}/{fleet}/tags</a></td>
<td>Gets, creates, updates and deletes tags.</td>
</tr>
<tr>
<td><a href="preferencesPut.html">/api/v1/preferences/{SETTINGS_KEY}</a></td>
<td>Stores a document into the preferences of a user</td>
</tr>
</tr>
<tr>
<td><a href="preferencesGet.html">/api/v1/preferences/{SETTINGS_KEY}</a></td>
<td>Retrives a document that was stored into the preferences of a user</td>
</tr>
</tr>
<tr>
<td><a href="preferencesDelete.html">/api/v1/preferences/{SETTINGS_KEY}</a></td>
<td>Removes a document from the preferences of a user</td>
</tr>
</tbody>
</table>
<div style="height: 1em;"></div>
@@ -14,6 +14,7 @@
<b>Description:</b>
<p>Gets all maneuvers performed by all competitors of a race.<br/>
All slashes contained within the {regattaname} and {racename} parameters must be replaced by "__" (double underscore).</p>
Additionally it is possible to reduce the results to only contain one competitor, by
<br/>
<table>
<tr>
@@ -30,8 +31,9 @@ All slashes contained within the {regattaname} and {racename} parameters must be
</tr>
<tr>
<td>Optional parameters:</td>
<td>None</td>
</tr>
<td> competitorId (UUID) , fromTime UnixTimestamp
</tr>
<tr>
<td>Request method:</td>
<td>GET</td>
@@ -40,6 +42,10 @@ All slashes contained within the {regattaname} and {racename} parameters must be
<td>Example:</td>
<td><a href="http://www.sapsailing.com/sailingserver/api/v1/regattas/TW%202013%20(Finn)/races/Finn%20Race%204/maneuvers">http://www.sapsailing.com/sailingserver/api/v1/regattas/TW%202013%20(Finn)/races/Finn%20Race%204/maneuvers</a></td>
</tr>
<tr>
<td>Example with optional Parameters</td>
<td><a href="https://www.sapsailing.com/sailingserver/api/v1/regattas/ESS 2016 Cardiff/races/Race 2/maneuvers?competitorId=b9a8a600-c829-0133-a4ec-448a5b8481d8&fromTime=1466689117500">https://www.sapsailing.com/sailingserver/api/v1/regattas/ESS 2016 Cardiff/races/Race 2/maneuvers?competitorId=b9a8a600-c829-0133-a4ec-448a5b8481d8&fromTime=1466689117500 </a></td>
</tr>
</table>
<div style="height: 1em;"></div>
<a href="index.html">Back to Web Service Overview</a>
@@ -0,0 +1,42 @@
<!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 1.0</title>
</head>
<body>
<h1>SAP Sailing Analytics Webservices API Version 1.0</h1>
<h2>URL: /api/v1/preferences/{SETTINGS_KEY}/</h2>
<b>Description:</b>
<p>Deletes whatever is stored under SETTINGS_KEY in currently logged in users preferences</p>
<br/>
<table>
<tr>
<td>Webservice Type:</td>
<td>REST</td>
</tr>
<tr>
<td>Output format:</td>
<td>none</td>
</tr>
<tr>
<td>Mandatory parameters:</td>
<td>none</td>
</tr>
<tr>
<td>Examples:</td>
<td>
Delete @ http://sapsailing.com/sailingserver/api/v1/preferences/testSetting/
<br>
Answer: Ok
</td>
</tr>
</table>
<div style="height: 1em;"></div>
<a href="index.html">Back to Web Service Overview</a>
</body>
</html>
@@ -0,0 +1,42 @@
<!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 1.0</title>
</head>
<body>
<h1>SAP Sailing Analytics Webservices API Version 1.0</h1>
<h2>URL: /api/v1/preferences/{SETTINGS_KEY}/</h2>
<b>Description:</b>
<p>Gets the stored Json data for the currently logged in user with the key SETTINGS_KEY</p>
<br/>
<table>
<tr>
<td>Webservice Type:</td>
<td>REST</td>
</tr>
<tr>
<td>Output format:</td>
<td>Json</td>
</tr>
<tr>
<td>Mandatory parameters:</td>
<td>none</td>
</tr>
<tr>
<td>Examples:</td>
<td>
Get @ http://sapsailing.com/sailingserver/api/v1/preferences/testSetting/
<br>
Answer: No Content Status code, or Ok and the stored document
</td>
</tr>
</table>
<div style="height: 1em;"></div>
<a href="index.html">Back to Web Service Overview</a>
</body>
</html>
@@ -0,0 +1,42 @@
<!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 1.0</title>
</head>
<body>
<h1>SAP Sailing Analytics Webservices API Version 1.0</h1>
<h2>URL: /api/v1/preferences/{SETTINGS_KEY}/</h2>
<b>Description:</b>
<p>Stores the body of the request in the currently logged in users preferences under the given SETTINGS_KEY, the body is expected to be UTF-8 Json</p>
<br/>
<table>
<tr>
<td>Webservice Type:</td>
<td>REST</td>
</tr>
<tr>
<td>Output format:</td>
<td>none</td>
</tr>
<tr>
<td>Mandatory parameters:</td>
<td>none</td>
</tr>
<tr>
<td>Examples:</td>
<td>
Put @ http://sapsailing.com/sailingserver/api/v1/preferences/testSetting/
<br>
Answer: Ok
</td>
</tr>
</table>
<div style="height: 1em;"></div>
<a href="index.html">Back to Web Service Overview</a>
</body>
</html>
@@ -1,11 +1,11 @@
<!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 1.0</title>
<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 1.0</title>
</head>
<body>
<h1>SAP Sailing Analytics Webservices API Version 1.0</h1>
@@ -14,101 +14,112 @@
<b>Description:</b>
<p>Gets the maneuvers of the race specified, keyed by competitor. The mark passings are computed
from the start of the race up to the end of the race, or the end of tracking or the current time point,
whichever is earliest.<p>
The maneuvers for each competitor are ordered by the time point.<br/>
All slashes contained within the {regattaname} and {racename} parameters must be replaced by "__" (double underscore).</p>
<br/>
whichever is earliest.
<p>
The maneuvers for each competitor are ordered by the time point.<br />
All slashes contained within the {regattaname} and {racename}
parameters must be replaced by "__" (double underscore).
</p>
There are two optional parameters: competitorId and fromTime.
<br>
With competitorId, it is possible to filter the result to include only the given competitor.
<br>
With fromTime it is possible to filter based on the maneuvers time. If not given, all maneuvers of a race will be returned.
<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>None</td>
</tr>
<tr>
<td>Request method:</td>
<td>GET</td>
</tr>
<tr>
<td>Example:</td>
<td><a href="http://www.sapsailing.com/sailingserver/api/v1/regattas/TW%202013%20(Finn)/races/Finn%20Race%204/maneuvers">http://www.sapsailing.com/sailingserver/api/v1/regattas/TW%202013%20(Finn)/races/Finn%20Race%204/maneuvers</a></td>
</tr>
<tr>
<td>Example Output:</td>
<td><pre>
{
"bycompetitor": [
{
"competitor": {
"idtype": "java.util.UUID",
"id": "acc1c7d1-cb00-44f6-bb8a-33b6386bc444",
"name": "Agerup/Agerup",
"displayColor": null,
"email": null,
"searchTag": null,
"sailID": "NOR 888",
"nationality": "NOR",
"nationalityISO2": "NO",
"nationalityISO3": "NOR",
"timeOnTimeFactor": 1,
"timeOnDistanceAllowanceInSecondsPerNauticalMile": 0
},
"maneuvers": [
{
"maneuverType": "TACK",
"newTack": "PORT",
"speedBeforeInKnots": 4.890263402216131,
"cogBeforeInTrueDegrees": 11.910947801778443,
"speedAfterInKnots": 7.882531591630032,
"cogAfterInTrueDegrees": 124.81663607363905,
"directionChangeInDegrees": 112.9056882718606,
"maneuverLoss": {
"geographicalMiles": 0.00022784339412441508,
"seaMiles": 0.0002278338270171018,
"nauticalMiles": 0.00022787073314458839,
"meters": 0.4220165977837777,
"kilometers": 0.0004220165977837777,
"centralAngleDeg": 0.0000037973899020735846,
"centralAngleRad": 6.62769567731691e-8
},
"positionAndTime": {
"type": "GPSFix",
"lat_deg": -34.462334,
"lon_deg": -58.460133000000006,
"unixtime": 1447696753000
<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>fromTime unixTimeStamp, competitorId UUID</td>
</tr>
<tr>
<td>Request method:</td>
<td>GET</td>
</tr>
<tr>
<td>Example:</td>
<td><a href="http://www.sapsailing.com/sailingserver/api/v1/regattas/TW%202013%20(Finn)/races/Finn%20Race%204/maneuvers">http://www.sapsailing.com/sailingserver/api/v1/regattas/TW%202013%20(Finn)/races/Finn%20Race%204/maneuvers</a></td>
</tr>
<tr>
<td>Example Output:</td>
<td><pre>
{
"bycompetitor":[
{
"competitor":{
"idtype":"java.util.UUID",
"id":"acc1c7d1-cb00-44f6-bb8a-33b6386bc444",
"name":"Agerup/Agerup",
"displayColor":null,
"email":null,
"searchTag":null,
"sailID":"NOR 888",
"nationality":"NOR",
"nationalityISO2":"NO",
"nationalityISO3":"NOR",
"timeOnTimeFactor":1,
"timeOnDistanceAllowanceInSecondsPerNauticalMile":0
},
"maneuvers":[
{
"maneuverType":"TACK",
"newTack":"PORT",
"speedBeforeInKnots":4.890263402216131,
"cogBeforeInTrueDegrees":11.910947801778443,
"speedAfterInKnots":7.882531591630032,
"cogAfterInTrueDegrees":124.81663607363905,
"directionChangeInDegrees":112.9056882718606,
"maneuverLoss":{
"geographicalMiles":0.00022784339412441508,
"seaMiles":0.0002278338270171018,
"nauticalMiles":0.00022787073314458839,
"meters":0.4220165977837777,
"kilometers":0.0004220165977837777,
"centralAngleDeg":0.0000037973899020735846,
"centralAngleRad":6.62769567731691e-8
},
"positionAndTime":{
"type":"GPSFix",
"lat_deg":-34.462334,
"lon_deg":-58.460133000000006,
"unixtime":1447696753000
}
},
{
"maneuverType":"HEAD_UP",
"newTack":"PORT",
"speedBeforeInKnots":7.882531591630032,
"cogBeforeInTrueDegrees":124.81663607363905,
"speedAfterInKnots":7.748306094677595,
"cogAfterInTrueDegrees":101.79781832895219,
"directionChangeInDegrees":-23.018817744686856,
"maneuverLoss":null,
"positionAndTime":{
"type":"GPSFix",
"lat_deg":-34.46242499999999,
"lon_deg":-58.45991099999999,
"unixtime":1447696762000
}
},
...
]
},
...
]
}
},
{
"maneuverType": "HEAD_UP",
"newTack": "PORT",
"speedBeforeInKnots": 7.882531591630032,
"cogBeforeInTrueDegrees": 124.81663607363905,
"speedAfterInKnots": 7.748306094677595,
"cogAfterInTrueDegrees": 101.79781832895219,
"directionChangeInDegrees": -23.018817744686856,
"maneuverLoss": null,
"positionAndTime": {
"type": "GPSFix",
"lat_deg": -34.46242499999999,
"lon_deg": -58.45991099999999,
"unixtime": 1447696762000
}
},
...
]
}
</pre>
</tr>
</pre>
</tr>
</table>
<div style="height: 1em;"></div>
<a href="index.html">Back to Web Service Overview</a>
@@ -0,0 +1,55 @@
<!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 1.0</title>
</head>
<body>
<h1>SAP Sailing Analytics Webservices API Version 1.0</h1>
<h2>URL: /api/v1/regattas/{regattaname}/competitors/createandaddwithboat</h2>
<b>Description:</b>
<p>Creates a new competitor and registers it with the regatta. Competitor creation can be controlled by a number of
parameter; defaults will be obtained from the authenticated user invoking the service.<br/>
All slashes contained within the {regattaname} parameter must be replaced by "__" (double underscore).</p>
<br/>
<table>
<tr>
<td>Webservice Type:</td>
<td>POST</td>
</tr>
<tr>
<td>Output format:</td>
<td>The competitor as a JSON document</td>
</tr>
<tr>
<td>Mandatory parameters:</td>
<td>boatId the string representation of the boat to use</td>
</tr>
<tr>
<td>Optional parameters:</td>
<td>
<div>nationalityIOC three-letter IOC country code</div>
<div>timeontimefactor a double value telling the ToT / TMF / time-on-time factor for handicap racing</div>
<div>timeondistanceallowancepernauticalmileasmillis distance-based allowance in milliseconds for handicap racing (ToD)</div>
<div>searchtag a search tag for the competitor</div>
<div>competitorName the name to use for the newly created/added competitor</div>
<div>competitorEmail the email to use for the newly created/added competitor</div>
</td>
</tr>
<tr>
<td>Request method:</td>
<td>POST</td>
</tr>
<tr>
<td>Example:</td>
<td>curl -X POST -H "Authorization: Bearer KMTuXevrA0CYyM6O1W2E0QB9gVeP8tIpgjafJOvj/6A=" "http://127.0.0.1:8888/sailingserver/api/v1/regattas/Smartphone%20Tracking%20Test%2022.06/competitors/createandadd?boatclass=49er&sailid=GER+123&nationalityIOC=GER"</td>
</tr>
</table>
<div style="height: 1em;"></div>
<a href="index.html">Back to Web Service Overview</a>
</body>
</html>
@@ -23,7 +23,8 @@ The supported race detail constants right now are:<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/>
- CURRENT_LEG
- OVERALL_MAXIMUM_SPEED_OVER_GROUND_IN_KNOTS<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>