mirror of
https://github.com/eclipse-sailing-analytics/sailing-analytics.git
synced 2026-09-21 21:25:38 +00:00
bug6077: first implementation of REST API end point for competitordata
This commit is contained in:
+151
@@ -17,7 +17,10 @@ import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.FutureTask;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@@ -76,6 +79,7 @@ import com.sap.sailing.domain.base.impl.DynamicBoat;
|
||||
import com.sap.sailing.domain.base.impl.PersonImpl;
|
||||
import com.sap.sailing.domain.base.impl.TeamImpl;
|
||||
import com.sap.sailing.domain.common.CompetitorRegistrationType;
|
||||
import com.sap.sailing.domain.common.DetailType;
|
||||
import com.sap.sailing.domain.common.DeviceIdentifier;
|
||||
import com.sap.sailing.domain.common.ManeuverType;
|
||||
import com.sap.sailing.domain.common.NoWindException;
|
||||
@@ -100,6 +104,7 @@ import com.sap.sailing.domain.common.tracking.GPSFix;
|
||||
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
|
||||
import com.sap.sailing.domain.common.tracking.impl.CompetitorJsonConstants;
|
||||
import com.sap.sailing.domain.leaderboard.Leaderboard;
|
||||
import com.sap.sailing.domain.leaderboard.LeaderboardGroup;
|
||||
import com.sap.sailing.domain.leaderboard.RegattaLeaderboard;
|
||||
import com.sap.sailing.domain.leaderboard.caching.LeaderboardDTOCalculationReuseCache;
|
||||
import com.sap.sailing.domain.racelogtracking.DeviceMappingWithRegattaLogEvent;
|
||||
@@ -194,6 +199,7 @@ import com.sap.sse.shared.json.JsonDeserializationException;
|
||||
import com.sap.sse.shared.json.JsonSerializer;
|
||||
import com.sap.sse.shared.util.impl.UUIDHelper;
|
||||
import com.sap.sse.util.SingleCalculationPerSubjectCache;
|
||||
import com.sap.sse.util.ThreadPoolUtil;
|
||||
|
||||
@Path("/v1/regattas")
|
||||
public class RegattasResource extends AbstractSailingServerResource {
|
||||
@@ -203,6 +209,8 @@ public class RegattasResource extends AbstractSailingServerResource {
|
||||
|
||||
private DataMiningResource dataMiningResource;
|
||||
|
||||
private final ScheduledExecutorService executor = ThreadPoolUtil.INSTANCE.getDefaultForegroundTaskThreadPoolExecutor();
|
||||
|
||||
private class CompetitorRanksRequest {
|
||||
private final Regatta regatta;
|
||||
private final RaceColumn raceColumn;
|
||||
@@ -2122,6 +2130,149 @@ public class RegattasResource extends AbstractSailingServerResource {
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
@GET
|
||||
@Produces("application/json;charset=UTF-8")
|
||||
@Path("{regattaname}/races/{racename}/competitordata")
|
||||
public Response getCompetitorRaceData(
|
||||
@PathParam("regattaname") final String regattaName,
|
||||
@PathParam("racename") final String raceName,
|
||||
@QueryParam("fromtime") final String fromtime, @QueryParam("fromtimeasmillis") Long fromtimeasmillis,
|
||||
@QueryParam("totime") final String totime, @QueryParam("totimeasmillis") Long totimeasmillis,
|
||||
@QueryParam("competitorId") final Set<String> competitorIds,
|
||||
@QueryParam("detailType") final Set<DetailType> detailTypes,
|
||||
@QueryParam("leaderboardGroupNameOrUUID") final String leaderboardGroupName,
|
||||
@QueryParam("leaderboardName") final String leaderboardName,
|
||||
@QueryParam("stepSizeMillis") final long stepSizeMillis) {
|
||||
final Response response;
|
||||
final ConcurrentHashMap<TimePoint, WindLegTypeAndLegBearingAndORCPerformanceCurveCache> cachesByTimePoint = new ConcurrentHashMap<>();
|
||||
final TimePoint from;
|
||||
final TimePoint to;
|
||||
final Regatta regatta = findRegattaByName(regattaName);
|
||||
final TrackedRace trackedRace;
|
||||
final Serializable uuid = UUIDHelper.tryUuidConversion(leaderboardGroupName);
|
||||
final LeaderboardGroup leaderboardGroup;
|
||||
if (uuid != leaderboardGroupName) {
|
||||
leaderboardGroup = getService().getLeaderboardGroupByID((UUID) uuid);
|
||||
} else {
|
||||
leaderboardGroup = getService().getLeaderboardGroupByName(leaderboardGroupName);
|
||||
}
|
||||
if (leaderboardGroup == null) {
|
||||
response = Response.status(Status.NOT_FOUND)
|
||||
.entity("Could not find a leaderboard group with name '"
|
||||
+ StringEscapeUtils.escapeHtml(leaderboardGroupName) + "'.")
|
||||
.type(MediaType.TEXT_PLAIN).build();
|
||||
} else {
|
||||
if (regatta == null) {
|
||||
return getBadRegattaErrorResponse(regattaName);
|
||||
} else {
|
||||
RaceDefinition race = findRaceByName(regatta, raceName);
|
||||
if (race == null) {
|
||||
return getBadRaceErrorResponse(regattaName, raceName);
|
||||
} else {
|
||||
trackedRace = findTrackedRace(regattaName, raceName);
|
||||
if (trackedRace == null) {
|
||||
return getBadRaceErrorResponse(regattaName, raceName);
|
||||
}
|
||||
getSecurityService().checkCurrentUserReadPermission(trackedRace);
|
||||
try {
|
||||
from = parseTimePoint(fromtime, fromtimeasmillis,
|
||||
trackedRace.getStartOfRace() == null ? new MillisecondsTimePoint(0) :
|
||||
/* 24h before race start */new MillisecondsTimePoint(trackedRace.getStartOfRace()
|
||||
.asMillis() - 24 * 3600 * 1000));
|
||||
} catch (InvalidDateException e1) {
|
||||
return Response.status(Status.INTERNAL_SERVER_ERROR).entity("Could not parse the 'from' time.")
|
||||
.type(MediaType.TEXT_PLAIN).build();
|
||||
}
|
||||
try {
|
||||
to = parseTimePoint(totime, totimeasmillis, MillisecondsTimePoint.now());
|
||||
} catch (InvalidDateException e1) {
|
||||
return Response.status(Status.INTERNAL_SERVER_ERROR).entity("Could not parse the 'to' time.")
|
||||
.type(MediaType.TEXT_PLAIN).build();
|
||||
}
|
||||
}
|
||||
}
|
||||
final int MAX_NUMBER_OF_FIXES_TO_QUERY = 10000; // TODO harmonize with SailingServiceConstants.MAX_NUMBER_OF_FIXES_TO_QUERY?
|
||||
final Leaderboard leaderboard = getService().getLeaderboardByName(leaderboardName);
|
||||
final TimePoint newestEvent = trackedRace.getTimePointOfNewestEvent();
|
||||
final TimePoint startTime = from == null ? trackedRace.getStartOfTracking() : from;
|
||||
final TimePoint endTime = (to == null || to.after(newestEvent)) ? newestEvent : to;
|
||||
final long adjustedStepSizeInMillis = (long) Math.max((double) stepSizeMillis, startTime.until(endTime).divide(MAX_NUMBER_OF_FIXES_TO_QUERY).asMillis());
|
||||
final int MAX_CACHE_SIZE = MAX_NUMBER_OF_FIXES_TO_QUERY;
|
||||
for (final DetailType detailType : detailTypes) {
|
||||
if (detailType.getPremiumAction() != null && leaderboard.getPermissionType().supports(detailType.getPremiumAction())) {
|
||||
getSecurityService().checkCurrentUserExplicitPermissions(leaderboard, detailType.getPremiumAction());
|
||||
}
|
||||
}
|
||||
final Map<Competitor, FutureTask<Iterable<Pair<TimePoint, Map<DetailType, Double>>>>> resultFutures = new HashMap<>();
|
||||
for (final String competitorIdAsString : competitorIds) {
|
||||
final Competitor competitor = getService().getCompetitorAndBoatStore().getExistingCompetitorByIdAsString(competitorIdAsString);
|
||||
if (competitor == null) {
|
||||
return getBadCompetitorIdResponse(competitorIdAsString);
|
||||
}
|
||||
getSecurityService().checkCurrentUserExplicitPermissions(competitor, SecuredSecurityTypes.PublicReadableActions.READ_PUBLIC);
|
||||
final FutureTask<Iterable<Pair<TimePoint, Map<DetailType, Double>>>> future =new FutureTask<Iterable<Pair<TimePoint, Map<DetailType, Double>>>>(
|
||||
new Callable<Iterable<Pair<TimePoint, Map<DetailType, Double>>>>() {
|
||||
@Override
|
||||
public Iterable<Pair<TimePoint, Map<DetailType, Double>>> call() throws NoWindException {
|
||||
final List<Pair<TimePoint, Map<DetailType, Double>>> raceData = new ArrayList<>();
|
||||
if (startTime != null && endTime != null) {
|
||||
for (long i = startTime.asMillis(); i <= endTime.asMillis(); i += adjustedStepSizeInMillis) {
|
||||
final TimePoint time = TimePoint.of(i);
|
||||
WindLegTypeAndLegBearingAndORCPerformanceCurveCache cache = cachesByTimePoint.get(time);
|
||||
if (cache == null) {
|
||||
cache = new LeaderboardDTOCalculationReuseCache(time);
|
||||
// ensure maximum number of cache objects:
|
||||
if (cachesByTimePoint.size() >= MAX_CACHE_SIZE) {
|
||||
final Iterator<Entry<TimePoint, WindLegTypeAndLegBearingAndORCPerformanceCurveCache>> iterator = cachesByTimePoint.entrySet().iterator();
|
||||
while (cachesByTimePoint.size() >= MAX_CACHE_SIZE && iterator.hasNext()) {
|
||||
iterator.next();
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
cachesByTimePoint.put(time, cache);
|
||||
}
|
||||
final Map<DetailType, Double> valuesForTimepoint = new HashMap<>();
|
||||
raceData.add(new Pair<>(time, valuesForTimepoint));
|
||||
for (final DetailType detailType : detailTypes) {
|
||||
final Double competitorRaceData = getService()
|
||||
.getCompetitorRaceDataEntry(detailType, trackedRace, competitor,
|
||||
time, leaderboardGroup, leaderboardName, cache);
|
||||
if (competitorRaceData != null) {
|
||||
valuesForTimepoint.put(detailType, competitorRaceData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return raceData;
|
||||
}
|
||||
});
|
||||
resultFutures.put(competitor, future);
|
||||
executor.execute(future); // security checks happen before; no need to associate future with Subject/session
|
||||
}
|
||||
final JSONObject result = new JSONObject();
|
||||
for (Entry<Competitor, FutureTask<Iterable<Pair<TimePoint, Map<DetailType, Double>>>>> e : resultFutures.entrySet()) {
|
||||
try {
|
||||
final JSONArray resultsForCompetitor = new JSONArray();
|
||||
result.put(e.getKey().getId().toString(), resultsForCompetitor);
|
||||
for (final Pair<TimePoint, Map<DetailType, Double>> competitorData : e.getValue().get()) {
|
||||
final JSONObject resultsForCompetitorAtTimepoint = new JSONObject();
|
||||
resultsForCompetitorAtTimepoint.put("timepoint-ms", competitorData.getA().asMillis());
|
||||
final JSONObject resultsForCompetitorByDetailType = new JSONObject();
|
||||
for (final Entry<DetailType, Double> entry : competitorData.getB().entrySet()) {
|
||||
resultsForCompetitorByDetailType.put(entry.getKey().name(), entry.getValue());
|
||||
}
|
||||
resultsForCompetitorAtTimepoint.put("values", resultsForCompetitorByDetailType);
|
||||
resultsForCompetitor.add(resultsForCompetitorAtTimepoint);
|
||||
}
|
||||
} catch (InterruptedException | ExecutionException e1) {
|
||||
logger.log(Level.SEVERE, "Exception while trying to compute competitor data "+detailTypes+" for competitor "+e.getKey().getName(), e1);
|
||||
}
|
||||
}
|
||||
response = Response.ok(streamingOutput(result)).build();
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
private Integer getNullableValueFromDefault(Integer value) {
|
||||
return Integer.MIN_VALUE == value ? null : value;
|
||||
|
||||
Reference in New Issue
Block a user