Merge remote-tracking branch 'origin/master' into ribdashboard

This commit is contained in:
Alexander Ries committed 2016-01-14 14:07:56 +01:00
commit 30f30bb5dd
141 files changed
+2795 -39831

No files matched your search

+3
View File
@@ -0,0 +1,3 @@
#!/bin/bash
sourceanalyzer -b com.sap.sailing.domain -Xmx4000m -debug -logfile FortifyScan.log -extdirs /home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins -cp "/home/sailing/code/java/com.google.gwt.osgi/lib/gwt-user.jar:/home/sailing/gwt-2.7.0/validation-api-1.0.0.GA.jar:/home/sailing/gwt-2.7.0/gwt-codeserver.jar:/home/sailing/gwt-2.7.0/validation-api-1.0.0.GA-sources.jar:/home/sailing/gwt-2.7.0/gwt-user.jar:/home/sailing/gwt-2.7.0/gwt-dev.jar:/home/sailing/code/java/com.google.gwt.dev/lib/gwt-dev.jar:/home/sailing/code/java/com.tractrac.clientmodule/lib/TracAPI.jar:/home/sailing/code/java/com.sap.sailing.dashboards.gwt/WEB-INF/lib/gwt-ajaxloader.jar:/home/sailing/code/java/com.sap.sailing.dashboards.gwt/WEB-INF/lib/gwt-maps-api-3.9.0-build-17.jar:/home/sailing/code/java/com.googlecode.mgwt/lib/mgwt-2.0.0.jar:/home/sailing/code/java/com.sap.sailing.gwt.ui/WEB-INF/lib/gwt-bootstrap-2.3.2.0.jar:/home/sailing/code/java/com.sap.sailing.gwt.ui/WEB-INF/lib/gwt-maps-api-3.9.0-build-17.jar:/home/sailing/code/java/com.sap.sailing.gwt.ui/WEB-INF/lib/gwt-ajaxloader.jar" -source 1.8 "/home/sailing/code/java/com.sap.sailing.ess40.resultimport" "/home/sailing/code/java/com.sap.sailing.manage2sail" "/home/sailing/code/java/com.sap.sailing.manage2sail.resultimport" "/home/sailing/code/java/com.tractrac.clientmodule" "/home/sailing/code/java/com.sap.sse.filestorage" "/home/sailing/code/java/com.sap.sailing.domain.swisstimingreplayadapter" "/home/sailing/code/java/com.sap.sse.mongodb" "/home/sailing/code/java/com.sap.sailing.udpconnector" "/home/sailing/code/java/com.sap.sailing.server.gateway" "/home/sailing/code/java/com.sap.sse" "/home/sailing/code/java/com.sap.sse.datamining" "/home/sailing/code/java/com.sap.sailing.domain.racelogtrackingadapter" "/home/sailing/code/java/com.sap.sailing.datamining.shared" "/home/sailing/code/java/com.sap.sailing.domain.igtimiadapter.persistence" "/home/sailing/code/java/com.sap.sailing.monitoring" "/home/sailing/code/java/com.sap.sailing.domain" "/home/sailing/code/java/com.sap.sse.datamining.annotations" "/home/sailing/code/java/com.sap.sailing.domain.swisstimingadapter.persistence" "/home/sailing/code/java/com.sap.sse.threadmanager" "/home/sailing/code/java/com.sap.sailing.xrr.structureimport" "/home/sailing/code/java/com.sap.sailing.freg.resultimport" "/home/sailing/code/java/com.sap.sailing.domain.igtimiadapter" "/home/sailing/code/java/com.sap.sailing.server.gateway.ess40" "/home/sailing/code/java/com.sap.sailing.xrr.schema" "/home/sailing/code/java/com.sap.sse.common" "/home/sailing/code/java/com.sap.sailing.expeditionconnector" "/home/sailing/code/java/com.sap.sailing.declination" "/home/sailing/code/java/com.sap.sailing.geocoding" "/home/sailing/code/java/com.sap.sailing.server.gateway.serialization" "/home/sailing/code/java/com.sap.sailing.kiworesultimport" "/home/sailing/code/java/com.sap.sailing.polars.datamining" "/home/sailing/code/java/com.sap.sailing.simulator" "/home/sailing/code/java/com.sap.sse.replication" "/home/sailing/code/java/com.sap.sailing.www" "/home/sailing/code/java/com.sap.sse.gwt.adminconsole" "/home/sailing/code/java/com.sap.sailing.dashboards.gwt" "/home/sailing/code/java/com.sap.sailing.news" "/home/sailing/code/java/com.sap.sailing.polars.datamining.shared" "/home/sailing/code/java/com.sap.sailing.barbados.resultimport" "/home/sailing/code/java/com.sap.sse.security.common" "/home/sailing/code/java/com.sap.sse.security.ui" "/home/sailing/code/java/com.sap.sse.shared.android" "/home/sailing/code/java/org.json.simple" "/home/sailing/code/java/com.sap.sailing.resultimport" "/home/sailing/code/java/com.sap.sse.security" "/home/sailing/code/java/com.sap.sailing.gwt.ui" "/home/sailing/code/java/com.sap.sailing.domain.tractracadapter" "/home/sailing/code/java/com.sap.sailing.server.trackfiles" "/home/sailing/code/java/com.googlecode.java-diff-utils" "/home/sailing/code/java/com.sap.sse.operationaltransformation" "/home/sailing/code/java/com.sap.sailing.domain.swisstimingadapter" "/home/sailing/code/java/com.sap.sse.gwt.theme" "/home/sailing/code/java/com.sap.sailing.domain.persistence" "/home/sailing/code/java/com.sap.sailing.sailwave.resultimport" "/home/sailing/code/java/com.sap.sailing.xmlexport" "/home/sailing/code/java/com.sap.sse.mail" "/home/sailing/code/java/com.sap.sailing.velum.resultimport" "/home/sailing/code/java/com.sap.sse.security.userstore.mongodb" "/home/sailing/code/java/com.sap.sailing.server.gateway.serialization.shared.android" "/home/sailing/code/java/com.sap.sse.datamining.shared" "/home/sailing/code/java/org.moxieapps.gwt.highcharts" "/home/sailing/code/java/com.sap.sailing.autoload" "/home/sailing/code/java/com.googlecode.mgwt" "/home/sailing/code/java/com.sap.sailing.datamining" "/home/sailing/code/java/com.sap.sailing.polars" "/home/sailing/code/java/com.sap.sailing.www.events" "/home/sailing/code/java/com.sap.sailing.domain.common" "/home/sailing/code/java/com.sap.sailing.domain.shared.android" "/home/sailingLine truncated
#sourceanalyzer -b com.sap.sailing -Xmx10g -debug -logfile FortifyScan.log "-show-build-warnings" "-machine-output" "-cp" "/home/sailing/code/java/com.sap.sse.gwt.theme/WEB-INF/classes:/home/sailing/code/java/com.sap.sailing.dashboards.gwt/WEB-INF/classes:/home/sailing/code/java/com.sap.sailing.gwt.ui/WEB-INF/classes:/home/sailing/code/java/com.sap.sse.security.ui/WEB-INF/classes:/home/sailing/code/java/com.sap.sailing.www.events/WEB-INF/classes:/home/sailing/code/java/com.google.gwt.osgi/lib/gwt-user.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/org.eclipse.osgi_3.10.2.v20150203-1939.jar:/home/sailing/gwt-2.7.0/validation-api-1.0.0.GA.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/org.eclipse.jetty.osgi.npn.fragment_9.2.2.v20140723.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/javax.servlet_3.1.0.v20140303-1611.jar:/home/sailing/gwt-2.7.0/gwt-codeserver.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/org.apache.servicemix.bundles.zxing_3.1.0.1.jar:/home/sailing/gwt-2.7.0/validation-api-1.0.0.GA-sources.jar:/home/sailing/gwt-2.7.0/gwt-user.jar:/home/sailing/gwt-2.7.0/gwt-dev.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/org.apache.commons.math_2.1.0.v201105210652.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/com.fasterxml.jackson.core.jackson-databind_2.5.0.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/org.hamcrest.core_1.1.0.v20090501071000.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/org.junit4_4.8.2.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/com.fasterxml.jackson.core.jackson-annotations_2.5.0.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/com.rabbitmq.client_2.8.4.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/lz4-java_1.3.0.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/org.apache.servicemix.bundles.aws-java-sdk_1.9.8.1.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/org.apache.commons.logging_1.1.3.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/javax.validation_1.0.0.GA_v201205091237.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/org.eclipse.jetty.webapp_9.2.2.v20140723.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/com.fasterxml.jackson.core.jackson-core_2.5.0.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/org.eclipse.jetty.server_9.2.2.v20140723.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/org.mongodb.mongo-java-driver_2.13.0.RELEASE.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/org.apache.httpcomponents.httpclient_4.2.6.v201311072007.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/org.apache.commons.io_2.2.0.v201405211200.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/com.sun.mail.javax.mail_1.4.5.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/org.eclipse.jetty.io_9.2.2.v20140723.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/javax.ws.rs_1.1.1.v20130318-1750.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/org.jvnet.mimepull_1.9.4.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/org.apache.shiro.ehcache_1.2.3.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/org.apache.commons.lang_2.6.0.v201404270220.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/org.apache.shiro.core_1.2.2.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/org.eclipse.jetty.websocket.servlet_9.2.2.v20140723.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/org.apache.commons.beanutils_1.9.0.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/com.sun.jersey.contribs.jersey-multipart_1.17.0.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/org.eclipse.jetty.websocket.client_9.2.2.v20140723.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/org.objectweb.asm_5.0.1.v201404251740.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/org.jdom_1.1.1.v201101151400.jar:/home/sailing/workspace/.metadaLine truncated
+2
View File
@@ -0,0 +1,2 @@
#!/bin/bash
sourceanalyzer -b com.sap.sailing.domain -clean
+3
View File
@@ -0,0 +1,3 @@
#!/bin/bash
#sourceanalyzer -b com.sap.sailing.domain -scan
sourceanalyzer -b com.sap.sailing.domain -Xmx6000m -scan "-f" "/home/sailing/code/java/com.sap.sailing_scan.fpr"
@@ -5,6 +5,7 @@ import java.util.logging.Level;
import java.util.logging.Logger;
import com.google.gwt.dom.client.Style.Visibility;
import com.google.gwt.safehtml.shared.UriUtils;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Image;
@@ -33,7 +34,7 @@ public class EventLogo extends Image {
if (event != null) {
final ImageDTO logo = event.getLogoImage();
if (logo != null) {
eventLogo.getElement().setAttribute("src", logo.getSourceRef());
eventLogo.getElement().setAttribute("src", UriUtils.fromString(logo.getSourceRef()).asString());
} else {
eventLogo.getElement().getStyle().setVisibility(Visibility.HIDDEN);
}
@@ -12,6 +12,7 @@ import com.google.gwt.dom.client.DivElement;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.i18n.client.NumberFormat;
import com.google.gwt.resources.client.CssResource;
import com.google.gwt.safehtml.shared.SafeHtmlUtils;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.client.ui.Composite;
@@ -92,20 +93,20 @@ public class StartAnalysisCard extends Composite implements HasWidgets, StartAna
private void fillWindAndStartLineData(StartAnalysisDTO startAnalysisDTO) {
if (startAnalysisDTO.startAnalysisWindLineInfoDTO != null) {
setLineAdvantageDivWidth(startAnalysisDTO.startAnalysisWindLineInfoDTO.startLineAdvantage.startLineAdvatageType);
String startLineAdvantageType;
final String startLineAdvantageType;
if (startAnalysisDTO.startAnalysisWindLineInfoDTO.startLineAdvantage.startLineAdvatageType
.equals(StartlineAdvantageType.GEOMETRIC)) {
startLineAdvantageType = stringMessages.dashboardStartlineAdvantageByGeometry();
}else{
} else {
startLineAdvantageType = stringMessages.dashboardStartlineAdvantagesByWind();
}
startanalysis_card_line_advantage
.setInnerHTML(startLineAdvantageType
.setInnerHTML(SafeHtmlUtils.fromString(startLineAdvantageType
+ ": "
+ NumberFormat
.getFormat("#0.0")
.format(startAnalysisDTO.startAnalysisWindLineInfoDTO.startLineAdvantage.startLineAdvantage)
+ " m");
+ " m").asString());
String formattedTimeSinceStart;
if (startAnalysisDTO.racingProcedureType.equals(RacingProcedureType.GateStart)) {
formattedTimeSinceStart = getRaceTimeStringFromMilliseconds(startAnalysisDTO.tailLenghtInMilliseconds);
@@ -57,4 +57,7 @@ RankGainsOrLosses=Positions-Verluste oder -Gewinne
DistanceToStarboardSideAtStart=Distanz zur Steuerbordseite (% der Länge)
NumberOfPodiumFinish=Azahl der Podium-Siege
NumberOfWins=Anzahl der Siege
RankGainsOrLossesBetweenFirstMarkAndFinish=Positions-Verluste oder -Gewinne (1ter Wegpunkt -> Ziel)
RankGainsOrLossesBetweenFirstMarkAndFinish=Positions-Verluste oder -Gewinne (1ter Wegpunkt -> Ziel)
IsTracked=Ist getracked
NumberOfCompetitorFixes=Anzahl der Teilnehmer-Fixes
NumberOfMarkFixes=Anzahl der Tonnen-Fixes
@@ -57,4 +57,7 @@ RankGainsOrLosses=Rank Gains or Losses
DistanceToStarboardSideAtStart=Distance to Starboard Side (% of Length)
NumberOfPodiumFinish=Number of Podium Finishes
NumberOfWins=Number of Wins
RankGainsOrLossesBetweenFirstMarkAndFinish=Rank Gains or Losses (1st Mark -> Finish)
RankGainsOrLossesBetweenFirstMarkAndFinish=Rank Gains or Losses (1st Mark -> Finish)
IsTracked=Is Tracked
NumberOfCompetitorFixes=Number of Competitor Fixes
NumberOfMarkFixes=Number of Mark Fixes
@@ -47,6 +47,7 @@ public class SailingDataRetrievalChainDefinitions {
leaderboardRetrieverChainDefinition, HasTrackedRaceContext.class, "RaceSailingDomainRetrieverChain");
trackedRaceRetrieverChainDefinition.endWith(LeaderboardRetrievalProcessor.class, TrackedRaceRetrievalProcessor.class,
HasTrackedRaceContext.class, "Race");
dataRetrieverChainDefinitions.add(trackedRaceRetrieverChainDefinition);
final DataRetrieverChainDefinition<RacingEventService, HasMarkPassingContext> markPassingRetrieverChainDefinition = new SimpleDataRetrieverChainDefinition<>(
trackedRaceRetrieverChainDefinition, HasMarkPassingContext.class, "MarkPassingSailingDomainRetrieverChain");
@@ -2,11 +2,13 @@ package com.sap.sailing.datamining.data;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.polars.PolarDataService;
import com.sap.sse.datamining.annotations.Dimension;
public interface HasLeaderboardContext {
Leaderboard getLeaderboard();
PolarDataService getPolarDataService();
@Dimension(messageKey="Leaderboard")
String getName();
}
@@ -8,6 +8,7 @@ import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sse.datamining.annotations.Connector;
import com.sap.sse.datamining.annotations.Dimension;
import com.sap.sse.datamining.annotations.Statistic;
public interface HasTrackedRaceContext {
@@ -34,4 +35,13 @@ public interface HasTrackedRaceContext {
@Dimension(messageKey="Year", ordinal=2)
public Integer getYear();
@Dimension(messageKey="IsTracked", ordinal=6)
public Boolean isTracked();
@Statistic(messageKey="NumberOfCompetitorFixes", resultDecimals=0, ordinal=0)
public int getNumberOfCompetitorFixes();
@Statistic(messageKey="NumberOfMarkFixes", resultDecimals=0, ordinal=1)
public int getNumberOfMarkFixes();
}
@@ -5,7 +5,6 @@ import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.polars.PolarDataService;
public class LeaderboardWithContext implements HasLeaderboardContext {
private final Leaderboard leaderboard;
private final PolarDataService polarDataService;
@@ -19,9 +18,13 @@ public class LeaderboardWithContext implements HasLeaderboardContext {
return leaderboard;
}
@Override
public PolarDataService getPolarDataService() {
return polarDataService;
}
@Override
public PolarDataService getPolarDataService() {
return polarDataService;
}
@Override
public String getName() {
return getLeaderboard().getName();
}
}
@@ -5,12 +5,19 @@ import java.util.Calendar;
import com.sap.sailing.datamining.data.HasLeaderboardContext;
import com.sap.sailing.datamining.data.HasTrackedRaceContext;
import com.sap.sailing.domain.base.BoatClass;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.CourseArea;
import com.sap.sailing.domain.base.Fleet;
import com.sap.sailing.domain.base.Mark;
import com.sap.sailing.domain.base.RaceDefinition;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.common.tracking.GPSFix;
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
import com.sap.sailing.domain.tracking.GPSFixTrack;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util;
import com.sap.sse.common.impl.MillisecondsTimePoint;
public class TrackedRaceWithContext implements HasTrackedRaceContext {
@@ -84,4 +91,39 @@ public class TrackedRaceWithContext implements HasTrackedRaceContext {
return calendar.get(Calendar.YEAR);
}
@Override
public Boolean isTracked() {
return getTrackedRace().hasStarted(MillisecondsTimePoint.now());
}
@Override
public int getNumberOfCompetitorFixes() {
int number = 0;
for (Competitor competitor : getRace().getCompetitors()) {
GPSFixTrack<Competitor, GPSFixMoving> track = getTrackedRace().getTrack(competitor);
track.lockForRead();
try {
number += Util.size(track.getRawFixes());
} finally {
track.unlockAfterRead();
}
}
return number;
}
@Override
public int getNumberOfMarkFixes() {
int number = 0;
for (Mark mark : getTrackedRace().getMarks()) {
GPSFixTrack<Mark, GPSFix> track = getTrackedRace().getTrack(mark);
track.lockForRead();
try {
number += Util.size(track.getRawFixes());
} finally {
track.unlockAfterRead();
}
}
return number;
}
}
@@ -1,4 +1,5 @@
eclipse.preferences.version=1
encoding//src/com/sap/sailing/domain/common/AbstractBearing.java=UTF-8
encoding//src/com/sap/sailing/domain/common/confidence/impl/ConfidenceBasedAveragerImpl.java=utf-8
encoding//src/com/sap/sailing/domain/common/impl/AbstractSpeedWithAbstractBearingImpl.java=UTF-8
encoding//src/com/sap/sailing/domain/common/tracking/impl/CompactGPSFixMovingImpl.java=UTF-8
@@ -2,7 +2,13 @@ package com.sap.sailing.domain.common;
import java.io.Serializable;
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
public interface Bearing extends Serializable {
Bearing NORTH = new DegreeBearingImpl(0);
Bearing EAST = new DegreeBearingImpl(90);
Bearing SOUTH = new DegreeBearingImpl(180);
Bearing WEST = new DegreeBearingImpl(270);
double getDegrees();
@@ -0,0 +1,105 @@
package com.sap.sailing.domain.common;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import com.sap.sse.common.Util;
/**
* Manages the set of competitors participating in a specific race. This is not trivial for a {@link RaceBoardPanel}
* because it is provided with the {@link CompetitorSelectionProvider} that is also used by the {@link LeaderboardPanel}
* and therefore has all competitors of the entire regatta, not just those of the race that the race board is showing.
* <p>
*
* The back-end knows the race-specific set of competitors and can tell the client. However, we'd like to keep the
* network traffic required to keep this information up to date at a minimum. The key idea is to use an MD5 hash across
* the ordered race's competitor IDs and send that with each {@link GetRaceMapDataAction} request. The server can then
* compare this hash with the current race's competitor set and send an update if and only if there is a difference
* detected. This will, for the most part, reduce the communication to an initial request by the client as long as the
* race's competitors are not known, and the additional MD5 hash being sent with each request.
* <p>
*
* Objects of this type start out with an "unknown" race competitor set. When the
* {@link #getMd5OfIdsAsStringOfCompetitorParticipatingInRaceInAlphanumericOrderOfTheirID() MD5 hash} for the currently
* known race competitors is asked, <code>null</code> will initially be returned. When the client has received the race
* competitors the client is expected to call {@link #setIdsAsStringsOfCompetitorsInRace(Iterable)}. If the argument to
* this call was not <code>null</code>,
* {@link #getMd5OfIdsAsStringOfCompetitorParticipatingInRaceInAlphanumericOrderOfTheirID()} will from then on return
* the MD5 hash of the competitor IDs in order.
*
* @author Axel Uhl (D043530)
*
*/
public class RaceCompetitorIdsAsStringWithMD5Hash implements Serializable {
private static final long serialVersionUID = 6881670366184539259L;
/**
* The {@link #competitorSelection} manages the competitors for the entire leaderboard which may be a
* superset of the competitors participating in the race shown by this map. For certain operations,
* however, it is useful to know exactly the competitors participating in the race shown by this map.
* For example, boat positions should only be requested from the server for those competitors, not all
* in the regatta, particularly when racing in split fleets.<p>
*
* To keep this set consistent, the client sends a secure hash of this set to the server where the hash
* will be compared with the hash across the set of competitor IDs as string.<p>
*
* If this field is <code>null</code>, nothing is known yet about the competitor-to-race assignment.
*/
private Set<String> idsAsStringOfCompetitorsParticipatingInRace;
/**
* The MD5 hash of the {@link #idsAsStringOfCompetitorsParticipatingInRace} where the competitors are first ordered
* alphanumerically by their ID as String using {@link String#compareTo(String)}, then concatenating these strings
* and converting to a byte[] using a UTF-8 encoding. May be <code>null</code> if it hasn't been computed before
* or if {@link #idsAsStringOfCompetitorsParticipatingInRace} is still <code>null</code>.
*/
private byte[] md5OfIdsAsStringOfCompetitorParticipatingInRaceInAlphanumericOrderOfTheirID;
/**
* Starts out with an unknown set of race competitors. The {@link #getIdsOfCompetitorsParticipatingInRaceAsStrings()}
* and the {@link #getMd5OfIdsAsStringOfCompetitorParticipatingInRaceInAlphanumericOrderOfTheirID()} will return
* <code>null</code> until a call to {@link #setIdsAsStringsOfCompetitorsInRace(Iterable)} with a non-
* <code>null</code> argument has been performed.
*/
public RaceCompetitorIdsAsStringWithMD5Hash() {
}
/**
* Immediately calls {@link #setIdsAsStringsOfCompetitorsInRace(Set)} with the argument provided. If that
* was <code>null</code> then this has the same effect as calling {@link #RaceCompetitorIdsAsStringWithMD5Hash()}.
*/
public RaceCompetitorIdsAsStringWithMD5Hash(Set<String> idsAsStringOfCompetitorsParticipatingInRace)
throws UnsupportedEncodingException, NoSuchAlgorithmException {
this.setIdsAsStringsOfCompetitorsInRace(idsAsStringOfCompetitorsParticipatingInRace);
}
public void setIdsAsStringsOfCompetitorsInRace(Set<String> idsAsStringsOfCompetitorsInRace) throws UnsupportedEncodingException, NoSuchAlgorithmException {
this.idsAsStringOfCompetitorsParticipatingInRace = idsAsStringsOfCompetitorsInRace;
md5OfIdsAsStringOfCompetitorParticipatingInRaceInAlphanumericOrderOfTheirID = computeMD5(idsAsStringsOfCompetitorsInRace);
}
public byte[] getMd5OfIdsAsStringOfCompetitorParticipatingInRaceInAlphanumericOrderOfTheirID() {
return md5OfIdsAsStringOfCompetitorParticipatingInRaceInAlphanumericOrderOfTheirID;
}
public Set<String> getIdsOfCompetitorsParticipatingInRaceAsStrings() {
return idsAsStringOfCompetitorsParticipatingInRace;
}
private byte[] computeMD5(Iterable<String> idsAsStringsOfCompetitorsInRace) throws NoSuchAlgorithmException, UnsupportedEncodingException {
List<String> l = new ArrayList<>();
Util.addAll(idsAsStringsOfCompetitorsInRace, l);
Collections.sort(l);
final MessageDigest md5 = MessageDigest.getInstance("MD5");
for (String c : l) {
md5.update(c.getBytes("UTF-8"));
}
return md5.digest();
}
}
@@ -1,164 +1,90 @@
// **********************************************************************
//
// <copyright>
//
// BBN Technologies
// 10 Moulton Street
// Cambridge, MA 02138
// (617) 873-8000
//
// Copyright (C) BBNT Solutions LLC. All rights reserved.
//
// </copyright>
// **********************************************************************
//
// $Source$
// $RCSfile$
// $Revision: 184 $
// $Date: 2008-09-16 16:17:21 +0200 (Tue, 16 Sep 2008) $
// $Author: axel.uhl $
//
// **********************************************************************
package com.sap.sailing.domain.common.quadtree;
import java.io.Serializable;
import java.util.Collection;
import java.util.Vector;
import com.sap.sailing.domain.common.Bounds;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.impl.BoundsImpl;
import com.sap.sailing.domain.common.impl.DegreePosition;
import com.sap.sailing.domain.common.quadtree.impl.Bounds;
import com.sap.sailing.domain.common.quadtree.impl.QuadTreeNode;
import com.sap.sailing.domain.common.quadtree.impl.Node;
import com.sap.sailing.domain.common.quadtree.impl.Node.GetResult;
/**
* The QuadTree lets you organize objects in a grid, that redefines itself and focuses more gridding when more objects
* appear in a certain area.
* <p>
*
* Note that this class is not thread safe. If multiple threads can access the same instance concurrently, callers have
* to ensure proper synchronization. Concurrent reads are permissible while any write should block all other operations.
* A spatial data structure that provides efficient (O(log n)) access to nearest neighbors and
* to objects in a certain radius around another object. The location of objects is provided as
* {@link Position}.
*
* @param <T>
* type of object stored by coordinates
* @author Axel Uhl (D043530)
*/
public class QuadTree<T> implements Serializable {
private static final long serialVersionUID = -5500716775749946017L;
static final long serialVersionUID = -7707825592455579873L;
private QuadTreeNode<T> top;
private final Node<T> root;
private final static int DEFAULT_MAX_NODE_ITEMS = 20;
public QuadTree() {
this(new Bounds(new DegreePosition(-90.0, -180.0), new DegreePosition(90.0, 180.0)), 20, QuadTreeNode.NO_MIN_SIZE);
this(new BoundsImpl(new DegreePosition(-90.0, -180.0), new DegreePosition(90.0, 180.0)), DEFAULT_MAX_NODE_ITEMS);
}
public QuadTree(Position southWest, Position northEast, int maxItems) {
this(new Bounds(southWest, northEast), maxItems, QuadTreeNode.NO_MIN_SIZE);
this(new BoundsImpl(southWest, northEast), maxItems);
}
public QuadTree(Bounds bounds, int maxItems, double minSize) {
top = new QuadTreeNode<T>(bounds, maxItems, minSize);
private QuadTree(Bounds bounds, int maxItems) {
root = new Node<T>(bounds, maxItems);
}
/**
* Add a object into the tree at a location.
*
* @param lat up-down location in QuadTree Grid (latitude, y)
* @param lon left-right location in QuadTree Grid (longitude, x)
* @return true if the insertion worked.
* @throws RuntimeException in case the leaf's lat/lng lies outside of the node's bounds.
* This would typically be caused by the point being outside the whole quad tree's bounds.
*/
public void put(Position point, T obj) {
getTop().put(point, obj);
public T put(Position point, T obj) {
return root.put(point, obj);
}
/**
* Remove a object out of the tree at a location.
*
* @param lat up-down location in QuadTree Grid (latitude, y)
* @param lon left-right location in QuadTree Grid (longitude, x)
* @return the object removed, null if the object not found.
*/
public T remove(Position point, T obj) {
return getTop().remove(point, obj);
return root.remove(point);
}
public void replace(Position point, T newObj) {
getTop().replace(point, newObj);
}
/** Clear the tree. */
/**
* Remove all elements from this tree.
*/
public void clear() {
getTop().clear();
root.clear();
}
/**
* Get an object closest to a lat/lon.
*
* @param lat up-down location in QuadTree Grid (latitude, y)
* @param lon left-right location in QuadTree Grid (longitude, x)
* @return the object that was found.
* Get the value nearest to <code>point</code>. If the tree is empty, <code>null</code> is returned. Distance
* is calculated using the method {@link #getLatLngDistance(Position, Position)} which is an approximation only,
* based on Euklidian geometry with the latitude/longitude values.
*/
public T get(Position point) {
return getTop().get(point);
final GetResult<T> result = root.get(point);
return result == null ? null : result.getValue();
}
/**
* Get an object closest to a lat/lon, within a maximum distance.
* Get the value closest to <code>point</code>, within a maximum distance, where distance
* is computed by the rules of {@link #getLatLngDistance(Position, Position)}. If no key
* is found within that distance, <code>null</code> is returned.
*
* @param lat up-down location in QuadTree Grid (latitude, y)
* @param lon left-right location in QuadTree Grid (longitude, x)
* @param withinDistance maximum get distance. The distance is given
* as the square root of the sum of
* the squares of the latitude and longitude differences, respectively.
* It therefore does not correspond to any distance in meters or
* any euclidian distance at all. However, it should be good enough
* (at least outside the polar regions, and in particular for smaller
* regions), and in particular to find <em>minimum</em> distances.
* @return the object that was found, null if nothing is within
* the maximum distance.
* @param withinDistance
* maximum get distance. The distance is given as the square root of the sum of the squares of the
* latitude and longitude differences, respectively. It therefore does not correspond to any distance in
* meters or any euclidian distance at all. However, it should be good enough (at least outside the polar
* regions, and in particular for smaller regions), and in particular to find <em>minimum</em> distances.
* See {@link #getLatLngDistance(Position, Position)}.
* @return the object that was found, null if nothing is within the maximum distance.
*/
public T get(Position point, double withinDistance) {
return getTop().get(point, withinDistance);
final GetResult<T> result = root.get(point, withinDistance);
return result == null ? null : result.getValue();
}
/**
* Get all the objects within a bounding box.
*
* @return Vector of objects.
* Get all values withing the <code>bounds</code>
*/
public Collection<T> get(Bounds rect) {
return get(rect, new Vector<T>());
}
/**
* Get all the objects within a bounding box, and return the
* objects within a given Vector.
*
* @param vector a vector to add objects to.
* @return Vector of objects.
*/
private Collection<T> get(Bounds rect, Collection<T> vector) {
if (vector == null) {
vector = new Vector<T>();
}
// crossing the dateline, right?? Or at least containing the
// entire earth. Might be trouble for VERY LARGE scales. The
// last check is for micro-errors that happen to lon points
// where there might be a smudge overlap for very small
// scales.
if (rect.getSouthWest().getLngDeg() > rect.getNorthEast().getLngDeg() || (Math.abs(rect.getSouthWest().getLngDeg() - rect.getNorthEast().getLngDeg()) < .001)) {
return getTop().get(new Bounds(rect.getSouthWest(), new DegreePosition(rect.getNorthEast().getLatDeg(), 180)),
getTop().get(new Bounds(new DegreePosition(rect.getSouthWest().getLatDeg(), -180), rect.getNorthEast()), vector));
} else
return getTop().get(rect, vector);
}
protected QuadTreeNode<T> getTop() {
return top;
public Iterable<T> get(Bounds bounds) {
return root.get(bounds);
}
/**
@@ -166,8 +92,10 @@ public class QuadTree<T> implements Serializable {
* and doing the "sqrt thing"
*/
public static double getLatLngDistance(Position a, Position b) {
double distance = Math.sqrt((a.getLatDeg() - b.getLatDeg()) * (a.getLatDeg() - b.getLatDeg()) + (a.getLngDeg() - b.getLngDeg())
* (a.getLngDeg() - b.getLngDeg()));
final double dy = a.getLatDeg() - b.getLatDeg();
final double dx = (a.getLngDeg() - b.getLngDeg());
final double dxWrapped = Math.abs(dx) <= 180 ? dx : 360.-Math.abs(dx);
double distance = Math.sqrt(dy * dy + dxWrapped * dxWrapped);
return distance;
}
}
@@ -1,36 +0,0 @@
package com.sap.sailing.domain.common.quadtree.impl;
import com.sap.sailing.domain.common.Position;
public class Bounds {
private final Position southWest;
private final Position northEast;
public Bounds(Position southWest, Position northEast) {
this.southWest = southWest;
this.northEast = northEast;
}
public Position getSouthWest() {
return southWest;
}
public Position getNorthEast() {
return northEast;
}
public boolean contains(Position p) {
return p.getLatDeg() >= getSouthWest().getLatDeg() && p.getLatDeg() <= getNorthEast().getLatDeg() &&
(getNorthEast().getLngDeg() >= getSouthWest().getLngDeg() && p.getLngDeg() >= getSouthWest().getLngDeg() && p.getLngDeg() <= getNorthEast().getLngDeg() ||
// cross date line bounds
getNorthEast().getLngDeg() < getSouthWest().getLngDeg() &&
(p.getLngDeg() <= 180 && p.getLngDeg() >= getSouthWest().getLngDeg() ||
p.getLngDeg() >= -180 && p.getLngDeg() <= getNorthEast().getLngDeg()));
}
public boolean containsBounds(Bounds rect) {
return this.contains(rect.getSouthWest()) && this.contains(rect.getNorthEast()) ||
rect.contains(this.getSouthWest()) && rect.contains(this.getNorthEast());
}
}
@@ -1,34 +0,0 @@
// **********************************************************************
//
// <copyright>
//
// BBN Technologies
// 10 Moulton Street
// Cambridge, MA 02138
// (617) 873-8000
//
// Copyright (C) BBNT Solutions LLC. All rights reserved.
//
// </copyright>
// **********************************************************************
//
// $Source$
// $RCSfile$
// $Revision: 15 $
// $Date: 2008-04-20 02:18:58 +0200 (Sun, 20 Apr 2008) $
// $Author: uhl $
//
// **********************************************************************
package com.sap.sailing.domain.common.quadtree.impl;
/**
* A *really* simple class used as a changable double.
*/
public class MutableDistance {
public double value = 0;
public MutableDistance(double distance) {
value = distance;
}
}
@@ -0,0 +1,423 @@
package com.sap.sailing.domain.common.quadtree.impl;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import com.sap.sailing.domain.common.Bounds;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.impl.BoundsImpl;
import com.sap.sailing.domain.common.impl.DegreePosition;
import com.sap.sailing.domain.common.quadtree.QuadTree;
import com.sap.sse.common.Util;
/**
* A node in a {@link QuadTree}. There may be internal nodes that have no elements in them but have exactly
* four child nodes, or leaf nodes that have no children but contain item elements.
*
* @author Axel Uhl (D043530)
*
* @param <T>
*/
public class Node<T> {
// TODO remove when done
public static int visitedItems;
public static int visitedNodes;
public static int skippedNodes;
/**
* Either an array of exactly four child nodes which are then all non-<code>null</code>, or <code>null</code>,
* meaning that this node is a child node, having no children but potentially having items. A node that has children
* has no items. Quadrants are numbered as usual in geometry. See {@link #NE}, {@link #NW}, {@link #SW} and
* {@link #SE}.
*/
private Node<T>[] children;
private Map<Position, T> items;
private final Bounds bounds;
/**
* The maximum number of items to hold in {@link #items}. If {@link #put(Position, Object) adding} an item to this node
* would increase the item collection's size beyond this number, the node is {@link #split() split} into four new leaf
* nodes, and its items are distributed across those new leaf nodes. This number must be a positive integer.
*/
private final int maxItems;
/**
* The quadrant index in {@link #children} for the north-east subtree
*/
private final int NE = 0;
/**
* The quadrant index in {@link #children} for the north-west subtree
*/
private final int NW = 1;
/**
* The quadrant index in {@link #children} for the south-west subtree
*/
private final int SW = 2;
/**
* The quadrant index in {@link #children} for the south-east subtree
*/
private final int SE = 3;
/**
* Creates a new node with the <code>bounds</code> as specified. The node starts out empty, as a leaf node that has
* an empty set of items.
*
* @param bounds
* must not have a north latitude less than the south latitude, or an {@link IllegalArgumentException}
* will result
*/
public Node(Bounds bounds, int maxItems) {
if (bounds.getNorthEast().getLatDeg() < bounds.getSouthEast().getLatDeg()) {
throw new IllegalArgumentException("North border of bounds "+bounds+" is further south than its south border");
}
if (maxItems <= 0) {
throw new IllegalArgumentException("Maximum number of items must be positive but was "+maxItems);
}
this.maxItems = maxItems;
this.bounds = bounds;
items = new HashMap<>(maxItems);
}
private void split() {
assert children == null;
assert items != null;
createChildren();
distributeItemsToChildren();
assert children != null;
assert items == null;
}
private void distributeItemsToChildren() {
assert items != null;
assert children != null;
final Map<Position, T> myItems = items;
items = null;
for (final Entry<Position, T> item : myItems.entrySet()) {
getChild(item.getKey()).put(item.getKey(), item.getValue());
}
assert items == null;
}
private Node<T> getChild(Position key) {
assert children != null;
assert items == null;
assert bounds.contains(key);
for (final Node<T> child : children) {
if (child.bounds.contains(key)) {
return child;
}
}
throw new RuntimeException("Internal error: position "+key+" is within node bounds "+bounds+" but no child contains it");
}
/**
* Adds the <code>value</code> to this node and ensures that the node still meets the requirements regarding size.
* If necessary, the node is split with its items distributed across the new children. If a value already existed at
* position <code>key</code>, it is replaced.
*
* @param key
* the position at which to insert <code>value</code>. Must be contained in this node's {@link #bounds}.
* If it is not, an {@link IllegalArgumentException} will be thrown. Must not be <code>null</code>.
* @return the value previously at position <code>key</code> or <code>null</code> if there was no value at that
* position.
*/
public T put(Position key, T value) {
if (value == null) {
throw new NullPointerException("Cannot insert null values into this node");
}
if (key == null) {
throw new NullPointerException("null keys not allowed");
}
if (!bounds.contains(key)) {
throw new IllegalArgumentException("key "+key+" must be within this node's bounds "+bounds);
}
final T result;
if (items != null) {
result = items.put(key, value);
if (result == null) {
// the size of this node has increased by one; check size constraint
if (items.size() > maxItems) {
split();
}
}
} else {
result = getChild(key).put(key, value);
}
return result;
}
/**
* Removes the element at position <code>key</code> from this node or any child nodes if such an element exists
*
* @return the value removed, or <code>null</code> if no element existed at position <code>key</code> in this node
* or any of its children
*/
public T remove(Position key) {
final T result;
if (key != null) {
if (items != null) {
result = items.remove(key);
} else {
result = getChild(key).remove(key);
}
} else {
result = null;
}
return result;
}
private void createChildren() {
assert children == null;
@SuppressWarnings("unchecked")
final Node<T>[] newChildren = (Node<T>[]) new Node<?>[4];
children = newChildren;
final Position middleWest = new DegreePosition((bounds.getSouthWest().getLatDeg() + bounds.getNorthEast().getLatDeg())/2.,
bounds.getSouthWest().getLngDeg());
final Position southCenter = new DegreePosition(bounds.getSouthWest().getLatDeg(),
(bounds.getNorthEast().getLngDeg() + bounds.getSouthWest().getLngDeg()) / 2. -
// adjust for date line crossing if necessary
(bounds.getNorthEast().getLngDeg() >= bounds.getSouthWest().getLngDeg() ? 0. : 360.));
final Position middleCenter = new DegreePosition(middleWest.getLatDeg(), southCenter.getLngDeg());
final Position middleEast = new DegreePosition(middleWest.getLatDeg(), bounds.getNorthEast().getLngDeg());
final Position northCenter = new DegreePosition(bounds.getNorthEast().getLatDeg(), southCenter.getLngDeg());
children[NE] = new Node<T>(new BoundsImpl(middleCenter, bounds.getNorthEast()), maxItems);
children[NW] = new Node<T>(new BoundsImpl(middleWest, northCenter), maxItems);
children[SW] = new Node<T>(new BoundsImpl(bounds.getSouthWest(), middleCenter), maxItems);
children[SE] = new Node<T>(new BoundsImpl(southCenter, middleEast), maxItems);
assert children != null;
assert children.length == 4;
assert children[NE] != null && children[NW] != null && children[SW] != null && children[SE] != null;
}
/**
* Remove all elements from this node by either removing all its items locally or by removing all children
* and converting this back into a leaf node.
*/
public void clear() {
if (items != null) {
items.clear();
} else {
items = new HashMap<>();
children = null;
}
}
public static class GetResult<T> {
private final Position position;
private final T value;
private final double distance;
public GetResult(Position position, T value, double distance) {
super();
this.position = position;
this.value = value;
this.distance = distance;
}
public Position getPosition() {
return position;
}
public T getValue() {
return value;
}
public double getDistance() {
return distance;
}
@Override
public String toString() {
return "GetResult [position=" + position + ", value=" + value + ", distance=" + distance + "]";
}
}
/**
* Get the value nearest to <code>point</code>. If the node is empty, <code>null</code> is returned. Distance is
* calculated using the method {@link QuadTree#getLatLngDistance(Position, Position)} which is an approximation
* only, based on Euklidian geometry with the latitude/longitude values.
* <p>
*
* If this is a leaf node, the nearest key by the definition above is used to determine the corresponding value and
* return it. Otherwise, the children are traversed. For the first child, the nearest key is determined recursively.
* Other children only need to be traversed if their bounds are closer to <code>point</code> than the key found so
* far.
*
* @param withinDistance
* a key is only considered if it has less than this {@link QuadTree#getLatLngDistance(Position, Position)
* distance} to <code>point</code>
*/
public GetResult<T> get(Position point, double withinDistance) {
assert (items == null) != (children == null);
visitedNodes++;
GetResult<T> result = null;
double minDistance = withinDistance;
if (items != null) {
for (final Entry<Position, T> item : items.entrySet()) {
double itemDistanceToPoint = QuadTree.getLatLngDistance(point, item.getKey());
visitedItems++;
if (itemDistanceToPoint < minDistance) {
result = new GetResult<T>(item.getKey(), item.getValue(), itemDistanceToPoint);
minDistance = itemDistanceToPoint;
}
}
} else {
// Traverse children in order of ascending distance, making it more likely to find good, near
// candidates early, hence enabling skipping more children, investigating fewer items. However,
// doing the full java.util.Collections / Arrays sort thing with Comparators and such is way too
// expensive as measurements have shown, and the overhead for just the four elements is too high.
// It is much more efficient to traverse the small children array multiple times and in each pass
// remembering the next minimal child distance so that during the next pass those children can be
// inspected. This process repeats until all children have been handled, taking at most four passes.
final double[] childDistanceToPoint = new double[4];
double minChildDistanceToPoint = Double.MAX_VALUE; // minimal distance of those children not yet done
for (int i=0; i<4; i++) {
childDistanceToPoint[i] = children[i].getDistance(point);
if (childDistanceToPoint[i] < minChildDistanceToPoint) {
minChildDistanceToPoint = childDistanceToPoint[i];
}
}
final boolean done[] = new boolean[4]; // have we handled that child already (skipped or searched)?
int doneCount = 0;
while (doneCount < 4) {
double nextMinChildDistanceToPoint = Double.MAX_VALUE;
for (int i=0; doneCount<4 && i<4; i++) {
if (!done[i]) {
if (childDistanceToPoint[i] == minChildDistanceToPoint) {
final Node<T> child = children[i];
// If we have a possible result already, only investigate the child node if it is nearer to point
// than the result candidate and not an empty leaf node (which can be checked more quickly than
// performing the rather expensive distance calculation)
if (!child.isEmptyLeaf() && childDistanceToPoint[i] < minDistance) {
assert minDistance <= withinDistance;
final GetResult<T> childResult = child.get(point, minDistance);
if (childResult != null) {
if (childResult.getDistance() < minDistance) {
result = childResult;
minDistance = childResult.getDistance();
}
}
} else {
skippedNodes++;
}
done[i] = true;
doneCount++;
} else {
if (childDistanceToPoint[i] < nextMinChildDistanceToPoint) {
nextMinChildDistanceToPoint = childDistanceToPoint[i]; // the distance to handle in the next pass
}
}
}
}
minChildDistanceToPoint = nextMinChildDistanceToPoint;
}
}
return result;
}
/**
* Get the value nearest to <code>point</code>. If the node is empty, <code>null</code> is returned. Distance is
* calculated using the method {@link QuadTree#getLatLngDistance(Position, Position)} which is an approximation
* only, based on Euklidian geometry with the latitude/longitude values.
* <p>
*
* If this is a leaf node, the nearest key by the definition above is used to determine the corresponding value and
* return it. Otherwise, the children are traversed. For the first child, the nearest key is determined recursively.
* Other children only need to be traversed if their bounds are closer to <code>point</code> than the key found so
* far.
*/
public GetResult<T> get(Position point) {
return get(point, Double.MAX_VALUE);
}
/**
* To avoid expensive distance checks, a shortcut can be to check whether this node is an empty
* leaf node. This is the case if {@link #items} is not <code>null</code> (indicating a leaf node)
* but empty.
*/
private boolean isEmptyLeaf() {
return items != null && items.isEmpty();
}
/**
* Computes a "distance" of this node's {@link #bounds} to the <code>point</code> based on the distance definition
* provided by {@link QuadTree#getLatLngDistance(Position, Position)}. If <code>point</code> is
* {@link Bounds#contains(Position) contained} in this node's {@link #bounds}, the distance returned is
* <code>0</code>. Otherwise, the distance to the nearest border of this node's {@link #bounds} is determined.
*/
protected double getDistance(Position point) {
final double result;
if (bounds.contains(point)) {
result = 0;
} else {
// Find out the point on the border defined by bounds that is nearest to point.
final double latDegNearestOnBorder =
point.getLatDeg() > bounds.getNorthEast().getLatDeg() ? bounds.getNorthEast().getLatDeg() :
point.getLatDeg() < bounds.getSouthWest().getLatDeg() ? bounds.getSouthWest().getLatDeg() : point.getLatDeg();
// Now construct a Bounds object of zero latitude span at point's latitude and with the longitude span
// copied from this Node's bounds. If these bounds contain point, use point's longitude as it is spanned by
// this node's bounds. Otherwise, extend the bounds to contain point, then check whether the west or east border
// was changed to contain point; point was closer to the border that was changed to include point
final Bounds boundsAtPointLat = new BoundsImpl(new DegreePosition(point.getLatDeg(), bounds.getSouthWest().getLngDeg()),
new DegreePosition(point.getLatDeg(), bounds.getNorthEast().getLngDeg()));
final double lngDegNearestOnBorder;
if (boundsAtPointLat.contains(point)) {
lngDegNearestOnBorder = point.getLngDeg();
} else {
final Bounds boundsAtPointLatExtendedToIncludePoint = boundsAtPointLat.extend(point);
if (boundsAtPointLatExtendedToIncludePoint.getSouthWest().getLngDeg() == bounds.getSouthWest().getLngDeg()) {
lngDegNearestOnBorder = bounds.getNorthEast().getLngDeg();
} else {
lngDegNearestOnBorder = bounds.getSouthWest().getLngDeg();
}
}
result = QuadTree.getLatLngDistance(point, new DegreePosition(latDegNearestOnBorder, lngDegNearestOnBorder));
}
return result;
}
/**
* Fetches all values within the <code>bounds</code> specified. Always returns a non-<code>null</code> iterable
* which may, however, be empty.
*
* @param bounds
* It is permissible for <code>bounds</code> to not even intersect with this node's {@link #bounds}.
* Then, however, the result will be empty.
*/
public Iterable<T> get(Bounds bounds) {
final Iterable<T> result;
if (this.bounds.intersects(bounds)) {
Set<T> preResult = new HashSet<>();
if (items != null) {
if (bounds.contains(this.bounds)) {
// all items must be contained, so no per-item check is required
preResult.addAll(items.values());
} else {
for (final Entry<Position, T> item : items.entrySet()) {
if (bounds.contains(item.getKey())) {
preResult.add(item.getValue());
}
}
}
} else {
for (final Node<T> child : children) {
Util.addAll(child.get(bounds), preResult);
}
}
result = preResult;
} else {
result = Collections.emptySet();
}
return result;
}
@Override
public String toString() {
return ""+bounds+": "+(items==null?0:items.size())+" items in node, "+
(children==null?0:children.length)+" child nodes";
}
}
@@ -1,55 +0,0 @@
// **********************************************************************
//
// <copyright>
//
// BBN Technologies
// 10 Moulton Street
// Cambridge, MA 02138
// (617) 873-8000
//
// Copyright (C) BBNT Solutions LLC. All rights reserved.
//
// </copyright>
// **********************************************************************
//
// $Source:
// /cvs/distapps/openmap/src/openmap/com/bbn/openmap/util/quadtree/QuadTreeLeaf.java,v
// $
// $RCSfile$
// $Revision: 45 $
// $Date: 2008-06-08 22:04:43 +0200 (Sun, 08 Jun 2008) $
// $Author: uhl $
//
// **********************************************************************
package com.sap.sailing.domain.common.quadtree.impl;
import java.io.Serializable;
import com.sap.sailing.domain.common.Position;
public class QuadTreeLeaf<T> implements Serializable {
static final long serialVersionUID = 7885745536157252519L;
private Position point;
private T object;
public QuadTreeLeaf(Position point, T obj) {
this.point = point;
this.object = obj;
}
public Position getPoint() {
return point;
}
public T getObject() {
return object;
}
public String toString() {
return "QuadTreeLeaf at (" + point.getLatDeg() + ", " + point.getLngDeg() + ") with object " + getObject();
}
}
@@ -1,407 +0,0 @@
// **********************************************************************
//
// <copyright>
//
// BBN Technologies
// 10 Moulton Street
// Cambridge, MA 02138
// (617) 873-8000
//
// Copyright (C) BBNT Solutions LLC. All rights reserved.
//
// </copyright>
// **********************************************************************
//
// $Source$
// $RCSfile$
// $Revision: 101 $
// $Date: 2008-07-09 23:09:42 +0200 (Wed, 09 Jul 2008) $
// $Author: axel.uhl $
//
// **********************************************************************
package com.sap.sailing.domain.common.quadtree.impl;
import java.io.Serializable;
import java.util.Collection;
import java.util.Iterator;
import java.util.Vector;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.impl.DegreePosition;
import com.sap.sailing.domain.common.quadtree.QuadTree;
/**
* The QuadTreeNode is the part of the QuadTree that either holds
* children nodes, or objects as leaves. Currently, the nodes that
* have children do not hold items that span across children
* boundaries, since this was designed to handle point data.
*/
public class QuadTreeNode<T> implements Serializable {
static final long serialVersionUID = -6111633198469889444L;
private final static int NORTHWEST = 0;
private final static int NORTHEAST = 1;
private final static int SOUTHEAST = 2;
private final static int SOUTHWEST = 3;
public final static double NO_MIN_SIZE = -1;
private Vector<QuadTreeLeaf<T>> items;
private QuadTreeNode<T>[] children;
private int maxItems;
private double minSize;
private Bounds bounds;
/**
* Added to avoid problems when a node is completely filled with a
* single point value.
*/
private boolean allTheSamePoint;
private Position firstPoint;
/**
* Constructor to use if you are going to store the objects in
* lat/lon space, and there is really no smallest node size.
*
* @param maximumItems number of items to hold in a node before
* splitting itself into four children and redispensing the
* items into them.
*/
public QuadTreeNode(Bounds rect, int maximumItems) {
this(rect, maximumItems, NO_MIN_SIZE);
}
/**
* Constructor to use if you are going to store the objects in x/y
* space, and there is a smallest node size because you don't want
* the nodes to be smaller than a group of pixels.
*
* @param north northern border of node coverage.
* @param west western border of node coverage.
* @param south southern border of node coverage.
* @param east eastern border of node coverage.
* @param maximumItems number of items to hold in a node before
* splitting itself into four children and redispensing the
* items into them.
* @param minimumSize the minimum difference between the
* boundaries of the node.
*/
public QuadTreeNode(Bounds rect, int maximumItems, double minimumSize) {
bounds = rect;
maxItems = maximumItems;
minSize = minimumSize;
items = new Vector<QuadTreeLeaf<T>>();
}
/** Return true if the node has children. */
public boolean hasChildren() {
if (children != null)
return true;
else
return false;
}
/**
* This method splits the node into four children, and disperses
* the items into the children. The split only happens if the
* boundary size of the node is larger than the minimum size (if
* we care). The items in this node are cleared after they are put
* into the children.
*/
@SuppressWarnings("unchecked")
protected void split() {
// Make sure we're bigger than the minimum, if we care,
if (minSize != NO_MIN_SIZE) {
if (Math.abs(bounds.getNorthEast().getLatDeg() - bounds.getSouthWest().getLatDeg()) < minSize
&& Math.abs(bounds.getNorthEast().getLngDeg() - bounds.getSouthWest().getLngDeg()) < minSize)
return;
}
double nsHalf = (bounds.getNorthEast().getLatDeg() + bounds.getSouthWest().getLatDeg()) / 2.0;
double ewHalf = (bounds.getNorthEast().getLngDeg() + bounds.getSouthWest().getLngDeg()) / 2.0;
children = new QuadTreeNode[4];
children[NORTHWEST] = new QuadTreeNode<T>(new Bounds(new DegreePosition(nsHalf, bounds.getSouthWest().getLngDeg()), new DegreePosition(bounds.getNorthEast().getLatDeg(), ewHalf)), maxItems);
children[NORTHEAST] = new QuadTreeNode<T>(new Bounds(new DegreePosition(nsHalf, ewHalf), bounds.getNorthEast()), maxItems);
children[SOUTHEAST] = new QuadTreeNode<T>(new Bounds(new DegreePosition(bounds.getSouthWest().getLatDeg(), ewHalf), new DegreePosition(nsHalf, bounds.getNorthEast().getLngDeg())), maxItems);
children[SOUTHWEST] = new QuadTreeNode<T>(new Bounds(bounds.getSouthWest(), new DegreePosition(nsHalf, ewHalf)), maxItems);
Vector<QuadTreeLeaf<T>> temp = new Vector<QuadTreeLeaf<T>>(items);
items.removeAllElements();
for (Iterator<QuadTreeLeaf<T>> i=temp.iterator(); i.hasNext(); ) {
put(i.next());
}
}
/**
* Get the node that covers a certain lat/lon pair.
*
* @param lat up-down location in QuadTree Grid (latitude, y)
* @param lon left-right location in QuadTree Grid (longitude, x)
* @return node if child covers the point, null if the point is
* out of range.
*/
protected QuadTreeNode<T> getChild(Position point) {
if (bounds.contains(point)) {
if (children != null) {
for (int i = 0; i < children.length; i++) {
if (children[i].bounds.contains(point))
return children[i].getChild(point);
}
} else
return this; // no children, lat, lon here...
}
return null;
}
/**
* Add a object into the tree at a location.
*
* @param lat up-down location in QuadTree Grid (latitude, y)
* @param lon left-right location in QuadTree Grid (longitude, x)
* @param obj object to add to the tree.
* @return true if the pution worked.
* @throws RuntimeException in case the leaf's lat/lng lies outside of the node's bounds.
* This would typically be caused by the point being outside the whole quad tree's bounds.
*/
public void put(Position point, T obj) {
put(new QuadTreeLeaf<T>(point, obj));
}
public void replace(Position point, T newObj) {
boolean inThis = false;
if (children == null) {
inThis = true;
} else {
QuadTreeNode<T> child = getChild(point);
if (child == null) {
inThis = true;
} else {
child.replace(point, newObj);
}
}
if (inThis) {
items.clear();
items.add(new QuadTreeLeaf<T>(point, newObj));
}
}
/**
* Add a QuadTreeLeaf into the tree at a location.
*
* @param leaf object-location composite
* @return true if the pution worked.
* @throws RuntimeException in case the leaf's lat/lng lies outside of the node's bounds.
* This would typically be caused by the point being outside the whole quad tree's bounds.
*/
public void put(QuadTreeLeaf<T> leaf) {
if (children == null) {
this.items.addElement(leaf);
if (this.items.size() == 1) {
this.allTheSamePoint = true;
this.firstPoint = leaf.getPoint();
} else {
if (!this.firstPoint.equals(leaf.getPoint())) {
this.allTheSamePoint = false;
}
}
if (this.items.size() > maxItems && !this.allTheSamePoint) {
split();
}
} else {
QuadTreeNode<T> node = getChild(leaf.getPoint());
if (node != null) {
node.put(leaf);
} else {
throw new RuntimeException("leaf "+leaf+" not contained in bounds (("+
bounds.getSouthWest().getLatDeg()+", "+bounds.getSouthWest().getLngDeg()+"), ("+
bounds.getNorthEast().getLatDeg()+", "+bounds.getNorthEast().getLngDeg()+"))");
}
}
}
/**
* Remove a object out of the tree at a location.
*
* @param lat up-down location in QuadTree Grid (latitude, y)
* @param lon left-right location in QuadTree Grid (longitude, x)
* @return the object removed, null if the object not found.
*/
public T remove(Position point, T obj) {
return remove(new QuadTreeLeaf<T>(point, obj));
}
/**
* Remove a QuadTreeLeaf out of the tree at a location.
*
* @param leaf object-location composite
* @return the object removed, null if the object not found.
*/
public T remove(QuadTreeLeaf<T> leaf) {
if (children == null) {
// This must be the node that has it...
for (int i = 0; i < items.size(); i++) {
QuadTreeLeaf<T> qtl = items.elementAt(i);
if (leaf.getObject() == qtl.getObject()) {
items.removeElementAt(i);
return qtl.getObject();
}
}
} else {
QuadTreeNode<T> node = getChild(leaf.getPoint());
if (node != null) {
return node.remove(leaf);
}
}
return null;
}
/** Clear the tree below this node. */
public void clear() {
this.items.removeAllElements();
if (children != null) {
for (int i = 0; i < children.length; i++) {
children[i].clear();
}
children = null;
}
}
/**
* Get an object closest to a <tt>point</tt>.
*
* @param lat up-down location in QuadTree Grid (latitude, y)
* @param lon left-right location in QuadTree Grid (longitude, x)
* @return the object that matches the best distance, null if no
* object was found.
*/
public T get(Position point) {
return get(point, Double.POSITIVE_INFINITY);
}
/**
* Get an object closest to a <tt>point</tt>. If there are children at
* this node, then the children are searched. The children are
* checked first, to see if they are closer than the best distance
* already found. If a closer object is found, bestDistance will
* be updated with a new Double object that has the new distance.
*
* @param lat up-down location in QuadTree Grid (latitude, y)
* @param lon left-right location in QuadTree Grid (longitude, x)
* @param withinDistance maximum get distance. The distance is given
* as the square root of the sum of
* the squares of the latitude and longitude differences, respectively.
* It therefore does not correspond to any distance in meters or
* any euclidian distance at all. However, it should be good enough
* (at least outside the polar regions, and in particular for smaller
* regions), and in particular to find <em>minimum</em> distances.
* @return the object that matches the best distance, null if no
* closer object was found.
*/
public T get(Position point, double withinDistance) {
return get(point, new MutableDistance(withinDistance));
}
/**
* Get an object closest to a <tt>point</tt>. If there are children at
* this node, then the children are searched. The children are
* checked first, to see if they are closer than the best distance
* already found. If a closer object is found, bestDistance will
* be updated with a new Double object that has the new distance.
* @param point location in QuadTree Grid
* @param bestDistance the closest distance of the object found so
* far. The distance is given as the square root of the sum of
* the squares of the latitude and longitude differences, respectively.
* It therefore does not correspond to any distance in meters or
* any euclidian distance at all. However, it should be good enough
* (at least outside the polar regions, and in particular for smaller
* regions), and in particular to find <em>minimum</em> distances.
*
* @return the object that matches the best distance, null if no
* closer object was found.
*/
public T get(Position point, MutableDistance bestDistance) {
T closest = null;
if (children == null) {
// This must be the node that has it...
for (QuadTreeLeaf<T> qtl:items) {
double distance = QuadTree.getLatLngDistance(point, qtl.getPoint());
if (distance < bestDistance.value) {
bestDistance.value = distance;
closest = qtl.getObject();
}
}
return closest;
} else {
// Check the distance of the bounds of the children,
// versus the bestDistance. If there is a boundary that
// is closer, then it is possible that another node has an
// object that is closer.
for (int i = 0; i < children.length; i++) {
double childDistance = borderDistance(children[i].bounds, point);
if (childDistance < bestDistance.value) {
T test = children[i].get(point, bestDistance);
if (test != null)
closest = test;
}
}
}
return closest;
}
/**
* A utility method to figure out the closest distance of a bound's border
* to a point. If the point is inside the bounds, return 0.
*
* @return closest distance to the point.
*/
private static double borderDistance(Bounds bounds, Position point) {
double nsdistance;
double ewdistance;
if (bounds.getSouthWest().getLatDeg() <= point.getLatDeg() && point.getLatDeg() <= bounds.getNorthEast().getLatDeg()) {
nsdistance = 0;
} else {
nsdistance = Math.min((Math.abs(point.getLatDeg() - bounds.getNorthEast().getLatDeg())), (Math.abs(point.getLatDeg()
- bounds.getSouthWest().getLatDeg())));
}
if (bounds.getSouthWest().getLngDeg() <= point.getLngDeg() && point.getLngDeg() <= bounds.getNorthEast().getLngDeg()) {
ewdistance = 0;
} else {
ewdistance = Math.min((Math.abs(point.getLngDeg() - bounds.getNorthEast().getLngDeg())),
(Math.abs(point.getLngDeg() - bounds.getSouthWest().getLngDeg())));
}
double distance = Math.sqrt(nsdistance*nsdistance + ewdistance*ewdistance);
return distance;
}
/**
* Get all the objects within a bounding box.
*
* @param rect boundary of area to fill.
* @param vector current vector of objects.
* @return updated Vector of objects.
*/
public Collection<T> get(Bounds rect, Collection<T> vector) {
if (children == null) {
for (Iterator<QuadTreeLeaf<T>> i=items.iterator(); i.hasNext(); ) {
QuadTreeLeaf<T> qtl = i.next();
if (rect.contains(new DegreePosition(qtl.getPoint().getLatDeg(), qtl.getPoint().getLngDeg()))) {
vector.add(qtl.getObject());
}
}
} else {
for (int i = 0; i < children.length; i++) {
if (children[i].bounds.containsBounds(rect)) {
children[i].get(rect, vector);
}
}
}
return vector;
}
}
@@ -20,6 +20,14 @@ import com.sap.sailing.domain.common.tracking.impl.CompactGPSFixMovingImpl;
import com.sap.sse.common.impl.MillisecondsTimePoint;
public class PositionTest {
@Test
public void testDistanceAcrossDateline() {
Position p1 = new DegreePosition(0, 179);
Position p2 = new DegreePosition(0, -179);
assertEquals(120., p1.getDistance(p2).getNauticalMiles(), 0.1);
assertEquals(120., p2.getDistance(p1).getNauticalMiles(), 0.1);
}
@Test
public void testEqualityBetweenCompactAndVerbosePosition() {
Position p1 = new DegreePosition(49.2, 008.3);
File diff suppressed because it is too large. Load diff
@@ -1,5 +1,13 @@
package com.sap.sailing.domain.test.measurements;
/**
* Create an instance of this class and {@link MeasurementCase#addMeasurement(Measurement) add it} to a
* measurement case. The final reporting is then done by invoking {@link MeasurementXMLFile#write()} on
* the measurement file created before.
*
* @author Axel Uhl (D043530)
*
*/
public class Measurement {
private final String name;
private final double value;
@@ -10,11 +18,11 @@ public class Measurement {
this.value = value;
}
public String getName() {
String getName() {
return name;
}
public double getValue() {
double getValue() {
return value;
}
@@ -3,17 +3,24 @@ package com.sap.sailing.domain.test.measurements;
import java.util.HashSet;
import java.util.Set;
/**
* Obtain an instance of this class by invoking {@link MeasurementXMLFile#addCase(String)}, then add
* measurements using the {@link #addMeasurement(Measurement)} method.
*
* @author Axel Uhl (D043530)
*
*/
public class MeasurementCase {
private final String name;
private final Set<Measurement> measurements;
public MeasurementCase(String name) {
MeasurementCase(String name) {
super();
this.name = name;
measurements = new HashSet<>();
}
public String getName() {
String getName() {
return name;
}
@@ -21,7 +28,7 @@ public class MeasurementCase {
measurements.add(measurement);
}
public Iterable<Measurement> getMeasurements() {
Iterable<Measurement> getMeasurements() {
return measurements;
}
}
@@ -15,7 +15,15 @@ import com.sap.sse.common.Util;
* &lt;measurement&gt; tags embedded which list named values for use by the Hudson/Jenkins Measurements Plot plug-in.
* The output is written to the <code>bin/surefire-reports/</code> directory by the {@link #write()} method, but only if
* a <code>bin/</code> directory exists as subdirectory of the current working directory. The <code>surefire-reports</code>
* subdirectory will be created if it doesn't exist.
* subdirectory will be created if it doesn't exist.<p>
*
* Use this class as in the following example:
* <pre>
* MeasurementXMLFile performanceReport = new MeasurementXMLFile(this.getClass());
* MeasurementCase performanceReportCase = performanceReport.addCase(getClass().getSimpleName());
* performanceReportCase.addMeasurement(new Measurement("This is a measurement name", 12345); // 12345 is the value to be recorded
* performanceReport.write();
* </pre>
*
* @author Axel Uhl (D043530)
*
@@ -81,6 +81,11 @@ public class MockedTrackedRaceWithFixedRank extends MockedTrackedRace {
public Boat getBoatOfCompetitorById(Serializable competitorID) {
return null;
}
@Override
public byte[] getCompetitorMD5() {
return null;
}
}
@Override
@@ -55,6 +55,10 @@ public class MockedTrackedRaceWithFixedRankAndManyCompetitors extends MockedTrac
public Boat getBoatOfCompetitorById(Serializable competitorID) {
return null;
}
@Override
public byte[] getCompetitorMD5() {
return null;
}
};
}
@@ -409,7 +409,7 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl implements
addListenersForStoredDataAndStartController(receivers);
}
private URI checkForCachedStoredData(URI storedURI){
private URI checkForCachedStoredData(URI storedURI) {
final String CACHE_DIR_PROPERTY = "tractrac.mtb.cache.dir";
if (System.getProperty(CACHE_DIR_PROPERTY) != null) {
final String directory = System.getProperty(CACHE_DIR_PROPERTY);
@@ -2,6 +2,7 @@ package com.sap.sailing.domain.base;
import java.io.Serializable;
import com.sap.sailing.domain.common.RaceCompetitorIdsAsStringWithMD5Hash;
import com.sap.sse.common.Named;
import com.sap.sse.common.WithID;
@@ -31,4 +32,11 @@ public interface RaceDefinition extends Named, WithID {
*/
Boat getBoatOfCompetitorById(Serializable competitorID);
/**
* The MD5 hash as produced by
* {@link RaceCompetitorIdsAsStringWithMD5Hash#getMd5OfIdsAsStringOfCompetitorParticipatingInRaceInAlphanumericOrderOfTheirID()}
* for this race's competitor set.
*/
byte[] getCompetitorMD5();
}
@@ -257,47 +257,26 @@ public class DomainFactoryImpl extends SharedDomainFactoryImpl implements Domain
double radiusCalculationFactor = 10.0;
Placemark startBest = null;
Placemark finishBest = null;
Position startPosition = null;
// Get start postition
Iterator<Mark> startMarks = race.getRace().getCourse().getFirstWaypoint().getMarks().iterator();
GPSFix startMarkFix = startMarks.hasNext() ? race.getOrCreateTrack(startMarks.next()).getLastRawFix() : null;
Position startPosition = startMarkFix != null ? startMarkFix.getPosition() : null;
if (startPosition != null) {
try {
// Get distance to nearest placemark and calculate the search radius
Placemark startNearest = ReverseGeocoder.INSTANCE.getPlacemarkNearest(startPosition);
if (startNearest != null) {
Distance startNearestDistance = startNearest.distanceFrom(startPosition);
double startRadius = startNearestDistance.getKilometers() * radiusCalculationFactor;
// Get the estimated best start place
startBest = ReverseGeocoder.INSTANCE.getPlacemarkLast(startPosition, startRadius,
new Placemark.ByPopulationDistanceRatio(startPosition));
}
} catch (IOException e) {
logger.throwing(TrackedRaceImpl.class.getName(), "getPlaceOrder()", e);
} catch (org.json.simple.parser.ParseException e) {
logger.throwing(TrackedRaceImpl.class.getName(), "getPlaceOrder()", e);
}
}
// Get finish position
Iterator<Mark> finishMarks = race.getRace().getCourse().getFirstWaypoint().getMarks().iterator();
GPSFix finishMarkFix = finishMarks.hasNext() ? race.getOrCreateTrack(finishMarks.next()).getLastRawFix() : null;
Position finishPosition = finishMarkFix != null ? finishMarkFix.getPosition() : null;
if (startPosition != null && finishPosition != null) {
if (startPosition.getDistance(finishPosition).getKilometers() <= ReverseGeocoder.POSITION_CACHE_DISTANCE_LIMIT_IN_KM) {
finishBest = startBest;
} else {
final Waypoint firstWaypoint = race.getRace().getCourse().getFirstWaypoint();
if (firstWaypoint != null) {
Iterator<Mark> startMarks = firstWaypoint.getMarks().iterator();
GPSFix startMarkFix = startMarks.hasNext() ? race.getOrCreateTrack(startMarks.next()).getLastRawFix() : null;
startPosition = startMarkFix != null ? startMarkFix.getPosition() : null;
if (startPosition != null) {
try {
// Get distance to nearest placemark and calculate the search radius
Placemark finishNearest = ReverseGeocoder.INSTANCE.getPlacemarkNearest(finishPosition);
Distance finishNearestDistance = finishNearest.distanceFrom(finishPosition);
double finishRadius = finishNearestDistance.getKilometers() * radiusCalculationFactor;
// Get the estimated best finish place
finishBest = ReverseGeocoder.INSTANCE.getPlacemarkLast(finishPosition, finishRadius,
new Placemark.ByPopulationDistanceRatio(finishPosition));
Placemark startNearest = ReverseGeocoder.INSTANCE.getPlacemarkNearest(startPosition);
if (startNearest != null) {
Distance startNearestDistance = startNearest.distanceFrom(startPosition);
double startRadius = startNearestDistance.getKilometers() * radiusCalculationFactor;
// Get the estimated best start place
startBest = ReverseGeocoder.INSTANCE.getPlacemarkLast(startPosition, startRadius,
new Placemark.ByPopulationDistanceRatio(startPosition));
}
} catch (IOException e) {
logger.throwing(TrackedRaceImpl.class.getName(), "getPlaceOrder()", e);
} catch (org.json.simple.parser.ParseException e) {
@@ -305,6 +284,34 @@ public class DomainFactoryImpl extends SharedDomainFactoryImpl implements Domain
}
}
}
// Get finish position
final Waypoint lastWaypoint = race.getRace().getCourse().getLastWaypoint();
if (lastWaypoint != null) {
Iterator<Mark> finishMarks = firstWaypoint.getMarks().iterator();
GPSFix finishMarkFix = finishMarks.hasNext() ? race.getOrCreateTrack(finishMarks.next()).getLastRawFix() : null;
Position finishPosition = finishMarkFix != null ? finishMarkFix.getPosition() : null;
if (startPosition != null && finishPosition != null) {
if (startPosition.getDistance(finishPosition).getKilometers() <= ReverseGeocoder.POSITION_CACHE_DISTANCE_LIMIT_IN_KM) {
finishBest = startBest;
} else {
try {
// Get distance to nearest placemark and calculate the search radius
Placemark finishNearest = ReverseGeocoder.INSTANCE.getPlacemarkNearest(finishPosition);
Distance finishNearestDistance = finishNearest.distanceFrom(finishPosition);
double finishRadius = finishNearestDistance.getKilometers() * radiusCalculationFactor;
// Get the estimated best finish place
finishBest = ReverseGeocoder.INSTANCE.getPlacemarkLast(finishPosition, finishRadius,
new Placemark.ByPopulationDistanceRatio(finishPosition));
} catch (IOException e) {
logger.throwing(TrackedRaceImpl.class.getName(), "getPlaceOrder()", e);
} catch (org.json.simple.parser.ParseException e) {
logger.throwing(TrackedRaceImpl.class.getName(), "getPlaceOrder()", e);
}
}
}
}
Util.Pair<Placemark, Placemark> placemarks = new Util.Pair<Placemark, Placemark>(startBest, finishBest);
return placemarks;
}
@@ -1,6 +1,8 @@
package com.sap.sailing.domain.base.impl;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.security.NoSuchAlgorithmException;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -15,6 +17,7 @@ import com.sap.sailing.domain.base.BoatClass;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.Course;
import com.sap.sailing.domain.base.RaceDefinition;
import com.sap.sailing.domain.common.RaceCompetitorIdsAsStringWithMD5Hash;
import com.sap.sse.common.impl.NamedImpl;
public class RaceDefinitionImpl extends NamedImpl implements RaceDefinition {
@@ -27,6 +30,7 @@ public class RaceDefinitionImpl extends NamedImpl implements RaceDefinition {
private final Map<Serializable, Boat> competitorBoats;
private final BoatClass boatClass;
private final Serializable id;
private final RaceCompetitorIdsAsStringWithMD5Hash raceCompetitorsMD5Hash;
public RaceDefinitionImpl(String name, Course course, BoatClass boatClass, Iterable<? extends Competitor> competitors) {
this(name, course, boatClass, competitors, Collections.emptyMap());
@@ -49,9 +53,11 @@ public class RaceDefinitionImpl extends NamedImpl implements RaceDefinition {
this.competitors = new HashSet<>();
this.competitorsById = new LinkedHashMap<>();
this.competitorBoats = new HashMap<>();
final Set<String> idsOfCompetitorsAsString = new HashSet<>();
for (Competitor competitor : competitors) {
Competitor competitorWithEqualID = competitorsById.put(competitor.getId(), competitor);
this.competitors.add(competitor);
idsOfCompetitorsAsString.add(competitor.getId().toString());
if (competitorWithEqualID != null && competitorWithEqualID != competitor) {
throw new IllegalArgumentException("Two distinct competitors with equal ID "+competitor.getId()+" are not allowed within the single race "+name);
}
@@ -65,6 +71,11 @@ public class RaceDefinitionImpl extends NamedImpl implements RaceDefinition {
" which is not part of race "+getName()+"'s set of competitors");
}
}
try {
raceCompetitorsMD5Hash = new RaceCompetitorIdsAsStringWithMD5Hash(idsOfCompetitorsAsString);
} catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
throw new RuntimeException("Internal error: issue with UTF8 or MD5 for encoding competitor IDs as MD5 hash", e);
}
this.boatClass = boatClass;
this.id = id;
}
@@ -78,11 +89,16 @@ public class RaceDefinitionImpl extends NamedImpl implements RaceDefinition {
public Course getCourse() {
return course;
}
@Override
public Iterable<Competitor> getCompetitors() {
return Collections.unmodifiableSet(competitors);
}
@Override
public byte[] getCompetitorMD5() {
return raceCompetitorsMD5Hash.getMd5OfIdsAsStringOfCompetitorParticipatingInRaceInAlphanumericOrderOfTheirID();
}
@Override
public Competitor getCompetitorById(Serializable competitorID) {
@@ -217,7 +217,7 @@ public class ReverseGeocoderImpl implements ReverseGeocoder {
private void updateCachedPlacemarks(Position cachedPoint, Double newRadius, List<Placemark> newPlacemarks) {
if (cachedPoint != null) {
synchronized (cache) {
cache.replace(cachedPoint, new Util.Triple<Position, Double, List<Placemark>>(cachedPoint, newRadius, newPlacemarks));
cache.put(cachedPoint, new Util.Triple<Position, Double, List<Placemark>>(cachedPoint, newRadius, newPlacemarks));
}
}
}
@@ -3,11 +3,21 @@ encoding//src/main/java/com/sap/sailing/gwt/common/client/i18n/TextMessages.prop
encoding//src/main/java/com/sap/sailing/gwt/common/client/i18n/TextMessages_de.properties=UTF-8
encoding//src/main/java/com/sap/sailing/gwt/common/client/i18n/TextMessages_ru.properties=UTF-8
encoding//src/main/java/com/sap/sailing/gwt/common/client/i18n/TextMessages_zh.properties=UTF-8
encoding//src/main/java/com/sap/sailing/gwt/home/mobile/partials/sectionHeader/SectionHeaderContent.java=utf-8
encoding//src/main/java/com/sap/sailing/gwt/regattaoverview/client/RegattaRaceStatesComponent.java=utf-8
encoding//src/main/java/com/sap/sailing/gwt/server/HomeServiceUtil.java=utf-8
encoding//src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventDetailsComposite.java=utf-8
encoding//src/main/java/com/sap/sailing/gwt/ui/adminconsole/IgtimiAccountsPanel.java=utf-8
encoding//src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardConfigPanel.java=utf-8
encoding//src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties=UTF-8
encoding//src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties=UTF-8
encoding//src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties=UTF-8
encoding//src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh.properties=UTF-8
encoding//src/main/java/com/sap/sailing/gwt/ui/client/media/MediaPlayerManagerComponent.java=utf-8
encoding//src/main/java/com/sap/sailing/gwt/ui/datamining/presentation/AbstractResultsPresenter.java=utf-8
encoding//src/main/java/com/sap/sailing/gwt/ui/datamining/presentation/PlainResultsPresenter.java=utf-8
encoding//src/main/java/com/sap/sailing/gwt/ui/leaderboard/CompetitorFilterPanel.java=utf-8
encoding//src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardEntryPoint.java=utf-8
encoding//src/main/java/com/sap/sailing/gwt/ui/leaderboard/MultiLeaderboardPanel.java=utf-8
encoding//src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardPanel.java=utf-8
encoding//src/main/java/com/sap/sailing/gwt/ui/spectator/LeaderboardGroupPanel.java=utf-8
@@ -16,14 +16,17 @@ public class GetRaceMapDataAction extends AbstractGetMapRelatedDataAction<RaceMa
private final Map<String, CompetitorDTO> competitorsByIdAsString;
private final Date date;
private final LegIdentifier simulationLegIdentifier;
private final byte[] md5OfIdsAsStringOfCompetitorParticipatingInRaceInAlphanumericOrderOfTheirID;
public GetRaceMapDataAction(SailingServiceAsync sailingService, Map<String, CompetitorDTO> competitorsByIdAsString,
RegattaAndRaceIdentifier raceIdentifier, Date date, Map<CompetitorDTO, Date> from,
Map<CompetitorDTO, Date> to, boolean extrapolate, LegIdentifier simulationLegIdentifier) {
Map<CompetitorDTO, Date> to, boolean extrapolate, LegIdentifier simulationLegIdentifier,
byte[] md5OfIdsAsStringOfCompetitorParticipatingInRaceInAlphanumericOrderOfTheirID) {
super(sailingService, raceIdentifier, from, to, extrapolate);
this.competitorsByIdAsString = competitorsByIdAsString;
this.date = date;
this.simulationLegIdentifier = simulationLegIdentifier;
this.md5OfIdsAsStringOfCompetitorParticipatingInRaceInAlphanumericOrderOfTheirID = md5OfIdsAsStringOfCompetitorParticipatingInRaceInAlphanumericOrderOfTheirID;
}
@Override
@@ -37,7 +40,8 @@ public class GetRaceMapDataAction extends AbstractGetMapRelatedDataAction<RaceMa
toByCompetitorIdAsString.put(toEntry.getKey().getIdAsString(), toEntry.getValue());
}
getSailingService().getRaceMapData(getRaceIdentifier(), date, fromByCompetitorIdAsString, toByCompetitorIdAsString,
isExtrapolate(), simulationLegIdentifier, new AsyncCallback<CompactRaceMapDataDTO>() {
isExtrapolate(), simulationLegIdentifier, md5OfIdsAsStringOfCompetitorParticipatingInRaceInAlphanumericOrderOfTheirID,
new AsyncCallback<CompactRaceMapDataDTO>() {
@Override
public void onFailure(Throwable caught) {
callback.onFailure(caught);
@@ -5,6 +5,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.google.gwt.safehtml.shared.UriUtils;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.CaptionPanel;
import com.google.gwt.user.client.ui.Composite;
@@ -139,7 +140,7 @@ public class EventDetailsComposite extends Composite {
regattaOverviewURLParameters.put("event", event.id.toString());
String regattaOverviewLink = EntryPointLinkFactory.createRegattaOverviewLink(regattaOverviewURLParameters);
eventOverviewURL.setText(regattaOverviewLink);
eventOverviewURL.setHref(regattaOverviewLink);
eventOverviewURL.setHref(UriUtils.fromString(regattaOverviewLink));
List<String> courseAreaNames = new ArrayList<>();
if (event.venue.getCourseAreas() != null && event.venue.getCourseAreas().size() > 0) {
@@ -9,6 +9,7 @@ import com.google.gwt.cell.client.FieldUpdater;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.safehtml.shared.UriUtils;
import com.google.gwt.text.shared.SafeHtmlRenderer;
import com.google.gwt.user.cellview.client.CellTable;
import com.google.gwt.user.cellview.client.Column;
@@ -160,7 +161,7 @@ public class IgtimiAccountsPanel extends FlowPanel {
@Override
public void onSuccess(String result) {
Anchor addIgtimiUserLink = new Anchor(stringMessages.addIgtimiUser(), result);
Anchor addIgtimiUserLink = new Anchor(stringMessages.addIgtimiUser(), UriUtils.fromString(result).asString());
controlsPanel.add(addIgtimiUserLink);
}
});
@@ -109,24 +109,30 @@ public abstract class ImageDialog extends DataEntryDialog<ImageDTO> {
imageURLAndUploadComposite.addValueChangeHandler(new ValueChangeHandler<String>() {
@Override
public void onValueChange(ValueChangeEvent<String> event) {
busyIndicator.setBusy(true);
String imageUrlAsString = event.getValue();
ImageDialog.this.sailingService.resolveImageDimensions(imageUrlAsString, new AsyncCallback<Util.Pair<Integer,Integer>>() {
@Override
public void onSuccess(Pair<Integer, Integer> imageSize) {
busyIndicator.setBusy(false);
if (imageSize != null) {
widthInPxBox.setValue(imageSize.getA());
heightInPxBox.setValue(imageSize.getB());
}
validate();
}
@Override
public void onFailure(Throwable caught) {
busyIndicator.setBusy(false);
}
});
if (imageUrlAsString == null || imageUrlAsString.isEmpty()) {
widthInPxBox.setText("");
heightInPxBox.setText("");
} else {
busyIndicator.setBusy(true);
ImageDialog.this.sailingService.resolveImageDimensions(imageUrlAsString,
new AsyncCallback<Util.Pair<Integer, Integer>>() {
@Override
public void onSuccess(Pair<Integer, Integer> imageSize) {
busyIndicator.setBusy(false);
if (imageSize != null) {
widthInPxBox.setValue(imageSize.getA());
heightInPxBox.setValue(imageSize.getB());
}
validate();
}
@Override
public void onFailure(Throwable caught) {
busyIndicator.setBusy(false);
}
});
}
validate();
}
});
@@ -18,6 +18,7 @@ import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.safehtml.client.SafeHtmlTemplates;
import com.google.gwt.safehtml.shared.SafeHtml;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.safehtml.shared.UriUtils;
import com.google.gwt.user.cellview.client.CellTable;
import com.google.gwt.user.cellview.client.Column;
import com.google.gwt.user.cellview.client.ColumnSortEvent.ListHandler;
@@ -46,6 +47,7 @@ import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.client.shared.controls.FlushableCellTable;
import com.sap.sailing.gwt.ui.client.shared.controls.SelectionCheckboxColumn;
import com.sap.sailing.gwt.ui.leaderboard.LeaderboardEntryPoint;
import com.sap.sailing.gwt.ui.leaderboard.LeaderboardUrlSettings;
import com.sap.sailing.gwt.ui.leaderboard.ScoringSchemeTypeFormatter;
import com.sap.sailing.gwt.ui.shared.EventDTO;
import com.sap.sailing.gwt.ui.shared.RaceLogSetStartTimeAndProcedureDTO;
@@ -53,7 +55,6 @@ import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO;
import com.sap.sse.common.Util;
import com.sap.sse.common.util.NaturalComparator;
import com.sap.sse.gwt.client.ErrorReporter;
import com.sap.sse.gwt.client.URLEncoder;
import com.sap.sse.gwt.client.async.MarkedAsyncCallback;
import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback;
@@ -136,11 +137,15 @@ TrackedRaceChangedListener, LeaderboardsDisplayer {
Column<StrippedLeaderboardDTO, SafeHtml> linkColumn = new Column<StrippedLeaderboardDTO, SafeHtml>(anchorCell) {
@Override
public SafeHtml getValue(StrippedLeaderboardDTO object) {
String debugParam = Window.Location.getParameter("gwt.codesvr");
String link = URLEncoder.encode("/gwt/Leaderboard.html?name=" + object.name
+ (showRaceDetails ? "&showRaceDetails=true" : "")
+ (object.displayName != null ? "&displayName="+object.displayName : "")
+ (debugParam != null && !debugParam.isEmpty() ? "&gwt.codesvr=" + debugParam : ""));
Map<String, String> leaderboardUrlParams = new HashMap<>();
leaderboardUrlParams.put("name", object.name);
if (showRaceDetails) {
leaderboardUrlParams.put(LeaderboardUrlSettings.PARAM_SHOW_RACE_DETAILS, "true");
}
if (object.displayName != null) {
leaderboardUrlParams.put("displayName", object.displayName);
}
String link = EntryPointLinkFactory.createLeaderboardLink(leaderboardUrlParams);
return ANCHORTEMPLATE.cell(link, object.name);
}
@@ -307,9 +312,10 @@ TrackedRaceChangedListener, LeaderboardsDisplayer {
}
}
} else if (LeaderboardConfigImagesBarCell.ACTION_EDIT_SCORES.equals(value)) {
String debugParam = Window.Location.getParameter("gwt.codesvr");
Window.open("/gwt/LeaderboardEditing.html?name=" + leaderboardDTO.name
+ (debugParam != null && !debugParam.isEmpty() ? "&gwt.codesvr=" + debugParam : ""), "_blank", null);
Map<String, String> leaderboardEditingParams = new HashMap<>();
leaderboardEditingParams.put("name", leaderboardDTO.name);
String leaderboardEditingUrl = EntryPointLinkFactory.createLeaderboardEditingLink(leaderboardEditingParams);
Window.open(leaderboardEditingUrl, "_blank", null);
} else if (LeaderboardConfigImagesBarCell.ACTION_EDIT_COMPETITORS.equals(value)) {
EditCompetitorsDialog editCompetitorsDialog = new EditCompetitorsDialog(sailingService, leaderboardDTO.name, stringMessages,
errorReporter, new DialogCallback<List<CompetitorDTO>>() {
@@ -326,7 +332,7 @@ TrackedRaceChangedListener, LeaderboardsDisplayer {
} else if (LeaderboardConfigImagesBarCell.ACTION_CONFIGURE_URL.equals(value)) {
openLeaderboardUrlConfigDialog(leaderboardDTO, stringMessages);
} else if (LeaderboardConfigImagesBarCell.ACTION_EXPORT_XML.equals(value)) {
Window.open("/export/xml?domain=leaderboard&name=" + leaderboardDTO.name, "", null);
Window.open(UriUtils.fromString("/export/xml?domain=leaderboard&name=" + leaderboardDTO.name).asString(), "", null);
} else if (LeaderboardConfigImagesBarCell.ACTION_OPEN_COACH_DASHBOARD.equals(value)) {
Map<String, String> dashboardURLParameters = new HashMap<String, String>();
dashboardURLParameters.put("leaderboardName", leaderboardDTO.name);
@@ -29,7 +29,6 @@ import com.google.gwt.user.client.ui.TextArea;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.view.client.ListDataProvider;
import com.google.gwt.view.client.ProvidesKey;
import com.google.gwt.view.client.SelectionChangeEvent;
import com.sap.sailing.domain.common.dto.RaceColumnDTO;
import com.sap.sailing.gwt.ui.adminconsole.LeaderboardConfigPanel.AnchorCell;
@@ -578,13 +577,7 @@ public class LeaderboardGroupConfigPanel extends AbstractRegattaPanel implements
groupsTable.addColumn(groupActionsColumn, stringMessages.actions());
groupsTable.addColumnSortHandler(leaderboardGroupsListHandler);
ProvidesKey<LeaderboardGroupDTO> groupsKeyProvider = new ProvidesKey<LeaderboardGroupDTO>() {
@Override
public Object getKey(LeaderboardGroupDTO group) {
return group.getName();
}
};
refreshableGroupsSelectionModel = new RefreshableMultiSelectionModel<>(groupsKeyProvider,
refreshableGroupsSelectionModel = new RefreshableMultiSelectionModel<>(
new EntityIdentityComparator<LeaderboardGroupDTO>() {
@Override
public boolean representSameEntity(LeaderboardGroupDTO dto1, LeaderboardGroupDTO dto2) {
@@ -13,7 +13,10 @@ import com.sap.sailing.domain.common.impl.ColorMapImpl;
import com.sap.sse.common.Color;
public class CompetitorColorProviderImpl implements CompetitorColorProvider {
private final ColorMap<CompetitorDTO> competitorsColorMap;
/**
* Uses the {@link CompetitorDTO#getIdAsString()} as the ID for the color map
*/
private final ColorMap<String> competitorsColorMap;
private final Map<RegattaAndRaceIdentifier, Map<CompetitorDTO, Color>> competitorsBoatColorsPerRace;
public CompetitorColorProviderImpl() {
@@ -22,7 +25,7 @@ public class CompetitorColorProviderImpl implements CompetitorColorProvider {
public CompetitorColorProviderImpl(RegattaAndRaceIdentifier raceIdentifier,
Map<CompetitorDTO, BoatDTO> competitorsAndTheirBoats) {
this.competitorsColorMap = new ColorMapImpl<CompetitorDTO>();
this.competitorsColorMap = new ColorMapImpl<>();
this.competitorsBoatColorsPerRace = new HashMap<RegattaAndRaceIdentifier, Map<CompetitorDTO, Color>>();
if (raceIdentifier != null) {
for (Entry<CompetitorDTO, BoatDTO> competitorAndBoat : competitorsAndTheirBoats.entrySet()) {
@@ -61,7 +64,7 @@ public class CompetitorColorProviderImpl implements CompetitorColorProvider {
}
// fallback
if (result == null) {
result = competitorsColorMap.getColorByID(competitor);
result = competitorsColorMap.getColorByID(competitor.getIdAsString());
}
return result;
}
@@ -3,9 +3,12 @@ package com.sap.sailing.gwt.ui.client;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
@@ -18,7 +21,10 @@ import com.sap.sse.common.filter.FilterSet;
public class CompetitorSelectionModel implements CompetitorSelectionProvider {
private final Set<CompetitorDTO> allCompetitors;
private final LinkedHashSet<CompetitorDTO> selectedCompetitors;
/**
* Keys are {@link CompetitorDTO#getIdAsString()} of their values
*/
private final LinkedHashMap<String, CompetitorDTO> selectedCompetitors;
private final Set<CompetitorSelectionChangeListener> listeners;
@@ -42,7 +48,7 @@ public class CompetitorSelectionModel implements CompetitorSelectionProvider {
this.competitorColorProvider = competitorColorProvider;
this.competitorsFilterSet = competitorsFilterSet;
this.allCompetitors = new LinkedHashSet<CompetitorDTO>();
this.selectedCompetitors = new LinkedHashSet<CompetitorDTO>();
this.selectedCompetitors = new LinkedHashMap<>();
this.listeners = new HashSet<CompetitorSelectionChangeListener>();
}
@@ -115,12 +121,12 @@ public class CompetitorSelectionModel implements CompetitorSelectionProvider {
@Override
public Iterable<CompetitorDTO> getSelectedCompetitors() {
return Collections.unmodifiableCollection(selectedCompetitors);
return Collections.unmodifiableCollection(selectedCompetitors.values());
}
@Override
public Iterable<CompetitorDTO> getSelectedFilteredCompetitors() {
Set<CompetitorDTO> result = new HashSet<>(selectedCompetitors);
Set<CompetitorDTO> result = new HashSet<>(selectedCompetitors.values());
result.retainAll(getFilteredCompetitors());
return result;
}
@@ -148,13 +154,13 @@ public class CompetitorSelectionModel implements CompetitorSelectionProvider {
public void setSelected(CompetitorDTO competitor, boolean selected, CompetitorSelectionChangeListener... listenersNotToNotify) {
if (selected) {
if (allCompetitors.contains(competitor) && !selectedCompetitors.contains(competitor)) {
selectedCompetitors.add(competitor);
if (allCompetitors.contains(competitor) && !selectedCompetitors.containsKey(competitor.getIdAsString())) {
selectedCompetitors.put(competitor.getIdAsString(), competitor);
fireAddedToSelection(competitor, listenersNotToNotify);
}
} else {
if (selectedCompetitors.contains(competitor)) {
selectedCompetitors.remove(competitor);
if (selectedCompetitors.containsKey(competitor.getIdAsString())) {
selectedCompetitors.remove(competitor.getIdAsString());
fireRemovedFromSelection(competitor, listenersNotToNotify);
}
}
@@ -186,7 +192,7 @@ public class CompetitorSelectionModel implements CompetitorSelectionProvider {
@Override
public boolean isSelected(CompetitorDTO competitor) {
return selectedCompetitors.contains(competitor);
return selectedCompetitors.containsKey(competitor.getIdAsString());
}
@Override
@@ -201,7 +207,7 @@ public class CompetitorSelectionModel implements CompetitorSelectionProvider {
@Override
public void setSelection(Iterable<CompetitorDTO> newSelection, CompetitorSelectionChangeListener... listenersNotToNotify) {
Set<CompetitorDTO> competitorsToRemoveFromSelection = new HashSet<CompetitorDTO>(selectedCompetitors);
Set<CompetitorDTO> competitorsToRemoveFromSelection = new HashSet<CompetitorDTO>(selectedCompetitors.values());
for (CompetitorDTO newSelectedCompetitor : newSelection) {
setSelected(newSelectedCompetitor, true, listenersNotToNotify);
competitorsToRemoveFromSelection.remove(newSelectedCompetitor);
@@ -214,17 +220,32 @@ public class CompetitorSelectionModel implements CompetitorSelectionProvider {
@Override
public void setCompetitors(Iterable<CompetitorDTO> newCompetitors, CompetitorSelectionChangeListener... listenersNotToNotify) {
boolean changed = false;
Set<CompetitorDTO> oldCompetitorsToRemove = new HashSet<CompetitorDTO>(allCompetitors);
Map<String, CompetitorDTO> oldCompetitorsToRemove = new HashMap<String, CompetitorDTO>();
for (final CompetitorDTO c : allCompetitors) {
oldCompetitorsToRemove.put(c.getIdAsString(), c);
}
for (CompetitorDTO newCompetitor : newCompetitors) {
if (allCompetitors.contains(newCompetitor)) { // TODO bug 3413 Lukas: should this use an EntityIdentityComparator instead?
oldCompetitorsToRemove.remove(newCompetitor); // TODO bug 3413 Lukas: the old competitor object remains in allCompetitors; it is equal but doesn't have to be identical to newCompetitor; should it be replaced?
// Search by equality, including all CompetitorDTO fields; if equal object is found, leave it in place;
// non-equal objects may still represent the same competitor entity; yet, replace the object to obtain the
// new, changed state; make sure the selection keeps unchanged regarding the selected competitor "entities";
// see also bug 3413 for more background.
if (allCompetitors.contains(newCompetitor)) {
oldCompetitorsToRemove.remove(newCompetitor.getIdAsString()); // the old competitor object remains in allCompetitors; it is equal but doesn't have to be identical to newCompetitor
} else {
boolean selected = isSelected(newCompetitor);
if (selected) {
remove(oldCompetitorsToRemove.get(newCompetitor.getIdAsString()));
oldCompetitorsToRemove.remove(newCompetitor.getIdAsString());
}
add(newCompetitor, false); // due to the equals definition of CompetitorDTOImpl that compares most fields, state changes lead to a replacement here
if (selected) {
// restore selection for replaced element
setSelected(newCompetitor, selected, listenersNotToNotify);
}
changed = true;
}
}
for (CompetitorDTO oldCompetitorToRemove : oldCompetitorsToRemove) {
for (CompetitorDTO oldCompetitorToRemove : oldCompetitorsToRemove.values()) {
remove(oldCompetitorToRemove);
changed = true;
}
@@ -241,7 +262,7 @@ public class CompetitorSelectionModel implements CompetitorSelectionProvider {
@Override
public Color getColor(CompetitorDTO competitor, RegattaAndRaceIdentifier raceIdentfier) {
return allCompetitors.contains(competitor) ? competitorColorProvider.getColor(competitor, raceIdentfier) : null;
return allCompetitors.contains(competitor) ? competitorColorProvider.getColor(competitor, raceIdentfier) : Color.WHITE /* safe default */;
}
@Override
@@ -1,5 +1,6 @@
package com.sap.sailing.gwt.ui.client;
import java.util.Collections;
import java.util.Map;
import com.sap.sse.gwt.client.AbstractEntryPointLinkFactory;
@@ -17,10 +18,21 @@ public class EntryPointLinkFactory extends AbstractEntryPointLinkFactory {
return createEntryPointLink("/gwt/Leaderboard.html", parameters);
}
public static String createLeaderboardEditingLink(Map<String, String> parameters) {
return createEntryPointLink("/gwt/LeaderboardEditing.html", parameters);
}
public static String createLeaderboardTabLink(String eventId, String regattaId, Map<String, String> parameters) {
return createEntryPointLink("/gwt/Home.html#/regatta/leaderboard/:eventId=" + eventId + "&regattaId=" + regattaId, parameters);
}
public static String createLeaderboardPlaceLink(String eventId, String leaderboardName) {
final Map<String, String> emptyParams = Collections.emptyMap();
return createEntryPointLink("/gwt/Home.html#EventPlace:eventId="+eventId+
"&navigationTab=Regatta&leaderboardName=" +leaderboardName, emptyParams);
}
public static String createLeaderboardGroupLink(Map<String, String> parameters) {
return createEntryPointLink("/gwt/Spectator.html", parameters);
}
@@ -157,7 +157,8 @@ public interface SailingService extends RemoteService, FileStorageManagementGwtS
Map<CompetitorDTO, BoatDTO> getCompetitorBoats(RegattaAndRaceIdentifier raceIdentifier);
CompactRaceMapDataDTO getRaceMapData(RegattaAndRaceIdentifier raceIdentifier, Date date, Map<String, Date> fromPerCompetitorIdAsString,
Map<String, Date> toPerCompetitorIdAsString, boolean extrapolate, LegIdentifier simulationLegIdentifier) throws NoWindException;
Map<String, Date> toPerCompetitorIdAsString, boolean extrapolate, LegIdentifier simulationLegIdentifier,
byte[] md5OfIdsAsStringOfCompetitorParticipatingInRaceInAlphanumericOrderOfTheirID) throws NoWindException;
CompactBoatPositionsDTO getBoatPositions(RegattaAndRaceIdentifier raceIdentifier,
Map<String, Date> fromPerCompetitorIdAsString, Map<String, Date> toPerCompetitorIdAsString,
@@ -364,10 +364,16 @@ public interface SailingServiceAsync extends ServerInfoRetriever, FileStorageMan
* @param date
* use <code>null</code> to indicate "live" in which case the server live time stamp for the race
* identified by <code>raceIdentifier</code> will be used, considering that race's delay.
* @param md5OfIdsAsStringOfCompetitorParticipatingInRaceInAlphanumericOrderOfTheirID
* used to decide whether the client requires an update to the race's competitor set. If the server
* has the same MD5 hash for the race's competitors, no competitor set is transmitted to the client.
* Otherwise, the full race competitor ID's as strings are sent to the client again for update.
*/
void getRaceMapData(RegattaAndRaceIdentifier raceIdentifier, Date date,
Map<String, Date> fromPerCompetitorIdAsString, Map<String, Date> toPerCompetitorIdAsString,
boolean extrapolate, LegIdentifier simulationLegIdentifier, AsyncCallback<CompactRaceMapDataDTO> callback);
boolean extrapolate, LegIdentifier simulationLegIdentifier,
byte[] md5OfIdsAsStringOfCompetitorParticipatingInRaceInAlphanumericOrderOfTheirID,
AsyncCallback<CompactRaceMapDataDTO> callback);
void getBoatPositions(RegattaAndRaceIdentifier raceIdentifier, Map<String, Date> fromPerCompetitorIdAsString,
Map<String, Date> toPerCompetitorIdAsString, boolean extrapolate,
@@ -15,6 +15,7 @@ import com.google.gwt.dom.client.MediaElement;
import com.google.gwt.event.logical.shared.CloseEvent;
import com.google.gwt.event.logical.shared.CloseHandler;
import com.google.gwt.media.client.Audio;
import com.google.gwt.safehtml.shared.UriUtils;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.Window.ClosingEvent;
import com.google.gwt.user.client.Window.ClosingHandler;
@@ -124,7 +125,7 @@ public class MediaPlayerManagerComponent implements Component<AbstractSettings>,
AudioElement mediaReachableTester = audio.getAudioElement();
addLoadMetadataHandler(mediaReachableTester, mediaTrack);
mediaReachableTester.setPreload(MediaElement.PRELOAD_METADATA);
mediaReachableTester.setSrc(mediaTrack.url);
mediaReachableTester.setSrc(UriUtils.fromString(mediaTrack.url).asString());
mediaReachableTester.load();
} else {
mediaTrack.status = Status.CANNOT_PLAY;
@@ -203,13 +203,13 @@ public class FixesAndTails {
* longer tail itself), such that the second request that uses the <em>same</em> map will be considered
* having an overlap now, not leading to a replacement of the previous update originating from the same
* request.
* @return
*
* @return a {@link Runnable} for each competitor that the caller must invoke before starting to create or update
* the key competitor's tail. The object returned may be a no-op, but it may also clear an existing tail if
* necessary. The reason why this is not executed immediately is that there may be a transition-based delay
* with which the tails are updated. In this case we would be producing severe flicker. By delaying the
* command to the point when the update is applied should avoid such flicker.
* @return a {@link Runnable} for each competitor from <code>fixesForCompetitors</code>' key set that the caller
* must invoke before starting to create or update the key competitor's tail. The object returned may be a
* no-op, but it may also clear an existing tail if necessary. The reason why this is not executed
* immediately is that there may be a transition-based delay with which the tails are updated. In this case
* we would be producing severe flicker. By delaying the command to the point when the update is applied we
* should avoid such flicker.
*/
protected Map<CompetitorDTO, Runnable> updateFixes(Map<CompetitorDTO, List<GPSFixDTO>> fixesForCompetitors,
Map<CompetitorDTO, Boolean> overlapsWithKnownFixes, TailFactory tailFactory, long timeForPositionTransitionMillis) {
@@ -397,8 +397,7 @@ public class FixesAndTails {
}
int vertexCount = tail.getPath().getLength();
final List<GPSFixDTO> fixesForCompetitor = getFixes(competitorDTO);
int indexOfFirstShownFix = firstShownFix.get(competitorDTO) == null ? -1 : firstShownFix
.get(competitorDTO);
int indexOfFirstShownFix = firstShownFix.get(competitorDTO) == null ? -1 : firstShownFix.get(competitorDTO);
// remove fixes before what is now to be the beginning of the polyline:
while (indexOfFirstShownFix != -1 && vertexCount > 0
&& fixesForCompetitor.get(indexOfFirstShownFix).timepoint.before(from)) {
@@ -417,8 +416,7 @@ public class FixesAndTails {
vertexCount++;
}
// now adjust the polyline's tail: remove excess vertices that are after "to"
int indexOfLastShownFix = lastShownFix.get(competitorDTO) == null ? -1 : lastShownFix
.get(competitorDTO);
int indexOfLastShownFix = lastShownFix.get(competitorDTO) == null ? -1 : lastShownFix.get(competitorDTO);
while (indexOfLastShownFix != -1 && vertexCount > 0
&& fixesForCompetitor.get(indexOfLastShownFix).timepoint.after(to)) {
if (vertexCount-1 == 0 || (indexOfLastShownFix-1 >= 0 && !fixesForCompetitor.get(indexOfLastShownFix-1).timepoint.after(to))) {
@@ -0,0 +1,124 @@
package com.sap.sailing.gwt.ui.client.shared.racemap;
import java.io.UnsupportedEncodingException;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import com.sap.sailing.domain.common.RaceCompetitorIdsAsStringWithMD5Hash;
import com.sap.sailing.domain.common.dto.CompetitorDTO;
import com.sap.sailing.gwt.ui.client.CompetitorSelectionChangeListener;
import com.sap.sailing.gwt.ui.client.CompetitorSelectionProvider;
import com.sap.sse.common.filter.Filter;
import com.sap.sse.common.filter.FilterSet;
/**
* As an extension of {@link RaceCompetitorIdsAsStringWithMD5Hash}, adds the link with a
* {@link CompetitorSelectionProvider} and allows its clients to obtain the {@link CompetitorDTO} objects for those
* competitors participating in the race under consideration.
* <p>
*
* The implementation caches the {@link CompetitorDTO}s and keeps this cache up to date by
* {@link CompetitorSelectionProvider#addCompetitorSelectionChangeListener(CompetitorSelectionChangeListener)}
* listening} on the competitor selection provider for changes. When either the set of competitors for the race
* {@link #setIdsAsStringsOfCompetitorsInRace(Iterable) changes} or the set of competitors
* {@link CompetitorSelectionChangeListener#competitorsListChanged(Iterable) changes}, the {@link CompetitorDTO}
* collection is re-calculated.
*
* @author Axel Uhl (D043530)
*
*/
public class RaceCompetitorSet extends RaceCompetitorIdsAsStringWithMD5Hash {
private static final long serialVersionUID = 3357742414149799988L;
private CompetitorSelectionProvider competitorSelection;
/**
* A subset of the competitor selection's {@link CompetitorSelectionProvider#getAllCompetitors()} describing the
* competitors participting in the context of a specific race, or <code>null</code> if this set hasn't been determined
* yet, usually meaning that we have to default to all competitors of the regatta.<p>
*
* When {@link #idsAsStringOfCompetitorsParticipatingInRace} is <code>null</code> then so is this field, and vice versa.
*/
private Iterable<CompetitorDTO> competitorsParticipatingInRace;
RaceCompetitorSet() {} // for GWT serialization only
/**
* Starts out using {@link CompetitorSelectionProvider#getAllCompetitors() all regatta competitors} as response for
* {@link #getCompetitorsParticipatingInRace()}. Only after a call to
* {@link #setIdsAsStringsOfCompetitorsInRace(Iterable)} has been received may this set become adjusted to the
* actual subset participating in the race.
*/
public RaceCompetitorSet(CompetitorSelectionProvider competitorSelection) {
super();
this.competitorSelection = competitorSelection;
this.competitorsParticipatingInRace = competitorSelection.getAllCompetitors();
competitorSelection.addCompetitorSelectionChangeListener(new CompetitorSelectionChangeListener() {
@Override
public void removedFromSelection(CompetitorDTO competitor) {
// we don't care about selection
}
@Override
public void filteredCompetitorsListChanged(Iterable<CompetitorDTO> filteredCompetitors) {
// filtering doesn't affect who we think participates in the race
}
@Override
public void filterChanged(FilterSet<CompetitorDTO, ? extends Filter<CompetitorDTO>> oldFilterSet,
FilterSet<CompetitorDTO, ? extends Filter<CompetitorDTO>> newFilterSet) {
// filtering doesn't affect who we think participates in the race
}
@Override
public void competitorsListChanged(final Iterable<CompetitorDTO> competitors) {
competitorsParticipatingInRace = computeCompetitorsFromIDs(competitors);
}
@Override
public void addedToSelection(CompetitorDTO competitor) {
// we don't care about selection
}
});
}
public void setIdsAsStringsOfCompetitorsInRace(Set<String> idsAsStringsOfCompetitorsInRace) throws UnsupportedEncodingException, NoSuchAlgorithmException {
super.setIdsAsStringsOfCompetitorsInRace(idsAsStringsOfCompetitorsInRace);
competitorsParticipatingInRace = computeCompetitorsFromIDs(competitorSelection.getAllCompetitors());
}
public Iterable<CompetitorDTO> getCompetitorsParticipatingInRace() {
return competitorsParticipatingInRace;
}
/**
* Tries to locate the competitors described by the IDs in {@link #idsAsStringOfCompetitorsParticipatingInRace} in
* <code>competitors</code> and returns them in a set. If not all competitors can be found, <code>null</code> is
* returned instead.
*/
private Set<CompetitorDTO> computeCompetitorsFromIDs(Iterable<CompetitorDTO> competitors) {
Set<CompetitorDTO> result;
if (getIdsOfCompetitorsParticipatingInRaceAsStrings() == null) {
result = null;
} else {
final Map<String, CompetitorDTO> competitorsByIdAsString = new HashMap<>();
for (CompetitorDTO c : competitors) {
competitorsByIdAsString.put(c.getIdAsString(), c);
}
result = new HashSet<>();
for (String id : getIdsOfCompetitorsParticipatingInRaceAsStrings()) {
CompetitorDTO c = competitorsByIdAsString.get(id);
if (c == null) {
result = null;
break;
} else {
result.add(c);
}
}
}
return result;
}
}
@@ -14,7 +14,6 @@ import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import com.google.gwt.canvas.dom.client.CssColor;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
@@ -121,6 +120,7 @@ import com.sap.sailing.gwt.ui.shared.racemap.GoogleMapAPIKey;
import com.sap.sailing.gwt.ui.shared.racemap.GoogleMapStyleHelper;
import com.sap.sailing.gwt.ui.shared.racemap.RaceSimulationOverlay;
import com.sap.sailing.gwt.ui.shared.racemap.WindStreamletsRaceboardOverlay;
import com.sap.sse.common.Color;
import com.sap.sse.common.Util;
import com.sap.sse.common.Util.Pair;
import com.sap.sse.common.Util.Triple;
@@ -140,6 +140,7 @@ import com.sap.sse.gwt.client.shared.components.SettingsDialogComponent;
public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSelectionChangeListener,
RaceTimesInfoProviderListener, TailFactory, Component<RaceMapSettings>, RequiresDataInitialization, RequiresResize, QuickRankProvider {
private static final Color LOWLIGHTED_TAIL_COLOR = new RGBColor(200, 200, 200);
public static final String GET_RACE_MAP_DATA_CATEGORY = "getRaceMapData";
public static final String GET_WIND_DATA_CATEGORY = "getWindData";
@@ -291,6 +292,8 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
private final CompetitorSelectionProvider competitorSelection;
private final RaceCompetitorSet raceCompetitorSet;
/**
* Used to check if the first initial zoom to the mark markers was already done.
*/
@@ -423,6 +426,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
windSensorOverlays = new HashMap<WindSource, WindSensorOverlay>();
courseMarkOverlays = new HashMap<String, CourseMarkOverlay>();
this.competitorSelection = competitorSelection;
this.raceCompetitorSet = new RaceCompetitorSet(competitorSelection);
competitorSelection.addCompetitorSelectionChangeListener(this);
settings = new RaceMapSettings();
coordinateSystem = new DelegateCoordinateSystem(new IdentityCoordinateSystem());
@@ -877,8 +881,10 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
// overlap if it happens after this update.
asyncActionsExecutor.execute(new GetRaceMapDataAction(sailingService, competitorsByIdAsString,
race, useNullAsTimePoint() ? null : newTime, fromTimesForQuickCall, toTimesForQuickCall, /* extrapolate */true,
(settings.isShowSimulationOverlay() ? simulationOverlay.getLegIdentifier() : null)), GET_RACE_MAP_DATA_CATEGORY,
getRaceMapDataCallback(newTime, transitionTimeInMillis, fromAndToAndOverlap.getC(), competitorsToShow, ++boatPositionRequestIDCounter));
(settings.isShowSimulationOverlay() ? simulationOverlay.getLegIdentifier() : null),
raceCompetitorSet.getMd5OfIdsAsStringOfCompetitorParticipatingInRaceInAlphanumericOrderOfTheirID()),
GET_RACE_MAP_DATA_CATEGORY,
getRaceMapDataCallback(newTime, transitionTimeInMillis, fromAndToAndOverlap.getC(), competitorsToShow, ++boatPositionRequestIDCounter));
// next, if necessary, do the full thing; the two calls have different action classes, so throttling should not drop one for the other
if (!fromTimesForNonOverlappingTailsCall.keySet().isEmpty()) {
asyncActionsExecutor.execute(new GetBoatPositionsAction(sailingService, race, fromTimesForNonOverlappingTailsCall, toTimesForNonOverlappingTailsCall,
@@ -919,6 +925,14 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
// process response only if not received out of order
if (startedProcessingRequestID < requestID) {
startedProcessingRequestID = requestID;
if (raceMapDataDTO.raceCompetitorIdsAsStrings != null) {
try {
raceCompetitorSet.setIdsAsStringsOfCompetitorsInRace(raceMapDataDTO.raceCompetitorIdsAsStrings);
} catch (Exception e) {
GWT.log("Error trying to update competitor set for race "+raceIdentifier.getRaceName()+
" in regatta "+raceIdentifier.getRegattaName(), e);
}
}
quickRanks = raceMapDataDTO.quickRanks;
competitorsInOrderOfWindwardDistanceTraveledWithOneBasedLegNumber =
raceMapDataDTO.competitorsInOrderOfWindwardDistanceTraveledWithOneBasedLegNumber;
@@ -1236,7 +1250,9 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
if (fixesAndTails.hasFixesFor(competitorDTO)) {
Polyline tail = fixesAndTails.getTail(competitorDTO);
if (tail == null) {
tailPreparer.run(); // presumably a no-op, but you never know...
if (tailPreparer != null) { // could be we didn't receive boat position data for this competitor; then no tailPreparer will have been created
tailPreparer.run(); // presumably a no-op, but you never know...
}
tail = fixesAndTails.createTailAndUpdateIndices(competitorDTO, tailsFromTime, tailsToTime, this);
} else {
fixesAndTails.updateTail(tail, competitorDTO, tailsFromTime, tailsToTime,
@@ -2080,15 +2096,30 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
return vPanel;
}
// FIXME bug3378: only fetch those competitors relevant to the race displayed by this RaceMap; see http://bugzilla.sapsailing.com/bugzilla/show_bug.cgi?id=3378#c20
// could be a method on CompetitorSelectionProvider, parameterized by a RaceIdentifier, and if nothing is known about the race association, all competitors are returned as a safe default
/**
* @return the {@link CompetitorSelectionProvider#getSelectedCompetitors()} if
* {@link RaceMapSettings#isShowOnlySelectedCompetitors() only selected competitors are to be shown}, the
* {@link CompetitorSelectionProvider#getFilteredCompetitors() filtered competitors} otherwise. In both cases,
* if we have {@link RaceCompetitorSet information about the competitors participating} in this map's race,
* the result set is reduced to those, no matter if other regatta participants would otherwise have been in
* the result set
*/
private Iterable<CompetitorDTO> getCompetitorsToShow() {
Iterable<CompetitorDTO> result;
final Set<CompetitorDTO> result = new HashSet<>();
Iterable<CompetitorDTO> selection = competitorSelection.getSelectedCompetitors();
final Set<String> raceCompetitorIdsAsString = raceCompetitorSet.getIdsOfCompetitorsParticipatingInRaceAsStrings();
if (!settings.isShowOnlySelectedCompetitors() || Util.isEmpty(selection)) {
result = competitorSelection.getFilteredCompetitors();
for (final CompetitorDTO filteredCompetitor : competitorSelection.getFilteredCompetitors()) {
if (raceCompetitorIdsAsString == null || raceCompetitorIdsAsString.contains(filteredCompetitor.getIdAsString())) {
result.add(filteredCompetitor);
}
}
} else {
result = selection;
for (final CompetitorDTO selectedCompetitor : selection) {
if (raceCompetitorIdsAsString == null || raceCompetitorIdsAsString.contains(selectedCompetitor.getIdAsString())) {
result.add(selectedCompetitor);
}
}
}
return result;
}
@@ -2590,10 +2621,10 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
options.setGeodesic(true);
options.setStrokeOpacity(1.0);
boolean noCompetitorSelected = Util.isEmpty(competitorSelection.getSelectedCompetitors());
if (isHighlighted || noCompetitorSelected) {
if (isHighlighted || noCompetitorSelected || getSettings().isShowOnlySelectedCompetitors()) {
options.setStrokeColor(competitorSelection.getColor(competitor, raceIdentifier).getAsHtml());
} else {
options.setStrokeColor(CssColor.make(200, 200, 200).toString());
options.setStrokeColor(LOWLIGHTED_TAIL_COLOR.getAsHtml());
}
if (isHighlighted) {
options.setStrokeWeight(2);
@@ -5,6 +5,7 @@ import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.safehtml.shared.SafeHtmlUtils;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.DeckLayoutPanel;
import com.google.gwt.user.client.ui.DockLayoutPanel;
@@ -124,7 +125,7 @@ public abstract class AbstractResultsPresenter<SettingsType extends Settings> im
public void showError(String error) {
if (state != ResultsPresenterState.ERROR) {
mainPanel.setWidgetHidden(controlsPanel, true);
errorLabel.setHTML(error);
errorLabel.setHTML(SafeHtmlUtils.fromString(error).asString());
state = ResultsPresenterState.ERROR;
}
@@ -5,6 +5,7 @@ import java.util.Collections;
import java.util.List;
import java.util.Map;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.ScrollPanel;
import com.google.gwt.user.client.ui.Widget;
@@ -29,18 +30,23 @@ public class PlainResultsPresenter extends AbstractResultsPresenterWithDataProvi
@Override
protected void internalShowNumberResult(Map<GroupKey, Number> resultValues) {
QueryResultDTO<?> result = getCurrentResult();
StringBuilder resultsBuilder = new StringBuilder("<b>" + result.getResultSignifier() + "</b></ br>");
resultsBuilder.append("<table>");
SafeHtmlBuilder resultsBuilder = new SafeHtmlBuilder();
resultsBuilder.appendHtmlConstant("<b>");
resultsBuilder.appendEscaped(result.getResultSignifier());
resultsBuilder.appendHtmlConstant("</b></ br>");
resultsBuilder.appendHtmlConstant("<table>");
for (GroupKey key : getSortedKeys(result)) {
resultsBuilder.append("<tr>");
resultsBuilder.append("<td><b>" + key.toString() + "</b>:</td>");
resultsBuilder.append("<td>" + resultValues.get(key).doubleValue() + "</td>");
resultsBuilder.append("</tr>");
resultsBuilder.appendHtmlConstant("<tr>");
resultsBuilder.appendHtmlConstant("<td><b>");
resultsBuilder.appendEscaped(key.toString());
resultsBuilder.appendHtmlConstant("</b>:</td>");
resultsBuilder.appendHtmlConstant("<td>");
resultsBuilder.append(resultValues.get(key).doubleValue());
resultsBuilder.appendHtmlConstant("</td>");
resultsBuilder.appendHtmlConstant("</tr>");
}
resultsBuilder.append("</table>");
resultsLabel.setHTML(resultsBuilder.toString());
resultsBuilder.appendHtmlConstant("</table>");
resultsLabel.setHTML(resultsBuilder.toSafeHtml().asString());
}
private Iterable<GroupKey> getSortedKeys(QueryResultDTO<?> result) {
@@ -1637,30 +1637,15 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS
*/
@Override
public void addedToSelection(CompetitorDTO competitor) {
getSelectionModel().setSelected(getRow(competitor), true);
/* final LeaderboardRowDTO row = getRow(competitor);
if (row != null) {
redrawRow(row);
}*/
getSelectionModel().setSelected(getRow(competitor.getIdAsString()), true);
}
/*
private void redrawRow(final LeaderboardRowDTO row) {
final List<LeaderboardRowDTO> leaderboardDataList = getData().getList();
synchronized (leaderboardDataList) {
redrawRow(row, leaderboardDataList);
}
}
*/
/**
* Ensure that the checkbox is redrawn when the competitor selection changes
*/
@Override
public void removedFromSelection(CompetitorDTO competitor) {
getSelectionModel().setSelected(getRow(competitor), false);
/* final LeaderboardRowDTO row = getRow(competitor);
if (row != null) {
redrawRow(row);
}*/
getSelectionModel().setSelected(getRow(competitor.getIdAsString()), false);
}
@Override
@@ -3124,10 +3109,10 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS
return stringMessages.leaderboard();
}
private LeaderboardRowDTO getRow(CompetitorDTO competitor) {
private LeaderboardRowDTO getRow(String competitorIdAsString) {
synchronized (getData().getList()) {
for (LeaderboardRowDTO row : getData().getList()) {
if (row.competitor.equals(competitor)) {
if (row.competitor.getIdAsString().equals(competitorIdAsString)) {
return row;
}
}
@@ -3137,7 +3122,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS
@Override
public void addedToSelection(CompetitorDTO competitor) {
LeaderboardRowDTO row = getRow(competitor);
LeaderboardRowDTO row = getRow(competitor.getIdAsString());
if (row != null) {
leaderboardSelectionModel.setSelected(row, true);
}
@@ -3145,7 +3130,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS
@Override
public void removedFromSelection(CompetitorDTO competitor) {
LeaderboardRowDTO row = getRow(competitor);
LeaderboardRowDTO row = getRow(competitor.getIdAsString());
if (row != null) {
leaderboardSelectionModel.setSelected(row, false);
}
@@ -181,40 +181,35 @@ public class MultiLeaderboardPanel extends AbstractLazyComponent<LeaderboardSett
});
}
// public LeaderboardPanel getSelectedLeaderboardPanel() {
// return selectedLeaderboardPanel;
// }
//
private void updateLeaderboardSelection() {
if(leaderboardsTabPanel != null) {
if (leaderboardsTabPanel != null) {
leaderboardsTabPanel.clear();
int index = 0;
int leaderboardCount = leaderboardNamesAndDisplayNames.size();
for (Util.Pair<String, String> leaderboardNameAndDisplayName : leaderboardNamesAndDisplayNames) {
FlowPanel tabFlowPanel = new FlowPanel();
leaderboardsTabPanel.add(tabFlowPanel, leaderboardNameAndDisplayName.getB(), false);
if(selectedLeaderboardName != null && selectedLeaderboardName.equals(leaderboardNameAndDisplayName.getA())) {
if (selectedLeaderboardName != null
&& selectedLeaderboardName.equals(leaderboardNameAndDisplayName.getA())) {
leaderboardsTabPanel.selectTab(index);
}
index++;
}
// show the last leaderboard when no leaderboard is selected yet
if(selectedLeaderboardName == null && leaderboardCount > 0) {
leaderboardsTabPanel.selectTab(leaderboardCount-1);
// show the last leaderboard when no leaderboard is selected yet
if (selectedLeaderboardName == null && leaderboardCount > 0) {
leaderboardsTabPanel.selectTab(leaderboardCount - 1);
}
leaderboardsTabPanel.setVisible(leaderboardCount > 0);
if(!isEmbedded) {
if (!isEmbedded) {
leaderboardsLabel.setVisible(leaderboardCount > 0);
}
}
}
private void updateSelectedLeaderboard(String newSelectedLeaderboardName, int newTabIndex) {
if(newSelectedLeaderboardName != null) {
if(selectedLeaderboardPanel != null && selectedLeaderboardFlowPanel != null) {
if (newSelectedLeaderboardName != null) {
if (selectedLeaderboardPanel != null && selectedLeaderboardFlowPanel != null) {
selectedLeaderboardPanel.removeAllListeners();
selectedLeaderboardFlowPanel.remove(selectedLeaderboardPanel);
selectedLeaderboardPanel = null;
@@ -229,12 +224,12 @@ public class MultiLeaderboardPanel extends AbstractLazyComponent<LeaderboardSett
showRaceDetails, /* competitorSearchTextBox */ null, /* showSelectionCheckbox */ true, /* raceTimesInfoProvider */null,
false, /* adjustTimerDelay */ true, /*autoApplyTopNFilter*/ false, false);
selectedLeaderboardFlowPanel.add(newSelectedLeaderboardPanel);
for(LeaderboardUpdateListener listener: leaderboardUpdateListeners) {
for (LeaderboardUpdateListener listener : leaderboardUpdateListeners) {
newSelectedLeaderboardPanel.addLeaderboardUpdateListener(listener);
}
setSelectedLeaderboard(newSelectedLeaderboardPanel);
} else {
if(selectedLeaderboardPanel != null && selectedLeaderboardFlowPanel != null) {
if (selectedLeaderboardPanel != null && selectedLeaderboardFlowPanel != null) {
selectedLeaderboardPanel.removeAllListeners();
selectedLeaderboardFlowPanel.remove(selectedLeaderboardPanel);
selectedLeaderboardPanel = null;
@@ -61,7 +61,6 @@ import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettingsFactory;
import com.sap.sse.common.filter.FilterSet;
import com.sap.sse.common.settings.AbstractSettings;
import com.sap.sse.gwt.client.ErrorReporter;
import com.sap.sse.gwt.client.URLEncoder;
import com.sap.sse.gwt.client.async.AsyncActionsExecutor;
import com.sap.sse.gwt.client.player.TimeRangeWithZoomModel;
import com.sap.sse.gwt.client.player.Timer;
@@ -80,8 +79,7 @@ import com.sap.sse.security.ui.client.UserService;
* @author Frank Mittag, Axel Uhl (d043530)
*
*/
public class RaceBoardPanel extends SimplePanel implements LeaderboardUpdateListener, PopupPositionProvider,
RequiresResize {
public class RaceBoardPanel extends SimplePanel implements LeaderboardUpdateListener, PopupPositionProvider, RequiresResize {
private final SailingServiceAsync sailingService;
private final MediaServiceAsync mediaService;
private final UUID eventId;
@@ -407,14 +405,7 @@ public class RaceBoardPanel extends SimplePanel implements LeaderboardUpdateList
final Anchor regattaNameAnchor = new Anchor(raceIdentifier.getRegattaName());
regattaNameAnchor.setTitle(raceIdentifier.getRegattaName());
if (eventId != null) {
// we don't use the EntryPointLinkFactory here, because of lacking support for Places
String debugParam = Window.Location.getParameter("gwt.codesvr");
String link = "/gwt/Home.html";
if (debugParam != null && !debugParam.isEmpty()) {
link += "?gwt.codesvr=" + debugParam;
}
link += "#EventPlace:eventId="+eventId.toString();
link += "&navigationTab=Regatta&leaderboardName=" + URLEncoder.encode(leaderboardName);
String link = EntryPointLinkFactory.createLeaderboardPlaceLink(eventId.toString(), leaderboardName);
regattaNameAnchor.setHref(link);
} else {
String leaderboardGroupNameParam = Window.Location.getParameter("leaderboardGroupName");
@@ -17,6 +17,7 @@ import java.net.URLEncoder;
import java.net.UnknownHostException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
@@ -1640,7 +1641,20 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
@Override
public CompactRaceMapDataDTO getRaceMapData(RegattaAndRaceIdentifier raceIdentifier, Date date,
Map<String, Date> fromPerCompetitorIdAsString, Map<String, Date> toPerCompetitorIdAsString,
boolean extrapolate, LegIdentifier simulationLegIdentifier) throws NoWindException {
boolean extrapolate, LegIdentifier simulationLegIdentifier,
byte[] md5OfIdsAsStringOfCompetitorParticipatingInRaceInAlphanumericOrderOfTheirID) throws NoWindException {
final HashSet<String> raceCompetitorIdsAsStrings;
final TrackedRace trackedRace = getExistingTrackedRace(raceIdentifier);
// if md5OfIdsAsStringOfCompetitorParticipatingInRaceInAlphanumericOrderOfTheirID is null, Arrays.equals will return false, and the
// competitor set will be calculated and returned to the client
if (trackedRace == null || Arrays.equals(md5OfIdsAsStringOfCompetitorParticipatingInRaceInAlphanumericOrderOfTheirID, trackedRace.getRace().getCompetitorMD5())) {
raceCompetitorIdsAsStrings = null; // tracked race not found or still same MD5 hash, suggesting unchanged competitor set
} else {
raceCompetitorIdsAsStrings = new HashSet<>();
for (final Competitor c : trackedRace.getRace().getCompetitors()) {
raceCompetitorIdsAsStrings.add(c.getId().toString());
}
}
final Map<CompetitorDTO, List<GPSFixDTO>> boatPositions = getBoatPositionsInternal(raceIdentifier,
fromPerCompetitorIdAsString, toPerCompetitorIdAsString, extrapolate);
final CoursePositionsDTO coursePositions = getCoursePositions(raceIdentifier, date);
@@ -1651,7 +1665,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
SimulationService simulationService = getService().getSimulationService();
simulationResultVersion = simulationService.getSimulationResultsVersion(simulationLegIdentifier);
}
return new CompactRaceMapDataDTO(boatPositions, coursePositions, courseSidelines, quickRanks, simulationResultVersion);
return new CompactRaceMapDataDTO(boatPositions, coursePositions, courseSidelines, quickRanks, simulationResultVersion, raceCompetitorIdsAsStrings);
}
private Map<CompetitorDTO, BoatDTO> getCompetitorBoatsForRace(RaceDefinition race, List<CompetitorDTO> competitorDTOs) {
@@ -3442,8 +3456,13 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
@Override
public Pair<Integer, Integer> resolveImageDimensions(String imageUrlAsString) throws Exception {
URL imageURL = new URL(imageUrlAsString);
Pair<Integer, Integer> imageDimensions = MediaUtils.getImageDimensions(imageURL);
final Pair<Integer, Integer> imageDimensions;
if (imageUrlAsString != null && !imageUrlAsString.isEmpty()) {
URL imageURL = new URL(imageUrlAsString);
imageDimensions = MediaUtils.getImageDimensions(imageURL);
} else {
imageDimensions = null;
}
return imageDimensions;
}
@@ -2,12 +2,14 @@ package com.sap.sailing.gwt.ui.shared;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import com.google.gwt.user.client.rpc.IsSerializable;
import com.sap.sailing.domain.base.RaceDefinition;
import com.sap.sailing.domain.common.dto.CompetitorDTO;
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
import com.sap.sse.common.Util;
@@ -31,11 +33,18 @@ public class CompactRaceMapDataDTO implements IsSerializable {
private LinkedHashMap<String, Integer> competitorsInOrderOfWindwardDistanceTraveledFarthestFirstIdAsStringAndOneBasedLegNumber;
private long simulationResultVersion;
/**
* <code>null</code> if the client's request contained an equal MD5 as the race's competitors {@link RaceDefinition#getCompetitorMD5() produce};
* otherwise, the set of competitor IDs, {@link Object#toString() converted to strings}, in no particular order.
*/
private HashSet<String> raceCompetitorIdsAsStrings;
CompactRaceMapDataDTO() {}
public CompactRaceMapDataDTO(Map<CompetitorDTO, List<GPSFixDTO>> boatPositions, CoursePositionsDTO coursePositions,
List<SidelineDTO> courseSidelines, QuickRanksDTO quickRanks, long simulationResultVersion) {
List<SidelineDTO> courseSidelines, QuickRanksDTO quickRanks, long simulationResultVersion, HashSet<String> raceCompetitorIdsAsStrings) {
this.boatPositionsByCompetitorIdAsString = new CompactBoatPositionsDTO(boatPositions);
this.raceCompetitorIdsAsStrings = raceCompetitorIdsAsStrings;
this.quickRanks = new ArrayList<CompactQuickRankDTO>(quickRanks == null ? 0 : Util.size(quickRanks.getQuickRanks()));
this.competitorsInOrderOfWindwardDistanceTraveledFarthestFirstIdAsStringAndOneBasedLegNumber = new LinkedHashMap<>();
Map<String, Integer> competitorIdAsStringToOneBasedLegNumber = new HashMap<>();
@@ -76,6 +85,7 @@ public class CompactRaceMapDataDTO implements IsSerializable {
}
}
result.simulationResultVersion = simulationResultVersion;
result.raceCompetitorIdsAsStrings = this.raceCompetitorIdsAsStrings;
return result;
}
}
@@ -1,5 +1,6 @@
package com.sap.sailing.gwt.ui.shared;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -14,4 +15,5 @@ public class RaceMapDataDTO implements IsSerializable {
public LinkedHashMap<CompetitorDTO, QuickRankDTO> quickRanks;
public LinkedHashMap<CompetitorDTO, Integer> competitorsInOrderOfWindwardDistanceTraveledWithOneBasedLegNumber;
public long simulationResultVersion;
public HashSet<String> raceCompetitorIdsAsStrings;
}
@@ -1,161 +0,0 @@
package com.sap.sailing.server.gateway.impl;
import static org.junit.Assert.assertEquals;
import static org.mockito.Matchers.argThat;
import static org.mockito.Matchers.isA;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.stub;
import static org.mockito.Mockito.when;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.hamcrest.Matcher;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentMatcher;
import com.sap.sailing.domain.base.RaceDefinition;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.Wind;
import com.sap.sailing.domain.common.WindSource;
import com.sap.sailing.domain.common.WindSourceType;
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
import com.sap.sailing.domain.common.impl.DegreePosition;
import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
import com.sap.sailing.domain.common.impl.WindImpl;
import com.sap.sailing.domain.common.impl.WindSourceImpl;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.WindTrack;
import com.sap.sailing.domain.tracking.impl.CombinedWindTrackImpl;
import com.sap.sailing.domain.tracking.impl.WindTrackImpl;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.impl.MillisecondsTimePoint;
/**
* After refactoring the {@link WindJsonGetServlet} to first copy the relevant fixes to hold the read lock only
* for a short time, this test uses the old implementation to compute the expected result and compares the
* output received from the servlet.
*
* @author Axel Uhl (d043530)
*
*/
public class WindJsonGetServletRefactoringTest {
private WindJsonGetServlet servlet;
@Before
public void setUp() {
servlet = new WindJsonGetServlet();
}
private JSONObject getExpectedResult(String windSourceToRead, TrackedRace trackedRace, TimePoint from, TimePoint to) {
JSONObject jsonWindTracks = new JSONObject();
List<WindSource> windSources = servlet.getAvailableWindSources(trackedRace);
JSONArray jsonWindSourcesDisplayed = new JSONArray();
for (WindSource windSource : windSources) {
JSONObject windSourceInformation = new JSONObject();
windSourceInformation.put("typeName", windSource.getType().name());
windSourceInformation.put("id", windSource.getId() != null ? windSource.getId().toString() : "");
jsonWindSourcesDisplayed.add(windSourceInformation);
}
jsonWindTracks.put("availableWindSources", jsonWindSourcesDisplayed);
for (WindSource windSource : windSources) {
if("ALL".equals(windSourceToRead) || windSource.getType().name().equalsIgnoreCase(windSourceToRead)) {
JSONArray jsonWindArray = new JSONArray();
WindTrack windTrack = trackedRace.getOrCreateWindTrack(windSource);
windTrack.lockForRead();
try {
Iterator<Wind> windIter = windTrack.getFixesIterator(from, /* inclusive */true);
while (windIter.hasNext()) {
Wind wind = windIter.next();
if (wind.getTimePoint().compareTo(to) > 0) {
break;
}
JSONObject jsonWind = new JSONObject();
jsonWind.put("truebearingdeg", wind.getBearing().getDegrees());
jsonWind.put("knotspeed", wind.getKnots());
jsonWind.put("meterspersecondspeed", wind.getMetersPerSecond());
if (wind.getTimePoint() != null) {
jsonWind.put("timepoint", wind.getTimePoint().asMillis());
jsonWind.put("dampenedtruebearingdeg",
windTrack.getAveragedWind(wind.getPosition(), wind.getTimePoint())
.getBearing().getDegrees());
jsonWind.put("dampenedknotspeed",
windTrack.getAveragedWind(wind.getPosition(), wind.getTimePoint()).getKnots());
jsonWind.put("dampenedmeterspersecondspeed",
windTrack.getAveragedWind(wind.getPosition(), wind.getTimePoint())
.getMetersPerSecond());
}
if (wind.getPosition() != null) {
jsonWind.put("latdeg", wind.getPosition().getLatDeg());
jsonWind.put("lngdeg", wind.getPosition().getLngDeg());
}
jsonWindArray.add(jsonWind);
}
} finally {
windTrack.unlockAfterRead();
}
jsonWindTracks.put(windSource.toString()+(windSource.getId()!=null ? "-"+windSource.getId():""), jsonWindArray);
}
}
return jsonWindTracks;
}
@Test
public void compareOldAndNewImplementation() {
TrackedRace trackedRace = mock(TrackedRace.class);
final WindSourceImpl webWindSource = new WindSourceImpl(WindSourceType.WEB);
when(trackedRace.getWindSources()).thenReturn(new HashSet<>(Arrays.asList(new WindSource[] { webWindSource })));
RaceDefinition race = mock(RaceDefinition.class);
when(race.getName()).thenReturn("Race Name");
when(trackedRace.getRace()).thenReturn(race);
Set<WindSource> noWindSourcesToExclude = Collections.emptySet();
when(trackedRace.getWindSourcesToExclude()).thenReturn(noWindSourcesToExclude);
WindTrackImpl webWindTrack = new WindTrackImpl(/* millisecondsOverWhichToAverage */ 30000, /* useSpeed */ true, /* nameForReadWriteLock */ "compareOldAndNewImplementation");
when(trackedRace.getOrCreateWindTrack(webWindSource)).thenReturn(webWindTrack);
WindSourceImpl combinedWindSource = new WindSourceImpl(WindSourceType.COMBINED);
CombinedWindTrackImpl combinedWindTrack = new CombinedWindTrackImpl(trackedRace, /* baseConfidence */ 0.5);
when(trackedRace.getOrCreateWindTrack(combinedWindSource)).thenReturn(combinedWindTrack);
final TimePoint now = MillisecondsTimePoint.now();
final TimePoint earlier = now.minus(10000);
final TimePoint later = now.plus(10000);
final Matcher<TimePoint> isTooEarlyOrTooLate = new ArgumentMatcher<TimePoint>() {
@Override
public boolean matches(Object item) {
return item instanceof TimePoint &&
((TimePoint) item).before(earlier);
}
};
final Matcher<TimePoint> isNeitherTooEarlyNorTooLate = new ArgumentMatcher<TimePoint>() {
@Override
public boolean matches(Object argument) {
return !isTooEarlyOrTooLate.matches(argument);
}
};
stub(trackedRace.getWind(isA(Position.class), argThat(isTooEarlyOrTooLate))).toReturn(null);
Wind someCombinedWindFix = new WindImpl(new DegreePosition(50, 4), now,
new KnotSpeedWithBearingImpl(15, new DegreeBearingImpl(234)));
stub(trackedRace.getWind(isA(Position.class), argThat(isNeitherTooEarlyNorTooLate))).toReturn(someCombinedWindFix);
webWindTrack.add(new WindImpl(new DegreePosition(49, 3), now,
new KnotSpeedWithBearingImpl(12, new DegreeBearingImpl(123))));
System.out.println(getExpectedResult(WindJsonGetServlet.ALL, trackedRace, earlier, later));
assertWindSourcesEquals(getExpectedResult(WindJsonGetServlet.ALL, trackedRace, earlier, later),
servlet.getResult(WindJsonGetServlet.ALL, "", trackedRace, earlier, later));
}
private void assertWindSourcesEquals(JSONObject expectedResult, JSONObject result) {
Set<String> expectedWindSourceNames = ((JSONArray) expectedResult.get("availableWindSources")).stream().map(o->o.toString()).collect(Collectors.toSet());
Set<String> actualWindSourceNames = ((JSONArray) result.get("availableWindSources")).stream().map(o->o.toString()).collect(Collectors.toSet());
assertEquals(expectedWindSourceNames, actualWindSourceNames);
for (String windSourceName : expectedWindSourceNames) {
assertEquals(expectedResult.get(windSourceName), result.get(windSourceName));
}
}
}
@@ -1,73 +0,0 @@
package com.sap.sailing.server.gateway.test;
import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.UUID;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;
import org.junit.Before;
import org.junit.Test;
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.FleetImpl;
import com.sap.sailing.domain.base.impl.RegattaImpl;
import com.sap.sailing.domain.base.impl.SeriesImpl;
import com.sap.sailing.domain.common.ScoringSchemeType;
import com.sap.sailing.domain.ranking.OneDesignRankingMetric;
import com.sap.sailing.server.gateway.impl.RegattasJsonGetServlet;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.impl.MillisecondsTimePoint;
public class RegattasJsonExportTest extends AbstractJsonExportTest {
private String boatClassName = "49er";
private String regattaName = "TestRegatta";
@Before
public void setUp() {
super.setUp();
List<Series> series = new ArrayList<Series>();
List<Fleet> fleets = new ArrayList<Fleet>();
List<String> raceColumnNames = new ArrayList<String>();
fleets.add(new FleetImpl("Fleet1"));
fleets.add(new FleetImpl("Fleet2"));
Series testSeries = new SeriesImpl("TestSeries", /* isMedal */false, fleets,
raceColumnNames, /* trackedRegattaRegistry */null);
series.add(testSeries);
final Calendar cal = new GregorianCalendar();
cal.set(2014, 5, 6, 10, 00);
final TimePoint startDate = new MillisecondsTimePoint(cal.getTime());
cal.set(2014, 5, 8, 16, 00);
final TimePoint endDate = new MillisecondsTimePoint(cal.getTime());
racingEventService.createRegatta(RegattaImpl.getDefaultName(regattaName, boatClassName), boatClassName, startDate, endDate,
UUID.randomUUID(), series, /*persistent*/ true,
DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null, /* useStartTimeInference */ true, OneDesignRankingMetric::new);
testSeries.addRaceColumn("R1", /* trackedRegattaRegistry */ null);
testSeries.addRaceColumn("R2", /* trackedRegattaRegistry */ null);
}
@Test
public void testExportRegattasAsJson() throws Exception {
String jsonString = callJsonHttpServlet(new RegattasJsonGetServlet(), "GET", null);
Object obj= JSONValue.parse(jsonString);
JSONArray array= (JSONArray) obj;
assertTrue(array.size() == 1);
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(boatClassName.equals(jsonBoatClass));
}
}
@@ -57,6 +57,14 @@ public abstract class AbstractJaxRsApiTest {
return new String(entity, "UTF-8");
}
protected TimePoint parseTimepointFromJsonNumber(Long timePointAsJsonNumber) throws ParseException {
TimePoint result = null;
if(timePointAsJsonNumber != null) {
result = new MillisecondsTimePoint(timePointAsJsonNumber);
}
return result;
}
protected TimePoint parseTimepointFromJsonString(String timePointAsJsonString) throws ParseException {
TimePoint result = null;
if(timePointAsJsonString != null && !timePointAsJsonString.isEmpty()) {
@@ -71,7 +79,7 @@ public abstract class AbstractJaxRsApiTest {
BoatClass boatClass = new BoatClassImpl("505", /* typicallyStartsUpwind */ true);
for (int i = 1; i <= numberOfCompetitorsToCreate; i++) {
String competitorName = "C" + i;
Competitor competitor = new CompetitorImpl(123, competitorName, Color.RED, null, null, new TeamImpl("STG", Collections.singleton(
Competitor competitor = new CompetitorImpl(new Integer(i), competitorName, Color.RED, null, null, new TeamImpl("STG", Collections.singleton(
new PersonImpl(competitorName, new NationalityImpl("GER"),
/* dateOfBirth */ null, "This is famous "+competitorName)),
new PersonImpl("Rigo van Maas", new NationalityImpl("NED"),
@@ -33,7 +33,7 @@ import com.sap.sse.filestorage.InvalidPropertiesException;
import com.sap.sse.filestorage.OperationFailedException;
import com.sap.sse.filestorage.testsupport.AmazonS3TestSupport;
public class TeamImageTest extends AbstractJaxRsApiTest {
public class CompetitorsResourceTeamImageTest extends AbstractJaxRsApiTest {
private static final String name = "Heiko KRÖGER";
private static final String id = "af855a56-9726-4a9c-a77e-da955bd289be";
private static final String boatClassName = "49er";
@@ -1,4 +1,4 @@
package com.sap.sailing.server.gateway.test;
package com.sap.sailing.server.gateway.test.jaxrs;
import static org.junit.Assert.assertTrue;
@@ -6,6 +6,8 @@ import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import javax.ws.rs.core.Response;
import org.json.simple.JSONArray;
import org.json.simple.JSONValue;
import org.junit.Before;
@@ -25,11 +27,11 @@ import com.sap.sailing.domain.leaderboard.LeaderboardGroup;
import com.sap.sailing.domain.ranking.OneDesignRankingMetric;
import com.sap.sailing.domain.test.mock.MockedTrackedRaceWithStartTimeAndRanks;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.server.gateway.impl.LeaderboardGroupsJsonGetServlet;
import com.sap.sailing.server.gateway.jaxrs.api.LeaderboardGroupsResource;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.impl.MillisecondsTimePoint;
public class LeaderboardGroupJsonExportTest extends AbstractJsonExportTest {
public class LeaderboardGroupResourceTest extends AbstractJaxRsApiTest {
private Regatta regatta;
private LeaderboardGroup leaderboardGroup;
private String boatClassName = "49er";
@@ -74,10 +76,15 @@ public class LeaderboardGroupJsonExportTest extends AbstractJsonExportTest {
}
@Test
public void testExportLeaderboardGroupsAsJson() throws Exception {
String jsonString = callJsonHttpServlet(new LeaderboardGroupsJsonGetServlet(), "GET", null);
Object obj = JSONValue.parse(jsonString);
JSONArray array = (JSONArray) obj;
public void testExportLeaderboardGroupsAsJson() throws Exception {
LeaderboardGroupsResource resource = new LeaderboardGroupsResource();
LeaderboardGroupsResource spyResource = spyResource(resource);
Response leaderboardGroupsResponse = spyResource.getLeaderboardGroups();
String jsonString = (String) leaderboardGroupsResponse.getEntity();
Object obj= JSONValue.parse(jsonString);
JSONArray array= (JSONArray) obj;
assertTrue(array.size() == 1);
String jsonFirstLeaderboardGroup = (String) array.get(0);
assertTrue(leaderboardGroup.getName().equals(jsonFirstLeaderboardGroup));
@@ -1,4 +1,4 @@
package com.sap.sailing.server.gateway.jaxrs.api;
package com.sap.sailing.server.gateway.test.jaxrs;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
@@ -29,7 +29,7 @@ import com.sap.sailing.domain.racelogtracking.DeviceIdentifier;
import com.sap.sailing.domain.racelogtracking.DeviceMapping;
import com.sap.sailing.domain.racelogtracking.RaceLogTrackingAdapterFactory;
import com.sap.sailing.domain.ranking.OneDesignRankingMetric;
import com.sap.sailing.server.gateway.test.jaxrs.AbstractJaxRsApiTest;
import com.sap.sailing.server.gateway.jaxrs.api.LeaderboardsResource;
import com.sap.sse.common.NoCorrespondingServiceRegisteredException;
import com.sap.sse.common.impl.MillisecondsTimePoint;
@@ -1,17 +1,16 @@
package com.sap.sailing.server.gateway.test;
package com.sap.sailing.server.gateway.test.jaxrs;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import javax.ws.rs.core.Response;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;
@@ -32,11 +31,11 @@ import com.sap.sailing.domain.leaderboard.RegattaLeaderboard;
import com.sap.sailing.domain.ranking.OneDesignRankingMetric;
import com.sap.sailing.domain.test.mock.MockedTrackedRaceWithStartTimeAndRanks;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.server.gateway.impl.LeaderboardJsonGetServlet;
import com.sap.sailing.server.gateway.jaxrs.api.LeaderboardsResource;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.impl.MillisecondsTimePoint;
public class LeaderboardJsonExportTest extends AbstractJsonExportTest {
public class LeaderboardsResourceTest extends AbstractJaxRsApiTest {
private Regatta regatta;
private RegattaLeaderboard regattaLeaderboard;
private String boatClassName = "49er";
@@ -69,8 +68,8 @@ public class LeaderboardJsonExportTest extends AbstractJsonExportTest {
TimePoint now = MillisecondsTimePoint.now();
RaceColumn r1Column = series.get(0).getRaceColumnByName("R1");
TrackedRace r1Fleet1 = new MockedTrackedRaceWithStartTimeAndRanks(now, fleet1Competitors);
TrackedRace r1Fleet2 = new MockedTrackedRaceWithStartTimeAndRanks(now, fleet2Competitors);
TrackedRace r1Fleet1 = new MockedTrackedRaceWithStartTimeAndRanks(now, fleet1Competitors, regatta);
TrackedRace r1Fleet2 = new MockedTrackedRaceWithStartTimeAndRanks(now, fleet2Competitors, regatta);
r1Column.setTrackedRace(r1Column.getFleetByName("Fleet1"), r1Fleet1);
r1Column.setTrackedRace(r1Column.getFleetByName("Fleet2"), r1Fleet2);
@@ -82,12 +81,10 @@ public class LeaderboardJsonExportTest extends AbstractJsonExportTest {
public void testExportEmptyLeaderboardAsJson() throws Exception {
dropAndCreateRegatta();
Map<String, String> requestParameters = new HashMap<>();
requestParameters.put("leaderboardName", regatta.getName());
requestParameters.put("resultState", LeaderboardJsonGetServlet.ResultStates.Final.name());
String jsonString = callJsonHttpServlet(new LeaderboardJsonGetServlet(), "GET", requestParameters);
LeaderboardsResource resource = spyResource(new LeaderboardsResource());
Response leaderboardReponse = resource.getLeaderboard(regatta.getName(), LeaderboardsResource.ResultStates.Final, null);
String jsonString = (String) leaderboardReponse.getEntity();
Object obj= JSONValue.parse(jsonString);
JSONObject jsonObject = (JSONObject) obj;
@@ -95,10 +92,10 @@ public class LeaderboardJsonExportTest extends AbstractJsonExportTest {
String jsonResultState = (String) jsonObject.get("resultState");
assertTrue(regattaLeaderboard.getName().equals(jsonLeaderboardName));
assertTrue(LeaderboardJsonGetServlet.ResultStates.Final.name().equals(jsonResultState));
assertTrue(LeaderboardsResource.ResultStates.Final.name().equals(jsonResultState));
// resultTimepoint should be null if there are no 'final' results yet
TimePoint resultTimePoint = parseTimepointFromJsonString((String) jsonObject.get("resultTimepoint"));
TimePoint resultTimePoint = parseTimepointFromJsonNumber((Long) jsonObject.get("resultTimepoint"));
assertNull(resultTimePoint);
JSONArray jsonCompetitors = (JSONArray) jsonObject.get("competitors");
@@ -109,12 +106,10 @@ public class LeaderboardJsonExportTest extends AbstractJsonExportTest {
public void testExportLeaderboardWithFinalResultStateAsJson() throws Exception {
dropAndCreateRegatta();
Map<String, String> requestParameters = new HashMap<>();
requestParameters.put("leaderboardName", regatta.getName());
requestParameters.put("resultState", LeaderboardJsonGetServlet.ResultStates.Final.name());
String jsonString = callJsonHttpServlet(new LeaderboardJsonGetServlet(), "GET", requestParameters);
LeaderboardsResource resource = spyResource(new LeaderboardsResource());
Response leaderboardReponse = resource.getLeaderboard(regatta.getName(), LeaderboardsResource.ResultStates.Final, null);
String jsonString = (String) leaderboardReponse.getEntity();
Object obj= JSONValue.parse(jsonString);
JSONObject jsonObject = (JSONObject) obj;
@@ -122,18 +117,21 @@ public class LeaderboardJsonExportTest extends AbstractJsonExportTest {
String jsonResultState = (String) jsonObject.get("resultState");
assertTrue(regattaLeaderboard.getName().equals(jsonLeaderboardName));
assertTrue(LeaderboardJsonGetServlet.ResultStates.Final.name().equals(jsonResultState));
assertTrue(LeaderboardsResource.ResultStates.Final.name().equals(jsonResultState));
TimePoint resultTimePoint = parseTimepointFromJsonString((String) jsonObject.get("resultTimepoint"));
TimePoint resultTimePoint = parseTimepointFromJsonNumber((Long) jsonObject.get("resultTimepoint"));
assertNull(resultTimePoint);
regattaLeaderboard.getScoreCorrection().setTimePointOfLastCorrectionsValidity(MillisecondsTimePoint.now());
jsonString = callJsonHttpServlet(new LeaderboardJsonGetServlet(), "GET", requestParameters);
regattaLeaderboard.getScoreCorrection().setTimePointOfLastCorrectionsValidity(MillisecondsTimePoint.now());
obj= JSONValue.parse(jsonString);
LeaderboardsResource resource2 = spyResource(new LeaderboardsResource());
Response leaderboardReponse2 = resource2.getLeaderboard(regatta.getName(), LeaderboardsResource.ResultStates.Final, null);
String jsonString2 = (String) leaderboardReponse2.getEntity();
obj= JSONValue.parse(jsonString2);
jsonObject = (JSONObject) obj;
resultTimePoint = parseTimepointFromJsonString((String) jsonObject.get("resultTimepoint"));
resultTimePoint = parseTimepointFromJsonNumber((Long) jsonObject.get("resultTimepoint"));
assertNotNull(resultTimePoint);
}
@@ -141,12 +139,10 @@ public class LeaderboardJsonExportTest extends AbstractJsonExportTest {
public void testExportLeaderboardWithLiveResultStateAsJson() throws Exception {
dropAndCreateRegatta();
Map<String, String> requestParameters = new HashMap<>();
requestParameters.put("leaderboardName", regatta.getName());
requestParameters.put("resultState", LeaderboardJsonGetServlet.ResultStates.Live.name());
String jsonString = callJsonHttpServlet(new LeaderboardJsonGetServlet(), "GET", requestParameters);
LeaderboardsResource resource = spyResource(new LeaderboardsResource());
Response leaderboardReponse = resource.getLeaderboard(regatta.getName(), LeaderboardsResource.ResultStates.Live, null);
String jsonString = (String) leaderboardReponse.getEntity();
Object obj= JSONValue.parse(jsonString);
JSONObject jsonObject = (JSONObject) obj;
@@ -154,85 +150,29 @@ public class LeaderboardJsonExportTest extends AbstractJsonExportTest {
String jsonResultState = (String) jsonObject.get("resultState");
assertTrue(regattaLeaderboard.getName().equals(jsonLeaderboardName));
assertTrue(LeaderboardJsonGetServlet.ResultStates.Live.name().equals(jsonResultState));
assertTrue(LeaderboardsResource.ResultStates.Live.name().equals(jsonResultState));
TimePoint resultTimePoint = parseTimepointFromJsonString((String) jsonObject.get("resultTimepoint"));
TimePoint resultTimePoint = parseTimepointFromJsonNumber((Long) jsonObject.get("resultTimepoint"));
assertNotNull(resultTimePoint);
JSONArray jsonCompetitors = (JSONArray) jsonObject.get("competitors");
assertTrue(jsonCompetitors.size() == 4);
}
@Test
public void testBypassSmartFutureCacheInExportLeaderboardAsJson() throws Exception {
dropAndCreateRegatta();
Map<String, String> requestParameters = new HashMap<>();
requestParameters.put("leaderboardName", regatta.getName());
requestParameters.put("resultState", LeaderboardJsonGetServlet.ResultStates.Live.name());
requestParameters.put("useCache", "false");
regattaLeaderboard.getScoreCorrection().setTimePointOfLastCorrectionsValidity(MillisecondsTimePoint.now());
Thread.sleep(100);
// call the servlet for the first time
String jsonString = callJsonHttpServlet(new LeaderboardJsonGetServlet(), "GET", requestParameters);
Object obj= JSONValue.parse(jsonString);
JSONObject jsonObject = (JSONObject) obj;
String jsonLeaderboardName = (String) jsonObject.get("name");
String jsonResultState = (String) jsonObject.get("resultState");
assertTrue(regattaLeaderboard.getName().equals(jsonLeaderboardName));
assertTrue(LeaderboardJsonGetServlet.ResultStates.Live.name().equals(jsonResultState));
TimePoint resultTimePoint = parseTimepointFromJsonString((String) jsonObject.get("resultTimepoint"));
TimePoint requestTimepoint = parseTimepointFromJsonString((String) jsonObject.get("requestTimepoint"));
assertNotNull(resultTimePoint);
regattaLeaderboard.getScoreCorrection().setTimePointOfLastCorrectionsValidity(MillisecondsTimePoint.now());
Thread.sleep(100);
// second call - it's expected NOT to get the same result as we bypass the cache
String jsonString2 = callJsonHttpServlet(new LeaderboardJsonGetServlet(), "GET", requestParameters);
Object obj2= JSONValue.parse(jsonString2);
JSONObject jsonObject2 = (JSONObject) obj2;
String jsonLeaderboardName2 = (String) jsonObject2.get("name");
String jsonResultState2 = (String) jsonObject2.get("resultState");
assertTrue(jsonLeaderboardName.equals(jsonLeaderboardName2));
assertTrue(jsonResultState.equals(jsonResultState2));
TimePoint resultTimePoint2 = parseTimepointFromJsonString((String) jsonObject2.get("resultTimepoint"));
TimePoint requestTimepoint2 = parseTimepointFromJsonString((String) jsonObject2.get("requestTimepoint"));
assertNotNull(resultTimePoint2);
System.out.println("resultTimePoint: " + resultTimePoint.toString());
System.out.println("resultTimePoint2: " + resultTimePoint2.toString());
assertFalse(resultTimePoint.equals(resultTimePoint2));
assertFalse(requestTimepoint.equals(requestTimepoint2));
}
@Test
public void testSmartFutureCacheInExportLeaderboardAsJson() throws Exception {
dropAndCreateRegatta();
Map<String, String> requestParameters = new HashMap<>();
requestParameters.put("leaderboardName", regatta.getName());
requestParameters.put("resultState", LeaderboardJsonGetServlet.ResultStates.Final.name());
TimePoint now = MillisecondsTimePoint.now();
regattaLeaderboard.getScoreCorrection().setTimePointOfLastCorrectionsValidity(now);
Thread.sleep(100);
// call the servlet for the first time
String jsonString = callJsonHttpServlet(new LeaderboardJsonGetServlet(), "GET", requestParameters);
LeaderboardsResource resource = spyResource(new LeaderboardsResource());
Response leaderboardReponse = resource.getLeaderboard(regatta.getName(), LeaderboardsResource.ResultStates.Final, null);
String jsonString = (String) leaderboardReponse.getEntity();
Object obj= JSONValue.parse(jsonString);
JSONObject jsonObject = (JSONObject) obj;
@@ -240,17 +180,18 @@ public class LeaderboardJsonExportTest extends AbstractJsonExportTest {
String jsonResultState = (String) jsonObject.get("resultState");
assertTrue(regattaLeaderboard.getName().equals(jsonLeaderboardName));
assertTrue(LeaderboardJsonGetServlet.ResultStates.Final.name().equals(jsonResultState));
assertTrue(LeaderboardsResource.ResultStates.Final.name().equals(jsonResultState));
TimePoint resultTimePoint = parseTimepointFromJsonString((String) jsonObject.get("resultTimepoint"));
TimePoint requestTimepoint = parseTimepointFromJsonString((String) jsonObject.get("requestTimepoint"));
TimePoint resultTimePoint = parseTimepointFromJsonNumber((Long) jsonObject.get("resultTimepoint"));
assertNotNull(resultTimePoint);
// second call - it's expected to get the same result from the cache
Thread.sleep(100);
String jsonString2 = callJsonHttpServlet(new LeaderboardJsonGetServlet(), "GET", requestParameters);
LeaderboardsResource resource2 = spyResource(new LeaderboardsResource());
Response leaderboardReponse2 = resource2.getLeaderboard(regatta.getName(), LeaderboardsResource.ResultStates.Final, null);
String jsonString2 = (String) leaderboardReponse2.getEntity();
Object obj2= JSONValue.parse(jsonString2);
JSONObject jsonObject2 = (JSONObject) obj2;
@@ -260,13 +201,9 @@ public class LeaderboardJsonExportTest extends AbstractJsonExportTest {
assertTrue(jsonLeaderboardName.equals(jsonLeaderboardName2));
assertTrue(jsonResultState.equals(jsonResultState2));
TimePoint resultTimePoint2 = parseTimepointFromJsonString((String) jsonObject2.get("resultTimepoint"));
TimePoint requestTimepoint2 = parseTimepointFromJsonString((String) jsonObject2.get("requestTimepoint"));
TimePoint resultTimePoint2 = parseTimepointFromJsonNumber((Long) jsonObject.get("resultTimepoint"));
assertNotNull(resultTimePoint2);
assertTrue(resultTimePoint.equals(resultTimePoint2));
System.out.println("requestTimepoint: " + requestTimepoint.toString());
System.out.println("requestTimepoint2: " + requestTimepoint2.toString());
assertFalse(requestTimepoint.equals(requestTimepoint2));
}
@Test
@@ -274,12 +211,10 @@ public class LeaderboardJsonExportTest extends AbstractJsonExportTest {
dropAndCreateRegatta();
Integer maxCompetitorsCount = 2;
Map<String, String> requestParameters = new HashMap<>();
requestParameters.put("leaderboardName", regatta.getName());
requestParameters.put("resultState", LeaderboardJsonGetServlet.ResultStates.Final.name());
String jsonString = callJsonHttpServlet(new LeaderboardJsonGetServlet(), "GET", requestParameters);
LeaderboardsResource resource = spyResource(new LeaderboardsResource());
Response leaderboardReponse = resource.getLeaderboard(regatta.getName(), LeaderboardsResource.ResultStates.Final, null);
String jsonString = (String) leaderboardReponse.getEntity();
Object obj= JSONValue.parse(jsonString);
JSONObject jsonObject = (JSONObject) obj;
@@ -287,16 +222,18 @@ public class LeaderboardJsonExportTest extends AbstractJsonExportTest {
String jsonResultState = (String) jsonObject.get("resultState");
assertTrue(regattaLeaderboard.getName().equals(jsonLeaderboardName));
assertTrue(LeaderboardJsonGetServlet.ResultStates.Final.name().equals(jsonResultState));
assertTrue(LeaderboardsResource.ResultStates.Final.name().equals(jsonResultState));
TimePoint resultTimePoint = parseTimepointFromJsonString((String) jsonObject.get("resultTimepoint"));
TimePoint resultTimePoint = parseTimepointFromJsonNumber((Long) jsonObject.get("resultTimepoint"));
assertNull(resultTimePoint);
regattaLeaderboard.getScoreCorrection().setTimePointOfLastCorrectionsValidity(MillisecondsTimePoint.now());
// first call for 'all' competitors
requestParameters.put("maxCompetitorsCount", null);
jsonString = callJsonHttpServlet(new LeaderboardJsonGetServlet(), "GET", requestParameters);
LeaderboardsResource resource2 = spyResource(new LeaderboardsResource());
Response leaderboardReponse2 = resource2.getLeaderboard(regatta.getName(), LeaderboardsResource.ResultStates.Final, null);
jsonString = (String) leaderboardReponse2.getEntity();
obj= JSONValue.parse(jsonString);
jsonObject = (JSONObject) obj;
JSONArray jsonCompetitors = (JSONArray) jsonObject.get("competitors");
@@ -305,8 +242,10 @@ public class LeaderboardJsonExportTest extends AbstractJsonExportTest {
Thread.sleep(100);
// second call with maxCompetitorsCount set
requestParameters.put("maxCompetitorsCount", maxCompetitorsCount.toString());
jsonString = callJsonHttpServlet(new LeaderboardJsonGetServlet(), "GET", requestParameters);
LeaderboardsResource resource3 = spyResource(new LeaderboardsResource());
Response leaderboardReponse3 = resource3.getLeaderboard(regatta.getName(), LeaderboardsResource.ResultStates.Final, maxCompetitorsCount);
jsonString = (String) leaderboardReponse3.getEntity();
obj= JSONValue.parse(jsonString);
jsonObject = (JSONObject) obj;
jsonCompetitors = (JSONArray) jsonObject.get("competitors");
@@ -315,8 +254,10 @@ public class LeaderboardJsonExportTest extends AbstractJsonExportTest {
Thread.sleep(100);
// third call for 'all' competitors
requestParameters.put("maxCompetitorsCount", null);
jsonString = callJsonHttpServlet(new LeaderboardJsonGetServlet(), "GET", requestParameters);
LeaderboardsResource resource4 = spyResource(new LeaderboardsResource());
Response leaderboardReponse4 = resource4.getLeaderboard(regatta.getName(), LeaderboardsResource.ResultStates.Final, null);
jsonString = (String) leaderboardReponse4.getEntity();
obj= JSONValue.parse(jsonString);
jsonObject = (JSONObject) obj;
jsonCompetitors = (JSONArray) jsonObject.get("competitors");
@@ -39,16 +39,7 @@
<dispatcher>INCLUDE</dispatcher>
<dispatcher>ERROR</dispatcher>
</filter-mapping>
<servlet>
<servlet-name>AdminApp</servlet-name>
<servlet-class>com.sap.sailing.server.gateway.impl.AdminApp</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>AdminApp</servlet-name>
<url-pattern>/admin</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>RCConfiguration</servlet-name>
<servlet-class>com.sap.sailing.server.gateway.impl.rc.ConfigurationJsonGetServlet</servlet-class>
@@ -113,16 +104,6 @@
</servlet-mapping>
<!-- RACE COMMITTEE END -->
<servlet>
<servlet-name>RegattaAndRaceDataJsonGetServlet</servlet-name>
<servlet-class>com.sap.sailing.server.gateway.impl.RegattaAndRaceDataJsonGetServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>RegattaAndRaceDataJsonGetServlet</servlet-name>
<url-pattern>/moderator</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>ExpeditionThroughHttpPostServlet</servlet-name>
<servlet-class>com.sap.sailing.server.gateway.impl.ExpeditionThroughHttpPostServlet</servlet-class>
@@ -132,86 +113,6 @@
<url-pattern>/expedition</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>EventsJsonGetServlet</servlet-name>
<servlet-class>com.sap.sailing.server.gateway.impl.EventsJsonGetServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>EventsJsonGetServlet</servlet-name>
<url-pattern>/events</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>EventRaceStatesServlet</servlet-name>
<servlet-class>com.sap.sailing.server.gateway.impl.EventRaceStatesJsonGetServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>EventRaceStatesServlet</servlet-name>
<url-pattern>/eventRaceStates</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>RegattasJsonGetServlet</servlet-name>
<servlet-class>com.sap.sailing.server.gateway.impl.RegattasJsonGetServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>RegattasJsonGetServlet</servlet-name>
<url-pattern>/regattas</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>RegattaJsonGetServlet</servlet-name>
<servlet-class>com.sap.sailing.server.gateway.impl.RegattaJsonGetServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>RegattaJsonGetServlet</servlet-name>
<url-pattern>/regatta</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>LeaderboardsJsonGetServlet</servlet-name>
<servlet-class>com.sap.sailing.server.gateway.impl.LeaderboardsJsonGetServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>LeaderboardsJsonGetServlet</servlet-name>
<url-pattern>/leaderboards</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>LeaderboardJsonGetServlet</servlet-name>
<servlet-class> com.sap.sailing.server.gateway.impl.LeaderboardJsonGetServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>LeaderboardJsonGetServlet</servlet-name>
<url-pattern>/leaderboard</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>LeaderboardGroupJsonGetServlet</servlet-name>
<servlet-class>com.sap.sailing.server.gateway.impl.LeaderboardGroupJsonGetServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>LeaderboardGroupJsonGetServlet</servlet-name>
<url-pattern>/leaderboardgroup</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>LeaderboardGroupsJsonGetServlet</servlet-name>
<servlet-class>com.sap.sailing.server.gateway.impl.LeaderboardGroupsJsonGetServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>LeaderboardGroupsJsonGetServlet</servlet-name>
<url-pattern>/leaderboardgroups</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>WindJsonGetServlet</servlet-name>
<servlet-class>com.sap.sailing.server.gateway.impl.WindJsonGetServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>WindJsonGetServlet</servlet-name>
<url-pattern>/wind</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>WindImportServlet</servlet-name>
<servlet-class>com.sap.sailing.server.gateway.expeditionimport.WindImportServlet</servlet-class>
@@ -1,453 +0,0 @@
package com.sap.sailing.server.gateway.impl;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.SocketException;
import java.net.URISyntaxException;
import java.net.URL;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import com.sap.sailing.domain.base.RaceDefinition;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.common.Bearing;
import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.NoWindException;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.SpeedWithBearing;
import com.sap.sailing.domain.common.Wind;
import com.sap.sailing.domain.common.WindSource;
import com.sap.sailing.domain.common.WindSourceType;
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
import com.sap.sailing.domain.common.impl.DegreePosition;
import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
import com.sap.sailing.domain.common.impl.WindImpl;
import com.sap.sailing.domain.common.impl.WindSourceImpl;
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.WindTrack;
import com.sap.sailing.domain.tractracadapter.RaceRecord;
import com.sap.sailing.server.gateway.SailingServerHttpServlet;
import com.sap.sse.InvalidDateException;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util;
import com.sap.sse.common.impl.MillisecondsTimePoint;
public class AdminApp extends SailingServerHttpServlet {
private static final long serialVersionUID = -6849138354941569249L;
private static final String ACTION_NAME_ADD_WIND_TO_MARKS = "addwindtomarksforonehour";
private static final String ACTION_NAME_LIST_RACES_IN_REGATTA = "listracesinregatta";
private static final String ACTION_NAME_STOP_REGATTA = "stopregatta";
private static final String ACTION_NAME_STOP_RACE = "stoprace";
private static final String ACTION_NAME_SET_WIND = "setwind";
private static final String ACTION_NAME_REMOVE_WIND = "removewind";
private static final String ACTION_NAME_SHOW_WIND = "showwind";
private static final String ACTION_NAME_SET_AVERAGING = "setaveraging";
private static final String PARAM_NAME_WIND_AVERAGING_INTERVAL_MILLIS = "windaveragingintervalmillis";
private static final String PARAM_NAME_SPEED_AVERAGING_INTERVAL_MILLIS = "speedaveragingintervalmillis";
private static final String PARAM_NAME_EVENT_JSON_URL = "eventJSONURL";
private static final String PARAM_NAME_FROM_TIME = "fromtime";
private static final String PARAM_NAME_FROM_TIME_MILLIS = "fromtimeasmillis";
private static final String PARAM_NAME_TO_TIME = "totime";
private static final String PARAM_NAME_TO_TIME_MILLIS = "totimeasmillis";
private static final String PARAM_NAME_WINDSOURCE_NAME = "sourcename";
private static final String PARAM_NAME_BEARING = "truebearingdegrees";
private static final String PARAM_NAME_SPEED = "knotspeed";
private static final String PARAM_NAME_LATDEG = "latdeg";
private static final String PARAM_NAME_LNGDEG = "lngdeg";
private static final String ACTION_NAME_LIST_WINDTRACKERS = "listwindtrackers";
private static final String ACTION_NAME_SUBSCRIBE_RACE_FOR_EXPEDITION_WIND = "receiveexpeditionwind";
private static final String ACTION_NAME_UNSUBSCRIBE_RACE_FOR_EXPEDITION_WIND = "stopreceivingexpeditionwind";
private static final String PARAM_NAME_PORT = "port";
private static final String PARAM_NAME_CORRECT_EXPEDITION_WIND_BEARING_BY_DECLINATION = "correctexpeditionwindbearingbydeclination";
public AdminApp() {
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
String action = req.getParameter(PARAM_ACTION);
if (action != null) {
if (ACTION_NAME_STOP_REGATTA.equals(action)) {
stopRegatta(req, resp);
} else if (ACTION_NAME_LIST_RACES_IN_REGATTA.equals(action)) {
listRacesInEvent(req, resp);
} else if (ACTION_NAME_STOP_RACE.equals(action)) {
stopRace(req, resp);
} else if (ACTION_NAME_SUBSCRIBE_RACE_FOR_EXPEDITION_WIND.equals(action)) {
startReceivingExpeditionWindForRace(req, resp);
} else if (ACTION_NAME_UNSUBSCRIBE_RACE_FOR_EXPEDITION_WIND.equals(action)) {
stopReceivingExpeditionWindForRace(req, resp);
} else if (ACTION_NAME_LIST_WINDTRACKERS.equals(action)) {
listWindTrackers(req, resp);
} else if (ACTION_NAME_SET_WIND.equals(action)) {
setWind(req, resp);
} else if (ACTION_NAME_REMOVE_WIND.equals(action)) {
removeWind(req, resp);
} else if (ACTION_NAME_SHOW_WIND.equals(action)) {
showWind(req, resp);
} else if (ACTION_NAME_ADD_WIND_TO_MARKS.equals(action)) {
addWindToMarks(req, resp);
} else if (ACTION_NAME_SET_AVERAGING.equals(action)) {
setAveraging(req, resp);
} else {
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Unknown action \""+action+"\"");
}
} else {
resp.getWriter().println("Hello admin!");
}
} catch (Exception e) {
resp.getWriter().println("Error processing request:");
e.printStackTrace(resp.getWriter());
}
}
private void setAveraging(HttpServletRequest req, HttpServletResponse resp) throws IOException {
Regatta regatta = getRegatta(req);
if (regatta == null) {
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Event not found");
} else {
RaceDefinition race = getRaceDefinition(req);
if (race == null) {
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Race not found");
} else {
DynamicTrackedRace trackedRace = getService().getOrCreateTrackedRegatta(regatta).getTrackedRace(race);
String windAveragingIntervalInMIllis = req.getParameter(PARAM_NAME_WIND_AVERAGING_INTERVAL_MILLIS);
if (windAveragingIntervalInMIllis != null) {
trackedRace.setMillisecondsOverWhichToAverageWind(Long.valueOf(windAveragingIntervalInMIllis));
}
String speedAveragingIntervalInMIllis = req.getParameter(PARAM_NAME_SPEED_AVERAGING_INTERVAL_MILLIS);
if (speedAveragingIntervalInMIllis != null) {
trackedRace.setMillisecondsOverWhichToAverageSpeed(Long.valueOf(speedAveragingIntervalInMIllis));
}
}
}
}
private void listRacesInEvent(HttpServletRequest req, HttpServletResponse resp) throws IOException, ParseException,
org.json.simple.parser.ParseException, URISyntaxException {
URL jsonURL = new URL(req.getParameter(PARAM_NAME_EVENT_JSON_URL));
List<RaceRecord> raceRecords = getTracTracAdapterFactory()
.getOrCreateTracTracAdapter(getService().getBaseDomainFactory()).getTracTracRaceRecords(jsonURL, true)
.getB();
JSONArray result = new JSONArray();
for (RaceRecord raceRecord : raceRecords) {
JSONObject jsonRaceRecord = new JSONObject();
jsonRaceRecord.put("name", raceRecord.getName());
jsonRaceRecord.put("ID", raceRecord.getID());
jsonRaceRecord.put("paramURL", raceRecord.getParamURL());
jsonRaceRecord.put("replayURL", raceRecord.getReplayURL());
result.add(jsonRaceRecord);
}
result.writeJSONString(resp.getWriter());
}
private void addWindToMarks(HttpServletRequest req, HttpServletResponse resp) throws IOException,
InvalidDateException, NoWindException {
Regatta regatta = getRegatta(req);
if (regatta == null) {
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Event not found");
} else {
RaceDefinition race = getRaceDefinition(req);
if (race == null) {
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Race not found");
} else {
TrackedRace trackedRace = getService().getOrCreateTrackedRegatta(regatta).getTrackedRace(race);
TimePoint time = readTimePointParam(req, PARAM_NAME_TIME, PARAM_NAME_TIME_MILLIS, MillisecondsTimePoint.now());
TimePoint oneHourLater = new MillisecondsTimePoint(time.asMillis()+3600*1000);
String[] latitudes = req.getParameterValues(PARAM_NAME_LATDEG);
String[] longitudes = req.getParameterValues(PARAM_NAME_LNGDEG);
JSONArray result = new JSONArray();
if (latitudes != null && longitudes != null) {
for (int i = 0; i < Math.max(latitudes.length, longitudes.length); i++) {
double latDeg = Double.valueOf(latitudes[i]);
double lngDeg = Double.valueOf(longitudes[i]);
DegreePosition pos = new DegreePosition(latDeg, lngDeg);
Wind wind = trackedRace.getWind(pos, time);
if (wind == null) {
throw new NoWindException("No wind set for race "+race.getName()+
" in regatta "+regatta.getName()+" while computing wind lines on marks");
}
Distance d = wind.travel(time, oneHourLater);
Position to = pos.translateGreatCircle(wind.getBearing(), d);
JSONObject record = new JSONObject();
record.put("markLatDeg", latDeg);
record.put("markLngDeg", lngDeg);
record.put("windTrueBearingDeg", wind.getBearing().getDegrees());
record.put("windKnotSpeed", wind.getKnots());
record.put("toLatDeg", to.getLatDeg());
record.put("toLngDeg", to.getLngDeg());
result.add(record);
}
}
result.writeJSONString(resp.getWriter());
}
}
}
private void showWind(HttpServletRequest req, HttpServletResponse resp) throws IOException, InvalidDateException {
Regatta regatta = getRegatta(req);
if (regatta == null) {
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Event not found");
} else {
RaceDefinition race = getRaceDefinition(req);
if (race == null) {
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Race not found");
} else {
TrackedRace trackedRace = getService().getOrCreateTrackedRegatta(regatta).getTrackedRace(race);
TimePoint from = readTimePointParam(req, PARAM_NAME_FROM_TIME, PARAM_NAME_FROM_TIME_MILLIS,
trackedRace.getStartOfRace()==null?new MillisecondsTimePoint(0):
/* 24h before race start */ new MillisecondsTimePoint(trackedRace.getStartOfRace().asMillis()-24*3600*1000));
TimePoint to = readTimePointParam(req, PARAM_NAME_TO_TIME, PARAM_NAME_TO_TIME_MILLIS, MillisecondsTimePoint.now());
JSONObject jsonWindTracks = new JSONObject();
List<WindSource> windSources = new ArrayList<WindSource>();
for (WindSource windSource : trackedRace.getWindSources()) {
windSources.add(windSource);
}
for (WindSource windSourceToExclude : trackedRace.getWindSourcesToExclude()) {
windSources.remove(windSourceToExclude);
}
windSources.add(new WindSourceImpl(WindSourceType.COMBINED));
for (WindSource windSource : windSources) {
JSONArray jsonWindArray = new JSONArray();
WindTrack windTrack = trackedRace.getOrCreateWindTrack(windSource);
windTrack.lockForRead();
try {
Iterator<Wind> windIter = windTrack.getFixesIterator(from, /* inclusive */true);
while (windIter.hasNext()) {
Wind wind = windIter.next();
if (wind.getTimePoint().compareTo(to) > 0) {
break;
}
JSONObject jsonWind = new JSONObject();
jsonWind.put("truebearingdeg", wind.getBearing().getDegrees());
jsonWind.put("knotspeed", wind.getKnots());
jsonWind.put("meterspersecondspeed", wind.getMetersPerSecond());
if (wind.getTimePoint() != null) {
jsonWind.put("timepoint", wind.getTimePoint().asMillis());
jsonWind.put("dampenedtruebearingdeg",
windTrack.getAveragedWind(wind.getPosition(), wind.getTimePoint())
.getBearing().getDegrees());
jsonWind.put("dampenedknotspeed",
windTrack.getAveragedWind(wind.getPosition(), wind.getTimePoint()).getKnots());
jsonWind.put("dampenedmeterspersecondspeed",
windTrack.getAveragedWind(wind.getPosition(), wind.getTimePoint())
.getMetersPerSecond());
}
if (wind.getPosition() != null) {
jsonWind.put("latdeg", wind.getPosition().getLatDeg());
jsonWind.put("lngdeg", wind.getPosition().getLngDeg());
}
jsonWindArray.add(jsonWind);
}
} finally {
windTrack.unlockAfterRead();
}
jsonWindTracks.put(windSource.toString(), jsonWindArray);
}
jsonWindTracks.writeJSONString(resp.getWriter());
}
}
}
private void setWind(HttpServletRequest req, HttpServletResponse resp) throws IOException {
Regatta e = getRegatta(req);
Iterable<Regatta> regattas;
if (e == null) {
regattas = getService().getAllRegattas();
} else {
regattas = Collections.singleton(e);
}
for (Regatta regatta : regattas) {
RaceDefinition r = getRaceDefinition(regatta, req);
Iterable<RaceDefinition> races;
if (r == null) {
races = regatta.getAllRaces();
} else {
races = Collections.singleton(r);
}
for (RaceDefinition race : races) {
String bearingAsString = req.getParameter(PARAM_NAME_BEARING);
if (bearingAsString != null) {
Bearing bearing = new DegreeBearingImpl(Double.valueOf(bearingAsString));
String speedAsString = req.getParameter(PARAM_NAME_SPEED);
SpeedWithBearing speed;
if (speedAsString == null) {
// only bearing provided; no speed; assume speed as 1kn
speed = new KnotSpeedWithBearingImpl(1, bearing);
} else {
speed = new KnotSpeedWithBearingImpl(Double.valueOf(speedAsString), bearing);
}
Position p = null;
String lat = req.getParameter(PARAM_NAME_LATDEG);
if (lat != null) {
String lng = req.getParameter(PARAM_NAME_LNGDEG);
if (lng != null) {
p = new DegreePosition(Double.valueOf(lat), Double.valueOf(lng));
}
}
try {
TimePoint timePoint = readTimePointParam(req, PARAM_NAME_TIME, PARAM_NAME_TIME_MILLIS, MillisecondsTimePoint.now());
Wind wind = new WindImpl(p, timePoint, speed);
final DynamicTrackedRace trackedRace = getService().getOrCreateTrackedRegatta(regatta).getExistingTrackedRace(race);
if (trackedRace != null) {
trackedRace.recordWind(wind, new WindSourceImpl(WindSourceType.WEB));
}
} catch (InvalidDateException ex) {
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Couldn't parse time specification " + ex.getMessage());
}
} else {
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "wind bearing parameter "+PARAM_NAME_BEARING+" missing");
}
}
}
}
private void removeWind(HttpServletRequest req, HttpServletResponse resp) throws IOException {
Regatta regatta = getRegatta(req);
if (regatta == null) {
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Event not found");
} else {
RaceDefinition race = getRaceDefinition(req);
if (race == null) {
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Race not found");
} else {
String sourceName = req.getParameter(PARAM_NAME_WINDSOURCE_NAME);
if (sourceName == null) {
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Wind source name not provided");
} else {
WindSourceType windSourceType = WindSourceType.valueOf(sourceName);
if (windSourceType == null) {
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Wind source type name " + sourceName + " unknown");
} else {
try {
final DynamicTrackedRace trackedRace = getService().getOrCreateTrackedRegatta(regatta)
.getTrackedRace(race);
WindTrack windTrack = trackedRace.getOrCreateWindTrack(trackedRace.getWindSources(windSourceType).iterator().next());
TimePoint timePoint = readTimePointParam(req, PARAM_NAME_TIME, PARAM_NAME_TIME_MILLIS,
MillisecondsTimePoint.now());
Wind wind = windTrack.getLastFixAtOrBefore(timePoint);
if (wind != null && wind.getTimePoint().equals(timePoint)) {
windTrack.remove(wind);
resp.getWriter().println("Successfully removed entry "+wind);
} else {
resp.getWriter().println(
"No wind recorded for regatta " + regatta.getName() + " and race " + race.getName()
+ " at " + timePoint.asDate()+". No error, just no effect :-)");
}
} catch (InvalidDateException e) {
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Couldn't parse time specification " + e.getMessage());
}
}
}
}
}
}
private void listWindTrackers(HttpServletRequest req, HttpServletResponse resp) throws IOException {
JSONArray windTrackers = new JSONArray();
for (Util.Triple<Regatta, RaceDefinition, String> regattaAndRaceAndPort : getService().getWindTrackedRaces()) {
JSONObject windTracker = new JSONObject();
windTracker.put("regattaname", regattaAndRaceAndPort.getA().getName());
windTracker.put("racename", regattaAndRaceAndPort.getB().getName());
windTracker.put("windtrackerinfo", regattaAndRaceAndPort.getC());
windTrackers.add(windTracker);
}
windTrackers.writeJSONString(resp.getWriter());
}
private void stopReceivingExpeditionWindForRace(HttpServletRequest req, HttpServletResponse resp) throws SocketException, IOException {
Regatta regatta = getRegatta(req);
if (regatta == null) {
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Event not found");
} else {
RaceDefinition race = getRaceDefinition(req);
if (race == null) {
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Race not found");
} else {
getService().stopTrackingWind(regatta, race);
}
}
}
private void startReceivingExpeditionWindForRace(HttpServletRequest req, HttpServletResponse resp) throws Exception {
Regatta regatta = getRegatta(req);
if (regatta == null) {
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Event not found");
} else {
RaceDefinition race = getRaceDefinition(req);
if (race == null) {
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Race not found");
} else {
String portParam = req.getParameter(PARAM_NAME_PORT);
if (portParam == null) {
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "No port parameter provided");
} else {
String correctByDeclination = req
.getParameter(PARAM_NAME_CORRECT_EXPEDITION_WIND_BEARING_BY_DECLINATION);
getService().startTrackingWind(regatta, race, Boolean.valueOf(correctByDeclination));
}
}
}
}
private void stopRegatta(HttpServletRequest req, HttpServletResponse resp) throws MalformedURLException, IOException,
InterruptedException {
Regatta regatta = getRegatta(req);
if (regatta != null) {
getService().stopTracking(regatta);
} else {
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Event not found");
}
}
private void stopRace(HttpServletRequest req, HttpServletResponse resp) throws MalformedURLException, IOException, InterruptedException {
Regatta regatta = getRegatta(req);
if (regatta != null) {
RaceDefinition race = getRaceDefinition(req);
if (race == null) {
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Race not found");
} else {
getService().stopTracking(regatta, race);
}
} else {
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Regatta not found");
}
}
}
@@ -1,70 +0,0 @@
package com.sap.sailing.server.gateway.impl;
import java.io.IOException;
import java.util.UUID;
import java.util.logging.Logger;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.json.simple.JSONObject;
import com.sap.sailing.domain.base.Event;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.server.gateway.AbstractJsonHttpServlet;
import com.sap.sailing.server.gateway.jaxrs.api.EventRaceStatesSerializer;
import com.sap.sse.common.Util.Pair;
public class EventRaceStatesJsonGetServlet extends AbstractJsonHttpServlet {
private static final long serialVersionUID = -4820965681871902242L;
private final static Logger logger = Logger.getLogger(EventRaceStatesJsonGetServlet.class.getName());
private static final String PARAM_NAME_EVENTID = "eventId";
private static final String PARAM_NAME_FILTER_BY_LEADERBOARD = "filterByLeaderboard";
private static final String PARAM_NAME_FILTER_BY_COURSEAREA = "filterByCourseArea";
private static final String PARAM_NAME_FILTER_BY_DAYOFFSET = "filterByDayOffset";
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String eventIdParam = request.getParameter(PARAM_NAME_EVENTID);
String filterByCourseAreaParam = request.getParameter(PARAM_NAME_FILTER_BY_COURSEAREA);
String filterByLeaderboardParam = request.getParameter(PARAM_NAME_FILTER_BY_LEADERBOARD);
String filterByDayOffsetParam = request.getParameter(PARAM_NAME_FILTER_BY_DAYOFFSET);
if (eventIdParam == null) {
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "You need to specify a event id using the "+
PARAM_NAME_EVENTID + " parameter");
} else {
Event event = getService().getEvent(convertIdentifierStringToUuid(eventIdParam));
if (event == null) {
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Event "+eventIdParam+" not found");
} else {
EventRaceStatesSerializer eventRaceStatesSerializer = new EventRaceStatesSerializer(filterByCourseAreaParam,
filterByLeaderboardParam, filterByDayOffsetParam, getService());
JSONObject result = eventRaceStatesSerializer.serialize(new Pair<Event, Iterable<Leaderboard>>(event, getService().getLeaderboards().values()));
setJsonResponseHeader(response);
result.writeJSONString(response.getWriter());
}
}
}
/**
* Tries to convert the given identifier to a UUID. When the identifier is not a UUID, null is returned.
* @param identifierToConvert the identifier as a String
* @return a UUID when the given identifier is a UUID, null otherwise
*/
private UUID convertIdentifierStringToUuid(String identifierToConvert) {
UUID result = null;
if (identifierToConvert != null) {
try {
result = UUID.fromString(identifierToConvert);
} catch (IllegalArgumentException iae) {
logger.warning("The identifier " + identifierToConvert + " could not be converted to a UUID");
//in this case null is returned
}
}
return result;
}
}
@@ -1,32 +0,0 @@
package com.sap.sailing.server.gateway.impl;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.json.simple.JSONArray;
import com.sap.sailing.domain.base.EventBase;
import com.sap.sailing.server.gateway.AbstractJsonHttpServlet;
import com.sap.sailing.server.gateway.serialization.JsonSerializer;
import com.sap.sailing.server.gateway.serialization.impl.CourseAreaJsonSerializer;
import com.sap.sailing.server.gateway.serialization.impl.EventBaseJsonSerializer;
import com.sap.sailing.server.gateway.serialization.impl.LeaderboardGroupBaseJsonSerializer;
import com.sap.sailing.server.gateway.serialization.impl.VenueJsonSerializer;
public class EventsJsonGetServlet extends AbstractJsonHttpServlet {
private static final long serialVersionUID = -7313766879733880267L;
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
JsonSerializer<EventBase> eventSerializer = new EventBaseJsonSerializer(new VenueJsonSerializer(new CourseAreaJsonSerializer()), new LeaderboardGroupBaseJsonSerializer());
JSONArray result = new JSONArray();
for (EventBase event : getService().getAllEvents()) {
result.add(eventSerializer.serialize(event));
}
setJsonResponseHeader(response);
result.writeJSONString(response.getWriter());
}
}
@@ -49,21 +49,22 @@ public class FileUploadServlet extends AbstractFileUploadServlet {
*/
final JSONArray resultList = new JSONArray();
for (FileItem fileItem : fileItems) {
JSONObject result = new JSONObject();
String fileExtension = "";
String fileType = fileItem.getContentType();
final JSONObject result = new JSONObject();
final String fileExtension;
final String fileType = fileItem.getContentType();
if (fileType.equals("image/jpeg")) {
fileExtension = ".jpg";
} else if (fileType.equals("image/png")) {
fileExtension = ".png";
} else {
fileExtension = "";
}
URI fileUri;
try {
if (fileItem.getSize() > 1024 * 1024 * MAX_SIZE_IN_MB) {
throw new WebApplicationException(Response.status(Status.BAD_REQUEST)
.entity("Image is larger than " + MAX_SIZE_IN_MB + "MB").build());
}
fileUri = getService().getFileStorageManagementService().getActiveFileStorageService()
final URI fileUri = getService().getFileStorageManagementService().getActiveFileStorageService()
.storeFile(fileItem.getInputStream(), fileExtension, fileItem.getSize());
result.put(JSON_FILE_NAME, fileItem.getName());
result.put(JSON_FILE_URI, fileUri.toString());
@@ -75,6 +76,12 @@ public class FileUploadServlet extends AbstractFileUploadServlet {
}
resultList.add(result);
}
resp.getOutputStream().write(resultList.toJSONString().getBytes("UTF-8"));
// surprise, surprise: see https://www.sencha.com/forum/showthread.php?132949-Fileupload-Invalid-JSON-string
// When sending a JSON response for a file upload, don't use application/json as the content type. It would lead
// to wrapping the content by a <pre> tag. Use text/html instead which should deliver the content to the app running
// in the browser unchanged.
resp.setContentType("text/html");
resp.setCharacterEncoding("UTF-8");
resp.getWriter().append(resultList.toJSONString());
}
}
@@ -1,124 +0,0 @@
package com.sap.sailing.server.gateway.impl;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import com.sap.sailing.domain.base.Fleet;
import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.common.LeaderboardNameConstants;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.leaderboard.LeaderboardGroup;
import com.sap.sailing.domain.leaderboard.MetaLeaderboard;
import com.sap.sailing.domain.leaderboard.RegattaLeaderboard;
import com.sap.sailing.domain.leaderboard.SettableScoreCorrection;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.server.gateway.AbstractJsonHttpServlet;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.impl.MillisecondsTimePoint;
public class LeaderboardGroupJsonGetServlet extends AbstractJsonHttpServlet {
private static final long serialVersionUID = 1351616646322956825L;
private static final String PARAM_NAME_LEADERBOARDGROUPNAME = "leaderboardGroupName";
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String leaderboardGroupName = req.getParameter(PARAM_NAME_LEADERBOARDGROUPNAME);
if (leaderboardGroupName == null) {
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Need to specify the name of a leaderboard group using the "+
PARAM_NAME_LEADERBOARDGROUPNAME+" parameter");
} else {
LeaderboardGroup leaderboardGroup = getService().getLeaderboardGroupByName(leaderboardGroupName);
if (leaderboardGroup == null) {
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "LeaderboardGroup "+leaderboardGroupName+" not found");
} else {
TimePoint timePoint = MillisecondsTimePoint.now();
JSONObject jsonLeaderboardGroup = new JSONObject();
jsonLeaderboardGroup.put("name", leaderboardGroup.getName());
jsonLeaderboardGroup.put("description", leaderboardGroup.getDescription());
jsonLeaderboardGroup.put("displayName", leaderboardGroup.getDisplayName());
jsonLeaderboardGroup.put("timepoint", timePoint.toString());
JSONArray jsonLeaderboardEntries = new JSONArray();
jsonLeaderboardGroup.put("leaderboards", jsonLeaderboardEntries);
for (Leaderboard leaderboard: leaderboardGroup.getLeaderboards()) {
boolean isMetaLeaderboard = leaderboard instanceof MetaLeaderboard ? true : false;
boolean isRegattaLeaderboard = leaderboard instanceof RegattaLeaderboard ? true : false;
JSONObject jsonLeaderboard = new JSONObject();
jsonLeaderboard.put("name", leaderboard.getName());
jsonLeaderboard.put("displayName", leaderboard.getDisplayName());
jsonLeaderboard.put("isMetaLeaderboard", isMetaLeaderboard);
jsonLeaderboard.put("isRegattaLeaderboard", isRegattaLeaderboard);
jsonLeaderboardEntries.add(jsonLeaderboard);
SettableScoreCorrection scoreCorrection = leaderboard.getScoreCorrection();
if (scoreCorrection != null) {
jsonLeaderboard.put("scoringComment", scoreCorrection.getComment());
TimePoint lastUpdateTimepoint = scoreCorrection.getTimePointOfLastCorrectionsValidity();
jsonLeaderboard.put("lastScoringUpdate", lastUpdateTimepoint != null ? lastUpdateTimepoint.asDate().toString(): null);
} else {
jsonLeaderboard.put("scoringComment", null);
jsonLeaderboard.put("lastScoringUpdate", null);
}
if (isRegattaLeaderboard) {
RegattaLeaderboard regattaLeaderboard = (RegattaLeaderboard) leaderboard;
Regatta regatta = regattaLeaderboard.getRegatta();
jsonLeaderboard.put("scoringScheme", leaderboard.getScoringScheme().getType());
jsonLeaderboard.put("regattaName", regatta.getName());
} else {
jsonLeaderboard.put("scoringScheme", leaderboard.getScoringScheme().getType());
jsonLeaderboard.put("regattaName", null);
JSONArray jsonSeriesEntries = new JSONArray();
jsonLeaderboard.put("series", jsonSeriesEntries);
// write a 'default' series to be conform with our common regatta structure
JSONObject jsonSeries = new JSONObject();
jsonSeriesEntries.add(jsonSeries);
jsonSeries.put("name", LeaderboardNameConstants.DEFAULT_SERIES_NAME);
jsonSeries.put("isMedalSeries", null);
JSONArray jsonFleetsEntries = new JSONArray();
jsonSeries.put("fleets", jsonFleetsEntries);
Fleet fleet = leaderboard.getFleet(LeaderboardNameConstants.DEFAULT_FLEET_NAME);
if (fleet != null) {
JSONObject jsonFleet = new JSONObject();
jsonFleet.put("name", fleet.getName());
jsonFleet.put("color", fleet.getColor() != null ? fleet.getColor().getAsHtml() : null);
jsonFleet.put("ordering", fleet.getOrdering());
jsonFleetsEntries.add(jsonFleet);
JSONArray jsonRacesEntries = new JSONArray();
jsonFleet.put("races", jsonRacesEntries);
for (RaceColumn raceColumn: leaderboard.getRaceColumns()) {
JSONObject jsonRaceColumn = new JSONObject();
jsonRaceColumn.put("name", raceColumn.getName());
jsonRaceColumn.put("isMedalRace" , raceColumn.isMedalRace());
TrackedRace trackedRace = raceColumn.getTrackedRace(fleet);
if (trackedRace != null) {
jsonRaceColumn.put("isTracked", true);
jsonRaceColumn.put("regattaName", trackedRace.getTrackedRegatta().getRegatta().getName());
jsonRaceColumn.put("trackedRaceName", trackedRace.getRace().getName());
jsonRaceColumn.put("hasGpsData", trackedRace.hasGPSData());
jsonRaceColumn.put("hasWindData", trackedRace.hasWindData());
} else {
jsonRaceColumn.put("isTracked", false);
jsonRaceColumn.put("trackedRaceName", null);
jsonRaceColumn.put("hasGpsData", false);
jsonRaceColumn.put("hasWindData", false);
}
jsonRacesEntries.add(jsonRaceColumn);
}
}
}
}
setJsonResponseHeader(resp);
jsonLeaderboardGroup.writeJSONString(resp.getWriter());
}
}
}
}
@@ -1,29 +0,0 @@
package com.sap.sailing.server.gateway.impl;
import java.io.IOException;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.json.simple.JSONArray;
import com.sap.sailing.domain.leaderboard.LeaderboardGroup;
import com.sap.sailing.server.gateway.AbstractJsonHttpServlet;
public class LeaderboardGroupsJsonGetServlet extends AbstractJsonHttpServlet {
private static final long serialVersionUID = -2193421590275280102L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
JSONArray jsonLeaderboardGroups = new JSONArray();
Map<String, LeaderboardGroup> leaderboardGroups = getService().getLeaderboardGroups();
for (String leaderboardGroupName : leaderboardGroups.keySet()) {
jsonLeaderboardGroups.add(leaderboardGroupName);
}
setJsonResponseHeader(resp);
jsonLeaderboardGroups.writeJSONString(resp.getWriter());
}
}
@@ -1,506 +0,0 @@
package com.sap.sailing.server.gateway.impl;
import java.io.IOException;
import java.io.StringWriter;
import java.text.SimpleDateFormat;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.logging.Logger;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.Fleet;
import com.sap.sailing.domain.base.Nationality;
import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.common.MaxPointsReason;
import com.sap.sailing.domain.common.NoWindException;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.leaderboard.LeaderboardCacheManager;
import com.sap.sailing.domain.leaderboard.SettableScoreCorrection;
import com.sap.sailing.domain.leaderboard.caching.LeaderboardCache;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.server.gateway.AbstractJsonHttpServlet;
import com.sap.sailing.server.gateway.HttpRequestUtils;
import com.sap.sailing.server.gateway.ParseHttpParameterException;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util;
import com.sap.sse.common.impl.MillisecondsTimePoint;
import com.sap.sse.util.SmartFutureCache;
import com.sap.sse.util.SmartFutureCache.CacheUpdater;
import com.sap.sse.util.SmartFutureCache.UpdateInterval;
/**
* Exports a leaderboard to the JSON format. Uses a {@link SmartFutureCache}.
* @author Frank, Axel Uhl
*
*/
public class LeaderboardJsonGetServlet extends AbstractJsonHttpServlet implements LeaderboardCache {
private static final long serialVersionUID = -2460691283231361152L;
private static final Logger logger = Logger.getLogger(LeaderboardJsonGetServlet.class.getName());
private static final String PARAM_NAME_LEADERBOARDNAME = "leaderboardName";
private static final String PARAM_NAME_RESULTSTATE = "resultState";
private static final String PARAM_NAME_USE_CACHE = "useCache";
private static final String PARAM_NAME_MAX_COMPETITORS_COUNT = "maxCompetitorsCount";
// Competitor constants used to build leaderboard json
private static final String COMPETITOR_ROOT_KEY = "competitors";
private static final String COMPETITOR_NAME = "name";
private static final String COMPETITOR_DISPLAY_NAME = "displayName";
private static final String COMPETITOR_ID = "id";
private static final String COMPETITOR_SAIL_ID = "sailID";
private static final String COMPETITOR_NATIONALITY = "nationality";
private static final String COMPETITOR_COUNTRY_CODE = "countryCode";
private static final String COMPETITOR_RANK = "rank";
private static final String COMPETITOR_CARRIED_POINTS = "carriedPoints";
private static final String COMPETITOR_TOTAL_POINTS = "totalPoints";
private static final String COMPETITOR_RACE_SCORES = "raceScores";
private static final String COMPETITOR_FLEET = "fleet";
private static final String COMPETITOR_NET_POINTS = "netPoints";
private static final String COMPETITOR_UNCORRECTED_NET_POINTS = "uncorrectedNetPoints";
private static final String COMPETITOR_MAX_POINTS_REASON = "maxPointsReason";
private static final String COMPETITOR_RACE_RANK = "raceRank";
private static final String COMPETITOR_IS_DISCARDED = "isDiscarded";
private static final String COMPETITOR_IS_CORRECTED = "isCorrected";
public static enum ResultStates { Live, Preliminary, Final };
// for backward compatibility the default result state is live
private final ResultStates DEFAULT_RESULT_STATE = ResultStates.Live;
private static SimpleDateFormat TIMEPOINT_FORMATTER = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
private static final int MAX_TOTAL_NUMBER_OF_CACHE_ENTRIES = 1000;
private int totalNumberOfCacheEntries;
private int cacheHits;
private int cacheMisses;
/**
* The cache values are linked hash maps keeping the rendered JSON objects as String per time point/result state pair. The cache uses
* a <em>linked</em> hash map because this way old values can be evicted easily, keeping the cache below a maximum size.
* Also, typing the {@link SmartFutureCache} with {@link LinkedHashMap} instead of only {@link Map} ensures that also when
* computing cache updates, new values are appended "at the end" and therefore tend to survive longer than any value appended
* earlier.
*/
private final SmartFutureCache<Leaderboard, LinkedHashMap<com.sap.sse.common.Util.Triple<TimePoint, ResultStates, Integer>, StringBuffer>, LeaderboardJsonCacheUpdateInterval> cache;
/**
* Used to observe the leaderboards cached so far and triggering {@link #cache} updates.
*/
private final LeaderboardCacheManager cacheManager;
private static class LeaderboardJsonCacheUpdateInterval implements UpdateInterval<LeaderboardJsonCacheUpdateInterval> {
private final LinkedHashSet<com.sap.sse.common.Util.Triple<TimePoint, ResultStates, Integer>> timePointsAndResultStates;
public LeaderboardJsonCacheUpdateInterval(Iterable<com.sap.sse.common.Util.Triple<TimePoint, ResultStates, Integer>> timePointsAndResultStates) {
assert timePointsAndResultStates != null;
this.timePointsAndResultStates = new LinkedHashSet<>();
Util.addAll(timePointsAndResultStates, this.timePointsAndResultStates);
}
public LinkedHashSet<com.sap.sse.common.Util.Triple<TimePoint, ResultStates, Integer>> getTimePointsAndResultStates() {
return timePointsAndResultStates;
}
/**
* Appends the <code>otherUpdateInterval</code> to this interval and returns the new interval. Using a
* {@link LinkedHashSet} internally, the ordering of time point and result state pairs is preserved, and
* in conjunction with the cache eviction strategy, the newest additions are the most likely to survive in
* the cache.
*/
@Override
public LeaderboardJsonCacheUpdateInterval join(LeaderboardJsonCacheUpdateInterval otherUpdateInterval) {
LinkedHashSet<com.sap.sse.common.Util.Triple<TimePoint, ResultStates, Integer>> newTimePointsAndResultStates = new LinkedHashSet<>();
newTimePointsAndResultStates.addAll(getTimePointsAndResultStates());
newTimePointsAndResultStates.addAll(otherUpdateInterval.getTimePointsAndResultStates());
return new LeaderboardJsonCacheUpdateInterval(newTimePointsAndResultStates);
}
}
private class LeaderboardJsonCacheUpdater
implements
CacheUpdater<Leaderboard, LinkedHashMap<com.sap.sse.common.Util.Triple<TimePoint, ResultStates, Integer>, StringBuffer>, LeaderboardJsonCacheUpdateInterval> {
@Override
public LinkedHashMap<com.sap.sse.common.Util.Triple<TimePoint, ResultStates, Integer>, StringBuffer> computeCacheUpdate(Leaderboard key,
LeaderboardJsonCacheUpdateInterval updateInterval) throws Exception {
final LinkedHashMap<com.sap.sse.common.Util.Triple<TimePoint, ResultStates, Integer>, StringBuffer> result;
if (updateInterval == null) {
result = null;
} else {
// no removeEldestEntry override required here; map is merged into another LinkedHashMap created in provideNewCacheValue
result = new LinkedHashMap<>();
for (com.sap.sse.common.Util.Triple<TimePoint, ResultStates, Integer> timePointAndResultState : updateInterval.getTimePointsAndResultStates()) {
StringWriter sw = new StringWriter();
computeLeaderboardJson(key, timePointAndResultState).writeJSONString(sw);
result.put(timePointAndResultState, sw.getBuffer());
}
}
return result;
}
@Override
public LinkedHashMap<com.sap.sse.common.Util.Triple<TimePoint, ResultStates, Integer>, StringBuffer> provideNewCacheValue(Leaderboard leaderboard,
LinkedHashMap<com.sap.sse.common.Util.Triple<TimePoint, ResultStates, Integer>, StringBuffer> oldCacheValue,
LinkedHashMap<com.sap.sse.common.Util.Triple<TimePoint, ResultStates, Integer>, StringBuffer> computedCacheUpdate,
LeaderboardJsonCacheUpdateInterval updateInterval) {
final LinkedHashMap<com.sap.sse.common.Util.Triple<TimePoint, ResultStates, Integer>, StringBuffer> result;
if (computedCacheUpdate == null) {
result = null;
if (oldCacheValue != null) {
totalNumberOfCacheEntries -= oldCacheValue.size();
}
} else {
// Note: don't use access-based ordering as it turns the get(...) call into a "write" access
result = new LinkedHashMap<com.sap.sse.common.Util.Triple<TimePoint, ResultStates, Integer>, StringBuffer>(16, 0.75f) {
private static final long serialVersionUID = -6197983565575024084L;
@Override
protected boolean removeEldestEntry(Entry<com.sap.sse.common.Util.Triple<TimePoint, ResultStates, Integer>, StringBuffer> eldest) {
final boolean result;
if (totalNumberOfCacheEntries > MAX_TOTAL_NUMBER_OF_CACHE_ENTRIES) {
totalNumberOfCacheEntries--;
result = true;
} else {
result = false;
}
return result;
}
};
if (oldCacheValue != null) {
result.putAll(oldCacheValue);
totalNumberOfCacheEntries -= oldCacheValue.size();
}
// ensure we get update triggers; note that oldCacheValue!=null doesn't mean that there are currently listeners active;
// the listeners could have been removed, and the SmartFutureCache will keep old values around as there is no explicit
// cache eviction for a SmartFutureCache.
cacheManager.add(leaderboard);
result.putAll(computedCacheUpdate);
totalNumberOfCacheEntries += result.size();
}
return result;
}
}
public LeaderboardJsonGetServlet() {
cache = new SmartFutureCache<>(new LeaderboardJsonCacheUpdater(), "LeaderboardJsonExporServlet SmartFutureCache for "+this);
cacheManager = new LeaderboardCacheManager(this);
}
@Override
public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
TimePoint requestTimePoint = MillisecondsTimePoint.now();
String leaderboardName = req.getParameter(PARAM_NAME_LEADERBOARDNAME);
String useCacheParam = req.getParameter(PARAM_NAME_USE_CACHE);
Integer maxCompetitorsCount = null;
try {
maxCompetitorsCount = HttpRequestUtils.getIntParameter(req, PARAM_NAME_MAX_COMPETITORS_COUNT);
} catch (ParseHttpParameterException e1) {
}
boolean useCache = true; // default
if (useCacheParam != null && "false".equalsIgnoreCase(useCacheParam)) {
useCache = false;
}
if (leaderboardName == null) {
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Need to specify a leaderboard name using the "+
PARAM_NAME_LEADERBOARDNAME+" parameter");
} else {
Leaderboard leaderboard = getService().getLeaderboardByName(leaderboardName);
if (leaderboard == null) {
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Leaderboard "+leaderboardName+" not found");
} else {
try {
ResultStates resultState = resolveRequestedResultState(req.getParameter(PARAM_NAME_RESULTSTATE));
TimePoint resultTimePoint = calculateTimePointForResultState(leaderboard, resultState);
StringBuffer jsonLeaderboardAsString;
if (resultTimePoint != null) {
com.sap.sse.common.Util.Triple<TimePoint, ResultStates, Integer> resultStateAndTimePointAndMaxCompetitorsCount =
new com.sap.sse.common.Util.Triple<>(resultTimePoint, resultState, maxCompetitorsCount);
if (useCache) {
jsonLeaderboardAsString = getLeaderboardJsonFromCacheOrCompute(leaderboard, resultStateAndTimePointAndMaxCompetitorsCount);
} else {
StringWriter sw = new StringWriter();
computeLeaderboardJson(leaderboard, resultStateAndTimePointAndMaxCompetitorsCount).writeJSONString(sw);
jsonLeaderboardAsString = sw.getBuffer();
}
} else {
StringWriter sw = new StringWriter();
createEmptyLeaderboardJson(leaderboard, resultState, requestTimePoint, maxCompetitorsCount).writeJSONString(sw);
jsonLeaderboardAsString = sw.getBuffer();
}
setJsonResponseHeader(resp);
synchronized (jsonLeaderboardAsString) {
int indexOfFirstOpeningBrace = jsonLeaderboardAsString.indexOf("{");
final String requestTimePointAsJson = "\"requestTimepoint\": \"" + formatTimePoint(requestTimePoint) + "\", ";
if (indexOfFirstOpeningBrace >= 0) {
jsonLeaderboardAsString.insert(indexOfFirstOpeningBrace+1, requestTimePointAsJson);
}
resp.getWriter().write(jsonLeaderboardAsString.toString());
if (indexOfFirstOpeningBrace >= 0) {
jsonLeaderboardAsString.delete(indexOfFirstOpeningBrace+1, indexOfFirstOpeningBrace+1+requestTimePointAsJson.length());
}
}
} catch (NoWindException e) {
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
}
}
}
}
private StringBuffer getLeaderboardJsonFromCacheOrCompute(Leaderboard leaderboard,
com.sap.sse.common.Util.Triple<TimePoint, ResultStates, Integer> timePointAndResultStateAndMaxCompetitorsCount) throws NoWindException {
final StringBuffer result;
Map<com.sap.sse.common.Util.Triple<TimePoint, ResultStates, Integer>, StringBuffer> cacheEntry = cache.get(leaderboard, /* waitForLatest */ false);
if (cacheEntry == null || !cacheEntry.containsKey(timePointAndResultStateAndMaxCompetitorsCount)) {
cacheMisses++;
LinkedHashSet<com.sap.sse.common.Util.Triple<TimePoint, ResultStates, Integer>> timePointsAndResultStatesAndMaxCompetitorsCount = new LinkedHashSet<>();
timePointsAndResultStatesAndMaxCompetitorsCount.add(timePointAndResultStateAndMaxCompetitorsCount);
cache.triggerUpdate(leaderboard, new LeaderboardJsonCacheUpdateInterval(timePointsAndResultStatesAndMaxCompetitorsCount));
// now wait for this entry to be computed
cacheEntry = cache.get(leaderboard, /* waitForLatest */ true);
StringBuffer jsonObjectAsString = cacheEntry.get(timePointAndResultStateAndMaxCompetitorsCount);
result = jsonObjectAsString;
} else {
cacheHits++;
result = cacheEntry.get(timePointAndResultStateAndMaxCompetitorsCount);
}
logger.finest(this+" cache hits/misses: "+cacheHits+"/"+cacheMisses);
return result;
}
private JSONObject computeLeaderboardJson(Leaderboard leaderboard,
com.sap.sse.common.Util.Triple<TimePoint, ResultStates, Integer> timePointAndResultStateAndMaxCompetitorsCount) throws NoWindException {
TimePoint resultTimePoint = timePointAndResultStateAndMaxCompetitorsCount.getA();
ResultStates resultState = timePointAndResultStateAndMaxCompetitorsCount.getB();
Integer maxCompetitorsCount = timePointAndResultStateAndMaxCompetitorsCount.getC();
List<Competitor> competitorsOrderedByTotalRank = leaderboard.getCompetitorsFromBestToWorst(resultTimePoint);
Map<RaceColumn, List<Competitor>> rankedCompetitorsPerColumn = new HashMap<RaceColumn, List<Competitor>>();
JSONObject jsonLeaderboard = new JSONObject();
writeCommonLeaderboardData(jsonLeaderboard, leaderboard, resultState, resultTimePoint, maxCompetitorsCount);
JSONArray jsonCompetitorEntries = new JSONArray();
jsonLeaderboard.put(COMPETITOR_ROOT_KEY, jsonCompetitorEntries);
int counter = 1;
for (Competitor competitor : competitorsOrderedByTotalRank) {
if(maxCompetitorsCount != null && counter > maxCompetitorsCount) {
break;
}
JSONObject jsonCompetitor = new JSONObject();
jsonCompetitor.put(COMPETITOR_NAME, competitor.getName());
final String displayName = leaderboard.getDisplayName(competitor);
jsonCompetitor.put(COMPETITOR_DISPLAY_NAME, displayName==null?competitor.getName():displayName);
jsonCompetitor.put(COMPETITOR_ID, competitor.getId().toString());
jsonCompetitor.put(COMPETITOR_SAIL_ID, competitor.getBoat().getSailID());
Nationality nationality = competitor.getTeam().getNationality();
jsonCompetitor.put(COMPETITOR_NATIONALITY, nationality != null ? nationality.getThreeLetterIOCAcronym(): null);
jsonCompetitor.put(COMPETITOR_COUNTRY_CODE, nationality != null ? (nationality.getCountryCode() != null ? nationality.getCountryCode().getTwoLetterISOCode(): null) : null);
jsonCompetitor.put(COMPETITOR_RANK, competitorsOrderedByTotalRank.indexOf(competitor) + 1);
jsonCompetitor.put(COMPETITOR_CARRIED_POINTS, leaderboard.getCarriedPoints(competitor));
jsonCompetitor.put(COMPETITOR_TOTAL_POINTS, leaderboard.getTotalPoints(competitor, resultTimePoint));
jsonCompetitorEntries.add(jsonCompetitor);
JSONObject jsonRaceColumns = new JSONObject();
jsonCompetitor.put(COMPETITOR_RACE_SCORES, jsonRaceColumns);
final Set<RaceColumn> discardedRaceColumns = leaderboard.getResultDiscardingRule().getDiscardedRaceColumns(competitor, leaderboard,
leaderboard.getRaceColumns(), resultTimePoint);
for (RaceColumn raceColumn : leaderboard.getRaceColumns()) {
List<Competitor> rankedCompetitorsForColumn = rankedCompetitorsPerColumn.get(raceColumn);
if (rankedCompetitorsForColumn == null) {
rankedCompetitorsForColumn = leaderboard.getCompetitorsFromBestToWorst(raceColumn, resultTimePoint);
rankedCompetitorsPerColumn.put(raceColumn, rankedCompetitorsForColumn);
}
JSONObject jsonEntry = new JSONObject();
jsonRaceColumns.put(raceColumn.getName(), jsonEntry);
final Fleet fleetOfCompetitor = raceColumn.getFleetOfCompetitor(competitor);
jsonEntry.put(COMPETITOR_FLEET, fleetOfCompetitor==null?"":fleetOfCompetitor.getName());
jsonEntry.put(COMPETITOR_NET_POINTS, leaderboard.getNetPoints(competitor, raceColumn, resultTimePoint));
jsonEntry.put(COMPETITOR_UNCORRECTED_NET_POINTS, leaderboard.getTrackedRank(competitor, raceColumn, resultTimePoint));
jsonEntry.put(COMPETITOR_TOTAL_POINTS, leaderboard.getTotalPoints(competitor, raceColumn, resultTimePoint, discardedRaceColumns));
MaxPointsReason maxPointsReason = leaderboard.getMaxPointsReason(competitor, raceColumn, resultTimePoint);
jsonEntry.put(COMPETITOR_MAX_POINTS_REASON, maxPointsReason != null ? maxPointsReason.toString(): null);
jsonEntry.put(COMPETITOR_RANK, rankedCompetitorsForColumn.indexOf(competitor)+1);
final TrackedRace trackedRace = raceColumn.getTrackedRace(competitor);
if (trackedRace != null) {
jsonEntry.put(COMPETITOR_RACE_RANK, trackedRace.getRank(competitor, resultTimePoint));
}
jsonEntry.put(COMPETITOR_IS_DISCARDED, leaderboard.isDiscarded(competitor, raceColumn, resultTimePoint));
jsonEntry.put(COMPETITOR_IS_CORRECTED, leaderboard.getScoreCorrection().isScoreCorrected(competitor, raceColumn, resultTimePoint));
}
counter++;
}
return jsonLeaderboard;
}
private String formatTimePoint(TimePoint timepoint) {
return timepoint == null ? null : TIMEPOINT_FORMATTER.format(timepoint.asDate());
}
private JSONObject createEmptyLeaderboardJson(Leaderboard leaderboard,
ResultStates resultState, TimePoint requestTimePoint, Integer maxCompetitorsCount) throws NoWindException {
JSONObject jsonLeaderboard = new JSONObject();
writeCommonLeaderboardData(jsonLeaderboard, leaderboard, resultState, null, maxCompetitorsCount);
JSONArray jsonCompetitorEntries = new JSONArray();
jsonLeaderboard.put(COMPETITOR_ROOT_KEY, jsonCompetitorEntries);
for (Competitor competitor : leaderboard.getCompetitors()) {
JSONObject jsonCompetitor = new JSONObject();
jsonCompetitor.put(COMPETITOR_NAME, competitor.getName());
final String displayName = leaderboard.getDisplayName(competitor);
jsonCompetitor.put(COMPETITOR_DISPLAY_NAME, displayName==null?competitor.getName():displayName);
jsonCompetitor.put(COMPETITOR_ID, competitor.getId().toString());
jsonCompetitor.put(COMPETITOR_SAIL_ID, competitor.getBoat().getSailID());
Nationality nationality = competitor.getTeam().getNationality();
jsonCompetitor.put(COMPETITOR_NATIONALITY, nationality != null ? nationality.getThreeLetterIOCAcronym(): null);
jsonCompetitor.put(COMPETITOR_COUNTRY_CODE, nationality != null ? (nationality.getCountryCode() != null ? nationality.getCountryCode().getTwoLetterISOCode(): null) : null);
jsonCompetitor.put(COMPETITOR_RANK, 0);
jsonCompetitor.put(COMPETITOR_CARRIED_POINTS, null);
jsonCompetitor.put(COMPETITOR_TOTAL_POINTS, null);
jsonCompetitorEntries.add(jsonCompetitor);
JSONObject jsonRaceColumns = new JSONObject();
jsonCompetitor.put(COMPETITOR_RACE_SCORES, jsonRaceColumns);
for (RaceColumn raceColumn : leaderboard.getRaceColumns()) {
JSONObject jsonEntry = new JSONObject();
jsonRaceColumns.put(raceColumn.getName(), jsonEntry);
final Fleet fleetOfCompetitor = raceColumn.getFleetOfCompetitor(competitor);
jsonEntry.put(COMPETITOR_FLEET, fleetOfCompetitor==null?"":fleetOfCompetitor.getName());
jsonEntry.put(COMPETITOR_NET_POINTS, null);
jsonEntry.put(COMPETITOR_TOTAL_POINTS, null);
jsonEntry.put(COMPETITOR_MAX_POINTS_REASON, "");
jsonEntry.put(COMPETITOR_RANK, 0);
jsonEntry.put(COMPETITOR_IS_DISCARDED, false);
jsonEntry.put(COMPETITOR_IS_CORRECTED, false);
}
}
return jsonLeaderboard;
}
private void writeCommonLeaderboardData(JSONObject jsonLeaderboard, Leaderboard leaderboard, ResultStates resultState,
TimePoint resultTimePoint, Integer maxCompetitorsCount) {
jsonLeaderboard.put("name", leaderboard.getName());
// for backward compatibility
jsonLeaderboard.put("timepoint", formatTimePoint(resultTimePoint));
jsonLeaderboard.put("resultTimepoint", formatTimePoint(resultTimePoint));
jsonLeaderboard.put("resultState", resultState.name());
jsonLeaderboard.put("maxCompetitorsCount", maxCompetitorsCount);
SettableScoreCorrection scoreCorrection = leaderboard.getScoreCorrection();
if (scoreCorrection != null) {
jsonLeaderboard.put("scoringComment", scoreCorrection.getComment());
TimePoint lastUpdateTimepoint = scoreCorrection.getTimePointOfLastCorrectionsValidity();
jsonLeaderboard.put("lastScoringUpdate", formatTimePoint(lastUpdateTimepoint));
} else {
jsonLeaderboard.put("scoringComment", null);
jsonLeaderboard.put("lastScoringUpdate", null);
}
JSONArray jsonColumnNames = new JSONArray();
jsonLeaderboard.put("columnNames", jsonColumnNames);
for (RaceColumn raceColumn : leaderboard.getRaceColumns()) {
jsonColumnNames.add(raceColumn.getName());
}
}
private ResultStates resolveRequestedResultState(String resultStateParam) {
ResultStates result = DEFAULT_RESULT_STATE;
if(resultStateParam != null) {
for(ResultStates state: ResultStates.values()) {
if(state.name().equalsIgnoreCase(resultStateParam)) {
result = state;
break;
}
}
}
return result;
}
private TimePoint calculateTimePointForResultState(Leaderboard leaderboard, ResultStates resultState) {
TimePoint result = null;
switch (resultState) {
case Live:
result = leaderboard.getTimePointOfLatestModification();
if (result == null) {
result = MillisecondsTimePoint.now();
}
break;
case Preliminary:
case Final:
if (leaderboard.getScoreCorrection() != null && leaderboard.getScoreCorrection().getTimePointOfLastCorrectionsValidity() != null) {
result = leaderboard.getScoreCorrection().getTimePointOfLastCorrectionsValidity();
// As we don't have implemented bug 1246 (Define a clear result state for races and leaderboards) so far
// we need to make sure that the timpoint for the final state is not determined in the middle of a running race,
// because this would deliver not only final results but also some "mixed-in" live results.
// Therefore, if there is a race that hasn't finished yet and whose first start mark passing is before
// the current result, move result to before the start mark passing.
for (TrackedRace trackedRace : leaderboard.getTrackedRaces()) {
TimePoint endOfRace = trackedRace.getEndOfRace();
if (endOfRace == null) {
Waypoint firstWaypoint = trackedRace.getRace().getCourse().getFirstWaypoint();
if (firstWaypoint != null) {
Iterable<MarkPassing> markPassingsForFirstWaypoint = trackedRace.getMarkPassingsInOrder(firstWaypoint);
if (markPassingsForFirstWaypoint != null) {
trackedRace.lockForRead(markPassingsForFirstWaypoint);
try {
Iterator<MarkPassing> i = markPassingsForFirstWaypoint.iterator();
if (i.hasNext()) {
TimePoint earliestMarkPassingTimePoint = i.next().getTimePoint();
if (result == null || earliestMarkPassingTimePoint.before(result)) {
result = earliestMarkPassingTimePoint.minus(1);
}
}
} finally {
trackedRace.unlockAfterRead(markPassingsForFirstWaypoint);
}
}
}
}
}
}
break;
}
return result;
}
@Override
public void add(Leaderboard leaderboard) {
// nothing to do; the triggerUpdate call on the SmartFutureCache in doGet ensures that a record for the leaderboard
// will be created in the SmartFutureCache
}
/**
* Invalidation triggers the recalculation for all time point / result state pairs for which the cache currently
* holds values for <code>leaderboard</code>. It preserves the order in which they were inserted, so values requested
* later (not necessarily having a later time point!) will be re-calculated and re-added later and therefore have a higher
* chance of surviving cache eviction. Still, the cache won't grow beyond its limits because it uses a {@link LinkedHashMap}
* with total size (aggregated size of all these maps)-based eviction.<p>
*
* At the same time, existing cache entries won't simply be removed by the invalidation but remain in place for clients
* asking with <code>waitForLatest==false</code> and will only be replaced once the re-calculation has completed. It will be
* the cache size set by {@link #MAX_TOTAL_NUMBER_OF_CACHE_ENTRIES} that roughly decides how long a recalculation can last.
* The higher this number, the more entries a map for a single leaderboard may have which all will be re-calculated when
* this method is called.
*/
@Override
public void invalidate(Leaderboard leaderboard) {
final LinkedHashMap<com.sap.sse.common.Util.Triple<TimePoint, ResultStates, Integer>, StringBuffer> currentCachedValueForLeaderboard =
cache.get(leaderboard, /* waitForLatest */ false);
cache.triggerUpdate(leaderboard, new LeaderboardJsonCacheUpdateInterval(currentCachedValueForLeaderboard.keySet()));
}
}
@@ -1,29 +0,0 @@
package com.sap.sailing.server.gateway.impl;
import java.io.IOException;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.json.simple.JSONArray;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.server.gateway.AbstractJsonHttpServlet;
public class LeaderboardsJsonGetServlet extends AbstractJsonHttpServlet {
private static final long serialVersionUID = -2193421590275280102L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
JSONArray jsonLeaderboards = new JSONArray();
Map<String, Leaderboard> leaderboards = getService().getLeaderboards();
for (String leaderboardName : leaderboards.keySet()) {
jsonLeaderboards.add(leaderboardName);
}
setJsonResponseHeader(resp);
jsonLeaderboards.writeJSONString(resp.getWriter());
}
}
@@ -1,407 +0,0 @@
package com.sap.sailing.server.gateway.impl;
import java.io.IOException;
import java.util.Iterator;
import java.util.Map;
import java.util.logging.Logger;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.Leg;
import com.sap.sailing.domain.base.Mark;
import com.sap.sailing.domain.base.Nationality;
import com.sap.sailing.domain.base.Person;
import com.sap.sailing.domain.base.RaceDefinition;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.NoWindException;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.Speed;
import com.sap.sailing.domain.common.Tack;
import com.sap.sailing.domain.common.Wind;
import com.sap.sailing.domain.common.tracking.GPSFix;
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
import com.sap.sailing.domain.ranking.RankingMetric.RankingInfo;
import com.sap.sailing.domain.tracking.GPSFixTrack;
import com.sap.sailing.domain.tracking.TrackedLeg;
import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.WindPositionMode;
import com.sap.sailing.server.gateway.AbstractJsonHttpServlet;
import com.sap.sse.InvalidDateException;
import com.sap.sse.common.CountryCode;
import com.sap.sse.common.Duration;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.impl.MillisecondsTimePoint;
public class RegattaAndRaceDataJsonGetServlet extends AbstractJsonHttpServlet {
private static final Logger logger = Logger.getLogger(RegattaAndRaceDataJsonGetServlet.class.getName());
private static final long serialVersionUID = 1333207389294903999L;
private static final String ACTION_NAME_LIST_EVENTS = "listevents";
private static final String ACTION_NAME_SHOW_RACE = "showrace";
private static final String ACTION_NAME_SHOW_WAYPOINTS = "showwaypoints";
private static final String ACTION_NAME_SHOW_BOAT_POSITIONS = "showboatpositions";
private static final String PARAM_NAME_SINCE_UPDATE = "sinceupdate";
private static final String PARAM_NAME_SINCE = "since";
private static final String PARAM_NAME_SINCE_MILLIS = "sinceasmillis";
private static final String PARAM_NAME_TO = "to";
private static final String PARAM_NAME_TO_MILLIS = "toasmillis";
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
String action = req.getParameter(PARAM_ACTION);
if (action != null) {
if (ACTION_NAME_LIST_EVENTS.equals(action)) {
listEvents(resp);
} else if (ACTION_NAME_SHOW_RACE.equals(action)) {
showRace(req, resp);
} else if (ACTION_NAME_SHOW_WAYPOINTS.equals(action)) {
showWaypoints(req, resp);
} else if (ACTION_NAME_SHOW_BOAT_POSITIONS.equals(action)) {
showBoatPositions(req, resp);
} else {
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Unknown action \""+action+"\"");
}
} else {
resp.getWriter().println("No action specified!");
}
} catch (Exception e) {
resp.getWriter().println("Error processing request:");
e.printStackTrace(resp.getWriter());
}
}
private void showBoatPositions(HttpServletRequest req, HttpServletResponse resp) throws IOException {
TrackedRace trackedRace = getTrackedRace(req);
if (trackedRace == null) {
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Race not found");
} else {
try {
TimePoint sinceTimePoint = readTimePointParam(req, PARAM_NAME_SINCE, PARAM_NAME_SINCE_MILLIS, null);
TimePoint toTimePoint = readTimePointParam(req, PARAM_NAME_TO, PARAM_NAME_TO_MILLIS, null);
JSONObject jsonRace = new JSONObject();
jsonRace.put("name", trackedRace.getRace().getName());
JSONArray jsonCompetitors = new JSONArray();
for (Competitor competitor : trackedRace.getRace().getCompetitors()) {
JSONObject jsonCompetitor = new JSONObject();
jsonCompetitor.put("name", competitor.getName());
GPSFixTrack<Competitor, GPSFixMoving> track = trackedRace.getTrack(competitor);
JSONArray jsonFixes = new JSONArray();
track.lockForRead();
try {
Iterator<GPSFixMoving> fixIter;
if (sinceTimePoint == null) {
fixIter = track.getFixes().iterator();
} else {
fixIter = track.getFixesIterator(sinceTimePoint, /* inclusive */true);
}
while (fixIter.hasNext()) {
GPSFixMoving fix = fixIter.next();
if (toTimePoint != null && fix.getTimePoint() != null
&& toTimePoint.compareTo(fix.getTimePoint()) < 0) {
break;
}
JSONObject jsonFix = new JSONObject();
jsonFix.put("timepoint", fix.getTimePoint().asMillis());
jsonFix.put("latdeg", fix.getPosition().getLatDeg());
jsonFix.put("lngdeg", fix.getPosition().getLngDeg());
jsonFix.put("truebearingdeg", fix.getSpeed().getBearing().getDegrees());
jsonFix.put("knotspeed", fix.getSpeed().getKnots());
String tackName;
try {
final Tack tack = trackedRace.getTack(competitor, fix.getTimePoint());
if (tack != null) {
tackName = tack.name();
jsonFix.put("tack", tackName);
}
} catch (NoWindException e) {
// don't output tack
}
jsonFixes.add(jsonFix);
}
} finally {
track.unlockAfterRead();
}
jsonCompetitor.put("track", jsonFixes);
jsonCompetitors.add(jsonCompetitor);
}
jsonRace.put("competitors", jsonCompetitors);
setJsonResponseHeader(resp);
jsonRace.writeJSONString(resp.getWriter());
} catch (InvalidDateException e) {
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Couldn't parse time specification " + e.getMessage());
}
}
}
private void showWaypoints(HttpServletRequest req, HttpServletResponse resp) throws IOException {
TrackedRace trackedRace = getTrackedRace(req);
if (trackedRace == null) {
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Race not found");
} else {
try {
TimePoint timePoint = readTimePointParam(req, PARAM_NAME_TIME, PARAM_NAME_TIME_MILLIS,
trackedRace.getStartOfRace() != null ? trackedRace.getStartOfRace()
: trackedRace.getStartOfTracking() != null ? trackedRace.getStartOfTracking()
: trackedRace.getTimePointOfNewestEvent() == null ? MillisecondsTimePoint.now()
: trackedRace.getTimePointOfNewestEvent());
JSONArray jsonWaypoints = new JSONArray();
for (Waypoint waypoint : trackedRace.getRace().getCourse().getWaypoints()) {
JSONObject jsonWaypoint = new JSONObject();
jsonWaypoint.put("name", waypoint.getName());
JSONArray jsonMarks = new JSONArray();
for (Mark mark : waypoint.getMarks()) {
JSONObject jsonMark = new JSONObject();
jsonMark.put("name", mark.getName());
GPSFixTrack<Mark, GPSFix> markTrack = trackedRace.getOrCreateTrack(mark);
GPSFix lastFixAtOrBefore = markTrack.getLastFixAtOrBefore(timePoint);
if (lastFixAtOrBefore != null) {
Position markPosition = lastFixAtOrBefore.getPosition();
if (markPosition != null) {
jsonMark.put("lat", markPosition.getLatDeg());
jsonMark.put("lng", markPosition.getLngDeg());
}
}
jsonMarks.add(jsonMark);
}
jsonWaypoint.put("marks", jsonMarks);
jsonWaypoints.add(jsonWaypoint);
}
setJsonResponseHeader(resp);
jsonWaypoints.writeJSONString(resp.getWriter());
} catch (InvalidDateException e) {
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Couldn't parse time specification " + e.getMessage());
}
}
}
private void showRace(HttpServletRequest req, HttpServletResponse resp) throws IOException, InterruptedException {
long start = System.currentTimeMillis();
TrackedRace trackedRace = getTrackedRace(req);
if (trackedRace == null) {
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Race not found");
} else {
try {
TimePoint timePoint = readTimePointParam(req, PARAM_NAME_TIME, PARAM_NAME_TIME_MILLIS,
trackedRace.getTimePointOfNewestEvent()==null?MillisecondsTimePoint.now():trackedRace.getTimePointOfNewestEvent());
String sinceUpdateString = req.getParameter(PARAM_NAME_SINCE_UPDATE);
if (sinceUpdateString != null) {
int sinceUpdate = Integer.valueOf(sinceUpdateString);
// block until there is new data:
System.out.println("Blocking at "+trackedRace.getUpdateCount()+" waiting for "+sinceUpdate);
trackedRace.waitForNextUpdate(sinceUpdate);
}
final RankingInfo rankingInfo = trackedRace.getRankingMetric().getRankingInfo(timePoint);
JSONObject jsonRace = new JSONObject();
jsonRace.put("name", trackedRace.getRace().getName());
jsonRace.put("startoftracking", trackedRace.getStartOfTracking() == null ? 0l : trackedRace
.getStartOfTracking().asMillis());
jsonRace.put("endoftracking", trackedRace.getEndOfTracking() == null ? 0l : trackedRace
.getEndOfTracking().asMillis());
jsonRace.put("start", trackedRace.getStartOfRace() == null ? 0l : trackedRace.getStartOfRace().asMillis());
jsonRace.put("end", trackedRace.getEndOfRace() == null ? 0l : trackedRace.getEndOfRace().asMillis());
jsonRace.put("timeofnewestevent", trackedRace.getTimePointOfNewestEvent() == null ? 0l : trackedRace
.getTimePointOfNewestEvent().asMillis());
jsonRace.put("timeoflastevent", trackedRace.getTimePointOfLastEvent() == null ? 0l : trackedRace
.getTimePointOfLastEvent().asMillis());
jsonRace.put("updatecount", trackedRace.getUpdateCount());
jsonRace.put("windaveragingintervalmillis", trackedRace.getMillisecondsOverWhichToAverageWind());
jsonRace.put("speedaveragingintervalmillis", trackedRace.getMillisecondsOverWhichToAverageSpeed());
Position positionForWind = null;
TrackedLeg currentLeg = trackedRace.getCurrentLeg(timePoint);
if (currentLeg != null) {
positionForWind = trackedRace.getOrCreateTrack(currentLeg.getLeg().getFrom().getMarks().iterator().next())
.getEstimatedPosition(timePoint, false);
}
Wind currentWind = trackedRace.getWind(positionForWind, timePoint);
if (currentWind != null) {
JSONObject jsonWind = new JSONObject();
jsonWind.put("truebearingdeg", currentWind.getBearing().getDegrees());
jsonWind.put("knotspeed", currentWind.getKnots());
jsonWind.put("meterspersecondspeed", currentWind.getMetersPerSecond());
jsonRace.put("wind", jsonWind);
}
JSONArray jsonLegs = new JSONArray();
for (TrackedLeg leg : trackedRace.getTrackedLegs()) {
JSONObject jsonLeg = new JSONObject();
jsonLeg.put("from", leg.getLeg().getFrom().getName());
jsonLeg.put("fromwaypointid", leg.getLeg().getFrom().getId());
jsonLeg.put("to", leg.getLeg().getTo().getName());
jsonLeg.put("towaypointid", leg.getLeg().getTo().getId());
try {
jsonLeg.put("upordownwindleg", leg.isUpOrDownwindLeg(timePoint));
} catch (NoWindException e) {
// no wind, then it's simply no upwind or downwind leg
jsonLeg.put("upordownwindleg", "false");
}
JSONArray jsonCompetitors = new JSONArray();
Map<Competitor, Integer> ranks = leg.getRanks(timePoint);
for (Competitor competitor : trackedRace.getRace().getCompetitors()) {
JSONObject jsonCompetitorInLeg = new JSONObject();
TrackedLegOfCompetitor trackedLegOfCompetitor = leg.getTrackedLeg(competitor);
if (trackedLegOfCompetitor != null) {
jsonCompetitorInLeg.put("name", competitor.getName());
Speed currentSpeedOverGround = trackedLegOfCompetitor.getSpeedOverGround(timePoint);
if (currentSpeedOverGround != null) {
jsonCompetitorInLeg.put("currentSpeedOverGroundInKnots",
currentSpeedOverGround == null ? null : currentSpeedOverGround.getKnots());
jsonCompetitorInLeg.put("currentSpeedOverGroundInMetersPerSecond",
currentSpeedOverGround == null ? null : currentSpeedOverGround.getMetersPerSecond());
}
Speed averageSpeedOverGround = trackedLegOfCompetitor.getAverageSpeedOverGround(timePoint);
if (averageSpeedOverGround != null) {
jsonCompetitorInLeg.put("averageSpeedOverGroundInKnots",
averageSpeedOverGround.getKnots());
jsonCompetitorInLeg.put("averageSpeedOverGroundInMetersPerSecond",
averageSpeedOverGround.getMetersPerSecond());
}
Speed currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinished =
trackedLegOfCompetitor.hasFinishedLeg(timePoint) ?
averageSpeedOverGround : currentSpeedOverGround;
if (currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinished != null) {
jsonCompetitorInLeg.put("currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots",
currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinished.getKnots());
jsonCompetitorInLeg.put("currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond",
currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinished.getMetersPerSecond());
}
Distance distanceTraveled = trackedLegOfCompetitor.getDistanceTraveled(timePoint);
if (distanceTraveled != null) {
jsonCompetitorInLeg.put("distanceTraveledOverGroundInMeters",
distanceTraveled.getMeters());
}
Speed velocityMadeGood = trackedLegOfCompetitor.getVelocityMadeGood(timePoint, WindPositionMode.EXACT);
if (velocityMadeGood != null) {
jsonCompetitorInLeg.put("velocityMadeGoodInKnots", velocityMadeGood.getKnots());
jsonCompetitorInLeg.put("velocityMadeGoodInMetersPerSecond", velocityMadeGood.getMetersPerSecond());
}
Speed averageVelocityMadeGood = trackedLegOfCompetitor
.getAverageVelocityMadeGood(timePoint);
if (averageVelocityMadeGood != null) {
jsonCompetitorInLeg.put("averageVelocityMadeGoodInKnots",
averageVelocityMadeGood.getKnots());
jsonCompetitorInLeg.put("averageVelocityMadeGoodInMetersPerSecond",
averageVelocityMadeGood.getMetersPerSecond());
}
try {
Integer rank = ranks.get(competitor);
if (rank == null) {
logger.warning("Can't find rank of competitor "+competitor+" in leg "+leg);
}
jsonCompetitorInLeg.put("rank", ranks.get(competitor));
} catch (RuntimeException re) {
if (re.getCause() != null && re.getCause() instanceof NoWindException) {
// well, we don't know the wind direction, so we can't compute a ranking
} else {
throw re;
}
}
jsonCompetitorInLeg.put("gapToLeaderInSeconds",
trackedLegOfCompetitor.getGapToLeader(timePoint, rankingInfo, WindPositionMode.LEG_MIDDLE));
final Duration estimatedTimeToNextMarkInSeconds = trackedLegOfCompetitor
.getEstimatedTimeToNextMark(timePoint, WindPositionMode.EXACT);
jsonCompetitorInLeg.put("estimatedTimeToNextMarkInSeconds",
estimatedTimeToNextMarkInSeconds == null ? null : estimatedTimeToNextMarkInSeconds);
jsonCompetitorInLeg.put("windwardDistanceToGoInMeters", trackedLegOfCompetitor
.getWindwardDistanceToGo(timePoint, WindPositionMode.LEG_MIDDLE).getMeters());
jsonCompetitorInLeg.put("started", trackedLegOfCompetitor.hasStartedLeg(timePoint));
jsonCompetitorInLeg.put("finished", trackedLegOfCompetitor.hasFinishedLeg(timePoint));
jsonCompetitors.add(jsonCompetitorInLeg);
}
}
jsonLeg.put("competitors", jsonCompetitors);
jsonLegs.add(jsonLeg);
}
jsonRace.put("legs", jsonLegs);
JSONArray jsonRaceRanking = new JSONArray();
for (Competitor competitor : trackedRace.getRace().getCompetitors()) {
JSONObject competitorRank = new JSONObject();
competitorRank.put("competitor", competitor.getName());
competitorRank.put("rank", trackedRace.getRank(competitor, timePoint));
jsonRaceRanking.add(competitorRank);
}
jsonRace.put("ranks", jsonRaceRanking);
setJsonResponseHeader(resp);
jsonRace.writeJSONString(resp.getWriter());
} catch (InvalidDateException e) {
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Couldn't parse time specification " + e.getMessage());
}
}
System.out.println("showrace took "+(System.currentTimeMillis()-start)+"ms");
}
private void listEvents(HttpServletResponse resp) throws IOException {
// TODO one a new top-level event will have been introduced, produce it here
JSONArray regattaList = new JSONArray();
for (Regatta regatta : getService().getAllRegattas()) {
JSONObject jsonEvent = new JSONObject();
jsonEvent.put("name", regatta.getName());
if (regatta.getBoatClass() != null) {
jsonEvent.put("boatclass", regatta.getBoatClass().getName());
}
JSONArray jsonCompetitors = new JSONArray();
for (Competitor competitor : regatta.getAllCompetitors()) {
JSONObject jsonCompetitor = new JSONObject();
jsonCompetitor.put("name", competitor.getName());
jsonCompetitor.put("sailID", competitor.getBoat()==null?"":competitor.getBoat().getSailID());
final Nationality nationality = competitor.getTeam().getNationality();
jsonCompetitor.put("nationality", nationality == null ? "" : nationality.getThreeLetterIOCAcronym());
CountryCode countryCode = nationality == null ? null : nationality.getCountryCode();
jsonCompetitor.put("nationalityISO2", countryCode == null ? "" : countryCode.getTwoLetterISOCode());
jsonCompetitor.put("nationalityISO3", countryCode == null ? "" : countryCode.getThreeLetterISOCode());
JSONArray jsonTeam = new JSONArray();
for (Person sailor : competitor.getTeam().getSailors()) {
JSONObject jsonSailor = new JSONObject();
jsonSailor.put("name", sailor.getName());
jsonSailor.put("description", sailor.getDescription()==null?"":sailor.getDescription());
jsonTeam.add(jsonSailor);
}
jsonCompetitor.put("team", jsonTeam);
jsonCompetitors.add(jsonCompetitor);
}
jsonEvent.put("competitors", jsonCompetitors);
JSONArray jsonRaces = new JSONArray();
for (RaceDefinition race : regatta.getAllRaces()) {
// don't wait for the arrival of a tracked race; just ignore it if it's not currently being tracked
TrackedRace trackedRace = getService().getOrCreateTrackedRegatta(regatta).getExistingTrackedRace(race);
if (trackedRace != null) {
JSONObject jsonRace = new JSONObject();
jsonRace.put("name", race.getName());
jsonRace.put("boatclass", race.getBoatClass() == null ? "" : race.getBoatClass().getName());
TimePoint start = trackedRace.getStartOfRace();
jsonRace.put("start", start == null ? Long.MAX_VALUE : start.asMillis());
JSONArray jsonLegs = new JSONArray();
for (Leg leg : race.getCourse().getLegs()) {
JSONObject jsonLeg = new JSONObject();
jsonLeg.put("start", leg.getFrom().getName());
jsonLeg.put("end", leg.getTo().getName());
jsonLegs.add(jsonLeg);
}
jsonRace.put("legs", jsonLegs);
jsonRaces.add(jsonRace);
}
}
jsonEvent.put("races", jsonRaces);
regattaList.add(jsonEvent);
}
setJsonResponseHeader(resp);
regattaList.writeJSONString(resp.getWriter());
}
}
@@ -1,49 +0,0 @@
package com.sap.sailing.server.gateway.impl;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.json.simple.JSONObject;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.common.RegattaName;
import com.sap.sailing.server.gateway.AbstractJsonHttpServlet;
import com.sap.sailing.server.gateway.serialization.JsonSerializer;
import com.sap.sailing.server.gateway.serialization.impl.CompetitorJsonSerializer;
import com.sap.sailing.server.gateway.serialization.impl.NationalityJsonSerializer;
import com.sap.sailing.server.gateway.serialization.impl.PersonJsonSerializer;
import com.sap.sailing.server.gateway.serialization.impl.RegattaJsonSerializer;
import com.sap.sailing.server.gateway.serialization.impl.TeamJsonSerializer;
public class RegattaEntryListJsonGetServlet extends AbstractJsonHttpServlet {
private static final long serialVersionUID = -5280787192024697254L;
private static final String PARAM_NAME_REGATTANAME = "name";
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String regattaName = request.getParameter(PARAM_NAME_REGATTANAME);
if (regattaName == null) {
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
"Need to specify a regatta name using the " + PARAM_NAME_REGATTANAME + " parameter");
} else {
Regatta regatta = getService().getRegatta(new RegattaName(regattaName));
if (regatta == null) {
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Regatta " + regattaName
+ " not found");
} else {
NationalityJsonSerializer nationalityJsonSerializer = new NationalityJsonSerializer();
CompetitorJsonSerializer competitorJsonSerializer = new CompetitorJsonSerializer(
new TeamJsonSerializer(new PersonJsonSerializer(nationalityJsonSerializer)), null);
JsonSerializer<Regatta> regattaSerializer = new RegattaJsonSerializer(null, competitorJsonSerializer);
JSONObject serializedRegatta = regattaSerializer.serialize(regatta);
setJsonResponseHeader(response);
serializedRegatta.writeJSONString(response.getWriter());
}
}
}
}
@@ -1,45 +0,0 @@
package com.sap.sailing.server.gateway.impl;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.json.simple.JSONObject;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.common.RegattaName;
import com.sap.sailing.server.gateway.AbstractJsonHttpServlet;
import com.sap.sailing.server.gateway.serialization.JsonSerializer;
import com.sap.sailing.server.gateway.serialization.impl.ColorJsonSerializer;
import com.sap.sailing.server.gateway.serialization.impl.FleetJsonSerializer;
import com.sap.sailing.server.gateway.serialization.impl.RegattaJsonSerializer;
import com.sap.sailing.server.gateway.serialization.impl.SeriesJsonSerializer;
public class RegattaJsonGetServlet extends AbstractJsonHttpServlet {
private static final long serialVersionUID = -5280787192024697254L;
private static final String PARAM_NAME_REGATTANAME = "name";
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String regattaName = request.getParameter(PARAM_NAME_REGATTANAME);
if (regattaName == null) {
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Need to specify a regatta name using the "+
PARAM_NAME_REGATTANAME + " parameter");
} else {
Regatta regatta = getService().getRegatta(new RegattaName(regattaName));
if (regatta == null) {
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Regatta " + regattaName + " not found");
} else {
SeriesJsonSerializer seriesJsonSerializer = new SeriesJsonSerializer(new FleetJsonSerializer(new ColorJsonSerializer()));
JsonSerializer<Regatta> regattaSerializer = new RegattaJsonSerializer(seriesJsonSerializer, null);
JSONObject serializedRegatta = regattaSerializer.serialize(regatta);
setJsonResponseHeader(response);
serializedRegatta.writeJSONString(response.getWriter());
}
}
}
}
@@ -1,29 +0,0 @@
package com.sap.sailing.server.gateway.impl;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.json.simple.JSONArray;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.server.gateway.AbstractJsonHttpServlet;
import com.sap.sailing.server.gateway.serialization.impl.RegattaJsonSerializer;
public class RegattasJsonGetServlet extends AbstractJsonHttpServlet {
private static final long serialVersionUID = 1333207389294903999L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
RegattaJsonSerializer regattaJsonSerializer = new RegattaJsonSerializer();
JSONArray regattasJson = new JSONArray();
for (Regatta regatta : getService().getAllRegattas()) {
regattasJson.add(regattaJsonSerializer.serialize(regatta));
}
setJsonResponseHeader(resp);
regattasJson.writeJSONString(resp.getWriter());
}
}
@@ -1,170 +0,0 @@
package com.sap.sailing.server.gateway.impl;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import com.sap.sailing.domain.base.RaceDefinition;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.common.Wind;
import com.sap.sailing.domain.common.WindSource;
import com.sap.sailing.domain.common.WindSourceType;
import com.sap.sailing.domain.common.impl.WindSourceImpl;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.WindTrack;
import com.sap.sailing.server.gateway.AbstractJsonHttpServlet;
import com.sap.sse.InvalidDateException;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.impl.MillisecondsTimePoint;
public class WindJsonGetServlet extends AbstractJsonHttpServlet {
static final String ALL = "ALL";
private static final long serialVersionUID = -1408004464252437535L;
private static final String PARAM_NAME_WINDSOURCE = "windsource";
private static final String PARAM_NAME_WINDSOURCE_ID = "windsourceid";
private static final String PARAM_NAME_FROM_TIME = "fromtime";
private static final String PARAM_NAME_FROM_TIME_MILLIS = "fromtimeasmillis";
private static final String PARAM_NAME_TO_TIME = "totime";
private static final String PARAM_NAME_TO_TIME_MILLIS = "totimeasmillis";
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Regatta regatta = getRegatta(req);
if (regatta == null) {
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Regatta not found");
} else {
RaceDefinition race = getRaceDefinition(req);
if (race == null) {
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Race not found");
} else {
String windSourceParam = req.getParameter(PARAM_NAME_WINDSOURCE);
String windSourceToRead = windSourceParam != null ? windSourceParam : ALL;
String windSourceIdParam = req.getParameter(PARAM_NAME_WINDSOURCE_ID);
String windSourceIdToRead = windSourceIdParam != null ? windSourceIdParam : null;
TrackedRace trackedRace = getService().getOrCreateTrackedRegatta(regatta).getTrackedRace(race);
TimePoint from;
TimePoint to;
try {
from = readTimePointParam(req, PARAM_NAME_FROM_TIME, PARAM_NAME_FROM_TIME_MILLIS,
trackedRace.getStartOfRace() == null ? new MillisecondsTimePoint(0) :
/* 24h before race start */ new MillisecondsTimePoint(trackedRace.getStartOfRace().asMillis()-24*3600*1000));
} catch (InvalidDateException e1) {
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Could not parse the 'from' time");
return;
}
try {
to = readTimePointParam(req, PARAM_NAME_TO_TIME, PARAM_NAME_TO_TIME_MILLIS, MillisecondsTimePoint.now());
} catch (InvalidDateException e1) {
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Could not parse the 'to' time");
return;
}
JSONObject jsonWindTracks = getResult(windSourceToRead, windSourceIdToRead, trackedRace, from, to);
setJsonResponseHeader(resp);
jsonWindTracks.writeJSONString(resp.getWriter());
}
}
}
JSONObject getResult(String windSourceToRead, String windSourceIdToRead, TrackedRace trackedRace, TimePoint from, TimePoint to) {
Map<WindSource, List<Wind>> fixes = getRelevantFixes(windSourceToRead, windSourceIdToRead, trackedRace, from, to);
JSONObject jsonWindTracks = getResultAsJsonObject(trackedRace, fixes);
return jsonWindTracks;
}
private Map<WindSource, List<Wind>> getRelevantFixes(String windSourceToRead, String windSourceIdToRead, TrackedRace trackedRace, TimePoint from,
TimePoint to) {
// quickly extract relevant fixes to hold locks as shortly as possible; process later
List<WindSource> windSources = getAvailableWindSources(trackedRace);
Map<WindSource, List<Wind>> fixes = new HashMap<>();
for (WindSource windSource : windSources) {
if (ALL.equals(windSourceToRead) || windSource.getType().name().equalsIgnoreCase(windSourceToRead)) {
if (windSourceIdToRead != null && windSource.getId() != null && !windSourceIdToRead.equalsIgnoreCase(windSource.getId().toString())) {
continue;
}
ArrayList<Wind> fixesForWindSource = new ArrayList<>();
fixes.put(windSource, fixesForWindSource);
WindTrack windTrack = trackedRace.getOrCreateWindTrack(windSource);
windTrack.lockForRead();
try {
Iterator<Wind> windIter = windTrack.getFixesIterator(from, /* inclusive */true);
while (windIter.hasNext()) {
Wind wind = windIter.next();
if (wind.getTimePoint().compareTo(to) > 0) {
break;
} else {
fixesForWindSource.add(wind);
}
}
} finally {
windTrack.unlockAfterRead();
}
}
}
return fixes;
}
private JSONObject getResultAsJsonObject(TrackedRace trackedRace, Map<WindSource, List<Wind>> fixes) {
JSONObject jsonWindTracks = new JSONObject();
JSONArray jsonWindSourcesDisplayed = new JSONArray();
for (WindSource windSource : fixes.keySet()) {
JSONObject windSourceInformation = new JSONObject();
windSourceInformation.put("typeName", windSource.getType().name());
windSourceInformation.put("id", windSource.getId() != null ? windSource.getId().toString() : "");
jsonWindSourcesDisplayed.add(windSourceInformation);
}
jsonWindTracks.put("availableWindSources", jsonWindSourcesDisplayed);
for (Map.Entry<WindSource, List<Wind>> e : fixes.entrySet()) {
WindSource windSource = e.getKey();
JSONArray jsonWindArray = new JSONArray();
WindTrack windTrack = trackedRace.getOrCreateWindTrack(windSource);
for (Wind wind : e.getValue()) {
JSONObject jsonWind = new JSONObject();
jsonWind.put("truebearingdeg", wind.getBearing().getDegrees());
jsonWind.put("knotspeed", wind.getKnots());
jsonWind.put("meterspersecondspeed", wind.getMetersPerSecond());
if (wind.getTimePoint() != null) {
jsonWind.put("timepoint", wind.getTimePoint().asMillis());
final Wind averagedWind = windTrack.getAveragedWind(wind.getPosition(), wind.getTimePoint());
jsonWind.put("dampenedtruebearingdeg", averagedWind.getBearing().getDegrees());
jsonWind.put("dampenedknotspeed", averagedWind.getKnots());
jsonWind.put("dampenedmeterspersecondspeed", averagedWind.getMetersPerSecond());
}
if (wind.getPosition() != null) {
jsonWind.put("latdeg", wind.getPosition().getLatDeg());
jsonWind.put("lngdeg", wind.getPosition().getLngDeg());
}
jsonWindArray.add(jsonWind);
}
jsonWindTracks.put(windSource.toString()+(windSource.getId() != null ? "-"+windSource.getId().toString() : ""), jsonWindArray);
}
return jsonWindTracks;
}
List<WindSource> getAvailableWindSources(TrackedRace trackedRace) {
List<WindSource> windSources = new ArrayList<WindSource>();
for (WindSource windSource : trackedRace.getWindSources()) {
windSources.add(windSource);
}
for (WindSource windSourceToExclude : trackedRace.getWindSourcesToExclude()) {
windSources.remove(windSourceToExclude);
}
windSources.add(new WindSourceImpl(WindSourceType.COMBINED));
return windSources;
}
}
@@ -12,6 +12,7 @@ import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.StringEscapeUtils;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;
import org.json.simple.parser.ParseException;
@@ -88,19 +89,19 @@ public class AddEntryToRaceLogJsonPostServlet extends AbstractJsonHttpServlet {
Leaderboard leaderboard = service.getLeaderboardByName(leaderboardName);
if (leaderboard == null) {
response.sendError(HttpServletResponse.SC_NOT_FOUND, "Leaderboard "+leaderboardName+" not found.");
response.sendError(HttpServletResponse.SC_NOT_FOUND, "Leaderboard "+StringEscapeUtils.escapeHtml(leaderboardName)+" not found.");
return;
}
RaceColumn raceColumn = leaderboard.getRaceColumnByName(raceColumnName);
if (raceColumn == null) {
response.sendError(HttpServletResponse.SC_NOT_FOUND, "Race column "+raceColumnName+" not found.");
response.sendError(HttpServletResponse.SC_NOT_FOUND, "Race column "+StringEscapeUtils.escapeHtml(raceColumnName)+" not found.");
return;
}
Fleet fleet = raceColumn.getFleetByName(fleetName);
if (fleet == null) {
response.sendError(HttpServletResponse.SC_NOT_FOUND, "Fleet "+fleetName+" not found.");
response.sendError(HttpServletResponse.SC_NOT_FOUND, "Fleet "+StringEscapeUtils.escapeHtml(fleetName)+" not found.");
return;
}
@@ -21,6 +21,7 @@ import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import org.apache.commons.fileupload.util.Streams;
import org.apache.commons.lang.StringEscapeUtils;
import org.json.simple.JSONObject;
import com.sap.sailing.domain.base.Competitor;
@@ -72,7 +73,7 @@ public class CompetitorsResource extends AbstractSailingServerResource {
competitorIdAsString);
if (competitor == null) {
response = Response.status(Status.NOT_FOUND)
.entity("Could not find a competitor with id '" + competitorIdAsString + "'.")
.entity("Could not find a competitor with id '" + StringEscapeUtils.escapeHtml(competitorIdAsString) + "'.")
.type(MediaType.TEXT_PLAIN).build();
} else {
String jsonString = getCompetitorJSON(competitor).toJSONString();
@@ -89,7 +90,7 @@ public class CompetitorsResource extends AbstractSailingServerResource {
if (competitor == null) {
return Response.status(Status.NOT_FOUND)
.entity("Could not find a competitor with id '" + competitorId + "'.").type(MediaType.TEXT_PLAIN)
.entity("Could not find a competitor with id '" + StringEscapeUtils.escapeHtml(competitorId) + "'.").type(MediaType.TEXT_PLAIN)
.build();
}
@@ -97,7 +98,7 @@ public class CompetitorsResource extends AbstractSailingServerResource {
if (team == null) {
return Response.status(Status.NOT_FOUND)
.entity("Could not find a team associated with competitor '" + competitorId + "'.")
.entity("Could not find a team associated with competitor '" + StringEscapeUtils.escapeHtml(competitorId) + "'.")
.type(MediaType.TEXT_PLAIN).build();
}
@@ -127,7 +128,7 @@ public class CompetitorsResource extends AbstractSailingServerResource {
CompetitorStore store = service.getCompetitorStore();
Competitor competitor = store.getExistingCompetitorByIdAsString(competitorId);
if (competitor == null) {
logger.log(Level.INFO, "Could not find competitor to store image for: " + competitorId);
logger.log(Level.INFO, "Could not find competitor to store image for: " + StringEscapeUtils.escapeHtml(competitorId));
throw new WebApplicationException(Response.status(Status.BAD_REQUEST)
.entity("Could not find competitor with id " + competitorId).type(MediaType.TEXT_PLAIN).build());
}
@@ -13,6 +13,7 @@ import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import org.apache.commons.lang.StringEscapeUtils;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.osgi.util.tracker.ServiceTracker;
@@ -60,7 +61,7 @@ public class DataMiningResource extends AbstractSailingServerResource {
}
private Response getBadQueryDefinitionErrorResponse(ModifiableStatisticQueryDefinitionDTO queryDefinitionDTO) {
return Response.status(Status.BAD_REQUEST).entity("Couldn't create a query for definition '" + queryDefinitionDTO + "'.")
return Response.status(Status.BAD_REQUEST).entity("Couldn't create a query for definition '" + StringEscapeUtils.escapeHtml(queryDefinitionDTO.toString()) + "'.")
.type(MediaType.TEXT_PLAIN).build();
}
@@ -11,6 +11,7 @@ import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import org.apache.commons.lang.StringEscapeUtils;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
@@ -29,7 +30,7 @@ import com.sap.sse.common.Util.Pair;
public class EventsResource extends AbstractSailingServerResource {
private Response getBadEventErrorResponse(String eventId) {
return Response.status(Status.NOT_FOUND).entity("Could not find an event with id '" + eventId + "'.").type(MediaType.TEXT_PLAIN).build();
return Response.status(Status.NOT_FOUND).entity("Could not find an event with id '" + StringEscapeUtils.escapeHtml(eventId) + "'.").type(MediaType.TEXT_PLAIN).build();
}
@GET
@@ -16,6 +16,7 @@ import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import org.apache.commons.lang.StringEscapeUtils;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
@@ -61,7 +62,8 @@ public class LeaderboardGroupsResource extends AbstractSailingServerResource {
Response response;
LeaderboardGroup leaderboardGroup = getService().getLeaderboardGroupByName(leaderboardGroupName);
if (leaderboardGroup == null) {
response = Response.status(Status.NOT_FOUND).entity("Could not find a leaderboard group with name '"+leaderboardGroupName+"'.").type(MediaType.TEXT_PLAIN).build();
response = Response.status(Status.NOT_FOUND).entity("Could not find a leaderboard group with name '"+
StringEscapeUtils.escapeHtml(leaderboardGroupName)+"'.").type(MediaType.TEXT_PLAIN).build();
} else {
TimePoint timePoint = MillisecondsTimePoint.now();
JSONObject jsonLeaderboardGroup = new JSONObject();
@@ -28,6 +28,7 @@ import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.xml.ws.http.HTTPException;
import org.apache.commons.lang.StringEscapeUtils;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;
@@ -116,15 +117,14 @@ public class LeaderboardsResource extends AbstractSailingServerResource {
@Path("{name}")
public Response getLeaderboard(@PathParam("name") String leaderboardName,
@DefaultValue("Live") @QueryParam("resultState") ResultStates resultState,
@DefaultValue("true") @QueryParam("useCache") boolean useCache,
@DefaultValue("1000") @QueryParam("maxCompetitorsCount") int maxCompetitorsCount) {
@QueryParam("maxCompetitorsCount") Integer maxCompetitorsCount) {
Response response;
TimePoint requestTimePoint = MillisecondsTimePoint.now();
Leaderboard leaderboard = getService().getLeaderboardByName(leaderboardName);
if (leaderboard == null) {
response = Response.status(Status.NOT_FOUND)
.entity("Could not find a leaderboard with name '" + leaderboardName + "'.")
.entity("Could not find a leaderboard with name '" + StringEscapeUtils.escapeHtml(leaderboardName) + "'.")
.type(MediaType.TEXT_PLAIN).build();
} else {
try {
@@ -516,11 +516,11 @@ public class LeaderboardsResource extends AbstractSailingServerResource {
if (competitor == null) {
response = Response.status(Status.NOT_FOUND)
.entity("Could not find a competitor with id '" + competitorIdAsString + "'.")
.entity("Could not find a competitor with id '" + StringEscapeUtils.escapeHtml(competitorIdAsString) + "'.")
.type(MediaType.TEXT_PLAIN).build();
} else if (leaderboard == null) {
response = Response.status(Status.NOT_FOUND)
.entity("Could not find a leaderboard with name '" + leaderboardName + "'.")
.entity("Could not find a leaderboard with name '" + StringEscapeUtils.escapeHtml(leaderboardName) + "'.")
.type(MediaType.TEXT_PLAIN).build();
} else {
JSONObject json = CompetitorsResource.getCompetitorJSON(competitor);
@@ -540,7 +540,7 @@ public class LeaderboardsResource extends AbstractSailingServerResource {
Leaderboard leaderboard = getService().getLeaderboardByName(leaderboardName);
if (leaderboard == null) {
return Response.status(Status.NOT_FOUND)
.entity("Could not find a leaderboard with name '" + leaderboardName + "'.")
.entity("Could not find a leaderboard with name '" + StringEscapeUtils.escapeHtml(leaderboardName) + "'.")
.type(MediaType.TEXT_PLAIN).build();
}
final Set<Mark> marks;
@@ -568,14 +568,15 @@ public class LeaderboardsResource extends AbstractSailingServerResource {
if (raceColumn == null) {
return Response
.status(Status.NOT_FOUND)
.entity("Could not find a race column '" + raceColumnName + "' in leaderboard '"
+ leaderboardName + "'.").type(MediaType.TEXT_PLAIN).build();
.entity("Could not find a race column '" + StringEscapeUtils.escapeHtml(raceColumnName) + "' in leaderboard '"
+ StringEscapeUtils.escapeHtml(leaderboardName) + "'.").type(MediaType.TEXT_PLAIN).build();
} else if (fleetName != null) {
Fleet fleet = raceColumn.getFleetByName(fleetName);
if (fleet == null) {
return Response
.status(Status.NOT_FOUND)
.entity("Could not find fleet '" + fleetName + "' in leaderboard '" + leaderboardName
.entity("Could not find fleet '" + StringEscapeUtils.escapeHtml(fleetName) + "' in leaderboard '" +
StringEscapeUtils.escapeHtml(leaderboardName)
+ "'.").type(MediaType.TEXT_PLAIN).build();
} else {
marks = getMarksForFleet(raceColumn, fleet);
@@ -642,7 +643,7 @@ public class LeaderboardsResource extends AbstractSailingServerResource {
Leaderboard leaderboard = getService().getLeaderboardByName(leaderboardName);
if (leaderboard == null) {
return Response.status(Status.NOT_FOUND)
.entity("Could not find a leaderboard with name '" + leaderboardName + "'.")
.entity("Could not find a leaderboard with name '" + StringEscapeUtils.escapeHtml(leaderboardName) + "'.")
.type(MediaType.TEXT_PLAIN).build();
}
@@ -651,13 +652,13 @@ public class LeaderboardsResource extends AbstractSailingServerResource {
regattaLog = ((HasRegattaLike) leaderboard).getRegattaLike().getRegattaLog();
} else {
return Response.status(Status.BAD_REQUEST)
.entity("Leaderboard '" + leaderboardName + "' does not have an attached RegattaLog.")
.entity("Leaderboard '" + StringEscapeUtils.escapeHtml(leaderboardName) + "' does not have an attached RegattaLog.")
.type(MediaType.TEXT_PLAIN).build();
}
Mark mark = getService().getBaseDomainFactory().getExistingMarkByIdAsString(markId);
if (mark == null) {
return Response.status(Status.NOT_FOUND).entity("Could not find a mark with ID '" + markId + "'.")
return Response.status(Status.NOT_FOUND).entity("Could not find a mark with ID '" + StringEscapeUtils.escapeHtml(markId) + "'.")
.type(MediaType.TEXT_PLAIN).build();
}
TimePoint now = MillisecondsTimePoint.now();
@@ -14,6 +14,7 @@ import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.ResponseBuilder;
import javax.ws.rs.core.Response.Status;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.math.analysis.polynomials.PolynomialFunction;
import org.json.simple.JSONArray;
@@ -134,11 +135,12 @@ public class PolarResource extends AbstractSailingServerResource {
}
private Response getBadRegattaErrorResponse(String regattaName) {
return Response.status(Status.NOT_FOUND).entity("Could not find a regatta with name '" + regattaName + "'.").type(MediaType.TEXT_PLAIN).build();
return Response.status(Status.NOT_FOUND).entity("Could not find a regatta with name '" + StringEscapeUtils.escapeHtml(regattaName) + "'.").type(MediaType.TEXT_PLAIN).build();
}
private Response getBadRaceErrorResponse(String regattaName, String raceName) {
return Response.status(Status.NOT_FOUND).entity("Could not find a race with name '" + raceName + "' in regatta '" + regattaName + "'.").type(MediaType.TEXT_PLAIN).build();
return Response.status(Status.NOT_FOUND).entity("Could not find a race with name '" + StringEscapeUtils.escapeHtml(raceName) +
"' in regatta '" + StringEscapeUtils.escapeHtml(regattaName) + "'.").type(MediaType.TEXT_PLAIN).build();
}
@GET
@@ -20,6 +20,7 @@ import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import org.apache.commons.lang.StringEscapeUtils;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
@@ -76,26 +77,26 @@ public class RegattasResource extends AbstractSailingServerResource {
private static final Logger logger = Logger.getLogger(RegattasResource.class.getName());
private Response getBadRegattaErrorResponse(String regattaName) {
return Response.status(Status.NOT_FOUND).entity("Could not find a regatta with name '" + regattaName + "'.")
return Response.status(Status.NOT_FOUND).entity("Could not find a regatta with name '" + StringEscapeUtils.escapeHtml(regattaName) + "'.")
.type(MediaType.TEXT_PLAIN).build();
}
private Response getBadRaceErrorResponse(String regattaName, String raceName) {
return Response.status(Status.NOT_FOUND)
.entity("Could not find a race with name '" + raceName + "' in regatta '" + regattaName + "'.")
.entity("Could not find a race with name '" + StringEscapeUtils.escapeHtml(raceName) + "' in regatta '" + StringEscapeUtils.escapeHtml(regattaName) + "'.")
.type(MediaType.TEXT_PLAIN).build();
}
private Response getNoTrackedRaceErrorResponse(String regattaName, String raceName) {
return Response.status(Status.NOT_FOUND)
.entity("No tracked race for race with name '" + raceName + "' in regatta '" + regattaName + "'.")
.entity("No tracked race for race with name '" + StringEscapeUtils.escapeHtml(raceName) + "' in regatta '" + StringEscapeUtils.escapeHtml(regattaName) + "'.")
.type(MediaType.TEXT_PLAIN).build();
}
private Response getNotEnoughDataAvailabeErrorResponse(String regattaName, String raceName) {
return Response.status(Status.NOT_FOUND)
.entity("No wind or polar data for race with name '" + raceName + "' in regatta '" + regattaName + "'.")
.entity("No wind or polar data for race with name '" + StringEscapeUtils.escapeHtml(raceName) + "' in regatta '" + StringEscapeUtils.escapeHtml(regattaName) + "'.")
.type(MediaType.TEXT_PLAIN).build();
}
@@ -603,13 +604,13 @@ public class RegattasResource extends AbstractSailingServerResource {
Regatta regatta = findRegattaByName(regattaName);
if (regatta == null) {
response = Response.status(Status.NOT_FOUND)
.entity("Could not find a regatta with name '" + regattaName + "'.").type(MediaType.TEXT_PLAIN)
.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 '" + raceName + "'.").type(MediaType.TEXT_PLAIN)
.entity("Could not find a race with name '" + StringEscapeUtils.escapeHtml(raceName) + "'.").type(MediaType.TEXT_PLAIN)
.build();
} else {
TrackedRace trackedRace = findTrackedRace(regattaName, raceName);
@@ -640,13 +641,13 @@ public class RegattasResource extends AbstractSailingServerResource {
Regatta regatta = findRegattaByName(regattaName);
if (regatta == null) {
response = Response.status(Status.NOT_FOUND)
.entity("Could not find a regatta with name '" + regattaName + "'.").type(MediaType.TEXT_PLAIN)
.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 '" + raceName + "'.").type(MediaType.TEXT_PLAIN)
.entity("Could not find a race with name '" + StringEscapeUtils.escapeHtml(raceName) + "'.").type(MediaType.TEXT_PLAIN)
.build();
} else {
TrackedRace trackedRace = findTrackedRace(regattaName, raceName);
@@ -696,13 +697,13 @@ public class RegattasResource extends AbstractSailingServerResource {
Regatta regatta = findRegattaByName(regattaName);
if (regatta == null) {
response = Response.status(Status.NOT_FOUND)
.entity("Could not find a regatta with name '" + regattaName + "'.").type(MediaType.TEXT_PLAIN)
.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 '" + raceName + "'.").type(MediaType.TEXT_PLAIN)
.entity("Could not find a race with name '" + StringEscapeUtils.escapeHtml(raceName) + "'.").type(MediaType.TEXT_PLAIN)
.build();
} else {
TrackedRace trackedRace = findTrackedRace(regattaName, raceName);
@@ -736,13 +737,13 @@ public class RegattasResource extends AbstractSailingServerResource {
Regatta regatta = findRegattaByName(regattaName);
if (regatta == null) {
response = Response.status(Status.NOT_FOUND)
.entity("Could not find a regatta with name '" + regattaName + "'.").type(MediaType.TEXT_PLAIN)
.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 '" + raceName + "'.").type(MediaType.TEXT_PLAIN)
.entity("Could not find a race with name '" + StringEscapeUtils.escapeHtml(raceName) + "'.").type(MediaType.TEXT_PLAIN)
.build();
} else {
TrackedRace trackedRace = findTrackedRace(regattaName, raceName);
@@ -763,7 +764,7 @@ public class RegattasResource extends AbstractSailingServerResource {
Regatta regatta = findRegattaByName(regattaName);
if (regatta == null) {
response = Response.status(Status.NOT_FOUND)
.entity("Could not find a regatta with name '" + regattaName + "'.").type(MediaType.TEXT_PLAIN)
.entity("Could not find a regatta with name '" + StringEscapeUtils.escapeHtml(regattaName) + "'.").type(MediaType.TEXT_PLAIN)
.build();
} else {
JSONObject jsonRaceResults = new JSONObject();
@@ -791,13 +792,13 @@ public class RegattasResource extends AbstractSailingServerResource {
Regatta regatta = findRegattaByName(regattaName);
if (regatta == null) {
response = Response.status(Status.NOT_FOUND)
.entity("Could not find a regatta with name '" + regattaName + "'.").type(MediaType.TEXT_PLAIN)
.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 '" + raceName + "'.").type(MediaType.TEXT_PLAIN)
.entity("Could not find a race with name '" + StringEscapeUtils.escapeHtml(raceName) + "'.").type(MediaType.TEXT_PLAIN)
.build();
} else {
TrackedRace trackedRace = findTrackedRace(regattaName, raceName);
@@ -939,13 +940,13 @@ public class RegattasResource extends AbstractSailingServerResource {
Regatta regatta = findRegattaByName(regattaName);
if (regatta == null) {
response = Response.status(Status.NOT_FOUND)
.entity("Could not find a regatta with name '" + regattaName + "'.").type(MediaType.TEXT_PLAIN)
.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 '" + raceName + "'.").type(MediaType.TEXT_PLAIN)
.entity("Could not find a race with name '" + StringEscapeUtils.escapeHtml(raceName) + "'.").type(MediaType.TEXT_PLAIN)
.build();
} else {
TrackedRace trackedRace = findTrackedRace(regattaName, raceName);
@@ -1,50 +0,0 @@
<!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>Deprecated Web Services API</title>
</head>
<body>
<h1>SAP Sailing Analytics - Web Services</h1>
<h2>Name: events</h2>
<b>Description:</b>
<p>This service exports all existing events with their venues and course areas</p>
<br/>
<table>
<tr>
<td>Webservice Type:</td>
<td>REST</td>
</tr>
<tr>
<td>Output format:</td>
<td>Json</td>
</tr>
<tr>
<td>Base-Url:</td>
<td>http://www.sapsailing.com/sailingserver/events</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/events">http://www.sapsailing.com/sailingserver/events</a></td>
</tr>
</table>
<a href="index.html">Back to Web Service Overview</a>
</body>
</html>
@@ -1 +0,0 @@
[{"id":"760f2748-2e48-4bd2-93ff-3452da02b4c5","name":"TestEvent","publicationUrl":"","venue":{"name":"Kiel","courseAreas":[{"name":"Area 1","id":"687da3c2-4fe0-4a3d-a791-f4f34c27b704"},{"name":"Area 2","id":"974d7258-3664-455f-82a2-e9103db938c9"}]}}]
@@ -1,110 +0,0 @@
<!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>Deprecated Web Services API</title>
</head>
<body>
<h1>SAP Sailing Analytics Deprecated Web Services API - Overview</h1>
<p>
The best entry point to navigating the contents of a server is usually the <b>leaderboardgroups</b> service,
listing the leaderboard groups offered by the server. For example,
<a href="http://www.sapsailing.com/sailingserver/leaderboardgroups">http://www.sapsailing.com/sailingserver/leaderboardgroups</a>
lists the names of the leaderboard groups that the server instance knows. The names returned in the list can then be used
as parameter for the <b>leaderboardgroup</b> service, e.g., as follows:
<a href="http://www.sapsailing.com/sailingserver/leaderboardgroup?leaderboardGroupName=Star%20IDM%202013">http://www.sapsailing.com/sailingserver/leaderboardgroup?leaderboardGroupName=Star%20IDM%202013</a>.
Note, of course, that the name needs to be URL-encoded with special characters and white spaces correctly
translated into URL format.
</p>
<div style="height: 1em;"></div>
<p>
The leaderboard group service list the leaderboards in the group. There are essentially two types of leaderboard:
flexible leaderboards and regatta leaderboards. For flexible leaderboards, the service tells the list of races
in the leaderboard, and for each race in particular the regatta name and the tracked race name. These two attributes
can, in turn, be used as parameters for further services to identify the race for which information shall be retrieved and
to fetch information about the regatta with its structure such as series and fleets.
Again, note that those names need to be URL-encoded accordingly when used as parameter to a service call.
</p>
<div style="height: 1em;"></div>
<p>
When a service takes a time point as a parameter, there are usually two ways of specifying it: either as an
ISO time stamp, e.g., 2013-05-08T10:12:59Z or as Unix time stamp (milliseconds since 1970-01-01T00:00:00Z). The parameters
in the latter format are usually called <b>...asmillis</b>.
</p>
<div style="height: 1em;"></div>
<table>
<thead>
<tr>
<td>Web Service</td>
<td>Description</td>
<td>Supported formats</td>
</trh>
</thead>
<tbody>
<tr>
<td><a href="eventsExportDoc.html">/events</a></td>
<td>Gets all events</td>
<td><a href="eventsExportSample.json">Json</a></td>
</tr>
<tr>
<td><a href="regattasExportDoc.html">/regattas</a></td>
<td>Gets all regattas</td>
<td><a href="regattasExportSample.json">Json</a></td>
</tr>
<tr>
<td><a href="regattaExportDoc.html">/regatta</a></td>
<td>Gets a regatta</td>
<td><a href="regattaExportSample.json">Json</a></td>
</tr>
<tr>
<td><a href="leaderboardGroupsExportDoc.html">/leaderboardgroups</a></td>
<td>Gets all leaderboard groups</td>
<td><a href="leaderboardGroupsExportSample.json">Json</a></td>
</tr>
<tr>
<td><a href="leaderboardGroupExportDoc.html">/leaderboardgroup</a></td>
<td>Gets a group of leaderboards</td>
<td><a href="leaderboardGroupExportSample.json">Json</a></td>
</tr>
<tr>
<td><a href="leaderboardsExportDoc.html">/leaderboards</a></td>
<td>Gets all leaderboards</td>
<td><a href="leaderboardsExportSample.json">Json</a></td>
</tr>
<tr>
<td><a href="leaderboardExportDoc.html">/leaderboard</a></td>
<td>Gets a leaderboard</td>
<td><a href="leaderboardExportSample.json">Json</a></td>
</tr>
<tr>
<td><a href="windExportDoc.html">/wind</a></td>
<td>Gets wind data for a race</td>
<td><a href="windExportSample.json">Json</a></td>
</tr>
<!--
<tr>
<td><a href="showraceDoc.html">showrace</a></td>
<td>Gets general time-dependent data for a race and the competitors</td>
<td><a href="showraceSample.json">Json</a></td>
</tr>
<tr>
<td><a href="showwaypointsDoc.html">showwaypoints</a></td>
<td>Gets course and waypoint data for a race</td>
<td><a href="showwaypointsSample.json">Json</a></td>
</tr>
<tr>
<td><a href="showboatpositionsDoc.html">showboatpositions</a></td>
<td>Gets boat GPS data for a race</td>
<td><a href="showboatpositionsSample.json">Json</a></td>
</tr>
-->
</tbody>
</table>
<div style="height: 1em;"></div>
<a href="../../index.html">Back to Web Services Home</a>
</body>
</html>
@@ -1,54 +0,0 @@
<!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>Deprecated Web Services API</title>
</head>
<body>
<h1>SAP Sailing Analytics - Web Services</h1>
<h2>Name: leaderboard</h2>
<b>Description:</b>
<p>This service exports the data of a leaderboard with all races, including total points, points per race and total rank.</p>
<br/>
<table>
<tr>
<td>Webservice Type:</td>
<td>REST</td>
</tr>
<tr>
<td>Output format:</td>
<td>Json</td>
</tr>
<tr>
<td>Base-Url:</td>
<td>http://www.sapsailing.com/sailingserver/leaderboard</td>
</tr>
<tr>
<td>Mandatory parameters:</td>
<td>leaderboardName</td>
</tr>
<tr>
<td>Optional parameters:</td>
<td>resultState [Live = Default, Preliminary, Final] </td>
</tr>
<tr>
<td>&nbsp;</td>
<td>maxCompetitorsCount</td>
</tr>
<tr>
<td>Request method:</td>
<td>GET</td>
</tr>
<tr>
<td>Example:</td>
<td><a href="http://www.sapsailing.com/sailingserver/leaderboard?leaderboardName=Extreme%20Sailing%20Series%202013%20Overall&resultState=Final">http://www.sapsailing.com/sailingserver/leaderboard?leaderboardName=Extreme%20Sailing%20Series%202013%20Overall&amp;resultState=Final</a></td>
</tr>
</table>
<a href="index.html">Back to Web Service Overview</a>
</body>
</html>
@@ -1 +0,0 @@
{"name":"Drachen IDM 2012","timepoint":"2013-03-22T11:42:56.310+0100","resultTimepoint":"2013-03-22T11:42:56.310+0100","requestTimepoint":"2013-03-22T11:42:56.310+0100","resultState":"Live","scoringComment":null,"lastScoringUpdate":null,"columnNames":["R1","R2","R3","R4","R5","R6"],"competitors":[{"name":"Scheinecker, Christian","displayName":"Scheinecker, Christian","id":"6dca639a-db47-11e1-867d-406186cbf87c","sailID":"AUT218","nationality":"AUT","countryCode":"AT","rank":1,"carriedPoints":0.0,"totalPoints":20.0,"raceScores":{"R1":{"fleet":"Default","netPoints":8.0,"totalPoints":8.0,"maxPointsReason":"NONE","rank":8,"raceRank":8,"isDiscarded":false,"isCorrected":false},"R2":{"fleet":"Default","netPoints":9.0,"totalPoints":9.0,"maxPointsReason":"NONE","rank":9,"raceRank":9,"isDiscarded":false,"isCorrected":false},"R3":{"fleet":"Default","netPoints":3.0,"totalPoints":3.0,"maxPointsReason":"NONE","rank":3,"raceRank":3,"isDiscarded":false,"isCorrected":false},"R4":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R5":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R6":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false}}},{"name":"van Cauwenbergh, Ben","displayName":"van Cauwenbergh, Ben","id":"6d3c34da-db47-11e1-867d-406186cbf87c","sailID":"BEL80","nationality":"BEL","countryCode":"BE","rank":2,"carriedPoints":0.0,"totalPoints":21.0,"raceScores":{"R1":{"fleet":"Default","netPoints":5.0,"totalPoints":5.0,"maxPointsReason":"NONE","rank":5,"raceRank":5,"isDiscarded":false,"isCorrected":false},"R2":{"fleet":"Default","netPoints":15.0,"totalPoints":15.0,"maxPointsReason":"NONE","rank":15,"raceRank":15,"isDiscarded":false,"isCorrected":false},"R3":{"fleet":"Default","netPoints":1.0,"totalPoints":1.0,"maxPointsReason":"NONE","rank":1,"raceRank":1,"isDiscarded":false,"isCorrected":false},"R4":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R5":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R6":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false}}},{"name":"Brennecke, Marcus","displayName":"Brennecke, Marcus","id":"6ca649de-db47-11e1-867d-406186cbf87c","sailID":"GER10","nationality":"GER","countryCode":"DE","rank":3,"carriedPoints":0.0,"totalPoints":25.0,"raceScores":{"R1":{"fleet":"Default","netPoints":7.0,"totalPoints":7.0,"maxPointsReason":"NONE","rank":7,"raceRank":7,"isDiscarded":false,"isCorrected":false},"R2":{"fleet":"Default","netPoints":2.0,"totalPoints":2.0,"maxPointsReason":"NONE","rank":2,"raceRank":2,"isDiscarded":false,"isCorrected":false},"R3":{"fleet":"Default","netPoints":16.0,"totalPoints":16.0,"maxPointsReason":"NONE","rank":16,"raceRank":16,"isDiscarded":false,"isCorrected":false},"R4":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R5":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R6":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false}}},{"name":"Jacob, Florian","displayName":"Jacob, Florian","id":"7dc29358-db47-11e1-867d-406186cbf87c","sailID":"GER976","nationality":"GER","countryCode":"DE","rank":4,"carriedPoints":0.0,"totalPoints":28.0,"raceScores":{"R1":{"fleet":"Default","netPoints":12.0,"totalPoints":12.0,"maxPointsReason":"NONE","rank":12,"raceRank":12,"isDiscarded":false,"isCorrected":false},"R2":{"fleet":"Default","netPoints":7.0,"totalPoints":7.0,"maxPointsReason":"NONE","rank":7,"raceRank":7,"isDiscarded":false,"isCorrected":false},"R3":{"fleet":"Default","netPoints":9.0,"totalPoints":9.0,"maxPointsReason":"NONE","rank":9,"raceRank":9,"isDiscarded":false,"isCorrected":false},"R4":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R5":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R6":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false}}},{"name":"Glas, Matthias","displayName":"Glas, Matthias","id":"8a6df7fa-db47-11e1-867d-406186cbf87c","sailID":"GER1038","nationality":"GER","countryCode":"DE","rank":5,"carriedPoints":0.0,"totalPoints":31.0,"raceScores":{"R1":{"fleet":"Default","netPoints":9.0,"totalPoints":9.0,"maxPointsReason":"NONE","rank":9,"raceRank":9,"isDiscarded":false,"isCorrected":false},"R2":{"fleet":"Default","netPoints":10.0,"totalPoints":10.0,"maxPointsReason":"NONE","rank":10,"raceRank":10,"isDiscardLine truncated
@@ -1,51 +0,0 @@
<!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>Deprecated Web Services API</title>
</head>
<body>
<h1>SAP Sailing Analytics - Web Services</h1>
<h2>Name: leaderboardgroup</h2>
<b>Description:</b>
<p>This service exports the data of a group of leaderboards including all contained leaderboards.</p>
<br/>
<table>
<tr>
<td>Webservice Type:</td>
<td>REST</td>
</tr>
<tr>
<td>Output format:</td>
<td>Json</td>
</tr>
<tr>
<td>Base-Url:</td>
<td>http://www.sapsailing.com/sailingserver/leaderboardgroup</td>
</tr>
<tr>
<td>Mandatory parameters:</td>
<td>leaderboardGroupName</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://tw2013.sapsailing.com/sailingserver/leaderboardgroup?leaderboardGroupName=Travem%C3%BCnder%20Woche%20201">http://tw2013.sapsailing.com/sailingserver/leaderboardgroup?leaderboardGroupName=Travem%C3%BCnder%20Woche%202013</a></td>
</tr>
</table>
<a href="index.html">Back to Web Service Overview</a>
</body>
</html>
@@ -1,50 +0,0 @@
<!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>Deprecated Web Services API</title>
</head>
<body>
<h1>SAP Sailing Analytics - Web Services</h1>
<h2>Name: leaderboardgroups</h2>
<b>Description:</b>
<p>This service exports the names of all existing leaderboard groups</p>
<br/>
<table>
<tr>
<td>Webservice Type:</td>
<td>REST</td>
</tr>
<tr>
<td>Output format:</td>
<td>Json</td>
</tr>
<tr>
<td>Base-Url:</td>
<td>http://www.sapsailing.com/sailingserver/leaderboardgroups</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/leaderboardgroups">http://www.sapsailing.com/sailingserver/leaderboardgroups</a></td>
</tr>
</table>
<a href="index.html">Back to Web Service Overview</a>
</body>
</html>
@@ -1 +0,0 @@
["BMW Cup","Test","Extreme Sailing Series","49er","STG-Berlin-MR2012","ESS All test","BMW Cup Overall","ESS 2013 Muscat","Test And Wech","49er Europeans 2012","Reverse display test"]
@@ -1,50 +0,0 @@
<!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>Deprecated Web Services API</title>
</head>
<body>
<h1>SAP Sailing Analytics - Web Services</h1>
<h2>Name: leaderboards</h2>
<b>Description:</b>
<p>This service exports the names of all existing leaderboards</p>
<br/>
<table>
<tr>
<td>Webservice Type:</td>
<td>REST</td>
</tr>
<tr>
<td>Output format:</td>
<td>Json</td>
</tr>
<tr>
<td>Base-Url:</td>
<td>http://www.sapsailing.com/sailingserver/leaderboards</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/leaderboards">http://www.sapsailing.com/sailingserver/leaderboards</a></td>
</tr>
</table>
<a href="index.html">Back to Web Service Overview</a>
</body>
</html>
@@ -1 +0,0 @@
["ESS 2013 Muscat (Extreme40)","Extreme 40 Nice","Silbernes Band","49er Training Palma 2013","ESS 2013 singapure (Extreme40)","Star IDM 2013","SwissTimingTest","Extreme 40 Muscat","Drachen IDM 2012","Extreme Sailing Series Overall","BMW Cup Overall Overall","ESS 2012 Cardiff (Extreme40)","KW 2013 Laser Radial (Laser Radial)","BMW Cup","Extreme 40 Rio","505 WM Barbados 2013 (505)","ESS All test Overall","Test Overall","Extreme 40 Cardiff","Extreme 40 Porto","Nixdorf Pokal 2013","KW 2013 Laser (Laser)","505 Worlds 2012"]
@@ -1,50 +0,0 @@
<!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>Deprecated Web Services API</title>
</head>
<body>
<h1>SAP Sailing Analytics - Web Services</h1>
<h2>Name: regatta</h2>
<b>Description:</b>
<p>This service exports the structure (series, fleets, races) of a regatta</p>
<br/>
<table>
<tr>
<td>Webservice Type:</td>
<td>REST</td>
</tr>
<tr>
<td>Output format:</td>
<td>Json</td>
</tr>
<tr>
<td>Base-Url:</td>
<td>http://www.sapsailing.com/sailingserver/regatta</td>
</tr>
<tr>
<td>Mandatory parameters:</td>
<td>name</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://tw2013.sapsailing.com/sailingserver/regatta?name=TW%202013%20%28Hobie%20Tiger%29">http://tw2013.sapsailing.com/sailingserver/regatta?name=TW%202013%20%28Hobie%20Tiger%29</a></td>
</tr>
</table>
<a href="index.html">Back to Web Service Overview</a>
</body>
</html>
@@ -1 +0,0 @@
{"name":"TW 2013 (Hobie Tiger)","scoringSystem":"LOW_POINT","boatclass":"Hobie Tiger","series":[{"name":"Default","isMedalSeries":false,"startsWithZeroScore":false,"fleets":[{"name":"Default","ordering":0}],"races":["R1","R2","R3","R4","R5","R6","R7","R8","R9","R10","R11","R12","R13","R14","R15","R16","R17","R18"],"trackedRaces":{"fleets":[{"name":"Default","races":[{"name":"R1","isMedalRace":false,"isLive":false,"isTracked":false,"trackedRaceName":null,"hasGpsData":false,"hasWindData":false},{"name":"R2","isMedalRace":false,"isLive":false,"isTracked":false,"trackedRaceName":null,"hasGpsData":false,"hasWindData":false},{"name":"R3","isMedalRace":false,"isLive":false,"isTracked":false,"trackedRaceName":null,"hasGpsData":false,"hasWindData":false},{"name":"R4","isMedalRace":false,"isLive":false,"isTracked":false,"trackedRaceName":null,"hasGpsData":false,"hasWindData":false},{"name":"R5","isMedalRace":false,"isLive":false,"isTracked":true,"trackedRaceName":"Hobie Tiger - Race 5","hasGpsData":true,"hasWindData":true},{"name":"R6","isMedalRace":false,"isLive":false,"isTracked":true,"trackedRaceName":"Hobie Tiger - Race 6","hasGpsData":true,"hasWindData":true},{"name":"R7","isMedalRace":false,"isLive":true,"isTracked":true,"trackedRaceName":"Hobie Tiger - Race 7","hasGpsData":true,"hasWindData":true},{"name":"R8","isMedalRace":false,"isLive":false,"isTracked":true,"trackedRaceName":"Hobie Tiger - Race 8","hasGpsData":false,"hasWindData":false},{"name":"R9","isMedalRace":false,"isLive":false,"isTracked":false,"trackedRaceName":null,"hasGpsData":false,"hasWindData":false},{"name":"R10","isMedalRace":false,"isLive":false,"isTracked":false,"trackedRaceName":null,"hasGpsData":false,"hasWindData":false},{"name":"R11","isMedalRace":false,"isLive":false,"isTracked":false,"trackedRaceName":null,"hasGpsData":false,"hasWindData":false},{"name":"R12","isMedalRace":false,"isLive":false,"isTracked":false,"trackedRaceName":null,"hasGpsData":false,"hasWindData":false},{"name":"R13","isMedalRace":false,"isLive":false,"isTracked":false,"trackedRaceName":null,"hasGpsData":false,"hasWindData":false},{"name":"R14","isMedalRace":false,"isLive":false,"isTracked":false,"trackedRaceName":null,"hasGpsData":false,"hasWindData":false},{"name":"R15","isMedalRace":false,"isLive":false,"isTracked":false,"trackedRaceName":null,"hasGpsData":false,"hasWindData":false},{"name":"R16","isMedalRace":false,"isLive":false,"isTracked":false,"trackedRaceName":null,"hasGpsData":false,"hasWindData":false},{"name":"R17","isMedalRace":false,"isLive":false,"isTracked":false,"trackedRaceName":null,"hasGpsData":false,"hasWindData":false},{"name":"R18","isMedalRace":false,"isLive":false,"isTracked":false,"trackedRaceName":null,"hasGpsData":false,"hasWindData":false}]}]}}]}
@@ -1,50 +0,0 @@
<!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>Deprecated Web Services API</title>
</head>
<body>
<h1>SAP Sailing Analytics - Web Services</h1>
<h2>Name: regattas</h2>
<b>Description:</b>
<p>This service exports the names of all existing regattas</p>
<br/>
<table>
<tr>
<td>Webservice Type:</td>
<td>REST</td>
</tr>
<tr>
<td>Output format:</td>
<td>Json</td>
</tr>
<tr>
<td>Base-Url:</td>
<td>http://www.sapsailing.com/sailingserver/regattas</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/regattas">http://www.sapsailing.com/sailingserver/regattas</a></td>
</tr>
</table>
<a href="index.html">Back to Web Service Overview</a>
</body>
</html>
Loaded 100 of 141 files, more files were not shown because too many files have changed in this diff. Show more