> newControlPoints, DomainFactory baseDomainFactory) throws PatchFailedException;
}
diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/DomainFactory.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/DomainFactory.java
index 2e31e3c2be0..660100318ca 100755
--- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/DomainFactory.java
+++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/DomainFactory.java
@@ -4,6 +4,7 @@ import java.io.IOException;
import java.io.InputStream;
import com.sap.sailing.domain.base.impl.DomainFactoryImpl;
+import com.sap.sailing.domain.common.NauticalSide;
import com.sap.sailing.domain.common.ScoringSchemeType;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.leaderboard.ScoringScheme;
@@ -12,6 +13,62 @@ import com.sap.sailing.domain.tracking.MarkPassing;
public interface DomainFactory extends SharedDomainFactory {
static DomainFactory INSTANCE = new DomainFactoryImpl();
+ /**
+ * Looks up or, if not found, creates a {@link Nationality} object and re-uses threeLetterIOCCode also as the
+ * nationality's name.
+ */
+ Nationality getOrCreateNationality(String threeLetterIOCCode);
+
+ /**
+ * The name will also be used as the mark's ID. If you have a unique ID, use {@link #getOrCreateMark(Serializable, String)} instead.
+ */
+ Mark getOrCreateMark(String name);
+
+ /**
+ * Since some ID types, such as {@link UUID}, cannot be serialized as objects to a GWT client, only the
+ * {@link Object#toString()} representations of those IDs are serialized to the clients. When a client then requests
+ * to identify a mark again, only the ID's string representation will be submitted to the server and now needs to be
+ * mapped to the actual ID. This domain factory keeps a mapping of all mark ID's string representations to the
+ * actual ID for all marks ever managed through any of the getOrCreateMark(...) overloads.
+ *
+ *
+ * This method first looks up the actual ID whose string representation is toStringRepresentationOfID
+ * and then calls {@link #getOrCreateMark(Serializable, String)} with the result and the name
+ * parameter, or with ToStringRepresentationOfID and name in case the string
+ * representation of the ID is not known. So in the latter case, the string is used as the ID for the new mark.
+ */
+ Mark getOrCreateMark(String toStringRepresentationOfID, String name);
+
+ Mark getOrCreateMark(Serializable id, String name);
+
+ /**
+ * If the single mark with ID id already exists, it is returned. Its color may differ from color
+ * in that case. Otherwise, a new {@link Mark} is created with color as its {@link Mark#getColor()}
+ * and shape as its {@link Mark#getShape()}.
+ */
+ Mark getOrCreateMark(Serializable id, String name, MarkType type, String color, String shape, String pattern);
+
+ /**
+ * @param name also uses the name as the gate's ID; if you have a real ID, use {@link #createGate(Serializable, Mark, Mark, String)} instead
+ */
+ Gate createGate(Mark left, Mark right, String name);
+
+ Gate createGate(Serializable id, Mark left, Mark right, String name);
+
+ /**
+ * The waypoint created is weakly cached so that when requested again by
+ * {@link #getExistingWaypointById(Waypoint)} it is found.
+ */
+ Waypoint createWaypoint(ControlPoint controlPoint, NauticalSide passingSide);
+
+ Waypoint getExistingWaypointById(Waypoint waypointPrototype);
+
+ /**
+ * Atomically checks if a waypoint by an equal {@link Waypoint#getId()} as waypoint exists in this domain factory's
+ * waypoint cache. If so, the cached waypoint is returned. Otherwise, waypoint is added to the cache and returned.
+ */
+ Waypoint getExistingWaypointByIdOrCache(Waypoint waypoint);
+
MarkPassing createMarkPassing(TimePoint timePoint, Waypoint waypoint, Competitor competitor);
/**
diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/CourseImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/CourseImpl.java
index 9989eea6f39..ae8f94b0da0 100755
--- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/CourseImpl.java
+++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/CourseImpl.java
@@ -21,7 +21,9 @@ import com.sap.sailing.domain.base.DomainFactory;
import com.sap.sailing.domain.base.Leg;
import com.sap.sailing.domain.base.Mark;
import com.sap.sailing.domain.base.Waypoint;
+import com.sap.sailing.domain.common.NauticalSide;
import com.sap.sailing.domain.common.impl.NamedImpl;
+import com.sap.sailing.domain.common.impl.Util.Pair;
import com.sap.sailing.util.CourseAsWaypointList;
import com.sap.sailing.util.impl.LockUtil;
import com.sap.sailing.util.impl.NamedReentrantReadWriteLock;
@@ -364,7 +366,7 @@ public class CourseImpl extends NamedImpl implements Course {
}
@Override
- public void update(List extends ControlPoint> newControlPoints, DomainFactory baseDomainFactory) throws PatchFailedException {
+ public void update(List> newControlPoints, DomainFactory baseDomainFactory) throws PatchFailedException {
LockUtil.lockForWrite(lock);
try {
Iterable courseWaypoints = getWaypoints();
@@ -383,12 +385,12 @@ public class CourseImpl extends NamedImpl implements Course {
}
wpl.add(waypoint);
}
- for (com.sap.sailing.domain.base.ControlPoint newDomainControlPoint : newControlPoints) {
- List waypoints = existingWaypointsByControlPoint.get(newDomainControlPoint);
+ for (Pair newDomainControlPoint : newControlPoints) {
+ List waypoints = existingWaypointsByControlPoint.get(newDomainControlPoint.getA());
Waypoint waypoint;
if (waypoints == null || waypoints.isEmpty()) {
// must be a new control point for which we don't have a waypoint yet
- waypoint = baseDomainFactory.createWaypoint(newDomainControlPoint);
+ waypoint = baseDomainFactory.createWaypoint(newDomainControlPoint.getA(), newDomainControlPoint.getB());
} else {
waypoint = waypoints.remove(0); // take the first from the list
}
diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/DomainFactoryImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/DomainFactoryImpl.java
index 6e967b3a1d2..4209941004c 100755
--- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/DomainFactoryImpl.java
+++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/DomainFactoryImpl.java
@@ -25,8 +25,10 @@ import com.sap.sailing.domain.base.ObjectInputStreamResolvingAgainstDomainFactor
import com.sap.sailing.domain.base.Team;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.common.MarkType;
+import com.sap.sailing.domain.common.NauticalSide;
import com.sap.sailing.domain.common.ScoringSchemeType;
import com.sap.sailing.domain.common.TimePoint;
+import com.sap.sailing.domain.common.WithID;
import com.sap.sailing.domain.leaderboard.ScoringScheme;
import com.sap.sailing.domain.leaderboard.impl.HighPoint;
import com.sap.sailing.domain.leaderboard.impl.HighPointExtremeSailingSeriesOverall;
@@ -169,10 +171,10 @@ public class DomainFactoryImpl implements DomainFactory {
}
@Override
- public Waypoint createWaypoint(ControlPoint controlPoint) {
+ public Waypoint createWaypoint(ControlPoint controlPoint, NauticalSide passingSide) {
synchronized (waypointCache) {
expungeStaleWaypointCacheEntries();
- Waypoint result = new WaypointImpl(controlPoint);
+ Waypoint result = new WaypointImpl(controlPoint, passingSide);
waypointCache.put(result.getId(), new WeakWaypointReference(result));
return result;
}
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/actions/GetCoursePositionsAction.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/actions/GetCoursePositionsAction.java
index aafae4e1ba5..e4792fb471e 100644
--- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/actions/GetCoursePositionsAction.java
+++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/actions/GetCoursePositionsAction.java
@@ -5,15 +5,15 @@ import java.util.Date;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
-import com.sap.sailing.gwt.ui.shared.CourseDTO;
+import com.sap.sailing.gwt.ui.shared.CoursePositionsDTO;
-public class GetCoursePositionsAction extends DefaultAsyncAction {
+public class GetCoursePositionsAction extends DefaultAsyncAction {
private final SailingServiceAsync sailingService;
private final RegattaAndRaceIdentifier raceIdentifier;
private final Date date;
public GetCoursePositionsAction(SailingServiceAsync sailingService, RegattaAndRaceIdentifier raceIdentifier, Date date,
- AsyncCallback callback) {
+ AsyncCallback callback) {
super(callback);
this.sailingService = sailingService;
this.raceIdentifier = raceIdentifier;
@@ -22,6 +22,6 @@ public class GetCoursePositionsAction extends DefaultAsyncAction {
@Override
public void execute(AsyncActionsExecutor asyncActionsExecutor) {
- sailingService.getCoursePositions(raceIdentifier, date, (AsyncCallback) getWrapperCallback(asyncActionsExecutor));
+ sailingService.getCoursePositions(raceIdentifier, date, (AsyncCallback) getWrapperCallback(asyncActionsExecutor));
}
}
\ No newline at end of file
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceCourseManagementPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceCourseManagementPanel.java
index ac2b349ab2d..3aa39aaf270 100755
--- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceCourseManagementPanel.java
+++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceCourseManagementPanel.java
@@ -33,6 +33,8 @@ import com.google.gwt.view.client.SelectionChangeEvent;
import com.google.gwt.view.client.SelectionChangeEvent.Handler;
import com.google.gwt.view.client.SelectionModel;
import com.google.gwt.view.client.SingleSelectionModel;
+import com.sap.sailing.domain.common.NauticalSide;
+import com.sap.sailing.domain.common.impl.Util.Pair;
import com.sap.sailing.gwt.ui.client.DataEntryDialog;
import com.sap.sailing.gwt.ui.client.DataEntryDialog.DialogCallback;
import com.sap.sailing.gwt.ui.client.ErrorReporter;
@@ -42,7 +44,8 @@ import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.shared.ControlPointDTO;
import com.sap.sailing.gwt.ui.shared.GateDTO;
import com.sap.sailing.gwt.ui.shared.MarkDTO;
-import com.sap.sailing.gwt.ui.shared.RaceCourseMarksDTO;
+import com.sap.sailing.gwt.ui.shared.RaceCourseDTO;
+import com.sap.sailing.gwt.ui.shared.WaypointDTO;
/**
* A panel that has a race selection (inherited from {@link AbstractRaceManagementPanel}) and which adds a table
@@ -62,12 +65,17 @@ public class RaceCourseManagementPanel extends AbstractRaceManagementPanel {
private final ControlPointDTO controlPoint;
private final MarkDTO oldMark;
private MarkDTO newMark;
- public ControlPointAndOldAndNewMark(ControlPointDTO controlPoint, MarkDTO oldMark) {
+ private NauticalSide passingSide;
+ public ControlPointAndOldAndNewMark(ControlPointDTO controlPoint, NauticalSide passingSide, MarkDTO oldMark) {
super();
this.controlPoint = controlPoint;
+ this.passingSide = passingSide;
this.oldMark = oldMark;
this.newMark = oldMark;
}
+ public NauticalSide getPassingSide() {
+ return passingSide;
+ }
public MarkDTO getNewMark() {
return newMark;
}
@@ -215,8 +223,19 @@ public class RaceCourseManagementPanel extends AbstractRaceManagementPanel {
public String getValue(ControlPointAndOldAndNewMark cpaoanb) {
return cpaoanb.getControlPoint().name;
}
- };
+ };
controlPointsTable.addColumn(nameColumn, stringMessages.controlPoint());
+ TextColumn passingSideColumn = new TextColumn() {
+ @Override
+ public String getValue(ControlPointAndOldAndNewMark cpaoanb) {
+ String result = "";
+ if(cpaoanb.getPassingSide() != null) {
+ result = cpaoanb.getPassingSide().name();
+ }
+ return result;
+ }
+ };
+ controlPointsTable.addColumn(passingSideColumn, "Passing side");
TextColumn oldMarkColumn = new TextColumn() {
@Override
public String getValue(ControlPointAndOldAndNewMark cpaoanb) {
@@ -326,7 +345,7 @@ public class RaceCourseManagementPanel extends AbstractRaceManagementPanel {
private void saveCourse(SailingServiceAsync sailingService, final StringMessages stringMessages) {
Set oldControlPointsFromTableAlreadyHandled = new HashSet();
- List controlPoints = new ArrayList();
+ List> controlPoints = new ArrayList>();
for (ControlPointAndOldAndNewMark cpaoanb : controlPointDataProvider.getList()) {
if (!oldControlPointsFromTableAlreadyHandled.contains(cpaoanb.getControlPoint())) {
oldControlPointsFromTableAlreadyHandled.add(cpaoanb.getControlPoint());
@@ -340,7 +359,7 @@ public class RaceCourseManagementPanel extends AbstractRaceManagementPanel {
} else {
controlPointToAdd = cpaoanb.getControlPoint();
}
- controlPoints.add(controlPointToAdd);
+ controlPoints.add(new Pair(controlPointToAdd, cpaoanb.passingSide));
}
}
sailingService.updateRaceCourse(singleSelectedRace, controlPoints, new AsyncCallback() {
@@ -396,7 +415,9 @@ public class RaceCourseManagementPanel extends AbstractRaceManagementPanel {
@Override
public SafeHtml getValue(MarkDTO mark) {
SafeHtmlBuilder builder = new SafeHtmlBuilder();
- builder.appendEscaped(mark.position.toFormattedString());
+ if(mark.position != null) {
+ builder.appendEscaped(mark.position.toFormattedString());
+ }
return builder.toSafeHtml();
}
};
@@ -480,7 +501,7 @@ public class RaceCourseManagementPanel extends AbstractRaceManagementPanel {
ControlPointAndOldAndNewMark selectedElement = selectedElements.iterator().next();
int insertPos = controlPointDataProvider.getList().indexOf(selectedElement) + (beforeSelection?0:1);
for (MarkDTO markDTO : result.getMarks()) {
- controlPointDataProvider.getList().add(insertPos++, new ControlPointAndOldAndNewMark(result, markDTO));
+ controlPointDataProvider.getList().add(insertPos++, new ControlPointAndOldAndNewMark(result, null, markDTO));
}
}
}
@@ -491,27 +512,17 @@ public class RaceCourseManagementPanel extends AbstractRaceManagementPanel {
void refreshSelectedRaceData() {
if (singleSelectedRace != null && selectedRaceDTO != null) {
courseActionsPanel.setVisible(true);
- sailingService.getRaceCourse(singleSelectedRace, new Date(), new AsyncCallback>() {
+ sailingService.getRaceCourse(singleSelectedRace, new Date(), new AsyncCallback() {
@Override
- public void onSuccess(final List controlPoints) {
- sailingService.getRaceCourseMarks(singleSelectedRace, new Date(), new AsyncCallback() {
- @Override
- public void onSuccess(RaceCourseMarksDTO raceCourseMarksDTO) {
- updateCourseAndMarksInfo(controlPoints, raceCourseMarksDTO);
- }
-
- @Override
- public void onFailure(Throwable caught) {
- RaceCourseManagementPanel.this.errorReporter.reportError(
- RaceCourseManagementPanel.this.stringMessages.errorTryingToObtainTheMarksOfTheRace(
- caught.getMessage()));
- }
- });
+ public void onSuccess(RaceCourseDTO raceCourseDTO) {
+ updateCourseAndMarksInfo(raceCourseDTO);
}
-
+
@Override
public void onFailure(Throwable caught) {
- RaceCourseManagementPanel.this.errorReporter.reportError(stringMessages.errorTryingToObtainRaceCourse(caught.getMessage()));
+ RaceCourseManagementPanel.this.errorReporter.reportError(
+ RaceCourseManagementPanel.this.stringMessages.errorTryingToObtainTheMarksOfTheRace(
+ caught.getMessage()));
}
});
} else {
@@ -519,11 +530,12 @@ public class RaceCourseManagementPanel extends AbstractRaceManagementPanel {
}
}
- private void updateCourseAndMarksInfo(List controlPoints, RaceCourseMarksDTO marksDTO) {
+ private void updateCourseAndMarksInfo(RaceCourseDTO raceCourseDTO) {
List waypointsAndOldAndNewMarks = new ArrayList();
- for (ControlPointDTO controlPointDTO : controlPoints) {
+ for (WaypointDTO waypointDTO : raceCourseDTO.waypoints) {
+ ControlPointDTO controlPointDTO = waypointDTO.controlPoint;
for (MarkDTO mark : controlPointDTO.getMarks()) {
- ControlPointAndOldAndNewMark waypointAndOldAndNewMark = new ControlPointAndOldAndNewMark(controlPointDTO, mark);
+ ControlPointAndOldAndNewMark waypointAndOldAndNewMark = new ControlPointAndOldAndNewMark(controlPointDTO, waypointDTO.passingSide, mark);
waypointsAndOldAndNewMarks.add(waypointAndOldAndNewMark);
}
}
@@ -531,7 +543,7 @@ public class RaceCourseManagementPanel extends AbstractRaceManagementPanel {
controlPointDataProvider.getList().addAll(waypointsAndOldAndNewMarks);
controlPointsNeedingReplacement.clear();
markDataProvider.getList().clear();
- markDataProvider.getList().addAll(marksDTO.marks);
+ markDataProvider.getList().addAll(raceCourseDTO.getMarks());
Collections.sort(markDataProvider.getList(), new Comparator() {
@Override
public int compare(MarkDTO o1, MarkDTO o2) {
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/WindPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/WindPanel.java
index cefddc61c74..141a78a1672 100755
--- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/WindPanel.java
+++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/WindPanel.java
@@ -41,7 +41,7 @@ import com.sap.sailing.gwt.ui.client.RegattaDisplayer;
import com.sap.sailing.gwt.ui.client.RegattaRefresher;
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
import com.sap.sailing.gwt.ui.client.StringMessages;
-import com.sap.sailing.gwt.ui.shared.CourseDTO;
+import com.sap.sailing.gwt.ui.shared.CoursePositionsDTO;
import com.sap.sailing.gwt.ui.shared.RaceDTO;
import com.sap.sailing.gwt.ui.shared.RegattaDTO;
import com.sap.sailing.gwt.ui.shared.WindDTO;
@@ -109,14 +109,14 @@ public class WindPanel extends FormPanel implements RegattaDisplayer, WindShower
RegattaAndRaceIdentifier selectedRace = getSelectedRace();
if(selectedRace != null) {
final RaceDTO race = trackedRacesListComposite.getRaceByIdentifier(selectedRace);
- sailingService.getCoursePositions(selectedRace, race.startOfRace, new AsyncCallback() {
+ sailingService.getCoursePositions(selectedRace, race.startOfRace, new AsyncCallback() {
@Override
public void onFailure(Throwable caught) {
showWindSettingDialog(race, null);
}
@Override
- public void onSuccess(final CourseDTO result) {
+ public void onSuccess(final CoursePositionsDTO result) {
showWindSettingDialog(race, result);
}
});
@@ -214,7 +214,7 @@ public class WindPanel extends FormPanel implements RegattaDisplayer, WindShower
windFixesDisplayPanel.add(rawWindFixesTable);
}
- private void showWindSettingDialog(RaceDTO race, CourseDTO course) {
+ private void showWindSettingDialog(RaceDTO race, CoursePositionsDTO course) {
WindSettingDialog windSettingDialog = new WindSettingDialog(race, course, stringMessages,
new DialogCallback() {
@Override
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/WindSettingDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/WindSettingDialog.java
index 76ed1a82928..18f9bef8b83 100755
--- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/WindSettingDialog.java
+++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/WindSettingDialog.java
@@ -8,7 +8,7 @@ import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.user.datepicker.client.DateBox;
import com.sap.sailing.gwt.ui.client.DataEntryDialog;
import com.sap.sailing.gwt.ui.client.StringMessages;
-import com.sap.sailing.gwt.ui.shared.CourseDTO;
+import com.sap.sailing.gwt.ui.shared.CoursePositionsDTO;
import com.sap.sailing.gwt.ui.shared.PositionDTO;
import com.sap.sailing.gwt.ui.shared.RaceDTO;
import com.sap.sailing.gwt.ui.shared.WindDTO;
@@ -54,7 +54,7 @@ public class WindSettingDialog extends DataEntryDialog {
}
}
- public WindSettingDialog(RaceDTO race, CourseDTO courseDTO, StringMessages stringMessages, DialogCallback callback) {
+ public WindSettingDialog(RaceDTO race, CoursePositionsDTO courseDTO, StringMessages stringMessages, DialogCallback callback) {
super(stringMessages.actionAddWindData(), null, stringMessages.ok(), stringMessages.cancel(), new WindDataValidator(stringMessages), callback);
this.stringMessages = stringMessages;
speedInKnotsBox = createDoubleBox(5);
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java
index d1d00b19708..f21c5c97717 100644
--- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java
+++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java
@@ -13,6 +13,7 @@ import com.google.gwt.user.client.rpc.RemoteServiceRelativePath;
import com.sap.sailing.domain.common.Color;
import com.sap.sailing.domain.common.DetailType;
import com.sap.sailing.domain.common.MaxPointsReason;
+import com.sap.sailing.domain.common.NauticalSide;
import com.sap.sailing.domain.common.NoWindException;
import com.sap.sailing.domain.common.PolarSheetGenerationTriggerResponse;
import com.sap.sailing.domain.common.PolarSheetsData;
@@ -29,14 +30,14 @@ import com.sap.sailing.gwt.ui.shared.CompetitorsRaceDataDTO;
import com.sap.sailing.gwt.ui.shared.ControlPointDTO;
import com.sap.sailing.gwt.ui.shared.CourseAreaDTO;
import com.sap.sailing.gwt.ui.shared.CourseDTO;
-import com.sap.sailing.gwt.ui.shared.EventDTO;
+import com.sap.sailing.gwt.ui.shared.CoursePositionsDTO;import com.sap.sailing.gwt.ui.shared.EventDTO;
import com.sap.sailing.gwt.ui.shared.GPSFixDTO;
import com.sap.sailing.gwt.ui.shared.LeaderboardDTO;
import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO;
import com.sap.sailing.gwt.ui.shared.ManeuverDTO;
import com.sap.sailing.gwt.ui.shared.QuickRankDTO;
import com.sap.sailing.gwt.ui.shared.RaceColumnInSeriesDTO;
-import com.sap.sailing.gwt.ui.shared.RaceCourseMarksDTO;
+import com.sap.sailing.gwt.ui.shared.RaceCourseDTO;
import com.sap.sailing.gwt.ui.shared.RaceDTO;
import com.sap.sailing.gwt.ui.shared.RaceEventLogDTO;
import com.sap.sailing.gwt.ui.shared.RaceMapDataDTO;
@@ -110,9 +111,9 @@ public interface SailingService extends RemoteService {
List getRaceTimesInfos(Collection raceIdentifiers);
- CourseDTO getCoursePositions(RegattaAndRaceIdentifier raceIdentifier, Date date);
+ CoursePositionsDTO getCoursePositions(RegattaAndRaceIdentifier raceIdentifier, Date date);
- List getRaceCourse(RegattaAndRaceIdentifier raceIdentifier, Date date);
+ RaceCourseDTO getRaceCourse(RegattaAndRaceIdentifier raceIdentifier, Date date);
List getQuickRanks(RegattaAndRaceIdentifier raceIdentifier, Date date) throws NoWindException;
@@ -265,9 +266,7 @@ public interface SailingService extends RemoteService {
Void updateLeaderboardScoreCorrectionMetadata(String leaderboardName, Date timePointOfLastCorrectionValidity,
String comment);
- RaceCourseMarksDTO getRaceCourseMarks(RegattaAndRaceIdentifier raceIdentifier, Date date);
-
- void updateRaceCourse(RegattaAndRaceIdentifier raceIdentifier, List controlPoints);
+ void updateRaceCourse(RegattaAndRaceIdentifier raceIdentifier, List> controlPoints);
void addColumnsToLeaderboard(String leaderboardName, List> columnsToAdd);
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java
index 58e587f4c70..9de3c0d2e28 100644
--- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java
+++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java
@@ -12,6 +12,7 @@ import com.google.gwt.user.client.rpc.AsyncCallback;
import com.sap.sailing.domain.common.Color;
import com.sap.sailing.domain.common.DetailType;
import com.sap.sailing.domain.common.MaxPointsReason;
+import com.sap.sailing.domain.common.NauticalSide;
import com.sap.sailing.domain.common.PolarSheetGenerationTriggerResponse;
import com.sap.sailing.domain.common.PolarSheetsData;
import com.sap.sailing.domain.common.PolarSheetsHistogramData;
@@ -29,7 +30,7 @@ import com.sap.sailing.gwt.ui.shared.CompetitorsRaceDataDTO;
import com.sap.sailing.gwt.ui.shared.ControlPointDTO;
import com.sap.sailing.gwt.ui.shared.CourseAreaDTO;
import com.sap.sailing.gwt.ui.shared.CourseDTO;
-import com.sap.sailing.gwt.ui.shared.EventDTO;
+import com.sap.sailing.gwt.ui.shared.CoursePositionsDTO;import com.sap.sailing.gwt.ui.shared.EventDTO;
import com.sap.sailing.gwt.ui.shared.GPSFixDTO;
import com.sap.sailing.gwt.ui.shared.LeaderboardDTO;
import com.sap.sailing.gwt.ui.shared.LeaderboardEntryDTO;
@@ -37,7 +38,7 @@ import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO;
import com.sap.sailing.gwt.ui.shared.ManeuverDTO;
import com.sap.sailing.gwt.ui.shared.QuickRankDTO;
import com.sap.sailing.gwt.ui.shared.RaceColumnInSeriesDTO;
-import com.sap.sailing.gwt.ui.shared.RaceCourseMarksDTO;
+import com.sap.sailing.gwt.ui.shared.RaceCourseDTO;
import com.sap.sailing.gwt.ui.shared.RaceDTO;
import com.sap.sailing.gwt.ui.shared.RaceEventLogDTO;
import com.sap.sailing.gwt.ui.shared.RaceMapDataDTO;
@@ -176,7 +177,7 @@ public interface SailingServiceAsync {
void getRaceTimesInfos(Collection raceIdentifiers, AsyncCallback> callback);
- void getCoursePositions(RegattaAndRaceIdentifier raceIdentifier, Date date, AsyncCallback asyncCallback);
+ void getCoursePositions(RegattaAndRaceIdentifier raceIdentifier, Date date, AsyncCallback asyncCallback);
void getQuickRanks(RegattaAndRaceIdentifier raceIdentifier, Date date, AsyncCallback> callback);
@@ -400,9 +401,9 @@ public interface SailingServiceAsync {
void getWindSourcesInfo(RegattaAndRaceIdentifier raceIdentifier, AsyncCallback callback);
- void getRaceCourse(RegattaAndRaceIdentifier raceIdentifier, Date date, AsyncCallback> callback);
+ void getRaceCourse(RegattaAndRaceIdentifier raceIdentifier, Date date, AsyncCallback callback);
- void updateRaceCourse(RegattaAndRaceIdentifier raceIdentifier, List controlPoints, AsyncCallback callback);
+ void updateRaceCourse(RegattaAndRaceIdentifier raceIdentifier, List> controlPoints, AsyncCallback callback);
void getFregResultUrls(AsyncCallback> asyncCallback);
@@ -410,8 +411,6 @@ public interface SailingServiceAsync {
void addFragUrl(String result, AsyncCallback asyncCallback);
- void getRaceCourseMarks(RegattaAndRaceIdentifier raceIdentifier, Date date, AsyncCallback callback);
-
void addColumnsToLeaderboard(String leaderboardName, List> columnsToAdd,
AsyncCallback callback);
@@ -451,5 +450,5 @@ public interface SailingServiceAsync {
void getPolarSheetsGenerationResults(String id, AsyncCallback asyncCallback);
+
void getPolarSheetData(String polarSheetId, int angle, int windSpeed, AsyncCallback wrapperCallback);
-}
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java
index 1145288036c..be1a23bc727 100755
--- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java
+++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java
@@ -1416,7 +1416,13 @@ public class LeaderboardPanel extends FormPanel implements TimeListener, PlaySta
private static class TotalAverageSpeedOverGroundField implements LegDetailField {
@Override
public Double get(LeaderboardRowDTO row) {
- return row.totalDistanceTraveledInMeters / row.totalTimeSailedInSeconds / Mile.METERS_PER_NAUTICAL_MILE * 3600;
+ final Double result;
+ if (row.totalTimeSailedInSeconds != null && row.totalTimeSailedInSeconds != 0.0) {
+ result = row.totalDistanceTraveledInMeters / row.totalTimeSailedInSeconds / Mile.METERS_PER_NAUTICAL_MILE * 3600;
+ } else {
+ result = null;
+ }
+ return result;
}
}
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java
index 85e438dcfa5..a21098cb95e 100755
--- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java
+++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java
@@ -75,6 +75,7 @@ import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.LegType;
import com.sap.sailing.domain.common.ManeuverType;
import com.sap.sailing.domain.common.MaxPointsReason;
+import com.sap.sailing.domain.common.NauticalSide;
import com.sap.sailing.domain.common.NoWindError;
import com.sap.sailing.domain.common.NoWindException;
import com.sap.sailing.domain.common.Placemark;
@@ -161,6 +162,7 @@ import com.sap.sailing.gwt.ui.shared.CompetitorsRaceDataDTO;
import com.sap.sailing.gwt.ui.shared.ControlPointDTO;
import com.sap.sailing.gwt.ui.shared.CourseAreaDTO;
import com.sap.sailing.gwt.ui.shared.CourseDTO;
+import com.sap.sailing.gwt.ui.shared.CoursePositionsDTO;
import com.sap.sailing.gwt.ui.shared.EventDTO;
import com.sap.sailing.gwt.ui.shared.FleetDTO;
import com.sap.sailing.gwt.ui.shared.GPSFixDTO;
@@ -181,7 +183,7 @@ import com.sap.sailing.gwt.ui.shared.PositionDTO;
import com.sap.sailing.gwt.ui.shared.QuickRankDTO;
import com.sap.sailing.gwt.ui.shared.RaceColumnDTO;
import com.sap.sailing.gwt.ui.shared.RaceColumnInSeriesDTO;
-import com.sap.sailing.gwt.ui.shared.RaceCourseMarksDTO;
+import com.sap.sailing.gwt.ui.shared.RaceCourseDTO;
import com.sap.sailing.gwt.ui.shared.RaceDTO;
import com.sap.sailing.gwt.ui.shared.RaceEventDTO;
import com.sap.sailing.gwt.ui.shared.RaceEventLogDTO;
@@ -207,6 +209,7 @@ import com.sap.sailing.gwt.ui.shared.TracTracConfigurationDTO;
import com.sap.sailing.gwt.ui.shared.TracTracRaceRecordDTO;
import com.sap.sailing.gwt.ui.shared.TrackedRaceDTO;
import com.sap.sailing.gwt.ui.shared.VenueDTO;
+import com.sap.sailing.gwt.ui.shared.WaypointDTO;
import com.sap.sailing.gwt.ui.shared.WindDTO;
import com.sap.sailing.gwt.ui.shared.WindInfoForRaceDTO;
import com.sap.sailing.gwt.ui.shared.WindTrackInfoDTO;
@@ -1025,7 +1028,12 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
return result;
}
- private MarkDTO convertToMarkDTO(Mark mark, Position position) {
+ MarkDTO markDTO;
+ if(position != null) {
+ markDTO = new MarkDTO(mark.getId().toString(), mark.getName(), position.getLatDeg(), position.getLngDeg());
+ } else {
+ markDTO = new MarkDTO(mark.getId().toString(), mark.getName());
+ }
MarkDTO markDTO = new MarkDTO(mark.getId().toString(), mark.getName(), position.getLatDeg(), position.getLngDeg());
markDTO.color = mark.getColor();
markDTO.shape = mark.getShape();
@@ -1710,8 +1718,8 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
return raceTimesInfos;
}
- @Override
- public CourseDTO getCoursePositions(RegattaAndRaceIdentifier raceIdentifier, Date date) {
+ public CoursePositionsDTO getCoursePositions(RegattaAndRaceIdentifier raceIdentifier, Date date) {
+ CoursePositionsDTO result = new CoursePositionsDTO();
CourseDTO result = new CourseDTO();
if (date != null) {
TimePoint dateAsTimePoint = new MillisecondsTimePoint(date);
@@ -1750,43 +1758,33 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
}
}
return result;
- }
+
- @Override
- public RaceCourseMarksDTO getRaceCourseMarks(RegattaAndRaceIdentifier raceIdentifier, Date date) {
- RaceCourseMarksDTO raceMarksDTO = new RaceCourseMarksDTO();
- TimePoint dateAsTimePoint = new MillisecondsTimePoint(date);
- TrackedRace trackedRace = getExistingTrackedRace(raceIdentifier);
- if (trackedRace != null) {
- raceMarksDTO.marks = new HashSet();
- Iterable marks = trackedRace.getMarks();
- for (Mark mark : marks) {
- GPSFixTrack track = trackedRace.getOrCreateTrack(mark);
- Position positionAtDate = track.getEstimatedPosition(dateAsTimePoint, /* extrapolate */false);
- if (positionAtDate != null) {
- raceMarksDTO.marks.add(convertToMarkDTO(mark, positionAtDate));
- }
- }
- }
- return raceMarksDTO;
- }
-
- @Override
- public List getRaceCourse(RegattaAndRaceIdentifier raceIdentifier, Date date) {
+ public RaceCourseDTO getRaceCourse(RegattaAndRaceIdentifier raceIdentifier, Date date) {
+ List waypointDTOs = new ArrayList();
+ RaceCourseDTO result = new RaceCourseDTO(waypointDTOs);
List result = new ArrayList();
if (date != null) {
TimePoint dateAsTimePoint = new MillisecondsTimePoint(date);
TrackedRace trackedRace = getExistingTrackedRace(raceIdentifier);
if (trackedRace != null) {
Course course = trackedRace.getRace().getCourse();
- for (Waypoint waypoint : course.getWaypoints()) {
+ List markDTOs = new ArrayList();
+ for (Mark mark : trackedRace.getMarks()) {
+ GPSFixTrack track = trackedRace.getOrCreateTrack(mark);
+ Position positionAtDate = track.getEstimatedPosition(dateAsTimePoint, /* extrapolate */false);
+ markDTOs.add(convertToMarkDTO(mark, positionAtDate));
+ }
+ ControlPointDTO controlPointDTO = convertToControlPointDTO(waypoint.getControlPoint(), trackedRace, dateAsTimePoint);
+ WaypointDTO waypointDTO = new WaypointDTO(waypoint.getName(), controlPointDTO, markDTOs, waypoint.getPassingSide());
+ waypointDTOs.add(waypointDTO);
result.add(createControlPointDTO(waypoint.getControlPoint(), trackedRace, dateAsTimePoint));
}
}
}
return result;
}
-
+ private ControlPointDTO convertToControlPointDTO(ControlPoint controlPoint, TrackedRace trackedRace, TimePoint timePoint) {
private ControlPointDTO createControlPointDTO(ControlPoint controlPoint, TrackedRace trackedRace, TimePoint timePoint) {
ControlPointDTO result;
if (controlPoint instanceof Gate) {
@@ -1811,21 +1809,22 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
* list of control points is then passed to {@link Course#update(List, com.sap.sailing.domain.base.DomainFactory)} for
* the course of the race identified by raceIdentifier.
*/
- @Override
+ public void updateRaceCourse(RegattaAndRaceIdentifier raceIdentifier, List> controlPoints) {
public void updateRaceCourse(RegattaAndRaceIdentifier raceIdentifier, List controlPoints) {
TrackedRace trackedRace = getExistingTrackedRace(raceIdentifier);
if (trackedRace != null) {
Course course = trackedRace.getRace().getCourse();
- Iterable waypoints = course.getWaypoints();
+ List> newControlPoints = new ArrayList>();
List newControlPoints = new ArrayList();
- int lastMatchPosition = -1;
+ for (Pair controlPointAndPassingSide : controlPoints) {
+ ControlPointDTO controlPointDTO = controlPointAndPassingSide.getA();
for (ControlPointDTO controlPointDTO : controlPoints) {
ControlPoint matchFromOldCourse = null;
for (int i=lastMatchPosition+1; matchFromOldCourse == null && i(matchFromOldCourse, null));
newControlPoints.add(matchFromOldCourse);
lastMatchPosition = i;
}
@@ -1843,10 +1842,12 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
}
Mark left = baseDomainFactory.getOrCreateMark(gateDTO.getLeft().getIdAsString(), gateDTO.getLeft().name);
Mark right = baseDomainFactory.getOrCreateMark(gateDTO.getRight().getIdAsString(), gateDTO.getRight().name);
+ newControlPoints.add(new Pair(newControlPoint, null));
newControlPoint = baseDomainFactory.createGate(id, left, right, gateDTO.name);
} else {
+ NauticalSide nauticalSide = controlPointAndPassingSide.getB();
+ newControlPoints.add(new Pair(newControlPoint, nauticalSide));
newControlPoint = baseDomainFactory.getOrCreateMark(controlPointDTO.getIdAsString(), controlPointDTO.name);
- }
newControlPoints.add(newControlPoint);
}
}
@@ -3298,5 +3299,3 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
return histogramData;
}
-
-}
\ No newline at end of file
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/CourseDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/CoursePositionsDTO.java
similarity index 84%
rename from java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/CourseDTO.java
rename to java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/CoursePositionsDTO.java
index 038cb5581ab..5baa492a14e 100644
--- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/CourseDTO.java
+++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/CoursePositionsDTO.java
@@ -5,7 +5,7 @@ import java.util.Set;
import com.google.gwt.user.client.rpc.IsSerializable;
-public class CourseDTO implements IsSerializable {
+public class CoursePositionsDTO implements IsSerializable {
public List waypointPositions;
public List startMarkPositions;
public List finishMarkPositions;
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/MarkDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/MarkDTO.java
index 3ef0756afb4..826fbf0c27e 100755
--- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/MarkDTO.java
+++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/MarkDTO.java
@@ -18,6 +18,10 @@ public class MarkDTO extends ControlPointDTO {
this.position = new PositionDTO(latDeg, lngDeg);
}
+ public MarkDTO(String idAsString, String name) {
+ super(idAsString, name);
+ }
+
@Override
public Iterable getMarks() {
return Collections.singleton(this);
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/RaceCourseDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/RaceCourseDTO.java
new file mode 100644
index 00000000000..74fa008f8f2
--- /dev/null
+++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/RaceCourseDTO.java
@@ -0,0 +1,39 @@
+package com.sap.sailing.gwt.ui.shared;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.google.gwt.user.client.rpc.IsSerializable;
+
+public class RaceCourseDTO implements IsSerializable {
+ public List waypoints;
+
+ RaceCourseDTO() {}
+
+ public RaceCourseDTO(List waypoints) {
+ this.waypoints = waypoints;
+ }
+
+ public List getControlPoints() {
+ List controlPoints = new ArrayList();
+ for(WaypointDTO waypoint: waypoints) {
+ controlPoints.add(waypoint.controlPoint);
+ }
+ return controlPoints;
+ }
+
+ public Collection getMarks() {
+ Map marks = new HashMap();
+ for(WaypointDTO waypoint: waypoints) {
+ for(MarkDTO mark: waypoint.marks) {
+ if(!marks.containsKey(mark.name)) {
+ marks.put(mark.name, mark);
+ }
+ }
+ }
+ return marks.values();
+ }
+}
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/RaceCourseMarksDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/RaceCourseMarksDTO.java
deleted file mode 100644
index c5853f58989..00000000000
--- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/RaceCourseMarksDTO.java
+++ /dev/null
@@ -1,9 +0,0 @@
-package com.sap.sailing.gwt.ui.shared;
-
-import java.util.Set;
-
-import com.google.gwt.user.client.rpc.IsSerializable;
-
-public class RaceCourseMarksDTO implements IsSerializable {
- public Set marks;
-}
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/RaceMapDataDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/RaceMapDataDTO.java
index 73cd75a1528..ff3526cccd1 100644
--- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/RaceMapDataDTO.java
+++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/RaceMapDataDTO.java
@@ -7,7 +7,7 @@ import com.google.gwt.user.client.rpc.IsSerializable;
public class RaceMapDataDTO implements IsSerializable {
public Map> boatPositions;
- public CourseDTO coursePositions;
+ public CoursePositionsDTO coursePositions;
public List quickRanks;
public BoatClassDTO boatClass;
}
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/WaypointDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/WaypointDTO.java
index 63d3055628b..f052ba70ba9 100644
--- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/WaypointDTO.java
+++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/WaypointDTO.java
@@ -2,45 +2,22 @@ package com.sap.sailing.gwt.ui.shared;
import java.util.List;
+import com.sap.sailing.domain.common.NauticalSide;
+
public class WaypointDTO extends NamedDTO {
public List marks;
- public int courseIndex;
-
- public int markPassingsCount;
+ public ControlPointDTO controlPoint;
- public WaypointDTO() {}
+ public NauticalSide passingSide;
- public WaypointDTO(String name, int courseIndex) {
+ WaypointDTO() {}
+
+ public WaypointDTO(String name, ControlPointDTO controlPoint, List marks, NauticalSide passingSide) {
super(name);
- this.courseIndex = courseIndex;
- }
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = super.hashCode();
- result = prime * result + courseIndex;
- result = prime * result + ((name == null) ? 0 : name.hashCode());
- return result;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj)
- return true;
- if (!super.equals(obj))
- return false;
- if (getClass() != obj.getClass())
- return false;
- WaypointDTO other = (WaypointDTO) obj;
- if (courseIndex != other.courseIndex)
- return false;
- if (name == null) {
- if (other.name != null)
- return false;
- } else if (!name.equals(other.name))
- return false;
- return true;
+ this.name = name;
+ this.marks = marks;
+ this.controlPoint = controlPoint;
+ this.passingSide = passingSide;
}
}
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/RaceMap.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/RaceMap.java
index a0843719688..0ebcc95c2f4 100755
--- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/RaceMap.java
+++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/RaceMap.java
@@ -73,7 +73,7 @@ import com.sap.sailing.gwt.ui.client.Timer;
import com.sap.sailing.gwt.ui.client.Timer.PlayStates;
import com.sap.sailing.gwt.ui.client.WindSourceTypeFormatter;
import com.sap.sailing.gwt.ui.shared.CompetitorDTO;
-import com.sap.sailing.gwt.ui.shared.CourseDTO;
+import com.sap.sailing.gwt.ui.shared.CoursePositionsDTO;
import com.sap.sailing.gwt.ui.shared.GPSFixDTO;
import com.sap.sailing.gwt.ui.shared.LegInfoDTO;
import com.sap.sailing.gwt.ui.shared.ManeuverDTO;
@@ -549,7 +549,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
}
}
- protected void showCourseMarksOnMap(CourseDTO courseDTO) {
+ protected void showCourseMarksOnMap(CoursePositionsDTO courseDTO) {
if (map != null && courseDTO != null) {
Map toRemoveCourseMarks = new HashMap(courseMarkOverlays);
if (courseDTO.marks != null) {
@@ -822,7 +822,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
}
}
- private void showStartAndFinishLines(final CourseDTO courseDTO) {
+ private void showStartAndFinishLines(final CoursePositionsDTO courseDTO) {
if (map != null && courseDTO != null && lastRaceTimesInfo != null) {
Pair leadingVisibleCompetitorInfo = getLeadingVisibleCompetitorInfo(getCompetitorsToShow());
int legOfLeadingCompetitor = leadingVisibleCompetitorInfo == null ? -1 : leadingVisibleCompetitorInfo.getA();
diff --git a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/ServerReplicationTest.java b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/ServerReplicationTest.java
index 4b0af2dbe8f..6687e48caf1 100755
--- a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/ServerReplicationTest.java
+++ b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/ServerReplicationTest.java
@@ -88,9 +88,9 @@ public class ServerReplicationTest extends AbstractServerReplicationTest {
new ArrayList());
AddRaceDefinition addRaceOperation = new AddRaceDefinition(new RegattaName(regatta.getName()), race);
master.apply(addRaceOperation);
- masterCourse.addWaypoint(0, masterDomainFactory.createWaypoint(masterDomainFactory.getOrCreateMark("Mark1")));
- masterCourse.addWaypoint(1, masterDomainFactory.createWaypoint(masterDomainFactory.getOrCreateMark("Mark2")));
- masterCourse.addWaypoint(2, masterDomainFactory.createWaypoint(masterDomainFactory.getOrCreateMark("Mark3")));
+ masterCourse.addWaypoint(0, masterDomainFactory.createWaypoint(masterDomainFactory.getOrCreateMark("Mark1"), /*passingSide*/ null));
+ masterCourse.addWaypoint(1, masterDomainFactory.createWaypoint(masterDomainFactory.getOrCreateMark("Mark2"), /*passingSide*/ null));
+ masterCourse.addWaypoint(2, masterDomainFactory.createWaypoint(masterDomainFactory.getOrCreateMark("Mark3"), /*passingSide*/ null));
masterCourse.removeWaypoint(1);
Thread.sleep(3000); // wait 1s for JMS to deliver the message and the message to be applied
Regatta replicaEvent = replica.getRegatta(new RegattaName(regatta.getName()));
diff --git a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/TrackedRaceContentsReplicationTest.java b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/TrackedRaceContentsReplicationTest.java
index 6a21686a4f0..257f8efa4e4 100755
--- a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/TrackedRaceContentsReplicationTest.java
+++ b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/TrackedRaceContentsReplicationTest.java
@@ -81,9 +81,9 @@ public class TrackedRaceContentsReplicationTest extends AbstractServerReplicatio
RaceDefinition race = new RaceDefinitionImpl(raceName, masterCourse, boatClass, Collections.singletonList(competitor));
AddRaceDefinition addRaceOperation = new AddRaceDefinition(new RegattaName(regatta.getName()), race);
master.apply(addRaceOperation);
- masterCourse.addWaypoint(0, masterDomainFactory.createWaypoint(masterDomainFactory.getOrCreateMark("Mark1")));
- masterCourse.addWaypoint(1, masterDomainFactory.createWaypoint(masterDomainFactory.getOrCreateMark("Mark2")));
- masterCourse.addWaypoint(2, masterDomainFactory.createWaypoint(masterDomainFactory.getOrCreateMark("Mark3")));
+ masterCourse.addWaypoint(0, masterDomainFactory.createWaypoint(masterDomainFactory.getOrCreateMark("Mark1"), /*passingSide*/ null));
+ masterCourse.addWaypoint(1, masterDomainFactory.createWaypoint(masterDomainFactory.getOrCreateMark("Mark2"), /*passingSide*/ null));
+ masterCourse.addWaypoint(2, masterDomainFactory.createWaypoint(masterDomainFactory.getOrCreateMark("Mark3"), /*passingSide*/ null));
masterCourse.removeWaypoint(1);
raceIdentifier = new RegattaNameAndRaceName(regatta.getName(), raceName);
trackedRegatta = master.apply(new TrackRegatta(raceIdentifier));
@@ -215,8 +215,4 @@ public class TrackedRaceContentsReplicationTest extends AbstractServerReplicatio
windTrack.unlockAfterRead();
}
}
-
-
-
-
}
diff --git a/java/com.sap.sailing.targetplatform.base/features/target-test/feature.xml b/java/com.sap.sailing.targetplatform.base/features/target-test/feature.xml
index d569061cadb..23ddf0a75d1 100644
--- a/java/com.sap.sailing.targetplatform.base/features/target-test/feature.xml
+++ b/java/com.sap.sailing.targetplatform.base/features/target-test/feature.xml
@@ -26,4 +26,18 @@
version="1.1.0.v20090501071000"
unpack="false"/>
+
+
+
+
diff --git a/java/com.sap.sailing.targetplatform.base/plugins/target-test/mockito-core-1.9.5.jar b/java/com.sap.sailing.targetplatform.base/plugins/target-test/mockito-core-1.9.5.jar
new file mode 100644
index 00000000000..df91508e53d
Binary files /dev/null and b/java/com.sap.sailing.targetplatform.base/plugins/target-test/mockito-core-1.9.5.jar differ
diff --git a/java/com.sap.sailing.targetplatform.base/plugins/target-test/objenesis-1.3.jar b/java/com.sap.sailing.targetplatform.base/plugins/target-test/objenesis-1.3.jar
new file mode 100644
index 00000000000..d56dc2b5c30
Binary files /dev/null and b/java/com.sap.sailing.targetplatform.base/plugins/target-test/objenesis-1.3.jar differ
diff --git a/java/com.sap.sailing.xcelsiusadapter/WEB-INF/web.xml b/java/com.sap.sailing.xcelsiusadapter/WEB-INF/web.xml
index c90b1cf8081..80869d66d34 100644
--- a/java/com.sap.sailing.xcelsiusadapter/WEB-INF/web.xml
+++ b/java/com.sap.sailing.xcelsiusadapter/WEB-INF/web.xml
@@ -4,11 +4,11 @@
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">
- XcelsiusApp
- com.sap.sailing.xcelsiusadapter.XcelsiusApp
+ XcelsiusServlet
+ com.sap.sailing.xcelsiusadapter.XcelsiusServlet
- XcelsiusApp
+ XcelsiusServlet
/xcelsius
diff --git a/java/com.sap.sailing.xcelsiusadapter/src/com/sap/sailing/xcelsiusadapter/GPSPerRace.java b/java/com.sap.sailing.xcelsiusadapter/src/com/sap/sailing/xcelsiusadapter/GPSPerRaceAction.java
similarity index 97%
rename from java/com.sap.sailing.xcelsiusadapter/src/com/sap/sailing/xcelsiusadapter/GPSPerRace.java
rename to java/com.sap.sailing.xcelsiusadapter/src/com/sap/sailing/xcelsiusadapter/GPSPerRaceAction.java
index 839a94e6e8b..de2e29de0b3 100755
--- a/java/com.sap.sailing.xcelsiusadapter/src/com/sap/sailing/xcelsiusadapter/GPSPerRace.java
+++ b/java/com.sap.sailing.xcelsiusadapter/src/com/sap/sailing/xcelsiusadapter/GPSPerRaceAction.java
@@ -26,10 +26,10 @@ import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.server.RacingEventService;
-public class GPSPerRace extends Action {
+public class GPSPerRaceAction extends HttpAction {
private final Set competitorNameSet;
- public GPSPerRace(HttpServletRequest req, HttpServletResponse res, RacingEventService service, int maxRows) {
+ public GPSPerRaceAction(HttpServletRequest req, HttpServletResponse res, RacingEventService service, int maxRows) {
super(req, res, service, maxRows);
String[] competitors = req.getParameterValues("competitor");
if (competitors == null) {
diff --git a/java/com.sap.sailing.xcelsiusadapter/src/com/sap/sailing/xcelsiusadapter/Action.java b/java/com.sap.sailing.xcelsiusadapter/src/com/sap/sailing/xcelsiusadapter/HttpAction.java
similarity index 67%
rename from java/com.sap.sailing.xcelsiusadapter/src/com/sap/sailing/xcelsiusadapter/Action.java
rename to java/com.sap.sailing.xcelsiusadapter/src/com/sap/sailing/xcelsiusadapter/HttpAction.java
index 660896bd777..1559d8a4730 100644
--- a/java/com.sap.sailing.xcelsiusadapter/src/com/sap/sailing/xcelsiusadapter/Action.java
+++ b/java/com.sap.sailing.xcelsiusadapter/src/com/sap/sailing/xcelsiusadapter/HttpAction.java
@@ -7,7 +7,6 @@ import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
-
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -31,12 +30,14 @@ import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.base.RaceDefinition;
import com.sap.sailing.domain.base.impl.MillisecondsTimePoint;
import com.sap.sailing.domain.common.TimePoint;
+import com.sap.sailing.domain.leaderboard.Leaderboard;
+import com.sap.sailing.domain.leaderboard.RegattaLeaderboard;
import com.sap.sailing.domain.tracking.DynamicTrackedRegatta;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.server.RacingEventService;
import com.sap.sailing.util.InvalidDateException;
-public class Action {
+public abstract class HttpAction {
private HttpServletRequest req;
private HttpServletResponse res;
@@ -49,7 +50,7 @@ public class Action {
private int rowCount;
private Element currentRow;
- public Action(HttpServletRequest req, HttpServletResponse res, RacingEventService service, int maxRows) {
+ public HttpAction(HttpServletRequest req, HttpServletResponse res, RacingEventService service, int maxRows) {
this.req = req;
this.res = res;
this.service = service;
@@ -88,6 +89,23 @@ public class Action {
}
return regatta;
}
+
+ public RegattaLeaderboard getRegattaLeaderboard() throws IOException {
+ /*
+ * Leaderboard name is always equal Regatta name
+ */
+ final String leaderboardName = getAttribute("regatta");
+ if (leaderboardName == null) {
+ say("Use the regatta= parameter to specify the regatta");
+ return null;
+ }
+ final Leaderboard leaderboard = getService().getLeaderboardByName(leaderboardName);
+ if (leaderboard == null || !(leaderboard instanceof RegattaLeaderboard)) {
+ say("Regatta " + leaderboardName + " not found.");
+ return null;
+ }
+ return (RegattaLeaderboard)leaderboard;
+ }
public Regatta getEvent(String name) {
for (final Regatta regatta : this.service.getAllRegattas()) {
@@ -100,9 +118,6 @@ public class Action {
}
public RaceDefinition getRace(Regatta regatta) throws IOException {
- /*
- * Get the race
- */
final String raceName = getAttribute("race");
if (raceName == null) {
@@ -111,9 +126,6 @@ public class Action {
return null;
}
- /*
- * RACE
- */
final RaceDefinition race = getRace(regatta, raceName);
if (race == null) {
@@ -163,13 +175,21 @@ public class Action {
if (start == null) {
return null;
}
- final long mTime = start.asMillis() + (StringUtility.StringToInteger(time, 0) * 1000 * 60);
+ final long mTime = start.asMillis() + (stringToInteger(time, 0) * 1000 * 60);
final TimePoint bTimePoint = new MillisecondsTimePoint(mTime);
return bTimePoint;
}
return null;
}
+ private int stringToInteger(String strString, int intDefault) {
+ try {
+ return Integer.parseInt(strString);
+ } catch (NumberFormatException e) {
+ return intDefault;
+ }
+ }
+
public Document getTable(String variable) {
this.table = new Document();
final Element data = new Element("data");
@@ -196,7 +216,7 @@ public class Action {
this.currentRow.addContent(col);
}
}
-
+
public void addNamedColumn(String content, String columnName) {
if (maxRows == -1 || rowCount <= maxRows) {
final Element col = new Element(columnName);
@@ -217,77 +237,69 @@ public class Action {
}
public void say(Document doc) throws IOException {
- this.res.setCharacterEncoding("UTF-8");
+ this.res.setCharacterEncoding("UTF-8");
this.res.getWriter().print(getXMLAsString(doc));
}
public void say(String msg) throws IOException {
say(msg, this.res);
}
- public void sendDocument(Document doc, String fileName){
- ServletOutputStream stream = null;
- BufferedInputStream buf = null;
- try {
- ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
-
- Result outputTarget = new StreamResult(outputStream);
- DOMOutputter outputter = new DOMOutputter() ;
-
- org.w3c.dom.Document w3cDoc = outputter.output(doc);
- Source xmlSource = new DOMSource(w3cDoc);
-
-
-
-
-
-
- stream = res.getOutputStream();
-
-
- res.setContentType("text/xml");
- res.addHeader("Content-Disposition", "attachment; filename="
- + fileName);
- TransformerFactory.newInstance().newTransformer().transform(xmlSource, outputTarget);
- InputStream is = new ByteArrayInputStream(outputStream.toByteArray());
-
- buf = new BufferedInputStream(is);
- int readBytes = 0;
- while ((readBytes = buf.read()) != -1)
- stream.write(readBytes);
-
- } catch(TransformerConfigurationException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- } catch(TransformerException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- } catch(TransformerFactoryConfigurationError e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }catch (JDOMException e2) {
- // TODO Auto-generated catch block
- e2.printStackTrace();
- }
- catch (IOException e) {
- e.printStackTrace();
- }finally {
- if (stream != null)
- try {
- stream.close();
- } catch (IOException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- if (buf != null)
- try {
- buf.close();
- } catch (IOException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- //
- }
+
+ public void sendDocument(Document doc, String fileName) {
+ ServletOutputStream stream = null;
+ BufferedInputStream buf = null;
+ try {
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+
+ Result outputTarget = new StreamResult(outputStream);
+ DOMOutputter outputter = new DOMOutputter();
+
+ org.w3c.dom.Document w3cDoc = outputter.output(doc);
+ Source xmlSource = new DOMSource(w3cDoc);
+
+ stream = res.getOutputStream();
+
+ res.setContentType("text/xml");
+ res.addHeader("Content-Disposition", "attachment; filename=" + fileName);
+ TransformerFactory.newInstance().newTransformer().transform(xmlSource, outputTarget);
+ InputStream is = new ByteArrayInputStream(outputStream.toByteArray());
+
+ buf = new BufferedInputStream(is);
+ int readBytes = 0;
+ while ((readBytes = buf.read()) != -1)
+ stream.write(readBytes);
+
+ } catch (TransformerConfigurationException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (TransformerException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (TransformerFactoryConfigurationError e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (JDOMException e2) {
+ // TODO Auto-generated catch block
+ e2.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ } finally {
+ if (stream != null)
+ try {
+ stream.close();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ if (buf != null)
+ try {
+ buf.close();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+ }
public static String getXMLAsString(Object context) {
return getXMLAsString(context, "UTF-8");
@@ -297,14 +309,15 @@ public class Action {
// final XMLOutputter outputter = new XMLOutputter(" ", true, encoding);
final Format format = Format.getPrettyFormat();
format.setIndent(" ");
- // Despite of setting the expand true (result is instead of browsers like firefox dont display the columns correctly
+ // Despite of setting the expand true (result is instead of browsers like firefox
+ // dont display the columns correctly
format.setExpandEmptyElements(true);
format.setEncoding(encoding);
-
+
final XMLOutputter outputter = new XMLOutputter(format);
if (context instanceof Document) {
- String res = outputter.outputString((Document) context);
+ String res = outputter.outputString((Document) context);
return res;
}
diff --git a/java/com.sap.sailing.xcelsiusadapter/src/com/sap/sailing/xcelsiusadapter/ListEvents.java b/java/com.sap.sailing.xcelsiusadapter/src/com/sap/sailing/xcelsiusadapter/ListEventsAction.java
similarity index 96%
rename from java/com.sap.sailing.xcelsiusadapter/src/com/sap/sailing/xcelsiusadapter/ListEvents.java
rename to java/com.sap.sailing.xcelsiusadapter/src/com/sap/sailing/xcelsiusadapter/ListEventsAction.java
index 148a8c334e3..475fa7e2715 100644
--- a/java/com.sap.sailing.xcelsiusadapter/src/com/sap/sailing/xcelsiusadapter/ListEvents.java
+++ b/java/com.sap.sailing.xcelsiusadapter/src/com/sap/sailing/xcelsiusadapter/ListEventsAction.java
@@ -26,8 +26,8 @@ import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.Wind;
import com.sap.sailing.server.RacingEventService;
-public class ListEvents extends Action {
- public ListEvents(HttpServletRequest req, HttpServletResponse res, RacingEventService service, int maxRows) {
+public class ListEventsAction extends HttpAction {
+ public ListEventsAction(HttpServletRequest req, HttpServletResponse res, RacingEventService service, int maxRows) {
super(req, res, service, maxRows);
}
diff --git a/java/com.sap.sailing.xcelsiusadapter/src/com/sap/sailing/xcelsiusadapter/RankPerLeg2.java b/java/com.sap.sailing.xcelsiusadapter/src/com/sap/sailing/xcelsiusadapter/RankPerLeg2Action.java
similarity index 97%
rename from java/com.sap.sailing.xcelsiusadapter/src/com/sap/sailing/xcelsiusadapter/RankPerLeg2.java
rename to java/com.sap.sailing.xcelsiusadapter/src/com/sap/sailing/xcelsiusadapter/RankPerLeg2Action.java
index 3d20a91cafe..aa91ca39f8e 100644
--- a/java/com.sap.sailing.xcelsiusadapter/src/com/sap/sailing/xcelsiusadapter/RankPerLeg2.java
+++ b/java/com.sap.sailing.xcelsiusadapter/src/com/sap/sailing/xcelsiusadapter/RankPerLeg2Action.java
@@ -23,10 +23,10 @@ import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.server.RacingEventService;
-public class RankPerLeg2 extends Action {
+public class RankPerLeg2Action extends HttpAction {
private final Set competitorNameSet;
- public RankPerLeg2(HttpServletRequest req, HttpServletResponse res, RacingEventService service, int maxRows) {
+ public RankPerLeg2Action(HttpServletRequest req, HttpServletResponse res, RacingEventService service, int maxRows) {
super(req, res, service, maxRows);
String[] competitors = req.getParameterValues("competitor");
if (competitors == null) {
diff --git a/java/com.sap.sailing.xcelsiusadapter/src/com/sap/sailing/xcelsiusadapter/RankPerLeg.java b/java/com.sap.sailing.xcelsiusadapter/src/com/sap/sailing/xcelsiusadapter/RankPerLegAction.java
similarity index 97%
rename from java/com.sap.sailing.xcelsiusadapter/src/com/sap/sailing/xcelsiusadapter/RankPerLeg.java
rename to java/com.sap.sailing.xcelsiusadapter/src/com/sap/sailing/xcelsiusadapter/RankPerLegAction.java
index da609a4b4ac..22f48bf31be 100644
--- a/java/com.sap.sailing.xcelsiusadapter/src/com/sap/sailing/xcelsiusadapter/RankPerLeg.java
+++ b/java/com.sap.sailing.xcelsiusadapter/src/com/sap/sailing/xcelsiusadapter/RankPerLegAction.java
@@ -24,10 +24,10 @@ import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.server.RacingEventService;
-public class RankPerLeg extends Action {
+public class RankPerLegAction extends HttpAction {
private final Set competitorNameSet;
- public RankPerLeg(HttpServletRequest req, HttpServletResponse res, RacingEventService service, int maxRows) {
+ public RankPerLegAction(HttpServletRequest req, HttpServletResponse res, RacingEventService service, int maxRows) {
super(req, res, service, maxRows);
String[] competitors = req.getParameterValues("competitor");
if (competitors == null) {
diff --git a/java/com.sap.sailing.xcelsiusadapter/src/com/sap/sailing/xcelsiusadapter/RankPerRace.java b/java/com.sap.sailing.xcelsiusadapter/src/com/sap/sailing/xcelsiusadapter/RankPerRaceAction.java
similarity index 94%
rename from java/com.sap.sailing.xcelsiusadapter/src/com/sap/sailing/xcelsiusadapter/RankPerRace.java
rename to java/com.sap.sailing.xcelsiusadapter/src/com/sap/sailing/xcelsiusadapter/RankPerRaceAction.java
index 788da738abd..c2e7c156cd7 100755
--- a/java/com.sap.sailing.xcelsiusadapter/src/com/sap/sailing/xcelsiusadapter/RankPerRace.java
+++ b/java/com.sap.sailing.xcelsiusadapter/src/com/sap/sailing/xcelsiusadapter/RankPerRaceAction.java
@@ -21,10 +21,10 @@ import com.sap.sailing.domain.tracking.TrackedLeg;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.server.RacingEventService;
-public class RankPerRace extends Action {
+public class RankPerRaceAction extends HttpAction {
private final Set competitorNameSet;
- public RankPerRace(HttpServletRequest req, HttpServletResponse res, RacingEventService service, int maxRows) {
+ public RankPerRaceAction(HttpServletRequest req, HttpServletResponse res, RacingEventService service, int maxRows) {
super(req, res, service, maxRows);
String[] competitors = req.getParameterValues("competitor");
if (competitors == null) {
diff --git a/java/com.sap.sailing.xcelsiusadapter/src/com/sap/sailing/xcelsiusadapter/RegattaDataPerLeg.java b/java/com.sap.sailing.xcelsiusadapter/src/com/sap/sailing/xcelsiusadapter/RegattaDataPerLeg.java
deleted file mode 100755
index 96be93c2d09..00000000000
--- a/java/com.sap.sailing.xcelsiusadapter/src/com/sap/sailing/xcelsiusadapter/RegattaDataPerLeg.java
+++ /dev/null
@@ -1,392 +0,0 @@
-package com.sap.sailing.xcelsiusadapter;
-
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.HashMap;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.jdom.Document;
-import org.jdom.Element;
-
-import com.sap.sailing.domain.base.Competitor;
-import com.sap.sailing.domain.base.Leg;
-import com.sap.sailing.domain.base.RaceDefinition;
-import com.sap.sailing.domain.base.Regatta;
-import com.sap.sailing.domain.base.impl.MillisecondsTimePoint;
-import com.sap.sailing.domain.common.Position;
-import com.sap.sailing.domain.common.TimePoint;
-import com.sap.sailing.domain.common.WindSource;
-import com.sap.sailing.domain.common.WindSourceType;
-import com.sap.sailing.domain.common.impl.Util.Pair;
-import com.sap.sailing.domain.common.impl.WindSourceImpl;
-import com.sap.sailing.domain.tracking.MarkPassing;
-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.WindTrack;
-import com.sap.sailing.domain.tracking.WindWithConfidence;
-import com.sap.sailing.server.RacingEventService;
-
-public class RegattaDataPerLeg extends Action {
- public RegattaDataPerLeg(HttpServletRequest req, HttpServletResponse res, RacingEventService service, int maxRows) {
- super(req, res, service, maxRows);
- }
-
-
- public void perform() throws Exception {
-
- final Regatta regatta = getRegatta(); // Get regatta data from request (get value for regatta name from URL parameter regatta)
- // if the regatta does not exist a tag will be returned with a text message from function
- // getRegatta().
- if (regatta == null) {
- return;
- }
-
- final Document doc = new Document(); // initialize xml document
- final Element regatta_node = addNamedElement(doc, "regatta"); // add root to xml
- addNamedElementWithValue(regatta_node, "name", regatta.getName());
- addNamedElementWithValue(regatta_node, "boat_class", regatta.getBoatClass().getName());
-
- /*
- * Races
- */
- final HashMap races = getRaces(regatta); // get races for the regatta
- final Element races_node = addNamedElement(regatta_node, "races"); // add node that contains all races
-
- for (final RaceDefinition race : races.values()) { // for each race in the list
- final Element race_node = addNamedElement(races_node, "race"); // add race node for the current race
- addNamedElementWithValue(race_node, "name", race.getName()); // add name node to current race
-
- // skip race if not tracked
- final TrackedRace trackedRace = getTrackedRace(regatta, race);
- if (trackedRace == null || !trackedRace.hasGPSData()) {
- continue;
- }
-
- final TimePoint raceStarted = getTimePoint(trackedRace); // get TimePoint for when the race started
- if (raceStarted == null) {
- continue;
- }
-
- long minNextLegStart = raceStarted.asMillis(); // variable for keeping track of when the first competitor
- // started the next leg
- TimePoint legStarted = new MillisecondsTimePoint(minNextLegStart); // get TimePoint for when the leg started
-
- addNamedElementWithValue(race_node, "start_time_ms", raceStarted.asMillis()); // add the starttime to the
- if (trackedRace.getEndOfRace() != null) {
- addNamedElementWithValue(race_node, "assumed_end_ms", trackedRace.getEndOfRace().asMillis()); // add the assumed enddtime
- } else {
- addNamedElementWithValue(race_node, "assumed_end_ms", 0); // add the assumed enddtime
- }
-
- Calendar cal = Calendar.getInstance();
- cal.setTime(raceStarted.asDate());
- addNamedElementWithValue(race_node, "start_time_year", cal.get(Calendar.YEAR));
- int month = cal.get(Calendar.MONTH) + 1;
- addNamedElementWithValue(race_node, "start_time_month", month);
- addNamedElementWithValue(race_node, "start_time_day", cal.get(Calendar.DAY_OF_MONTH));
- addNamedElementWithValue(race_node, "start_time_hour", cal.get(Calendar.HOUR_OF_DAY));
- addNamedElementWithValue(race_node, "start_time_minute", cal.get(Calendar.MINUTE));
- addNamedElementWithValue(race_node, "start_time_second", cal.get(Calendar.SECOND));
- addNamedElementWithValue(race_node, "start_time_formatted", (cal.get(Calendar.DAY_OF_MONTH) < 10 ? ("0" + cal.get(Calendar.DAY_OF_MONTH)) : cal.get(Calendar.DAY_OF_MONTH))
- + "."
- + (month < 10 ? ("0" + month) : month)
- + "."
- + cal.get(Calendar.YEAR)
- + " - "
- + (cal.get(Calendar.HOUR_OF_DAY) < 10 ? ("0" + cal.get(Calendar.HOUR_OF_DAY)) : cal
- .get(Calendar.HOUR_OF_DAY))
- + ":"
- + (cal.get(Calendar.MINUTE) < 10 ? ("0" + cal.get(Calendar.MINUTE)) : cal
- .get(Calendar.MINUTE))
- + ":"
- + (cal.get(Calendar.SECOND) < 10 ? ("0" + cal.get(Calendar.SECOND)) : cal
- .get(Calendar.SECOND)));
-
- Pair averageWindSpeedofRace = calculateAverageWindSpeedofRace(trackedRace);
- String wind_strength = "";
- if (averageWindSpeedofRace != null) {
- if (averageWindSpeedofRace.getA() < 4) {
- wind_strength = "Very light";
- } else if (averageWindSpeedofRace.getA() >= 4 && averageWindSpeedofRace.getA() < 8) {
- wind_strength = "Light";
- } else if (averageWindSpeedofRace.getA() >= 8 && averageWindSpeedofRace.getA() < 14) {
- wind_strength = "Medium";
- } else if (averageWindSpeedofRace.getA() >= 14 && averageWindSpeedofRace.getA() < 20) {
- wind_strength = "Strong";
- } else if (averageWindSpeedofRace.getA() >= 20) {
- wind_strength = "Very strong";
- }
- }
-
- Double wind_speed = 0.0;
- Double wind_confi = 0.0;
-
- if (averageWindSpeedofRace != null) {
- wind_speed = averageWindSpeedofRace.getA();
- wind_confi = averageWindSpeedofRace.getB();
- }
-
- addNamedElementWithValue(race_node, "average_wind_speed", wind_speed);
- addNamedElementWithValue(race_node, "average_wind_speed_confidence", wind_confi);
- addNamedElementWithValue(race_node, "wind_strength", wind_strength);
-
- /*
- * Legs
- */
- int i = 0; // initialize leg index
- TrackedLeg previousLeg = null;
-
- final Element legs_node = addNamedElement(race_node, "legs"); // add element that holds all legs for the
- // race
-
- for (final TrackedLeg trackedLeg : trackedRace.getTrackedLegs()) {
- final Leg leg = trackedLeg.getLeg();
- final Element leg_node = addNamedElement(legs_node, "leg"); // add element for single leg
-
- addNamedElementWithValue(leg_node, "leg_number", ++i);
- addNamedElementWithValue(leg_node, "mark_from", leg.getFrom().getName());
- addNamedElementWithValue(leg_node, "mark_to", leg.getTo().getName());
- addNamedElementWithValue(leg_node, "leg_type", (trackedLeg.getLegType(legStarted).toString()));
-
- /*
- * Competitors
- */
- LinkedHashMap ranks = trackedLeg.getRanks(legStarted); // get the ranking
- // information for the race
- // (at leg start time)
- final Element competitor_data_node = addNamedElement(leg_node, "competitor_data"); // add element that
- // holds the leg
- // summary data for
- // each competitor
-
- for (final Competitor competitor : ranks.keySet()) { // Get competitor data
- TrackedLegOfCompetitor trackedLegOfCompetitor = trackedLeg.getTrackedLeg(competitor); // Get data
- TimePoint compareLegEnd = new MillisecondsTimePoint(0);
- // time elapsed / when did the competitor pass the end mark of the leg
- MarkPassing mp = trackedRace.getMarkPassing(competitor, leg.getTo());
- if (mp != null) {
- compareLegEnd = mp.getTimePoint();
- }
- final long compLegTimeAlt = trackedLegOfCompetitor.getTimeInMilliSeconds(compareLegEnd);
- final TimePoint compFinishedLeg = compareLegEnd;
-
- // plausibility check
- // competitor has finished the leg and the leg end time is not the race start time
- if (trackedLegOfCompetitor.hasFinishedLeg(compFinishedLeg) && compareLegEnd.asMillis() != 0) {
- // Calculate rank loss/gain
- int posGL = 0;
- if (previousLeg != null) {
- posGL = trackedLegOfCompetitor.getRank(compFinishedLeg)
- - previousLeg.getTrackedLeg(competitor).getRank(compFinishedLeg);
- }
-
- final Element competitor_node = addNamedElement(competitor_data_node, "competitor");
- try {
- addNamedElementWithValue(competitor_node, "name", competitor.getName());
- addNamedElementWithValue(competitor_node, "nationality", competitor.getTeam()
- .getNationality().getThreeLetterIOCAcronym());
- addNamedElementWithValue(competitor_node, "sail_id", competitor.getBoat().getSailID());
- String sail_id = competitor.getBoat().getSailID();
- if (sail_id.matches("^[A-Z]{3}\\s[0-9]*")) {
-
- Pattern regex = Pattern.compile("(^[A-Z]{3})\\s([0-9]*)");
- Matcher regexMatcher = regex.matcher(sail_id);
- try {
- String resultString = regexMatcher.replaceAll("$1$2");
- addNamedElementWithValue(competitor_node, "sail_id_formatted", resultString);
- } catch (Exception e) {
- e.printStackTrace();
- }
-
-
- } else if (sail_id.matches("^[A-Z]{3}\\S[0-9]*")) {
- addNamedElementWithValue(competitor_node, "sail_id_formatted", sail_id);
- } else if (sail_id.matches("[0-9]*")){
- addNamedElementWithValue(competitor_node, "sail_id_formatted", competitor.getTeam().getNationality().getThreeLetterIOCAcronym() + sail_id);
- } else {
- addNamedElementWithValue(competitor_node, "sail_id_formatted", sail_id);
- }
- addNamedElementWithValue(competitor_node, "leg_finished_time_ms", compFinishedLeg.asMillis());
- addNamedElementWithValue(competitor_node, "time_elapsed_ms", compFinishedLeg.asMillis() - raceStarted.asMillis());
- addNamedElementWithValue(competitor_node, "leg_time_ms", compLegTimeAlt);
- addNamedElementWithValue(competitor_node, "leg_rank", trackedLegOfCompetitor.getRank(compFinishedLeg));
- addNamedElementWithValue(competitor_node, "race_final_rank", trackedRace.getRank(competitor));
- addNamedElementWithValue(competitor_node, "rank_gain", posGL * -1); // Ranks Gained/Lost
- addNamedElementWithValue(competitor_node, "gap_to_leader_s", trackedLegOfCompetitor.getGapToLeaderInSeconds(compFinishedLeg));
- addNamedElementWithValue(competitor_node, "ww_distance_to_leader_m", 0);
-// addNamedElementWithValue(competitor_node, "avg_xte", trackedLegOfCompetitor.getAverageCrossTrackError(compFinishedLeg).getMeters());
-// addNamedElementWithValue(competitor_node, "avg_vmg_kn", trackedLegOfCompetitor.getAverageVelocityMadeGood(compFinishedLeg).getKnots());//windwardSpeed over ground on leg finished time
- addNamedElementWithValue(competitor_node, "avg_speed_og_kn", trackedLegOfCompetitor.getAverageSpeedOverGround(compFinishedLeg).getKnots());
- addNamedElementWithValue(competitor_node, "avg_speed_ww_kn", trackedLegOfCompetitor.getAverageVelocityMadeGood(compFinishedLeg).getKnots());// windwardSpeed over
- // ground on leg finished
- // time
- addNamedElementWithValue(competitor_node, "distance_traveled_m", trackedLegOfCompetitor.getDistanceTraveled(compFinishedLeg).getMeters());
- addNamedElementWithValue(competitor_node, "number_of_jibes", trackedLegOfCompetitor.getNumberOfJibes(compFinishedLeg));
- addNamedElementWithValue(competitor_node, "number_of_tacks", trackedLegOfCompetitor.getNumberOfTacks(compFinishedLeg));
- addNamedElementWithValue(competitor_node, "number_of_penalty_circles", trackedLegOfCompetitor.getNumberOfPenaltyCircles(compFinishedLeg));
-
- addNamedElementWithValue(competitor_node, "average_wind_speed", wind_speed);
- addNamedElementWithValue(competitor_node, "average_wind_speed_confidence", wind_confi);
-
-// Waypoint finish = trackedRace.getRace().getCourse().getLastWaypoint();
-//
-// Iterable markpassings = trackedRace.getMarkPassingsInOrder(finish);
-// Iterator iter = markpassings.iterator();
-// MarkPassing firstMarkPassing = null;
-// if (iter.hasNext()) {
-// firstMarkPassing = iter.next();
-// }
-//
-//
-//// Map> markPassingsForWaypoint = new HashMap>();
-//// for (Waypoint waypoint : trackedRace.getRace().getCourse().getWaypoints()) {
-//// markPassingsForWaypoint.put(waypoint, new ConcurrentSkipListSet(
-//// MarkPassingByTimeComparator.INSTANCE));
-//// }
-////
-//// NavigableSet markPassingsInOrder = markPassingsForWaypoint.get(finish);
-//// MarkPassing firstMarkPassing = null;
-//// synchronized (markPassingsInOrder) {
-//// if (!markPassingsInOrder.isEmpty()) {
-//// firstMarkPassing = markPassingsInOrder.first();
-//// }
-//// }
-// TimePoint timeOfFirstMarkPassing = null;
-// if (firstMarkPassing != null) {
-// timeOfFirstMarkPassing = firstMarkPassing.getTimePoint();
-// }
-//
-// trackedRace.getWindwardDistanceToOverallLeader(competitor, timeOfFirstMarkPassing);
-
-
-
-
- // assign the smallest start time for the next leg
- minNextLegStart = (minNextLegStart > compFinishedLeg.asMillis() ? compFinishedLeg
- .asMillis() : minNextLegStart);
- } catch (Exception ex) {
- //competitor_data_node.removeContent(competitor_node); // if the competitor dataset is not complete, remove it from the list
- // complete, remove it from the list
- }
- } else {
- System.err.print("Competitpr Skipped:" + competitor.getName() + "; Race:" + race.getName()
- + "; Leg+" + ((Integer) i).toString());
- }
-
- } // competitor data end
- legStarted = new MillisecondsTimePoint(minNextLegStart);
- minNextLegStart = Long.MAX_VALUE;
- previousLeg = trackedLeg;
-
- // if the leg does not contain any competitor info
- if (competitor_data_node.getChildren("competitor").size() == 0) {
- legs_node.removeContent(leg_node);
- }
-
- } // leg end
- // if the race does not contain any leg info
- if (legs_node.getChildren("leg").size() == 0) {
- races_node.removeContent(race_node);
- }
- } // regatta end
- sendDocument(doc, regatta.getName() + ".xml");// output doc to client
- } // function end
-
- private Pair calculateAverageWindSpeedofRace(TrackedRace trackedRace) {
- Pair result = null;
- if (trackedRace.getEndOfRace() != null) {
- TimePoint fromTimePoint = trackedRace.getStartOfRace();
- TimePoint toTimePoint = trackedRace.getEndOfRace();
- long resolutionInMilliseconds = 60 * 1000 * 5; // 5 min
-
- List windSourcesToDeliver = new ArrayList();
- WindSourceImpl windSource = new WindSourceImpl(WindSourceType.COMBINED);
- windSourcesToDeliver.add(windSource);
-
- double sumWindSpeed = 0.0;
- double sumWindSpeedConfidence = 0.0;
- int speedCounter = 0;
-
- int numberOfFixes = (int) ((toTimePoint.asMillis() - fromTimePoint.asMillis()) / resolutionInMilliseconds);
- WindTrack windTrack = trackedRace.getOrCreateWindTrack(windSource);
- TimePoint timePoint = fromTimePoint;
- for (int i = 0; i < numberOfFixes && toTimePoint != null && timePoint.compareTo(toTimePoint) < 0; i++) {
- WindWithConfidence> averagedWindWithConfidence = windTrack
- .getAveragedWindWithConfidence(null, timePoint);
- if (averagedWindWithConfidence != null) {
- double windSpeedinKnots = averagedWindWithConfidence.getObject().getKnots();
- double confidence = averagedWindWithConfidence.getConfidence();
-
- sumWindSpeed += windSpeedinKnots;
- sumWindSpeedConfidence += confidence;
-
- speedCounter++;
- }
- timePoint = new MillisecondsTimePoint(timePoint.asMillis() + resolutionInMilliseconds);
- }
- if (speedCounter > 0) {
- double averageWindSpeed = sumWindSpeed / speedCounter;
- double averageWindSpeedConfidence = sumWindSpeedConfidence / speedCounter;
-
- result = new Pair(averageWindSpeed, averageWindSpeedConfidence);
- }
- } else {
- result = new Pair(0.0, 0.0);
- }
- return result;
- }
-
- private void addNamedElementWithValue(Element parent, String newChildName, Integer i) {
- if (i == null) {
- addNamedElementWithValue(parent, newChildName, "0");
- } else {
- addNamedElementWithValue(parent, newChildName, i.toString());
- }
-
- }
-
- private void addNamedElementWithValue(Element parent, String newChildName, Double dbl) {
- if (dbl == null) {
- addNamedElementWithValue(parent, newChildName, "0");
- } else {
- addNamedElementWithValue(parent, newChildName, dbl.toString());
- }
-
- }
-
- private void addNamedElementWithValue(Element parent, String newChildName, Long l) {
- if (l == null) {
- addNamedElementWithValue(parent, newChildName, "0");
- } else {
- addNamedElementWithValue(parent, newChildName, l.toString());
- }
-
- }
-
- private Element addNamedElement(Document doc, String newChildName) {
- final Element newChild = new Element(newChildName);
- doc.addContent(newChild);
- return newChild;
- }
-
- private Element addNamedElementWithValue(Element parent, String newChildName, String value) {
- final Element newChild = new Element(newChildName);
- newChild.addContent(value);
- parent.addContent(newChild);
- return newChild;
- }
-
- private Element addNamedElement(Element parent, String newChildName) {
- final Element newChild = new Element(newChildName);
- parent.addContent(newChild);
- return newChild;
- }
-
-}
diff --git a/java/com.sap.sailing.xcelsiusadapter/src/com/sap/sailing/xcelsiusadapter/RegattaDataPerLegAction.java b/java/com.sap.sailing.xcelsiusadapter/src/com/sap/sailing/xcelsiusadapter/RegattaDataPerLegAction.java
new file mode 100755
index 00000000000..9c64dd2de77
--- /dev/null
+++ b/java/com.sap.sailing.xcelsiusadapter/src/com/sap/sailing/xcelsiusadapter/RegattaDataPerLegAction.java
@@ -0,0 +1,636 @@
+package com.sap.sailing.xcelsiusadapter;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.jdom.Document;
+import org.jdom.Element;
+
+import com.sap.sailing.domain.base.Competitor;
+import com.sap.sailing.domain.base.Fleet;
+import com.sap.sailing.domain.base.Leg;
+import com.sap.sailing.domain.base.RaceColumn;
+import com.sap.sailing.domain.base.RaceDefinition;
+import com.sap.sailing.domain.base.impl.MillisecondsTimePoint;
+import com.sap.sailing.domain.common.Position;
+import com.sap.sailing.domain.common.TimePoint;
+import com.sap.sailing.domain.common.WindSource;
+import com.sap.sailing.domain.common.WindSourceType;
+import com.sap.sailing.domain.common.impl.Util.Pair;
+import com.sap.sailing.domain.common.impl.WindSourceImpl;
+import com.sap.sailing.domain.leaderboard.Leaderboard;
+import com.sap.sailing.domain.leaderboard.RegattaLeaderboard;
+import com.sap.sailing.domain.tracking.MarkPassing;
+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.WindTrack;
+import com.sap.sailing.domain.tracking.WindWithConfidence;
+import com.sap.sailing.server.RacingEventService;
+
+public class RegattaDataPerLegAction extends HttpAction {
+ public RegattaDataPerLegAction(HttpServletRequest req, HttpServletResponse res, RacingEventService service, int maxRows) {
+ super(req, res, service, maxRows);
+ }
+
+
+ public void perform() throws Exception {
+
+// final Regatta regatta = getRegatta(); // Get regatta data from request (get value for regatta name from URL parameter regatta)
+// // if the regatta does not exist a tag will be returned with a text message from function
+// // getRegatta().
+// if (regatta == null) {
+// return;
+// }
+
+ final RegattaLeaderboard leaderboard = getRegattaLeaderboard(); // Get leaderboard data from request (get value for regatta name from URL parameter regatta)
+ // if the regatta does not exist a tag will be returned with a text message from function
+ // getLeaderboard().
+ if (leaderboard == null) {
+ return;
+ }
+
+ final Document doc = new Document(); // initialize xml document
+ final Element regatta_node = addNamedElement(doc, "regatta"); // add root to xml
+ addNamedElementWithValue(regatta_node, "name", leaderboard.getName());
+ addNamedElementWithValue(regatta_node, "boat_class", leaderboard.getRegatta().getBoatClass().getName());
+
+
+
+ /*
+ * Races
+ */
+// final HashMap races = getRaces(regatta); // get races for the regatta
+ final Element races_node = addNamedElement(regatta_node, "races"); // add node that contains all races
+
+ for (RaceColumn r : leaderboard.getRaceColumns()) {
+ for (Fleet f : r.getFleets()) {
+ TrackedRace trackedRace = r.getTrackedRace(f);
+
+ // skip race if not tracked
+ if (trackedRace == null || !trackedRace.hasGPSData()) {
+ continue;
+ }
+
+ RaceDefinition race = trackedRace.getRace();
+
+ final Element race_node = addNamedElement(races_node, "race"); // add race node for the current race
+ addNamedElementWithValue(race_node, "name", race.getName()); // add name node to current race
+
+
+ final TimePoint raceStarted = getTimePoint(trackedRace); // get TimePoint for when the race started
+ if (raceStarted == null) {
+ continue;
+ }
+
+ long minNextLegStart = raceStarted.asMillis(); // variable for keeping track of when the first competitor
+ // started the next leg
+ TimePoint legStarted = new MillisecondsTimePoint(minNextLegStart); // get TimePoint for when the leg started
+
+ addNamedElementWithValue(race_node, "start_time_ms", raceStarted.asMillis()); // add the starttime to the
+ if (trackedRace.getEndOfRace() != null) {
+ addNamedElementWithValue(race_node, "assumed_end_ms", trackedRace.getEndOfRace().asMillis()); // add the assumed enddtime
+ } else {
+ addNamedElementWithValue(race_node, "assumed_end_ms", 0); // add the assumed enddtime
+ }
+
+ Calendar cal = Calendar.getInstance();
+ cal.setTime(raceStarted.asDate());
+ addNamedElementWithValue(race_node, "start_time_year", cal.get(Calendar.YEAR));
+ int month = cal.get(Calendar.MONTH) + 1;
+ addNamedElementWithValue(race_node, "start_time_month", month);
+ addNamedElementWithValue(race_node, "start_time_day", cal.get(Calendar.DAY_OF_MONTH));
+ addNamedElementWithValue(race_node, "start_time_hour", cal.get(Calendar.HOUR_OF_DAY));
+ addNamedElementWithValue(race_node, "start_time_minute", cal.get(Calendar.MINUTE));
+ addNamedElementWithValue(race_node, "start_time_second", cal.get(Calendar.SECOND));
+ addNamedElementWithValue(race_node, "start_time_formatted", (cal.get(Calendar.DAY_OF_MONTH) < 10 ? ("0" + cal.get(Calendar.DAY_OF_MONTH)) : cal.get(Calendar.DAY_OF_MONTH))
+ + "."
+ + (month < 10 ? ("0" + month) : month)
+ + "."
+ + cal.get(Calendar.YEAR)
+ + " - "
+ + (cal.get(Calendar.HOUR_OF_DAY) < 10 ? ("0" + cal.get(Calendar.HOUR_OF_DAY)) : cal
+ .get(Calendar.HOUR_OF_DAY))
+ + ":"
+ + (cal.get(Calendar.MINUTE) < 10 ? ("0" + cal.get(Calendar.MINUTE)) : cal
+ .get(Calendar.MINUTE))
+ + ":"
+ + (cal.get(Calendar.SECOND) < 10 ? ("0" + cal.get(Calendar.SECOND)) : cal
+ .get(Calendar.SECOND)));
+
+ Pair averageWindSpeedofRace = calculateAverageWindSpeedofRace(trackedRace);
+ String wind_strength = "";
+ if (averageWindSpeedofRace != null) {
+ if (averageWindSpeedofRace.getA() < 4) {
+ wind_strength = "Very light";
+ } else if (averageWindSpeedofRace.getA() >= 4 && averageWindSpeedofRace.getA() < 8) {
+ wind_strength = "Light";
+ } else if (averageWindSpeedofRace.getA() >= 8 && averageWindSpeedofRace.getA() < 14) {
+ wind_strength = "Medium";
+ } else if (averageWindSpeedofRace.getA() >= 14 && averageWindSpeedofRace.getA() < 20) {
+ wind_strength = "Strong";
+ } else if (averageWindSpeedofRace.getA() >= 20) {
+ wind_strength = "Very strong";
+ }
+ }
+
+ Double wind_speed = 0.0;
+ Double wind_confi = 0.0;
+
+ if (averageWindSpeedofRace != null) {
+ wind_speed = averageWindSpeedofRace.getA();
+ wind_confi = averageWindSpeedofRace.getB();
+ }
+
+ addNamedElementWithValue(race_node, "average_wind_speed", wind_speed);
+ addNamedElementWithValue(race_node, "average_wind_speed_confidence", wind_confi);
+ addNamedElementWithValue(race_node, "wind_strength", wind_strength);
+
+ /*
+ * Legs
+ */
+ int i = 0; // initialize leg index
+ TrackedLeg previousLeg = null;
+
+ final Element legs_node = addNamedElement(race_node, "legs"); // add element that holds all legs for the
+ // race
+
+ for (final TrackedLeg trackedLeg : trackedRace.getTrackedLegs()) {
+ final Leg leg = trackedLeg.getLeg();
+ final Element leg_node = addNamedElement(legs_node, "leg"); // add element for single leg
+
+ addNamedElementWithValue(leg_node, "leg_number", ++i);
+ addNamedElementWithValue(leg_node, "mark_from", leg.getFrom().getName());
+ addNamedElementWithValue(leg_node, "mark_to", leg.getTo().getName());
+ addNamedElementWithValue(leg_node, "leg_type", (trackedLeg.getLegType(legStarted).toString()));
+
+ /*
+ * Competitors
+ */
+// LinkedHashMap ranks = trackedLeg.getRanks(legStarted); // get the ranking
+ // information for the race
+ // (at leg start time)
+ final Element competitor_data_node = addNamedElement(leg_node, "competitor_data"); // add element that
+ // holds the leg
+ // summary data for
+ // each competitor
+
+
+
+ for (Competitor competitor : leaderboard.getAllCompetitors()) { // Get competitor data
+ TrackedLegOfCompetitor trackedLegOfCompetitor = trackedLeg.getTrackedLeg(competitor); // Get data
+ TimePoint compareLegEnd = new MillisecondsTimePoint(0);
+ // time elapsed / when did the competitor pass the end mark of the leg
+ MarkPassing mp = trackedRace.getMarkPassing(competitor, leg.getTo());
+ if (mp != null) {
+ compareLegEnd = mp.getTimePoint();
+
+ } else {
+ continue;
+ }
+
+ if (trackedLegOfCompetitor == null || compareLegEnd == null) {
+ continue;
+ }
+
+ long compLegTimeAlt = -1;
+ try {
+ compLegTimeAlt = trackedLegOfCompetitor.getTimeInMilliSeconds(compareLegEnd);
+ } catch (Exception e1) {
+// e1.printStackTrace();
+// If this happens, the race is probably broken
+ }
+ TimePoint compFinishedLeg = compareLegEnd;
+
+ // plausibility check
+ // competitor has finished the leg and the leg end time is not the race start time
+ if (trackedLegOfCompetitor.hasFinishedLeg(compFinishedLeg) && compareLegEnd.asMillis() != 0) {
+ // Calculate rank loss/gain
+ int posGL = 0;
+ if (previousLeg != null) {
+ posGL = trackedLegOfCompetitor.getRank(compFinishedLeg)
+ - previousLeg.getTrackedLeg(competitor).getRank(compFinishedLeg);
+ }
+
+ final Element competitor_node = addNamedElement(competitor_data_node, "competitor");
+ try {
+ addNamedElementWithValue(competitor_node, "name", competitor.getName());
+ addNamedElementWithValue(competitor_node, "nationality", competitor.getTeam()
+ .getNationality().getThreeLetterIOCAcronym());
+ addNamedElementWithValue(competitor_node, "sail_id", competitor.getBoat().getSailID());
+ String sail_id = competitor.getBoat().getSailID();
+ if (sail_id.matches("^[A-Z]{3}\\s[0-9]*")) {
+
+ Pattern regex = Pattern.compile("(^[A-Z]{3})\\s([0-9]*)");
+ Matcher regexMatcher = regex.matcher(sail_id);
+ try {
+ String resultString = regexMatcher.replaceAll("$1$2");
+ addNamedElementWithValue(competitor_node, "sail_id_formatted", resultString);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+
+ } else if (sail_id.matches("^[A-Z]{3}\\S[0-9]*")) {
+ addNamedElementWithValue(competitor_node, "sail_id_formatted", sail_id);
+ } else if (sail_id.matches("[0-9]*")){
+ addNamedElementWithValue(competitor_node, "sail_id_formatted", competitor.getTeam().getNationality().getThreeLetterIOCAcronym() + sail_id);
+ } else {
+ addNamedElementWithValue(competitor_node, "sail_id_formatted", sail_id);
+ }
+ addNamedElementWithValue(competitor_node, "leg_finished_time_ms", compFinishedLeg.asMillis());
+ addNamedElementWithValue(competitor_node, "time_elapsed_ms", compFinishedLeg.asMillis() - raceStarted.asMillis());
+ addNamedElementWithValue(competitor_node, "leg_time_ms", compLegTimeAlt);
+ addNamedElementWithValue(competitor_node, "leg_rank", trackedLegOfCompetitor.getRank(compFinishedLeg));
+ addNamedElementWithValue(competitor_node, "race_final_rank", trackedRace.getRank(competitor));
+ //
+// addNamedElementWithValue(competitor_node, "race_points", leaderboard.getTotalPoints(competitor, r, MillisecondsTimePoint.now()));
+ //
+ addNamedElementWithValue(competitor_node, "rank_gain", posGL * -1); // Ranks Gained/Lost
+ addNamedElementWithValue(competitor_node, "gap_to_leader_s", trackedLegOfCompetitor.getGapToLeaderInSeconds(compFinishedLeg));
+ addNamedElementWithValue(competitor_node, "ww_distance_to_leader_m", 0);
+// addNamedElementWithValue(competitor_node, "avg_xte", trackedLegOfCompetitor.getAverageCrossTrackError(compFinishedLeg).getMeters());
+// addNamedElementWithValue(competitor_node, "avg_vmg_kn", trackedLegOfCompetitor.getAverageVelocityMadeGood(compFinishedLeg).getKnots());//windwardSpeed over ground on leg finished time
+ addNamedElementWithValue(competitor_node, "avg_speed_og_kn", trackedLegOfCompetitor.getAverageSpeedOverGround(compFinishedLeg).getKnots());
+ addNamedElementWithValue(competitor_node, "avg_speed_ww_kn", trackedLegOfCompetitor.getAverageVelocityMadeGood(compFinishedLeg).getKnots());// windwardSpeed over
+ // ground on leg finished
+ // time
+ addNamedElementWithValue(competitor_node, "distance_traveled_m", trackedLegOfCompetitor.getDistanceTraveled(compFinishedLeg).getMeters());
+ addNamedElementWithValue(competitor_node, "number_of_jibes", trackedLegOfCompetitor.getNumberOfJibes(compFinishedLeg));
+ addNamedElementWithValue(competitor_node, "number_of_tacks", trackedLegOfCompetitor.getNumberOfTacks(compFinishedLeg));
+ addNamedElementWithValue(competitor_node, "number_of_penalty_circles", trackedLegOfCompetitor.getNumberOfPenaltyCircles(compFinishedLeg));
+
+ addNamedElementWithValue(competitor_node, "average_wind_speed", wind_speed);
+ addNamedElementWithValue(competitor_node, "average_wind_speed_confidence", wind_confi);
+
+ // assign the smallest start time for the next leg
+ minNextLegStart = (minNextLegStart > compFinishedLeg.asMillis() ? compFinishedLeg
+ .asMillis() : minNextLegStart);
+ } catch (Exception ex) {
+ //competitor_data_node.removeContent(competitor_node); // if the competitor dataset is not complete, remove it from the list
+ // complete, remove it from the list
+ }
+ } else {
+ System.err.print("Competitpr Skipped:" + competitor.getName() + "; Race:" + race.getName()
+ + "; Leg+" + ((Integer) i).toString());
+ }
+
+ } // competitor data end
+ legStarted = new MillisecondsTimePoint(minNextLegStart);
+ minNextLegStart = Long.MAX_VALUE;
+ previousLeg = trackedLeg;
+
+ // if the leg does not contain any competitor info
+ if (competitor_data_node.getChildren("competitor").size() == 0) {
+ legs_node.removeContent(leg_node);
+ }
+
+ } // leg end
+ // if the race does not contain any leg info
+ if (legs_node.getChildren("leg").size() == 0) {
+ races_node.removeContent(race_node);
+ }
+
+ trackedRace.getRace().getBoatClass();
+ }
+ }
+ sendDocument(doc, leaderboard.getName() + ".xml");// output doc to client
+
+// for (final RaceDefinition race : races.values()) { // for each race in the list
+// final Element race_node = addNamedElement(races_node, "race"); // add race node for the current race
+// addNamedElementWithValue(race_node, "name", race.getName()); // add name node to current race
+//
+// // skip race if not tracked
+// final TrackedRace trackedRace = getTrackedRace(regatta, race);
+// if (trackedRace == null || !trackedRace.hasGPSData()) {
+// continue;
+// }
+//
+// final TimePoint raceStarted = getTimePoint(trackedRace); // get TimePoint for when the race started
+// if (raceStarted == null) {
+// continue;
+// }
+//
+// long minNextLegStart = raceStarted.asMillis(); // variable for keeping track of when the first competitor
+// // started the next leg
+// TimePoint legStarted = new MillisecondsTimePoint(minNextLegStart); // get TimePoint for when the leg started
+//
+// addNamedElementWithValue(race_node, "start_time_ms", raceStarted.asMillis()); // add the starttime to the
+// if (trackedRace.getEndOfRace() != null) {
+// addNamedElementWithValue(race_node, "assumed_end_ms", trackedRace.getEndOfRace().asMillis()); // add the assumed enddtime
+// } else {
+// addNamedElementWithValue(race_node, "assumed_end_ms", 0); // add the assumed enddtime
+// }
+//
+// Calendar cal = Calendar.getInstance();
+// cal.setTime(raceStarted.asDate());
+// addNamedElementWithValue(race_node, "start_time_year", cal.get(Calendar.YEAR));
+// int month = cal.get(Calendar.MONTH) + 1;
+// addNamedElementWithValue(race_node, "start_time_month", month);
+// addNamedElementWithValue(race_node, "start_time_day", cal.get(Calendar.DAY_OF_MONTH));
+// addNamedElementWithValue(race_node, "start_time_hour", cal.get(Calendar.HOUR_OF_DAY));
+// addNamedElementWithValue(race_node, "start_time_minute", cal.get(Calendar.MINUTE));
+// addNamedElementWithValue(race_node, "start_time_second", cal.get(Calendar.SECOND));
+// addNamedElementWithValue(race_node, "start_time_formatted", (cal.get(Calendar.DAY_OF_MONTH) < 10 ? ("0" + cal.get(Calendar.DAY_OF_MONTH)) : cal.get(Calendar.DAY_OF_MONTH))
+// + "."
+// + (month < 10 ? ("0" + month) : month)
+// + "."
+// + cal.get(Calendar.YEAR)
+// + " - "
+// + (cal.get(Calendar.HOUR_OF_DAY) < 10 ? ("0" + cal.get(Calendar.HOUR_OF_DAY)) : cal
+// .get(Calendar.HOUR_OF_DAY))
+// + ":"
+// + (cal.get(Calendar.MINUTE) < 10 ? ("0" + cal.get(Calendar.MINUTE)) : cal
+// .get(Calendar.MINUTE))
+// + ":"
+// + (cal.get(Calendar.SECOND) < 10 ? ("0" + cal.get(Calendar.SECOND)) : cal
+// .get(Calendar.SECOND)));
+//
+// Pair averageWindSpeedofRace = calculateAverageWindSpeedofRace(trackedRace);
+// String wind_strength = "";
+// if (averageWindSpeedofRace != null) {
+// if (averageWindSpeedofRace.getA() < 4) {
+// wind_strength = "Very light";
+// } else if (averageWindSpeedofRace.getA() >= 4 && averageWindSpeedofRace.getA() < 8) {
+// wind_strength = "Light";
+// } else if (averageWindSpeedofRace.getA() >= 8 && averageWindSpeedofRace.getA() < 14) {
+// wind_strength = "Medium";
+// } else if (averageWindSpeedofRace.getA() >= 14 && averageWindSpeedofRace.getA() < 20) {
+// wind_strength = "Strong";
+// } else if (averageWindSpeedofRace.getA() >= 20) {
+// wind_strength = "Very strong";
+// }
+// }
+//
+// Double wind_speed = 0.0;
+// Double wind_confi = 0.0;
+//
+// if (averageWindSpeedofRace != null) {
+// wind_speed = averageWindSpeedofRace.getA();
+// wind_confi = averageWindSpeedofRace.getB();
+// }
+//
+// addNamedElementWithValue(race_node, "average_wind_speed", wind_speed);
+// addNamedElementWithValue(race_node, "average_wind_speed_confidence", wind_confi);
+// addNamedElementWithValue(race_node, "wind_strength", wind_strength);
+//
+// /*
+// * Legs
+// */
+// int i = 0; // initialize leg index
+// TrackedLeg previousLeg = null;
+//
+// final Element legs_node = addNamedElement(race_node, "legs"); // add element that holds all legs for the
+// // race
+//
+// for (final TrackedLeg trackedLeg : trackedRace.getTrackedLegs()) {
+// final Leg leg = trackedLeg.getLeg();
+// final Element leg_node = addNamedElement(legs_node, "leg"); // add element for single leg
+//
+// addNamedElementWithValue(leg_node, "leg_number", ++i);
+// addNamedElementWithValue(leg_node, "mark_from", leg.getFrom().getName());
+// addNamedElementWithValue(leg_node, "mark_to", leg.getTo().getName());
+// addNamedElementWithValue(leg_node, "leg_type", (trackedLeg.getLegType(legStarted).toString()));
+//
+// /*
+// * Competitors
+// */
+// LinkedHashMap ranks = trackedLeg.getRanks(legStarted); // get the ranking
+// // information for the race
+// // (at leg start time)
+// final Element competitor_data_node = addNamedElement(leg_node, "competitor_data"); // add element that
+// // holds the leg
+// // summary data for
+// // each competitor
+//
+// for (final Competitor competitor : ranks.keySet()) { // Get competitor data
+// TrackedLegOfCompetitor trackedLegOfCompetitor = trackedLeg.getTrackedLeg(competitor); // Get data
+// TimePoint compareLegEnd = new MillisecondsTimePoint(0);
+// // time elapsed / when did the competitor pass the end mark of the leg
+// MarkPassing mp = trackedRace.getMarkPassing(competitor, leg.getTo());
+// if (mp != null) {
+// compareLegEnd = mp.getTimePoint();
+// }
+// final long compLegTimeAlt = trackedLegOfCompetitor.getTimeInMilliSeconds(compareLegEnd);
+// final TimePoint compFinishedLeg = compareLegEnd;
+//
+// // plausibility check
+// // competitor has finished the leg and the leg end time is not the race start time
+// if (trackedLegOfCompetitor.hasFinishedLeg(compFinishedLeg) && compareLegEnd.asMillis() != 0) {
+// // Calculate rank loss/gain
+// int posGL = 0;
+// if (previousLeg != null) {
+// posGL = trackedLegOfCompetitor.getRank(compFinishedLeg)
+// - previousLeg.getTrackedLeg(competitor).getRank(compFinishedLeg);
+// }
+//
+// final Element competitor_node = addNamedElement(competitor_data_node, "competitor");
+// try {
+// addNamedElementWithValue(competitor_node, "name", competitor.getName());
+// addNamedElementWithValue(competitor_node, "nationality", competitor.getTeam()
+// .getNationality().getThreeLetterIOCAcronym());
+// addNamedElementWithValue(competitor_node, "sail_id", competitor.getBoat().getSailID());
+// String sail_id = competitor.getBoat().getSailID();
+// if (sail_id.matches("^[A-Z]{3}\\s[0-9]*")) {
+//
+// Pattern regex = Pattern.compile("(^[A-Z]{3})\\s([0-9]*)");
+// Matcher regexMatcher = regex.matcher(sail_id);
+// try {
+// String resultString = regexMatcher.replaceAll("$1$2");
+// addNamedElementWithValue(competitor_node, "sail_id_formatted", resultString);
+// } catch (Exception e) {
+// e.printStackTrace();
+// }
+//
+//
+// } else if (sail_id.matches("^[A-Z]{3}\\S[0-9]*")) {
+// addNamedElementWithValue(competitor_node, "sail_id_formatted", sail_id);
+// } else if (sail_id.matches("[0-9]*")){
+// addNamedElementWithValue(competitor_node, "sail_id_formatted", competitor.getTeam().getNationality().getThreeLetterIOCAcronym() + sail_id);
+// } else {
+// addNamedElementWithValue(competitor_node, "sail_id_formatted", sail_id);
+// }
+// addNamedElementWithValue(competitor_node, "leg_finished_time_ms", compFinishedLeg.asMillis());
+// addNamedElementWithValue(competitor_node, "time_elapsed_ms", compFinishedLeg.asMillis() - raceStarted.asMillis());
+// addNamedElementWithValue(competitor_node, "leg_time_ms", compLegTimeAlt);
+// addNamedElementWithValue(competitor_node, "leg_rank", trackedLegOfCompetitor.getRank(compFinishedLeg));
+// addNamedElementWithValue(competitor_node, "race_final_rank", trackedRace.getRank(competitor));
+// addNamedElementWithValue(competitor_node, "rank_gain", posGL * -1); // Ranks Gained/Lost
+// addNamedElementWithValue(competitor_node, "gap_to_leader_s", trackedLegOfCompetitor.getGapToLeaderInSeconds(compFinishedLeg));
+// addNamedElementWithValue(competitor_node, "ww_distance_to_leader_m", 0);
+//// addNamedElementWithValue(competitor_node, "avg_xte", trackedLegOfCompetitor.getAverageCrossTrackError(compFinishedLeg).getMeters());
+//// addNamedElementWithValue(competitor_node, "avg_vmg_kn", trackedLegOfCompetitor.getAverageVelocityMadeGood(compFinishedLeg).getKnots());//windwardSpeed over ground on leg finished time
+// addNamedElementWithValue(competitor_node, "avg_speed_og_kn", trackedLegOfCompetitor.getAverageSpeedOverGround(compFinishedLeg).getKnots());
+// addNamedElementWithValue(competitor_node, "avg_speed_ww_kn", trackedLegOfCompetitor.getAverageVelocityMadeGood(compFinishedLeg).getKnots());// windwardSpeed over
+// // ground on leg finished
+// // time
+// addNamedElementWithValue(competitor_node, "distance_traveled_m", trackedLegOfCompetitor.getDistanceTraveled(compFinishedLeg).getMeters());
+// addNamedElementWithValue(competitor_node, "number_of_jibes", trackedLegOfCompetitor.getNumberOfJibes(compFinishedLeg));
+// addNamedElementWithValue(competitor_node, "number_of_tacks", trackedLegOfCompetitor.getNumberOfTacks(compFinishedLeg));
+// addNamedElementWithValue(competitor_node, "number_of_penalty_circles", trackedLegOfCompetitor.getNumberOfPenaltyCircles(compFinishedLeg));
+//
+// addNamedElementWithValue(competitor_node, "average_wind_speed", wind_speed);
+// addNamedElementWithValue(competitor_node, "average_wind_speed_confidence", wind_confi);
+//
+//// Waypoint finish = trackedRace.getRace().getCourse().getLastWaypoint();
+////
+//// Iterable markpassings = trackedRace.getMarkPassingsInOrder(finish);
+//// Iterator iter = markpassings.iterator();
+//// MarkPassing firstMarkPassing = null;
+//// if (iter.hasNext()) {
+//// firstMarkPassing = iter.next();
+//// }
+////
+////
+////// Map> markPassingsForWaypoint = new HashMap>();
+////// for (Waypoint waypoint : trackedRace.getRace().getCourse().getWaypoints()) {
+////// markPassingsForWaypoint.put(waypoint, new ConcurrentSkipListSet(
+////// MarkPassingByTimeComparator.INSTANCE));
+////// }
+//////
+////// NavigableSet markPassingsInOrder = markPassingsForWaypoint.get(finish);
+////// MarkPassing firstMarkPassing = null;
+////// synchronized (markPassingsInOrder) {
+////// if (!markPassingsInOrder.isEmpty()) {
+////// firstMarkPassing = markPassingsInOrder.first();
+////// }
+////// }
+//// TimePoint timeOfFirstMarkPassing = null;
+//// if (firstMarkPassing != null) {
+//// timeOfFirstMarkPassing = firstMarkPassing.getTimePoint();
+//// }
+////
+//// trackedRace.getWindwardDistanceToOverallLeader(competitor, timeOfFirstMarkPassing);
+//
+//
+//
+//
+// // assign the smallest start time for the next leg
+// minNextLegStart = (minNextLegStart > compFinishedLeg.asMillis() ? compFinishedLeg
+// .asMillis() : minNextLegStart);
+// } catch (Exception ex) {
+// //competitor_data_node.removeContent(competitor_node); // if the competitor dataset is not complete, remove it from the list
+// // complete, remove it from the list
+// }
+// } else {
+// System.err.print("Competitpr Skipped:" + competitor.getName() + "; Race:" + race.getName()
+// + "; Leg+" + ((Integer) i).toString());
+// }
+//
+// } // competitor data end
+// legStarted = new MillisecondsTimePoint(minNextLegStart);
+// minNextLegStart = Long.MAX_VALUE;
+// previousLeg = trackedLeg;
+//
+// // if the leg does not contain any competitor info
+// if (competitor_data_node.getChildren("competitor").size() == 0) {
+// legs_node.removeContent(leg_node);
+// }
+//
+// } // leg end
+// // if the race does not contain any leg info
+// if (legs_node.getChildren("leg").size() == 0) {
+// races_node.removeContent(race_node);
+// }
+// } // regatta end
+// sendDocument(doc, regatta.getName() + ".xml");// output doc to client
+ } // function end
+
+ private Pair calculateAverageWindSpeedofRace(TrackedRace trackedRace) {
+ Pair result = null;
+ if (trackedRace.getEndOfRace() != null) {
+ TimePoint fromTimePoint = trackedRace.getStartOfRace();
+ TimePoint toTimePoint = trackedRace.getEndOfRace();
+ long resolutionInMilliseconds = 60 * 1000 * 5; // 5 min
+
+ List windSourcesToDeliver = new ArrayList();
+ WindSourceImpl windSource = new WindSourceImpl(WindSourceType.COMBINED);
+ windSourcesToDeliver.add(windSource);
+
+ double sumWindSpeed = 0.0;
+ double sumWindSpeedConfidence = 0.0;
+ int speedCounter = 0;
+
+ int numberOfFixes = (int) ((toTimePoint.asMillis() - fromTimePoint.asMillis()) / resolutionInMilliseconds);
+ WindTrack windTrack = trackedRace.getOrCreateWindTrack(windSource);
+ TimePoint timePoint = fromTimePoint;
+ for (int i = 0; i < numberOfFixes && toTimePoint != null && timePoint.compareTo(toTimePoint) < 0; i++) {
+ WindWithConfidence> averagedWindWithConfidence = windTrack
+ .getAveragedWindWithConfidence(null, timePoint);
+ if (averagedWindWithConfidence != null) {
+ double windSpeedinKnots = averagedWindWithConfidence.getObject().getKnots();
+ double confidence = averagedWindWithConfidence.getConfidence();
+
+ sumWindSpeed += windSpeedinKnots;
+ sumWindSpeedConfidence += confidence;
+
+ speedCounter++;
+ }
+ timePoint = new MillisecondsTimePoint(timePoint.asMillis() + resolutionInMilliseconds);
+ }
+ if (speedCounter > 0) {
+ double averageWindSpeed = sumWindSpeed / speedCounter;
+ double averageWindSpeedConfidence = sumWindSpeedConfidence / speedCounter;
+
+ result = new Pair(averageWindSpeed, averageWindSpeedConfidence);
+ }
+ } else {
+ result = new Pair(0.0, 0.0);
+ }
+ return result;
+ }
+
+ private void addNamedElementWithValue(Element parent, String newChildName, Integer i) {
+ if (i == null) {
+ addNamedElementWithValue(parent, newChildName, "0");
+ } else {
+ addNamedElementWithValue(parent, newChildName, i.toString());
+ }
+
+ }
+
+ private void addNamedElementWithValue(Element parent, String newChildName, Double dbl) {
+ if (dbl == null) {
+ addNamedElementWithValue(parent, newChildName, "0");
+ } else {
+ addNamedElementWithValue(parent, newChildName, dbl.toString());
+ }
+
+ }
+
+ private void addNamedElementWithValue(Element parent, String newChildName, Long l) {
+ if (l == null) {
+ addNamedElementWithValue(parent, newChildName, "0");
+ } else {
+ addNamedElementWithValue(parent, newChildName, l.toString());
+ }
+
+ }
+
+ private Element addNamedElement(Document doc, String newChildName) {
+ final Element newChild = new Element(newChildName);
+ doc.addContent(newChild);
+ return newChild;
+ }
+
+ private Element addNamedElementWithValue(Element parent, String newChildName, String value) {
+ final Element newChild = new Element(newChildName);
+ newChild.addContent(value);
+ parent.addContent(newChild);
+ return newChild;
+ }
+
+ private Element addNamedElement(Element parent, String newChildName) {
+ final Element newChild = new Element(newChildName);
+ parent.addContent(newChild);
+ return newChild;
+ }
+
+}
diff --git a/java/com.sap.sailing.xcelsiusadapter/src/com/sap/sailing/xcelsiusadapter/RegattaList.java b/java/com.sap.sailing.xcelsiusadapter/src/com/sap/sailing/xcelsiusadapter/RegattaList.java
deleted file mode 100644
index c9ca7dda884..00000000000
--- a/java/com.sap.sailing.xcelsiusadapter/src/com/sap/sailing/xcelsiusadapter/RegattaList.java
+++ /dev/null
@@ -1,47 +0,0 @@
-package com.sap.sailing.xcelsiusadapter;
-
-
-
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-
-import org.jdom.Document;
-
-
-
-
-import com.sap.sailing.domain.base.*;
-
-import com.sap.sailing.server.RacingEventService;
-
-public class RegattaList extends Action {
- public RegattaList(HttpServletRequest req, HttpServletResponse res, RacingEventService service, int maxRows) {
- super(req, res, service, maxRows);
- }
-
- public void perform() throws Exception {
- final Document table = getTable("data");
-
-
- for(Regatta regatta : getRegattas().values()){
- if(regatta == null){
- continue;
- }
-
-
- addRow();
- addColumn(regatta.getName());
-
- }
- say(table);// output doc to client
-
-
-
-
-
- } // function end
-
-
-
-}
diff --git a/java/com.sap.sailing.xcelsiusadapter/src/com/sap/sailing/xcelsiusadapter/RegattaListAction.java b/java/com.sap.sailing.xcelsiusadapter/src/com/sap/sailing/xcelsiusadapter/RegattaListAction.java
new file mode 100644
index 00000000000..484d6464987
--- /dev/null
+++ b/java/com.sap.sailing.xcelsiusadapter/src/com/sap/sailing/xcelsiusadapter/RegattaListAction.java
@@ -0,0 +1,31 @@
+package com.sap.sailing.xcelsiusadapter;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.jdom.Document;
+
+import com.sap.sailing.domain.base.*;
+
+import com.sap.sailing.server.RacingEventService;
+
+public class RegattaListAction extends HttpAction {
+ public RegattaListAction(HttpServletRequest req, HttpServletResponse res, RacingEventService service, int maxRows) {
+ super(req, res, service, maxRows);
+ }
+
+ public void perform() throws Exception {
+ final Document table = getTable("data");
+
+ for (Regatta regatta : getRegattas().values()) {
+ if (regatta == null) {
+ continue;
+ }
+
+ addRow();
+ addColumn(regatta.getName());
+
+ }
+ say(table);
+ }
+}
diff --git a/java/com.sap.sailing.xcelsiusadapter/src/com/sap/sailing/xcelsiusadapter/StringUtility.java b/java/com.sap.sailing.xcelsiusadapter/src/com/sap/sailing/xcelsiusadapter/StringUtility.java
deleted file mode 100644
index 0558ca04e5a..00000000000
--- a/java/com.sap.sailing.xcelsiusadapter/src/com/sap/sailing/xcelsiusadapter/StringUtility.java
+++ /dev/null
@@ -1,662 +0,0 @@
-package com.sap.sailing.xcelsiusadapter;
-
-import java.util.ArrayList;
-import java.util.List;
-
-
-/**
- * Some String Utilities.
- */
-public class StringUtility {
- /**
- * Return a hashcode as a positive Integer for a String.
- *
- * In contrast to .hashCode(), this function returns a positive
- * Integer. This is helpful to use Strings as a unique key in
- * places (e.g. JavaScript) where you would be bothered by
- * special characters in the String.
- *
- * @param sIn The String to get a hashcode for.
- * @return The hashcode as a positive Integer in a String.
- */
- public static String hashCode(String sIn) {
- if (sIn == null) {
- return null;
- }
-
- return "" + Math.abs(sIn.hashCode());
- }
-
-
- /**
- * Escape quotes and linebreaks in a String.
- *
- * @param sIn the String that has to be escaped
- * @return The escaped String
- */
- public static String escapeQuotes(String sIn) {
- if (sIn == null) {
- return "";
- }
-
- int len = sIn.length();
- String res = "";
- char c;
- for (int i = 0; i < len; i++) {
- c = sIn.charAt(i);
- switch (c) {
- case '\n':
- res += "\\n";
-
- break;
-
- case '\r':
- break;
-
- case '\\':
- res += "\\\\";
-
- break;
-
- case '"':
- res += "\\" + c;
-
- break;
-
- case '\'':
- res += "\\" + c;
-
- break;
-
- default:
- res += c;
- }
- }
-
- return res;
- }
-
-
- /**
- * Returns an array of string related to the key value string.
- *
- * If there is nothing left or right of the separator, that position
- * is returned as an empty string:
- *
- *
- * a/b/c returns "a", "b", "c".
- * /a/b/c returns "", "a", "b", "c".
- * a/b/c/ returns "a", "b", "c", "".
- * /a/b/c/ returns "", "a", "b", "c", "".
- *
- *
- * If you do not want empty Strings, you can operate
- * {@link #condense(String[])} on the return value.
- *
- * @param strParamVal the String that has to be splitted
- * @param strSep the Separator at which the String shall be splitted
- * @return Array of Strings with the Substrings.
- */
- public static String[] split(String strParamVal, String strSep) {
- if ((strSep == null) || "".equals(strSep)) {
- return new String[] { strParamVal };
- }
-
- String[] strRetList = new String[0];
-
- /*
- * This has bugged me a lot: We do not want to
- * return anything if we have an empty String.
- * Previously, as we did not find the separator,
- * the recursive algorithm returned a String
- * array of length one with the string itself.
- * If then we checked for length, we were
- * like thinking there was a length, while the
- * String in reality had been empty.
- */
- if ((strParamVal == null) || (strParamVal.length() == 0)) {
- return strRetList;
- }
-
- final int separatorPosition = strParamVal.indexOf(strSep);
- final int separatorLength = strSep.length();
-
- /*
- * If we no longer have the separator,
- * we do not need to split the String
- * again.
- */
- if (separatorPosition < 0) {
- strRetList = new String[1];
- strRetList[0] = strParamVal;
-
- return strRetList;
- }
-
- String lowValue = "";
- String highValue = "";
-
- lowValue = strParamVal.substring(0, separatorPosition);
- if (strParamVal.length() >= (separatorPosition + separatorLength)) {
- highValue = strParamVal.substring((strParamVal.equals(strSep)) ? 0 : (separatorPosition + separatorLength));
- }
-
- final String[] recursion = ("".equals(highValue) || strParamVal.equals(strSep)) ? new String[] { "" } : split(highValue, strSep);
- final int recursionLength = recursion.length;
-
- strRetList = new String[recursionLength + 1];
- strRetList[0] = lowValue;
-
- for (int i = 0; i < recursionLength; i++) {
- strRetList[i + 1] = recursion[i];
- }
-
- /* I rewrote this completely passing to a recursive
- * way of doing things. This cleans up considerably
- * the code. For legacy, here is the old way:
-
- String[] strRetList = new String[0];
- String strVar = "";
- int iPos = 0;
- int i;
- if (strParamVal != null) { strVar = strParamVal; }
-
- if (strVar.length() > 0) {
- //-- Get the Number of items in strParamVal value
- int iCpt = 0;
- while (iPos >= 0) {
- iPos = strVar.indexOf(strSep);
- if (iPos >= 0) {
- strVar = strVar.substring(iPos + 1);
- iCpt = iCpt + 1;
- } else {
- if (strVar.length() >= 0) { iCpt = iCpt + 1; }
- }
- }
-
- //-- Initialize the size of the list
- strRetList = new String[iCpt];
-
- //-- Get the list of values
- strVar = strParamVal;
- i = 0;
- iPos = 0;
- while (iPos >= 0) {
- iPos = strVar.indexOf(strSep);
- if (iPos >= 0) {
- strRetList[i++] = strVar.substring(0, iPos);
- strVar = strVar.substring(iPos + 1);
- } else {
- if (strVar.length() > 0) { strRetList[i++] = strVar; }
- }
- }
- }
-
- */
- return strRetList;
- }
-
-
- /**
- * Returns an array of string related to the key value string.
- * An array of string is passed; every single string is split, and
- * an array containing all substrings of all strings is returned.
- *
- * @param strParamVals the String array containing Strings that have to be splitted
- * @param strSep the Separator at which the String shall be splitted
- * @return Array of Strings with the Substrings.
- */
- public static String[] split(String[] strParamVals, String strSep) {
- int count = 0;
- if (strParamVals == null) {
- return new String[0];
- }
-
- for (int i = 0; i < strParamVals.length; i++) {
- String[] s = split(strParamVals[i], strSep);
- count += s.length;
- }
-
- String[] strRetList = new String[count];
-
- count = 0;
- for (int i = 0; i < strParamVals.length; i++) {
- String[] s = split(strParamVals[i], strSep);
- for (int j = 0; j < s.length; j++) {
- strRetList[count++] = s[j];
- }
- }
-
- return strRetList;
- }
-
-
- /**
- * Condense a String array by removing all empty Strings.
- *
- * @param strParamValues The String Array.
- * @return A String Array in the same order, but without
- * empty Strings.
- */
- public static String[] condense(String[] strParamValues) {
- return condense(strParamValues, 0);
- }
-
-
- /**
- * Condense a String array by removing all empty Strings
- * except at the beginning the number of strings that can
- * be ignored even if they are empty.
- *
- * @param strParamValues The String Array.
- * @param ignore the number of possible empty strings that
- * may pass through at the beginning.
- * @return A String Array in the same order, but without
- * empty Strings.
- */
- public static String[] condense(String[] strParamValues, int ignore) {
- final List theValues = new ArrayList();
-
- for (int i = 0; i < strParamValues.length; i++) {
- if ((ignore < i) || !"".equals(strParamValues[i])) {
- theValues.add(strParamValues[i]);
- }
- }
-
- final String[] result = new String[theValues.size()];
-
- int i = 0;
- for (final String theValue : theValues) {
- result[i++] = theValue;
- }
-
- return result;
- }
-
-
- /**
- * Converts an array to a string.
- *
- * Only Strings that are not empty are put into the concatenated string.
- *
- * @param strParamVals the String Array that has to be joined
- * @param strSep the Separator that has to be put between the Strings
- * @return The concatenated String
- */
- public static String join(String[] strParamVals, String strSep) {
- String strRet = "";
- int numValues = 0;
- if (null != strParamVals) {
- for (int i = 0; i < strParamVals.length; i++) {
- if ((strParamVals[i] != null) && (strParamVals[i].length() > 0)) {
- if (numValues == 0) {
- strRet = strParamVals[i];
- } else {
- strRet += strSep + strParamVals[i];
- }
-
- numValues++;
- }
- }
- }
-
- return strRet;
- }
-
-
- /**
- * Removes blank spaces between values and separator.
- *
- * @param strPromptValue The String to be trimmed around separators
- * @param strSep the Separator the String has to be trimmed around
- * @return The updated String
- */
- public static String trimPrompt(String strPromptValue, String strSep) {
- if (null != strPromptValue) {
- String[] arrPromptValues = split(strPromptValue, strSep);
- for (int j = 0; j < arrPromptValues.length; j++) {
- if (arrPromptValues[j] != null) {
- arrPromptValues[j] = arrPromptValues[j].trim();
- }
- }
-
- strPromptValue = join(arrPromptValues, strSep);
- }
-
- return strPromptValue;
- }
-
-
- /**
- * Replaces String into a text.
- *
- * @param strText The Text in which the substitution shall be done
- * @param strOldString The String that has to be replaced
- * @param strNewString The replacement String
- * @param blnAll true, if all matches shall be replaced, false for only first match.
- * @return The updated String
- */
- public static String replace(String strText, String strOldString, String strNewString, boolean blnAll) {
- if (null != strText) {
- int iLength = strOldString.length();
- int iPos = 0;
- while (iPos >= 0) {
- iPos = strText.indexOf(strOldString);
- if (iPos >= 0) {
- String strLeft = strText.substring(0, iPos);
- String strRight = strText.substring(iPos + iLength);
- strText = strLeft + strNewString + strRight;
- iPos += iLength;
- if (!blnAll) {
- break;
- }
- }
- }
- }
-
- return strText;
- }
-
-
- /**
- * Strip leading String from a String.
- *
- * Example: From /BISDK, strip
- * the /, so that you get BISDK.
- *
- * If there are multiple occurrences at the beginning,
- * they are all suppressed.
- *
- * @param strText The String to strip from.
- * @param toStrip The Fraction to suppress.
- * @return String where the leading part was eliminated.
- */
- public static String stripLeading(String strText, String toStrip) {
- String stripped = strText;
-
- while (stripped.startsWith(toStrip)) {
- stripped = stripped.substring(toStrip.length());
- }
-
- return stripped;
- }
-
-
- /**
- * Strip trailing String from a String.
- *
- * Example: From BISDK/, strip
- * the /, so that you get BISDK.
- *
- * If there are multiple occurrences at the beginning,
- * they are all suppressed.
- *
- * @param strText The String to strip from.
- * @param toStrip The Fraction to suppress.
- * @return String where the trailing part was eliminated.
- */
- public static String stripTrailing(String strText, String toStrip) {
- String stripped = strText;
-
- while (stripped.endsWith(toStrip)) {
- stripped = stripped.substring(0, stripped.length() - toStrip.length());
- }
-
- return stripped;
- }
-
-
- /**
- * Strip leading and trailing String from a String.
- *
- * Example: From /BISDK/, strip
- * the /, so that you get BISDK.
- *
- * If there are multiple occurrences at the beginning or end,
- * they are all suppressed.
- *
- * @param strText The String to strip from.
- * @param toStrip The Fraction to suppress.
- * @return String where the leading and trailing parts were eliminated.
- */
- public static String stripBoth(String strText, String toStrip) {
- return StringUtility.stripLeading(StringUtility.stripTrailing(strText, toStrip), toStrip);
- }
-
-
- /**
- * Parse year, month, day out of a date string.
- *
- * @param strDate The Date String that is to be parsed, e.g. "2004-12-30"
- * @param strFormat The Date Format pattern, e.g. "y-mm-dd". The following
- * pattern entries are possible: "y" for 4-digit year, "yy" for two-digit year;
- * "mm" for the month number, zero-padded;
- * "dd" for the day number, zero-padded.
- * @return String Array with the following entries:
- *
- * - [ 0]=Year
- * - [ 1]=Month
- * - [ 2]=Day
- *
- */
- public static String[] parseDate(String strDate, String strFormat) {
- /* get the year positions */
- int yrStart = strFormat.indexOf("y");
- int yrEnd = strFormat.lastIndexOf("y");
- int yrComp = 0;
- if ((yrStart < 0) || (yrEnd < 0)) {
- return new String[0];
- }
-
- if (yrStart == yrEnd) {
- yrComp = 3;
- }
-
- yrEnd += yrComp + 1;
-
- /* get the month positions */
- int moStart = strFormat.indexOf("m");
- int moEnd = strFormat.lastIndexOf("m");
- if ((moStart < 0) || (moEnd < 0)) {
- return new String[0];
- }
-
- if (moStart > yrStart) {
- moStart += yrComp;
- moEnd += yrComp + 1;
- }
-
- /* get the day positions */
- int dyStart = strFormat.indexOf("d");
- int dyEnd = strFormat.lastIndexOf("d");
- if ((dyStart < 0) || (dyEnd < 0)) {
- return new String[0];
- }
-
- if (dyStart > yrStart) {
- dyStart += yrComp;
- dyEnd += yrComp + 1;
- }
-
- String[] ret = new String[3];
- ret[0] = strDate.substring(yrStart, yrEnd);
- ret[1] = strDate.substring(moStart, moEnd);
- ret[2] = strDate.substring(dyStart, dyEnd);
-
- return ret;
- }
-
-
- /**
- * Convert a String into an Integer and return 0 if this is not possible.
- *
- * @param strString The String that has to be converted
- * @return The Integer Value
- */
- public static int StringToInteger(String strString) {
- try {
- return Integer.parseInt(strString);
- } catch (NumberFormatException e) {
- return 0;
- }
- }
-
-
- /**
- * Convert a String into an Integer and return a default value if this not possible.
- *
- * @param strString The String that has to be converted
- * @param intDefault The default value that shall be returned on error
- * @return The Integer Value
- */
- public static int StringToInteger(String strString, int intDefault) {
- try {
- return Integer.parseInt(strString);
- } catch (NumberFormatException e) {
- return intDefault;
- }
- }
-
-
- /**
- * Return a non null value for the given string.
- *
- * @param strString The String that has to be checked against null
- * @param strDefaultValue The default value in case the String is null
- * @return The checked String.
- */
- public static String getNonNullValue(String strString, String strDefaultValue) {
- if (strString == null) {
- strString = strDefaultValue;
- } else if ("null".equals(strString)) {
- strString = strDefaultValue;
- }
-
- return strString;
- }
-
-
- /**
- * Return a non null value for the given string,
- * considering that default value is an empty string.
- *
- * @param strString The String that has to be checked against null
- * @return The checked String.
- */
- public static String getNonNullValue(String strString) {
- return getNonNullValue(strString, "");
- }
-
-
- /**
- * Return an array of non null values for the given string array.
- *
- * @param strString The String array that has to be checked against null
- * @return The checked String array.
- */
- public static String[] getNonNullValues(String[] strString) {
- if (strString == null) {
- strString = new String[0];
- }
-
- return strString;
- }
-
-
- /**
- * Get the name or the value of a parameter. The parameter may look like:
- *
- *
- * aname=bvalue
- * -x=y
- * --parameter=value
- *
- *
- * @param parameter A String as shown in the above examples.
- * @param name true if the name is required, false if the value is required.
- * @param casesensitive false if the parameters are to be converted to all upper case, else true.
- * @return Name or value, depending on the name parameter; null if an error occurred.
- */
- public static String getParameter(String parameter, boolean name, boolean casesensitive) {
- if (parameter == null) {
- return null;
- }
-
- String[] parametertokens = split(parameter, "=");
-
- if ((parametertokens == null) || ((parametertokens.length < 1) && name) || ((parametertokens.length < 2) && !name)) {
- return null;
- }
-
- if (name) {
- String parametername = parametertokens[0];
- while ("-".equals(parametername.substring(0, 1))) {
- parametername = parametername.substring(1);
- }
-
- if (casesensitive) {
- return parametername;
- } else {
- return parametername.toUpperCase();
- }
- }
-
- return parametertokens[1];
- }
-
-
- /**
- * URLEncode a String.
- *
- * This is a proxy to java.net.URLEncoder.encode(), as the
- * one parameter version is deprecated and the two parameter
- * version throws an UnsupportedEncodingException which we
- * normally never run into.
- *
- * @param s The String to encode.
- * @return The encoded String.
- */
- public static String URLEncode(String s) {
- // /*
- // * JDK 1.3.1
- // */
- // return java.net.URLEncoder.encode(s);
-
- /*
- * JDK 1.4.2
- */
- try {
- return java.net.URLEncoder.encode(s, "UTF-8");
- } catch (java.io.UnsupportedEncodingException e) {
- return s;
- }
- }
-
-
- /**
- * URLDecode a String.
- *
- * This is a proxy to java.net.URLDecoder.decode(), as the
- * one parameter version is deprecated and the two parameter
- * version throws an UnsupportedEncodingException which we
- * normally never run into.
- *
- * @param s The String to decode.
- * @return The decoded String.
- */
- public static String URLDecode(String s) {
- // /*
- // * JDK 1.3.1
- // */
- // return java.net.URLDecoder.decode(s);
-
- /*
- * JDK 1.4.2
- */
- try {
- return java.net.URLDecoder.decode(s, "UTF-8");
- } catch (java.io.UnsupportedEncodingException e) {
- return s;
- }
- }
-}
diff --git a/java/com.sap.sailing.xcelsiusadapter/src/com/sap/sailing/xcelsiusadapter/XcelsiusApp.java b/java/com.sap.sailing.xcelsiusadapter/src/com/sap/sailing/xcelsiusadapter/XcelsiusServlet.java
similarity index 67%
rename from java/com.sap.sailing.xcelsiusadapter/src/com/sap/sailing/xcelsiusadapter/XcelsiusApp.java
rename to java/com.sap.sailing.xcelsiusadapter/src/com/sap/sailing/xcelsiusadapter/XcelsiusServlet.java
index 77c8339a2a2..479a49166a5 100755
--- a/java/com.sap.sailing.xcelsiusadapter/src/com/sap/sailing/xcelsiusadapter/XcelsiusApp.java
+++ b/java/com.sap.sailing.xcelsiusadapter/src/com/sap/sailing/xcelsiusadapter/XcelsiusServlet.java
@@ -8,10 +8,10 @@ import javax.servlet.http.HttpServletResponse;
import com.sap.sailing.server.gateway.SailingServerHttpServlet;
-public class XcelsiusApp extends SailingServerHttpServlet {
+public class XcelsiusServlet extends SailingServerHttpServlet {
private static final long serialVersionUID = -6849138354941569249L;
- public XcelsiusApp() {
+ public XcelsiusServlet() {
}
@Override
@@ -30,38 +30,38 @@ public class XcelsiusApp extends SailingServerHttpServlet {
// ignore and leave at -1
}
if ("getRankPerLeg".equals(action)) {
- final RankPerLeg a = new RankPerLeg(req, res, getService(), maxRows);
+ final RankPerLegAction a = new RankPerLegAction(req, res, getService(), maxRows);
a.perform();
return;
} else if ("getRankPerLeg2".equals(action)) {
- new RankPerLeg2(req, res, getService(), maxRows).perform();
+ new RankPerLeg2Action(req, res, getService(), maxRows).perform();
return;
} else if ("gpsPerRace".equals(action)) {
- final GPSPerRace a = new GPSPerRace(req, res, getService(), maxRows);
+ final GPSPerRaceAction a = new GPSPerRaceAction(req, res, getService(), maxRows);
a.perform();
return;
} else if ("listEvents".equals(action)) {
- final ListEvents a = new ListEvents(req, res, getService(), maxRows);
+ final ListEventsAction a = new ListEventsAction(req, res, getService(), maxRows);
a.perform();
return;
} else if ("getRankPerRace".equals(action)) {
- final RankPerRace a = new RankPerRace(req, res, getService(), maxRows);
+ final RankPerRaceAction a = new RankPerRaceAction(req, res, getService(), maxRows);
a.perform();
return;
} else if ("getRegattaDataPerLeg".equals(action)) {
- final RegattaDataPerLeg a = new RegattaDataPerLeg(req, res, getService(), maxRows);
+ final RegattaDataPerLegAction a = new RegattaDataPerLegAction(req, res, getService(), maxRows);
a.perform();
return;
}else if ("getRegattaList".equals(action)) {
- final RegattaList a = new RegattaList(req, res, getService(), maxRows);
+ final RegattaListAction a = new RegattaListAction(req, res, getService(), maxRows);
a.perform();
return;
}else {
}
- Action.say("Unknown action", res);
+ HttpAction.say("Unknown action", res);
return;
}
- Action.say("Please use the action= parameter to specify an action.", res);
+ HttpAction.say("Please use the action= parameter to specify an action.", res);
return;
} catch (Exception e) {
throw (new ServletException(e));
diff --git a/wiki/general-information.md b/wiki/general-information.md
new file mode 100644
index 00000000000..88292cbd139
--- /dev/null
+++ b/wiki/general-information.md
@@ -0,0 +1,22 @@
+# General Project Information
+
+### The Situation
+Sailing races can be difficult to understand and follow for an audience as they happen live, out on the water. The trouble starts with figuring out who's in the lead and doesn't stop with understanding the impact of the current race's ranks on the overall regatta standings which are subject to non-trivial scoring rules.
+
+### Project Description
+Using GPS trackers and wind sensors, it is possible to collect real-world data about a sailing race and analyze and visualize the race live. These visualizations can be used by a commentator while talking to an audience, directly by the audience over online channels on various devices, as well as by the sailors who can assess their performance, also relative to their competitors, in replay mode.
+
+
+
+### Objective
+We're doing this in the scope of SAP's sport sponsorship efforts, also in order to show technology leadership and to combine an exciting technology showcase with our sponsorship.
+
+### Approach
+The project started shortly before Kiel Week 2011, in May, leaving little time for planning and developing a first prototype to show in Kiel. The SAP Center of Excellence (CoE) supported (and still supports) the project by skilled development resources and good connections to flexible external hosting providers assisting in the operations of the application. We started from scratch with a Java application using the Equinox OSGi implementation as its runtime infrastructure. The first bundles to be developed were the sailing domain abstractions (boats, boat classes, sailors, courses, races, events, wind and the geometry model) and the connectors for GPS and wind data connectivity. The web front end was initially developed using Python, connecting to the Java backend through HTTP. We chose a NoSQL database (MongoDB) to store data not otherwise retrievable from the tracking provider later, such as the wind data and administrative information. JUnit tests for the back end, a central Maven build with Hudson/Jenkins integration, a GIT repository for change management and the use of Bugzilla laid the technical ground for our development environment. Given our microscopic team size that varied between one and three developers, we skipped daily scrums and used on-demand ad-hoc chats, also through IM systems, as a replacement. Our deadlines for a release are rigid: the sailing regattas at which we present the solution.
+
+After successfully demonstrating the prototype at Kiel Week, the project entered a second phase with major architecture clean-ups. We removed the Python server and replaced it by the use of the Google Web Toolkit (GWT). We were particularly impressed by the client-side performance and flexibility of its powerful controls such as tables and list displays. Web designers, though, need some time to find their way into the world of GWT. We ported the leaderboard functionality from Python to GWT and successfully presented it in its new shape at the International German Championship 2011 in Travemuende and at the Championship of the Champions later that year in Hamburg.
+
+What followed were improvements in the analytical treatment of GPS data and the integration of a flexible Google Map-based race visualization. We added algorithms to identify and count the various sailing maneuvers for the competitors (tacks, jibes, penalty circles, etc.) and implemented a wind estimation algorithm that analyzes the boats' courses and by means of clustering them by tack and leg type infers the most probable wind direction together with a confidence value. A first pragmatic approach to replicating the so far UDP-provided wind data through an HTTP push channel to additional server instances running on spectator boats and serving the solution to mobile devices on these boats rounded off the release presented at an SAP-internal sailing event. When this release with only minor extensions was presented at a training regatta in S'Arenal, Mallorca, our server got under heavy load because the URL had virally spread, and sailors showed high interest in the solution. We saved the event with some ad-hoc performance patches. The event taught us the limitations of dynamically re-computing various key figures each time a tiny bit of raw data changed and brought us to think more about smarter cache invalidation strategies. Additionally, we now started to work on server instance replication techniques that will help us leverage the cloud to scale our solution out during the peak loads experienced during live events.
+
+### The Java In-Memory Challenge
+More or less co-incidentally we chose an architecture in which we keep all relevant race data in main memory. We applied a few techniques to avoid excessive amounts of object headers to consume too much heap space while preserving the API "look & feel" of individual, fine-grained objects for things like speed, bearing, time points, distances or positions. With this, all tracking data of Kiel Week and several other events easily fit into the 16GB of heap space we allocate for our production instance. Key technical challenges remain the performance of live calculations of complex key figures with quickly changing tracking data because it makes caching and cache invalidation tough topics scaling out the server with its in-memory state while allowing client sessions to run modifying transactions on each server instance achieving great performance with a Google Maps integration when the number of boats to display and the expectations regarding the quality of the boat displays rise.
\ No newline at end of file
diff --git a/wiki/howto.md b/wiki/howto.md
new file mode 100644
index 00000000000..1e38c21afe1
--- /dev/null
+++ b/wiki/howto.md
@@ -0,0 +1,13 @@
+# How to work with this Wiki
+
+This Wiki is being stored directly into the default GIT repository on branch **master**. You can edit information here freely but do not leave any nonsense here.
+
+The syntax of the markup is relatively easy and is documented here http://daringfireball.net/projects/markdown/dingus. When you create a new Page then make sure to put some thinking into the right tree structure and then just extend the URL above.
+
+**ATTENTION**:
+
+* Make sure to always have _wiki/_ as the base path. So if you want to create `server/SystemBoot` then the correct URL is `wiki/server/SystemBoot`. Make sure to add you Page to the Table of Contents below by editing the document.
+
+* Password can be found in the encrypted page [[Passwords|wiki/passwords]]. You can decrypt it by using `gpg -d < passwords.md` after having entered the password. If you want to edit, first decrypt and then encrypt again using `gpg -a --symmetric passwords.md`. Make sure to never save the unencrypted version!
+
+If you have any questions then either contact Axel Uhl, Jens Rommel, Frank Mittag or Simon Pamiés.
\ No newline at end of file
diff --git a/wiki/images/ScreenShotRunningApp.jpg b/wiki/images/ScreenShotRunningApp.jpg
new file mode 100644
index 00000000000..ce9be568bc5
Binary files /dev/null and b/wiki/images/ScreenShotRunningApp.jpg differ
diff --git a/wiki/onboarding.md b/wiki/onboarding.md
new file mode 100644
index 00000000000..439b092bb87
--- /dev/null
+++ b/wiki/onboarding.md
@@ -0,0 +1,75 @@
+# OnBoarding Information
+
+This document describes the onboarding process for a new team member (developer)
+
+### Race Analysis Development Setup
+
+#### Installations
+
+1. Eclipse (e.g. Eclipse Classic 3.7.2), http://www.eclipse.org
+2. Eclipse Extensions
+ * Install Eclipse GWT should be version 2.4
+3. Git (e.g. msysGit for Windows v1.7.10), http://git-scm.com
+4. MongoDB (e.g. Production Release 2.0.4), download: http://www.mongodb.org/
+5. RabbitMQ, download from http://www.rabbitmq.com/. Requires Erlang to be installed. RabbitMQ installer will assist in installing Erlang.
+6. JDK 1.6 (Java SE 6), http://jdk6.java.net (for GWT)
+7. JDK 1.7 (Java SE 7), http://jdk7.java.net
+8. Maven 3, http://maven.apache.org
+
+#### Further optional but recommended installations
+
+1. Cygwin, http://www.cygwin.com/
+2. Eclipse Mylyn Bugzilla extension
+3. kdiff3 (git tool)
+4. Firebug (javascript & .css debugging)
+
+#### Accounts
+
+1. Git Account
+- Register yourself as a Git user in the SAP-Git under: https://git.wdf.sap.corp:8080/
+- Ask the Git administrator (Axel Uhl) to get on the list of enabled committers
+
+2. Bugzilla
+- Ask the Bugzilla administrator (Frank Mittag, Axel Uhl) to create a bugzilla account for you.
+- Bugzilla url: http://sapcoe-app01.pironet-ndh.com/bugzilla/
+
+3. Race Analysis user
+- Add yourself as an user to the Race Analysis suite by adding a Jetty user in the file
+ java\target\configuration\jetty\etc\realm.properties
+
+#### Steps to build and run the Race Analysis Suite
+
+1. Get the content of the git repository
+- Clone the repository to your local file system from ssh://[SAP-User]@git.wdf.sap.corp:29418/SAPSail/sapsailingcapture.git or ssh://[user]@sapsailing.com/home/trac/git
+
+2. Check out the 'master' branch from the git repository. The 'master' branch is the main development branch. Please check that you start your work on this branch.
+
+3. Setup and configure Eclipse
+ - Make absolutely sure to import CodeFormatter.xml (from $GIT_HOME/java) into your Eclipse preferences (Preferences->Java->Code Style->Fortmatter)
+ - Install the Eclipse GWT-Plugin (now called Google Plugin for Eclipse, you need the Gogle WebToolkit SDK from the same update site, too)
+ - Install Eclipse eGit (optional)
+ - Check that JDK 1.7 is available and has been set for compilation in Eclipse
+ - Check that the both JDKs are available (Windows->Preferences->Java->Installed JREs)
+ - Check that JDK 1.6 has been matched to JavaSE-1.6 and that JDK 1.7 has been matched to JavaSE-1.7 (...>Installed JREs>Execution Environments)
+ - It is also possible to match the SAPJVM 6 or 7 to the JavaSE-1.6 (for profiling purposes)
+ - Import all Race Analysis projects from the /java(!!!) subdirectory of the git main folder
+ - Set the Eclipse target platform to race-analysis-p2-ide-local.target (located in com.sap.sailing.targetplatform/definitions)
+ - Wait until the target platform has been resolved completely
+ - In the project com.sap.sailing.gwt.ui create a new subfolder "classes" in the folder WEB-INF
+ - Rebuild all projects
+
+4. Run the Race Analysis Suite
+ - Start the MongoDB
+ - Start the appropriate Eclipse launch configuration (e.g. 'Sailing Server (Proxy)')
+ - Start the GWT UI
+
+5. Within the Race Analysis Suite
+ - For TracTrac Events: (Date 27.11.2012) Use Live URI tcp://10.18.22.156:4412, Stored URI tcp://10.18.22.156:4413, JSON URL http://germanmaster.traclive.dk/events/event_20120905_erEuropean/jsonservice.php
+ - Press List Races
+
+#### Maven Setup
+Copy the settings.xml from the top-level git folder to your ~/.m2 directory and adjust the proxy settings accordingly. Make sure the mvn executable you installed above is in your path. Open a shell (preferrably a git bash or a cygwin bash), cd to the java/ subfolder of the git workspace and issue "mvn -fae clean install". This should build the software and run all the tests. If you want to avoid the tests being executed, run "mvn -Dmaven.test.skip=true -fae clean install". If you want to make sure the GWT compiler really re-builds all artifacts, remove all java/com.sap.sailing.gwt.ui/com.sap.sailing.gwt.ui.* directories, then re-build.
+
+#### Further hints
+- Configure Eclipse to use Chrome or Firefox as the default browser
+- Install the GWT Browser Plugin (Chrome or Firefox) for the GWT Development mode
\ No newline at end of file
diff --git a/wiki/server-replication.md b/wiki/server-replication.md
new file mode 100644
index 00000000000..5a2638c4b82
--- /dev/null
+++ b/wiki/server-replication.md
@@ -0,0 +1,93 @@
+# Replicating an SAP Sailing Analytics Server
+
+[[_TOC_]]
+
+### Requirements
+Scaling to many (>100) concurrent users following a live sailing race requires providing more than one OSGi server instance. To an extent this is cause by an aspect of our architecture that decides how clients access server data. We use the Google Web Toolkit's (GWT) Remote Procedure Call (RPC) to let the clients request data from the server. These are handled as HTTP POST requests with a servlet composing the response message. This approach makes caching servlet responses to similar requests by a generic HTTP caching / proxy infrastructure difficult.
+
+Using more than one server instance is additionally motivated by the ManyPlayers client architecture. ManyPlayers wants to use our code base but run it on their premises and only be assured that once their instance connects to our "master" instance then all relevant state will be replicated to their instance.
+
+### Server State
+The server's state at the current time roughly consists of the following:
+
+* list of races with their tracking data loaded by the server
+* a race's state consists of
+ * RaceDefinition object with name, boat class and event association
+ * Course object with Waypoints and ControlPoints
+* a tracked race's state consisting of
+ * wind tracks
+ * raw boat position tracks
+ * mark position tracks
+ * mark passings
+ * race tracking start/finish times
+* list of leaderboard groups with their names and descriptions
+* list of leaderboards and their association with leaderboard groups
+ * leaderboard names, columns and the columns' associated tracked races, if any
+ * discarding rules
+ * competitor renamings
+ * explicit score corrections
+ * disqualification / "max-points" reasons (DSQ, OCS, DNS, DNF, DND, ...)
+
+### Reliability
+When a server is configured to be a replica of another "master" server, all updates to the master server must be replicated as quickly as possible to the replica until the replica permanently disconnects from the master server. If the connection breaks temporarily and is restored at a later point in time, all updates that occurred since the connection broke need to be updated to the client.
+
+### Connectivity
+Replicated instances may be able to only connect to the master through an HTTP connection. In this case, the master server needs to push updates through an HTTP connection reliably.
+
+### Single Master, Multiple Replicas
+For Kiel Week 2012 we want to support a single master server with multiple replicated copies, so-called "replica instances." The replicas are configured for their master node. This configuration doesn't change over the replica's life time. If the master dies, it need to be started again. Replicas then need to recover and re-sync once the new master instance is alive again.
+
+### Concurrency Challenges
+Different types of updates to the master server can happen concurrently. Tracking data for multiple races is being received in multiple threads which can execute truly concurrently on a multi-core architecture. Wind data may be received in parallel to receiving other tracking data. Leaderboard and LeaderboardGroup configurations may happen concurrently to all other types of changes. New races may start to get tracked concurrently to the many other server operations. Do we need to ensure that all these events whose effects need to be replicated have to be serialized in an order that is kept the same for all replica to ensure equal resulting states in all replica? What would serializing all these many concurrent events mean to the server's internal concurrency which is important to improve performance?
+
+### Thoughts on Implementation
+HTTP push protocols are implemented, e.g., by the Atmosphere framework and by our own, proprietary AbstractHttpPostServlet in the com.sap.sailing.server package. They both use a heartbeat mechanism to re-establish the HTTP connection after it got disconnected. However, both currently don't handle reliable data transmission. If a client is disconnected and re-connects later, it may have missed a number or messages sent while it was disconnected. This would need to be overcome in order to achieve the reliabiliy required for our replication scenario.
+
+### Initial Load
+When a replica is first set up and connected to a master server, the master server's current state at some consistent snapshot needs to be replicated to the replica and from then on be kept in sync. How can we produce such a consistent snapshot while understanding which subsequent change events to broadcast to which client?
+
+One approach we're currently testing is using Java Serialization to copy an initial "image" of one server to the next. There are several tricky issues with this approach:
+
+* listeners / observers: several classes offer listeners to be attached; for example, a DynamicTrackedRaceImpl object supports RaceChangeListener objects to attach to it. Similarly, Course objects support CourseListeners to observe the course for changes. Currently, our strategy is to serialize the listener graph together with the object graph they observe by declaring the listener interfaces as "extends Serializable." This unfortunately requires all other listener implementations, even the inner anonymous classes used by test cases, to be serializable too. On the other hand this avoids NotSerializableExceptions.
+* non-serializable JDK classes: For example, TrackedRaceImpl uses java.util.Timer to delay cache invalidation. In this fortunate case, making the field transient has no negative effects because the next action that would need the cache to be invalidated will set the timer correctly, so no explicit initialization in readObject() is required. There may be other cases, though, where a readObject() implementation is needed to establish some objects in transient fields after deserialization.
+* synchronization: serializing a data structure while it is being modified is a bad idea. The java.util.* collection classes are not thread safe. In those cases where concurrent modifications have to be expected, a writeObject() definition is required that synchronizes the serialization with modifying operations. For example, serializing a TrackedRace object together with its GPSFixTracks and WindTracks needs to synchronize with inbound GPSFix / Wind updates.
+
+### Incremental Updates
+Many updates to the server's state are small compared to the overall state. For example, adding a GPS fix to a boat's track is a very small change; so is the modification of a leaderboard name or a leaderboard group's description. Such events need to be replicated incrementally. A message queuing system with persistent message queues may be an implementation option that can guarantee delivery also through HTTP connections.
+
+### Implications for the MongoDB Store
+A replica may or may not use the same MongoDB store that the master uses. If it uses the same store, replication events must not update the database. This then also holds for the initial load procedure. On the other hand, if the replica uses its own database instance, even the initial load has to update all structures that require persistent storage into the local database.
+
+Using MongoDB's built-in replication requires a robust TCP connection between the MongoDB replicas. This is hard to establish in case only HTTP connections are possible between the replica and the master.
+
+### Use of a Message Queuing System
+An MQ system such as ActiveMQ could serve as basis for the implementation of the replication mechanism. We'd have to wrap all commands in messages. To be clarified: what happens when a client subscribes to a topic? Will it receive messages sent to that topic only after it connected? Or will it also receive older messages that have been queued persistently?
+
+### Design
+Changes applied to a server that are relevant for replication have to be applied through a single entry point at RacingEventService, using the command pattern provided by the RacingEventServiceOperation interface. This will allow future extensions towards the use of an Operation Transformation (OT) algorithm in case operations can be performed not only on a single master but throughout the replicas. In addition to the course-grained leaderboard / group creations and editing operations, all tracking data recordings need to pass through this interface in order to be replicated properly.
+
+### RacingEventServiceOperations and RacingEventService
+Most of the state changes relevant for replication are triggered through the RacingEventService interface. A few changes, such as the recording of GPS and wind data received, are currently immediately updated into internal data structures, such as a DynamicTrackedRace and the track objects it manages, or the Course object owned by a RaceDefinition object. It would be possible to restructure things in such a way that also the tracking data are funneled through the RacingEventService interface.
+
+A RacingEventServiceOperation describes serializable operations that can be applied to a RacingEventService and which can be transmitted to another server instance (a replica) to be applied to another RacingEventService, eventually leading to equal states of the RacingEventService instances.
+
+Question: Where and how do we ensure that all modifications to a RacingEventService that are relevant for replication are actually turned into RacingEventServiceOperations and then reliably serialized to all replicas?
+
+We could let RacingEventServiceImpl manage the creation of the operation objects whenever a changing method is called. This would require distinguishing between an "external" and an "internal" API where the external API methods create the operations which, when applied, use the internal API methods to perform the actual updates.
+
+Or we restrict the externally visible API of RacingEventService to applying RacingEventServiceOperations. Then, all modifications that are relevant for replication have to go through a RacingEventServiceOperation. This is very hard to ensure because internally all APIs, such as those of Leaderboard and LeaderboardGroup and everything are easily accessible.
+
+Answer: We create the operations already in SailingServiceImpl and only call apply on the RacingEventService whose implementation can then notify an optional replicator of the operations that were executed. At this time it would make sense to remove the fine-grained operations which are now only used by the operation implementations from the RacingEventService interface at least. The operation implementations can cast the RacingEventService object to RacingEventServiceImpl and then access the operations again.
+
+### Parallel Processing of Operations
+Many types of operations can potentially be processed in parallel. For example, GPS fixes for different tracked races can be inserted into their respective tracks in parallel. So can edit operations for different leaderboards. A thread pool can be used to process the operations. However, each thread in this pool may need to obtain the monitors for the objects it will modify, and the scheduler that sequentially passes the operations to the thread pool for processing needs to wait for the processing thread's acknowledgement that all monitors required have been obtained before triggering the processing of the next operation. The general problem reminds of the versioning graph as created, e.g., by a git repository. Also, Leslie Lamport's vector clocks come to mind.
+
+Some operations are more tricky to parallelize, particularly if we have to assume race conditions during execution. For example, if an operation creates a leaderboard and a follow-up operation adds a column to the leaderboard just created, the leaderboard creation must be complete before the column can be added. Parallelizing such operations is generally dangerous.
+
+Fortunately, the dangerous-to-parallelize operations seem to be the ones that occur with low frequency (leaderboard creation, score corrections, leaderboard column configuration) and have relaxed requirements regarding the lag with which they have to be replicated. We can therefore partition the data and transactions to be replicated into two categories
+
+### Sequential operations
+These encompass leaderboard and leaderboard group creation and manipulation, including score corrections. In a replica these operations must be executed in the same sequence they were executed in on the master. It probably encompasses the creation of a tracked race and its course, we well as the updates to a race course with waypoint additions and mark passings because mark passing events relate to the waypoints.
+Paralllel operations
+
+These encompass the receiving of raw sensor data. The order in which this data is re-played doesn't matter. The data is time-stamped, and its history is not rewritten (as maybe the case for the mark passings or for the race tracking start/finish times). Applying these operations to a replica is idempotent, so serializing an initial state doesn't need to be synchronized with the receiving of such data. Duplicate application of these operations to a server doesn't do any harm.
\ No newline at end of file