Merge branch 'permission-vertical' into bug4768

This commit is contained in:
Kai Börnert committed 2018-10-11 13:11:23 +02:00
commit 0aa4556bf7
16 files changed
+506 -181

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);
}
}
@@ -78,7 +78,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
@@ -87,3 +87,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;
}
}
@@ -64,6 +64,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;
@@ -117,6 +118,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;
@@ -1029,29 +1031,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,7 +29,8 @@ public class RestApiApplication extends Application {
classes.add(StatisticsResource.class);
classes.add(WindResource.class);
classes.add(MarkRessource.class);
classes.add(PreferencesResource.class);
// Exception Mappers
classes.add(ShiroAuthorizationExceptionTo401ResponseMapper.class);
classes.add(ExceptionManager.class);
@@ -290,6 +290,21 @@ name to make sure you get to the event you're interested in.
<td><a href="addMarkToRegatta.html">/api/v1/mark/addMarkToRegatta</a></td>
<td>Adds a Mark to a specified Regatta</td>
</tr>
</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>
@@ -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>
@@ -7,6 +7,7 @@ import java.util.concurrent.ConcurrentHashMap;
import com.sap.sse.security.AccessControlStore;
import com.sap.sse.security.UserStore;
import com.sap.sse.security.shared.AccessControlListAnnotation;
import com.sap.sse.security.shared.Ownership;
import com.sap.sse.security.shared.OwnershipAnnotation;
import com.sap.sse.security.shared.QualifiedObjectIdentifier;
import com.sap.sse.security.shared.SecurityUser;
@@ -158,9 +159,32 @@ public class AccessControlStoreImpl implements AccessControlStore {
@Override
public OwnershipAnnotation getOwnership(QualifiedObjectIdentifier idOfOwnedObjectAsString) {
return ownerships.get(idOfOwnedObjectAsString);
final OwnershipAnnotation storedOwnership = ownerships.get(idOfOwnedObjectAsString);
final OwnershipAnnotation result;
if (storedOwnership != null) {
result = storedOwnership;
} else {
result = createDefaultOwnership(idOfOwnedObjectAsString);
}
return result;
}
/**
* If there is no ownership information for an object and there is a {@link #defaultTenant} available,
* create a default {@link Ownership} information that lists the {@link #defaultTenant} as the tenant owner
* for the object in question; no user owner is specified. If no {@link #defaultTenant} is available,
* {@code null} is returned.
*/
private OwnershipAnnotation createDefaultOwnership(QualifiedObjectIdentifier idOfOwnedObjectAsString) {
final Ownership result;
if (defaultTenant != null) {
result = new OwnershipImpl(/* userOwner */ null, /* tenantOwner */ defaultTenant);
} else {
result = null;
}
return result == null ? null : new OwnershipAnnotation(result, idOfOwnedObjectAsString, /* display name */ null);
}
@Override
public Iterable<OwnershipAnnotation> getOwnerships() {
return new ArrayList<>(ownerships.values());