diff --git a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/ManeuversJsonSerializer.java b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/ManeuversJsonSerializer.java index 4c658d59fc7..0f4f02a3588 100755 --- a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/ManeuversJsonSerializer.java +++ b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/ManeuversJsonSerializer.java @@ -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>>> { 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>> data) { final JSONObject result = new JSONObject(); JSONArray byCompetitorJson = new JSONArray(); - result.put(BYCOMPETITOR, byCompetitorJson); - for (Entry competitorAndBoatEntry : trackedRace.getRace().getCompetitorsAndTheirBoats().entrySet()) { - Competitor competitor = competitorAndBoatEntry.getKey(); - Boat boat = competitorAndBoatEntry.getValue(); + result.put(AbstractTrackedRaceDataJsonSerializer.BYCOMPETITOR, byCompetitorJson); + for (Pair> 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 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); - } - } diff --git a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/RegattasResourceTest.java b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/RegattasResourceTest.java index 0b28ae7e9b8..5210a1d378d 100644 --- a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/RegattasResourceTest.java +++ b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/RegattasResourceTest.java @@ -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); + } + } diff --git a/java/com.sap.sailing.server.gateway/resources/shiro.ini b/java/com.sap.sailing.server.gateway/resources/shiro.ini index 68f9f603bb9..4687581e24c 100755 --- a/java/com.sap.sailing.server.gateway/resources/shiro.ini +++ b/java/com.sap.sailing.server.gateway/resources/shiro.ini @@ -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 diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/AbstractLeaderboardsResource.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/AbstractLeaderboardsResource.java index 158d58f0f5f..8fbe1ab213b 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/AbstractLeaderboardsResource.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/AbstractLeaderboardsResource.java @@ -196,15 +196,14 @@ public abstract class AbstractLeaderboardsResource extends AbstractSailingServer protected JSONObject getLeaderboardJson(ResultStates resultState, Integer maxCompetitorsCount, TimePoint requestTimePoint, Leaderboard leaderboard, TimePoint timePoint, List raceColumnNames, - List 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 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; + } } diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/LeaderboardsResourceV2.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/LeaderboardsResourceV2.java index a22681d1bbb..d333d61ef86 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/LeaderboardsResourceV2.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/LeaderboardsResourceV2.java @@ -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 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 valueForRaceDetailType = getValueForRaceDetailType(type, leaderboardEntry, currentLegEntry); + Pair 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 raceDetailNames) { + final HashSet 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 calculateRaceDetailTypesToShow(List raceDetailTypesNames) { List 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 getValueForRaceDetailType(DetailType type, LeaderboardEntryDTO entry, LegEntryDTO currentLegEntry) { + private DetailType[] getAvailableOverallDetailColumnTypes() { + return new DetailType[] { DetailType.OVERALL_MAXIMUM_SPEED_OVER_GROUND_IN_KNOTS }; + } + + private Pair getValueForRaceDetailType(DetailType type, LeaderboardRowDTO leaderboardRowDTO, LeaderboardEntryDTO entry, LegEntryDTO currentLegEntry) { String name; Object value = null; Pair 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; diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/PreferencesResource.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/PreferencesResource.java new file mode 100644 index 00000000000..1245a74695e --- /dev/null +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/PreferencesResource.java @@ -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; + } +} \ No newline at end of file diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RegattasResource.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RegattasResource.java index d10bb49cd3e..90a6d14296f 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RegattasResource.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RegattasResource.java @@ -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>> data = new ArrayList<>(); + Iterable 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 maneuversForCompetitor = trackedRace.getManeuvers(competitor, startTime, + endTime, false); + data.add(new Pair>(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; diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RestApiApplication.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RestApiApplication.java index 5474e184a28..dc9c71bcac7 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RestApiApplication.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RestApiApplication.java @@ -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> getClasses() { HashSet> 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); diff --git a/java/com.sap.sailing.server.gateway/webservices/api/v1/index.html b/java/com.sap.sailing.server.gateway/webservices/api/v1/index.html index 509d812844b..fca229b251f 100644 --- a/java/com.sap.sailing.server.gateway/webservices/api/v1/index.html +++ b/java/com.sap.sailing.server.gateway/webservices/api/v1/index.html @@ -290,6 +290,21 @@ name to make sure you get to the event you're interested in. /api/v1/mark/addMarkToRegatta Adds a Mark to a specified Regatta + + + /api/v1/preferences/{SETTINGS_KEY} + Stores a document into the preferences of a user + + + + /api/v1/preferences/{SETTINGS_KEY} + Retrives a document that was stored into the preferences of a user + + + + /api/v1/preferences/{SETTINGS_KEY} + Removes a document from the preferences of a user +
diff --git a/java/com.sap.sailing.server.gateway/webservices/api/v1/maneuversGetDoc.html b/java/com.sap.sailing.server.gateway/webservices/api/v1/maneuversGetDoc.html index 0c92936438f..decf81d0fcf 100644 --- a/java/com.sap.sailing.server.gateway/webservices/api/v1/maneuversGetDoc.html +++ b/java/com.sap.sailing.server.gateway/webservices/api/v1/maneuversGetDoc.html @@ -14,6 +14,7 @@ Description:

Gets all maneuvers performed by all competitors of a race.
All slashes contained within the {regattaname} and {racename} parameters must be replaced by "__" (double underscore).

+Additionally it is possible to reduce the results to only contain one competitor, by
@@ -30,8 +31,9 @@ All slashes contained within the {regattaname} and {racename} parameters must be - - + + @@ -40,6 +42,10 @@ All slashes contained within the {regattaname} and {racename} parameters must be + + + +
Optional parameters:None
competitorId (UUID) , fromTime UnixTimestamp +
Request method: GETExample: http://www.sapsailing.com/sailingserver/api/v1/regattas/TW%202013%20(Finn)/races/Finn%20Race%204/maneuvers
Example with optional Parametershttps://www.sapsailing.com/sailingserver/api/v1/regattas/ESS 2016 Cardiff/races/Race 2/maneuvers?competitorId=b9a8a600-c829-0133-a4ec-448a5b8481d8&fromTime=1466689117500
Back to Web Service Overview diff --git a/java/com.sap.sailing.server.gateway/webservices/api/v1/preferencesDelete.html b/java/com.sap.sailing.server.gateway/webservices/api/v1/preferencesDelete.html new file mode 100644 index 00000000000..772301ddb3c --- /dev/null +++ b/java/com.sap.sailing.server.gateway/webservices/api/v1/preferencesDelete.html @@ -0,0 +1,42 @@ + + + + + + + + SAP Sailing Analytics Webservices API Version 1.0 + + +

SAP Sailing Analytics Webservices API Version 1.0

+

URL: /api/v1/preferences/{SETTINGS_KEY}/

+ +Description: +

Deletes whatever is stored under SETTINGS_KEY in currently logged in users preferences

+
+ + + + + + + + + + + + + + + + + +
Webservice Type:REST
Output format:none
Mandatory parameters:none
Examples: + Delete @ http://sapsailing.com/sailingserver/api/v1/preferences/testSetting/ +
+ Answer: Ok +
+
+Back to Web Service Overview + + diff --git a/java/com.sap.sailing.server.gateway/webservices/api/v1/preferencesGet.html b/java/com.sap.sailing.server.gateway/webservices/api/v1/preferencesGet.html new file mode 100644 index 00000000000..a6a023a935c --- /dev/null +++ b/java/com.sap.sailing.server.gateway/webservices/api/v1/preferencesGet.html @@ -0,0 +1,42 @@ + + + + + + + + SAP Sailing Analytics Webservices API Version 1.0 + + +

SAP Sailing Analytics Webservices API Version 1.0

+

URL: /api/v1/preferences/{SETTINGS_KEY}/

+ +Description: +

Gets the stored Json data for the currently logged in user with the key SETTINGS_KEY

+
+ + + + + + + + + + + + + + + + + +
Webservice Type:REST
Output format:Json
Mandatory parameters:none
Examples: + Get @ http://sapsailing.com/sailingserver/api/v1/preferences/testSetting/ +
+ Answer: No Content Status code, or Ok and the stored document +
+
+Back to Web Service Overview + + diff --git a/java/com.sap.sailing.server.gateway/webservices/api/v1/preferencesPut.html b/java/com.sap.sailing.server.gateway/webservices/api/v1/preferencesPut.html new file mode 100644 index 00000000000..5acf8a45e6d --- /dev/null +++ b/java/com.sap.sailing.server.gateway/webservices/api/v1/preferencesPut.html @@ -0,0 +1,42 @@ + + + + + + + + SAP Sailing Analytics Webservices API Version 1.0 + + +

SAP Sailing Analytics Webservices API Version 1.0

+

URL: /api/v1/preferences/{SETTINGS_KEY}/

+ +Description: +

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

+
+ + + + + + + + + + + + + + + + + +
Webservice Type:REST
Output format:none
Mandatory parameters:none
Examples: + Put @ http://sapsailing.com/sailingserver/api/v1/preferences/testSetting/ +
+ Answer: Ok +
+
+Back to Web Service Overview + + diff --git a/java/com.sap.sailing.server.gateway/webservices/api/v1/raceManeuversGetDoc.html b/java/com.sap.sailing.server.gateway/webservices/api/v1/raceManeuversGetDoc.html index b5156d810d4..37a16f51f54 100755 --- a/java/com.sap.sailing.server.gateway/webservices/api/v1/raceManeuversGetDoc.html +++ b/java/com.sap.sailing.server.gateway/webservices/api/v1/raceManeuversGetDoc.html @@ -1,11 +1,11 @@ - - - - - SAP Sailing Analytics Webservices API Version 1.0 + + + + + SAP Sailing Analytics Webservices API Version 1.0

SAP Sailing Analytics Webservices API Version 1.0

@@ -14,101 +14,112 @@ Description:

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.

-The maneuvers for each competitor are ordered by the time point.
-All slashes contained within the {regattaname} and {racename} parameters must be replaced by "__" (double underscore).

-
+whichever is earliest. +

+ The maneuvers for each competitor are ordered by the time point.
+ All slashes contained within the {regattaname} and {racename} + parameters must be replaced by "__" (double underscore). +

+ There are two optional parameters: competitorId and fromTime. +
+ With competitorId, it is possible to filter the result to include only the given competitor. +
+ With fromTime it is possible to filter based on the maneuvers time. If not given, all maneuvers of a race will be returned. +
- - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Webservice Type:REST
Output format:Json
Mandatory parameters:None
Optional parameters:None
Request method:GET
Example:http://www.sapsailing.com/sailingserver/api/v1/regattas/TW%202013%20(Finn)/races/Finn%20Race%204/maneuvers
Example Output:
-{
-"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
+    
Webservice Type:REST
Output format:Json
Mandatory parameters:None
Optional parameters:fromTime unixTimeStamp, competitorId UUID
Request method:GET
Example:http://www.sapsailing.com/sailingserver/api/v1/regattas/TW%202013%20(Finn)/races/Finn%20Race%204/maneuvers
Example Output:
+{  
+    "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
-}
-},
-...
-]
-}
-		
-
Back to Web Service Overview diff --git a/java/com.sap.sailing.server.gateway/webservices/api/v2/leaderboardGetDoc.html b/java/com.sap.sailing.server.gateway/webservices/api/v2/leaderboardGetDoc.html index 57567152c93..fd8275f9d18 100644 --- a/java/com.sap.sailing.server.gateway/webservices/api/v2/leaderboardGetDoc.html +++ b/java/com.sap.sailing.server.gateway/webservices/api/v2/leaderboardGetDoc.html @@ -23,7 +23,8 @@ The supported race detail constants right now are:
- RACE_CURRENT_SPEED_OVER_GROUND_IN_KNOTS
- RACE_DISTANCE_TO_COMPETITOR_FARTHEST_AHEAD_IN_METERS
- NUMBER_OF_MANEUVERS
-- CURRENT_LEG
+- CURRENT_LEG +- OVERALL_MAXIMUM_SPEED_OVER_GROUND_IN_KNOTS
There is also the 'ALL' shortcut constant which can be used to get all available race details without the need to enumerate them.

diff --git a/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/AccessControlStoreImpl.java b/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/AccessControlStoreImpl.java index 696ed7d5104..c09a2d9f8b2 100755 --- a/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/AccessControlStoreImpl.java +++ b/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/AccessControlStoreImpl.java @@ -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 getOwnerships() { return new ArrayList<>(ownerships.values());