mirror of
https://github.com/eclipse-sailing-analytics/sailing-analytics.git
synced 2026-10-11 23:00:59 +00:00
Merge remote-tracking branch 'origin/master' into newhome
This commit is contained in:
commit
df0aa72c72
142 files changed
+3431
-690
No files matched your search
@@ -1,4 +1,4 @@
|
||||
#!/bin/sh
|
||||
#!/bin/bash
|
||||
#
|
||||
# Copyright (c) 2006 Johannes E. Schindelin
|
||||
#
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/bin/sh
|
||||
#!/bin/bash
|
||||
#
|
||||
# Copyright (c) 2006 Johannes E. Schindelin
|
||||
#
|
||||
|
||||
+2
-1
@@ -2,5 +2,6 @@ package com.sap.sailing.domain.common;
|
||||
|
||||
public enum ScoringSchemeType {
|
||||
LOW_POINT, HIGH_POINT, HIGH_POINT_ESS_OVERALL, HIGH_POINT_LAST_BREAKS_TIE, HIGH_POINT_FIRST_GETS_TEN,
|
||||
HIGH_POINT_FIRST_GETS_ONE, LOW_POINT_WINNER_GETS_ZERO, HIGH_POINT_WINNER_GETS_SIX, HIGH_POINT_WINNER_GETS_FIVE
|
||||
HIGH_POINT_FIRST_GETS_ONE, LOW_POINT_WINNER_GETS_ZERO, HIGH_POINT_WINNER_GETS_SIX, HIGH_POINT_WINNER_GETS_FIVE,
|
||||
HIGH_POINT_WINNER_GETS_FIVE_IGNORING_RACE_COUNT
|
||||
}
|
||||
+1
@@ -509,6 +509,7 @@ public class IgtimiConnectionFactoryImpl implements IgtimiConnectionFactory {
|
||||
return o1Score - o2Score;
|
||||
}
|
||||
});
|
||||
logger.info("Trying Igtimi WebSocket servers in the following order: "+result);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
+13
-26
@@ -485,44 +485,31 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
|
||||
|
||||
result = new FlexibleLeaderboardImpl(raceLogStore, (String) dbLeaderboard.get(FieldNames.LEADERBOARD_NAME
|
||||
.name()), resultDiscardingRule, scoringScheme, courseArea);
|
||||
// For a FlexibleLeaderboard, fleets are owned by the leaderboard's RaceColumn objects. We need to manage
|
||||
// them here:
|
||||
Map<String, Fleet> fleetsByName = new HashMap<String, Fleet>();
|
||||
// For a FlexibleLeaderboard, there should be only the default fleet for any race column
|
||||
for (Object dbRaceColumnAsObject : dbRaceColumns) {
|
||||
BasicDBObject dbRaceColumn = (BasicDBObject) dbRaceColumnAsObject;
|
||||
String columnName = (String) dbRaceColumn.get(FieldNames.LEADERBOARD_COLUMN_NAME.name());
|
||||
|
||||
RaceColumn raceColumn = result.addRaceColumn(columnName,
|
||||
(Boolean) dbRaceColumn.get(FieldNames.LEADERBOARD_IS_MEDAL_RACE_COLUMN.name()));
|
||||
|
||||
Map<String, RaceIdentifier> raceIdentifiers = loadRaceIdentifiers(dbRaceColumn);
|
||||
RaceIdentifier defaultFleetRaceIdentifier = raceIdentifiers.get(null);
|
||||
RaceIdentifier defaultFleetRaceIdentifier = raceIdentifiers.get(result.getFleet(null).getName());
|
||||
if (defaultFleetRaceIdentifier == null) {
|
||||
// Backward compatibility
|
||||
defaultFleetRaceIdentifier = raceIdentifiers.get(null);
|
||||
}
|
||||
if (defaultFleetRaceIdentifier != null) {
|
||||
Fleet defaultFleet = result.getFleet(null);
|
||||
if (defaultFleet != null) {
|
||||
raceIdentifiers.put(defaultFleet.getName(), defaultFleetRaceIdentifier);
|
||||
|
||||
raceColumn.setRaceIdentifier(defaultFleet, defaultFleetRaceIdentifier);
|
||||
} else {
|
||||
// leaderboard has no default fleet; don't know what to do with default RaceIdentifier
|
||||
logger.warning("Discarding RaceIdentifier " + defaultFleetRaceIdentifier
|
||||
+ " for default fleet for leaderboard " + result.getName()
|
||||
+ " because no default fleet was found in leaderboard");
|
||||
}
|
||||
raceIdentifiers.remove(null);
|
||||
}
|
||||
List<Fleet> fleets = new ArrayList<Fleet>();
|
||||
for (String fleetName : raceIdentifiers.keySet()) {
|
||||
Fleet fleet = fleetsByName.get(fleetName);
|
||||
if (fleet == null) {
|
||||
fleet = new FleetImpl(fleetName);
|
||||
fleetsByName.put(fleetName, fleet);
|
||||
}
|
||||
fleets.add(fleet);
|
||||
}
|
||||
if (fleets.isEmpty()) {
|
||||
fleets.add(result.getFleet(null));
|
||||
}
|
||||
String columnName = (String) dbRaceColumn.get(FieldNames.LEADERBOARD_COLUMN_NAME.name());
|
||||
|
||||
RaceColumn raceColumn = result.addRaceColumn(columnName,
|
||||
(Boolean) dbRaceColumn.get(FieldNames.LEADERBOARD_IS_MEDAL_RACE_COLUMN.name()),
|
||||
fleets.toArray(new Fleet[0]));
|
||||
for (Map.Entry<String, RaceIdentifier> e : raceIdentifiers.entrySet()) {
|
||||
raceColumn.setRaceIdentifier(fleetsByName.get(e.getKey()), e.getValue());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+1
-1
@@ -64,7 +64,7 @@ public class MediaDBImpl implements MediaDB {
|
||||
try {
|
||||
dbVideos.insert(dbMediaTrack);
|
||||
} catch (MongoException.DuplicateKey e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
throw new IllegalArgumentException("Duplicate key '" + dbId + "' caused an error when importing media (title: '" + title + "')", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+11
@@ -15,6 +15,9 @@ import com.sap.sailing.domain.common.dto.CompetitorDTO;
|
||||
*
|
||||
*/
|
||||
public interface CompetitorStore extends CompetitorFactory {
|
||||
public interface CompetitorUpdateListener {
|
||||
void competitorUpdated(Competitor competitor);
|
||||
}
|
||||
|
||||
/**
|
||||
* If a valid competitor is returned and the caller has information available that could be used to update the competitor,
|
||||
@@ -62,4 +65,12 @@ public interface CompetitorStore extends CompetitorFactory {
|
||||
Competitor updateCompetitor(String idAsString, String newName, Color newDisplayColor, String newSailId, Nationality newNationality);
|
||||
|
||||
CompetitorDTO convertToCompetitorDTO(Competitor c);
|
||||
|
||||
/**
|
||||
* Listeners added here are notified whenever {@link #updateCompetitor(String, String, Color, String, Nationality)} is called
|
||||
* for any competitor in this store.
|
||||
*/
|
||||
void addCompetitorUpdateListener(CompetitorUpdateListener listener);
|
||||
|
||||
void removeCompetitorUpdateListener(CompetitorUpdateListener listener);
|
||||
}
|
||||
+19
@@ -4,6 +4,7 @@ import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
@@ -25,6 +26,7 @@ public class TransientCompetitorStoreImpl implements CompetitorStore, Serializab
|
||||
private static final long serialVersionUID = -4198298775476586931L;
|
||||
private final Map<Serializable, Competitor> competitorCache;
|
||||
private final Map<String, Competitor> competitorsByIdAsString;
|
||||
private transient Set<CompetitorUpdateListener> listeners;
|
||||
|
||||
/**
|
||||
* The competitors contained in this map will have their changeable properties
|
||||
@@ -43,13 +45,25 @@ public class TransientCompetitorStoreImpl implements CompetitorStore, Serializab
|
||||
competitorsByIdAsString = new HashMap<String, Competitor>();
|
||||
competitorsToUpdateDuringGetOrCreate = new HashSet<Competitor>();
|
||||
weakCompetitorDTOCache = new WeakHashMap<Competitor, CompetitorDTO>();
|
||||
listeners = Collections.synchronizedSet(new HashSet<CompetitorStore.CompetitorUpdateListener>());
|
||||
}
|
||||
|
||||
private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException {
|
||||
ois.defaultReadObject();
|
||||
weakCompetitorDTOCache = new WeakHashMap<Competitor, CompetitorDTO>();
|
||||
listeners = Collections.synchronizedSet(new HashSet<CompetitorStore.CompetitorUpdateListener>());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCompetitorUpdateListener(CompetitorUpdateListener listener) {
|
||||
listeners.add(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeCompetitorUpdateListener(CompetitorUpdateListener listener) {
|
||||
listeners.remove(listener);
|
||||
}
|
||||
|
||||
private Competitor createCompetitor(Serializable id, String name, Color displayColor, DynamicTeam team, DynamicBoat boat) {
|
||||
Competitor result = new CompetitorImpl(id, name, displayColor, team, boat);
|
||||
addNewCompetitor(id, result);
|
||||
@@ -179,6 +193,11 @@ public class TransientCompetitorStoreImpl implements CompetitorStore, Serializab
|
||||
LockUtil.unlockAfterWrite(lock);
|
||||
}
|
||||
}
|
||||
synchronized (listeners) {
|
||||
for (CompetitorUpdateListener listener : listeners) {
|
||||
listener.competitorUpdated(competitor);
|
||||
}
|
||||
}
|
||||
return competitor;
|
||||
}
|
||||
|
||||
|
||||
+1
-7
@@ -1,7 +1,6 @@
|
||||
package com.sap.sailing.domain.swisstimingreplayadapter.impl;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
@@ -25,6 +24,7 @@ import com.sap.sailing.domain.swisstimingreplayadapter.SwissTimingReplayListener
|
||||
import com.sap.sailing.domain.swisstimingreplayadapter.SwissTimingReplayRace;
|
||||
import com.sap.sailing.domain.swisstimingreplayadapter.SwissTimingReplayService;
|
||||
import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
|
||||
import com.sap.sailing.util.ByteArrayOutputStreamWithVisibleBuffer;
|
||||
|
||||
public class SwissTimingReplayServiceImpl implements SwissTimingReplayService {
|
||||
|
||||
@@ -119,12 +119,6 @@ public class SwissTimingReplayServiceImpl implements SwissTimingReplayService {
|
||||
return startTimeFormat;
|
||||
}
|
||||
|
||||
private static class ByteArrayOutputStreamWithVisibleBuffer extends ByteArrayOutputStream {
|
||||
public byte[] getBuffer() {
|
||||
return buf;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadRaceData(String link, SwissTimingReplayListener replayListener) {
|
||||
final URL raceDataUrl;
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
#!/bin/sh
|
||||
#!/bin/bash
|
||||
java -Dfile.encoding=en_US.UTF-8 -cp bin/com.sap.sailing.domain.test-1.0.0-SNAPSHOT.jar:../com.sap.sailing.domain.tractracadapter/bin/com.sap.sailing.domain.tractracadapter-1.0.0-SNAPSHOT.jar:../com.sap.sailing.domain/bin/com.sap.sailing.domain-1.0.0-SNAPSHOT.jar:../com.tractrac.clientmodule/bin/com.tractrac.clientmodule-1.0.0-SNAPSHOT.jar:../com.tractrac.clientmodule/lib/TracTracClientModule.jar:../com.googlecode.java-diff-utils/bin/com.googlecode.java-diff-utils-1.3.0-SNAPSHOT.jar com.sap.sailing.domain.test.UnicodeCharactersInCompetitorNamesTest
|
||||
+4
-6
@@ -14,7 +14,6 @@ import java.util.logging.Logger;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.base.Fleet;
|
||||
import com.sap.sailing.domain.base.RaceColumn;
|
||||
import com.sap.sailing.domain.common.NoWindException;
|
||||
import com.sap.sailing.domain.common.WindSourceType;
|
||||
@@ -27,8 +26,8 @@ import com.sap.sailing.domain.leaderboard.impl.LowPoint;
|
||||
import com.sap.sailing.domain.leaderboard.impl.ThresholdBasedResultDiscardingRuleImpl;
|
||||
import com.sap.sailing.domain.tracking.impl.WindImpl;
|
||||
import com.sap.sailing.domain.tractracadapter.ReceiverType;
|
||||
import com.tractrac.model.lib.api.event.CreateModelException;
|
||||
import com.sap.sse.common.Util;
|
||||
import com.tractrac.model.lib.api.event.CreateModelException;
|
||||
import com.tractrac.subscription.lib.api.SubscriberInitializationException;
|
||||
|
||||
public class LeaderboardForKielWeekTest extends OnlineTracTracBasedTest {
|
||||
@@ -44,24 +43,23 @@ public class LeaderboardForKielWeekTest extends OnlineTracTracBasedTest {
|
||||
public void leaderboardWithOneRaceTest() throws URISyntaxException, NoWindException, IOException, InterruptedException, SubscriberInitializationException, CreateModelException {
|
||||
leaderboard = new FlexibleLeaderboardImpl("Kiel Week 2011 505s", new ThresholdBasedResultDiscardingRuleImpl(new int[] { 3, 6 }),
|
||||
new LowPoint(), null);
|
||||
Fleet defaultFleet = leaderboard.getFleet(null);
|
||||
MillisecondsTimePoint now = MillisecondsTimePoint.now();
|
||||
loadRace("event_20110609_KielerWoch-505_Race_2.txt", "event_20110609_KielerWoch-505_Race_2.mtb"); // 505 Race 2
|
||||
Competitor kevin = getCompetitorByName("Kevin");
|
||||
RaceColumn column = leaderboard.addRace(getTrackedRace(), "Test Race 1", /* medalRace */ false, defaultFleet);
|
||||
RaceColumn column = leaderboard.addRace(getTrackedRace(), "Test Race 1", /* medalRace */false);
|
||||
assertEquals(17, leaderboard.getTotalPoints(kevin, now), 0.000000001);
|
||||
Util.Pair<Competitor, RaceColumn> key = new Util.Pair<Competitor, RaceColumn>(kevin, column);
|
||||
assertEquals(17, leaderboard.getContent(now).get(key).getTotalPoints(), 0.000000001);
|
||||
assertEquals(17, leaderboard.getEntry(kevin, column, now).getTotalPoints(), 0.000000001);
|
||||
loadRace("event_20110609_KielerWoch-505_Race_3.txt", "event_20110609_KielerWoch-505_Race_3.mtb"); // 505 Race 3
|
||||
column = leaderboard.addRace(getTrackedRace(), "Test Race 2", /* medalRace */ false, defaultFleet);
|
||||
column = leaderboard.addRace(getTrackedRace(), "Test Race 2", /* medalRace */false);
|
||||
key = new Util.Pair<Competitor, RaceColumn>(kevin, column);
|
||||
// In Race 3, Hasso ranked 31st
|
||||
assertEquals(29, leaderboard.getTotalPoints(kevin, now), 0.000000001);
|
||||
assertEquals(12, leaderboard.getContent(now).get(key).getTotalPoints(), 0.000000001);
|
||||
assertEquals(12, leaderboard.getEntry(kevin, column, now).getTotalPoints(), 0.000000001);
|
||||
loadRace("event_20110609_KielerWoch-505_race_4.txt", "event_20110609_KielerWoch-505_race_4.mtb"); // 505 Race 4
|
||||
column = leaderboard.addRace(getTrackedRace(), "Test Race 3", /* medalRace */ false, defaultFleet);
|
||||
column = leaderboard.addRace(getTrackedRace(), "Test Race 3", /* medalRace */false);
|
||||
key = new Util.Pair<Competitor, RaceColumn>(kevin, column);
|
||||
// now the first race is discarded because Kevin ranked worst compared to the other two; in race 4 he ranked 10th
|
||||
assertEquals(22, leaderboard.getTotalPoints(kevin, now), 0.000000001);
|
||||
|
||||
+8
-12
@@ -102,12 +102,11 @@ public class LeaderboardOfflineTest extends AbstractLeaderboardTest {
|
||||
public void ensureMedalRaceParamIsIgnoredIfRaceColumnAlreadyExists() {
|
||||
FlexibleLeaderboard leaderboard = new FlexibleLeaderboardImpl("Test Leaderboard", new ThresholdBasedResultDiscardingRuleImpl(
|
||||
new int[] { 5, 8 }), new LowPoint(), null);
|
||||
Fleet defaultFleet = leaderboard.getFleet(null);
|
||||
final String columnName = "abc";
|
||||
setupRaces(1, 0);
|
||||
|
||||
leaderboard.addRaceColumn(columnName, /* medalRace */ true);
|
||||
leaderboard.addRace(testRaces.iterator().next(), columnName, /* medalRace */ false, defaultFleet);
|
||||
leaderboard.addRace(testRaces.iterator().next(), columnName, /* medalRace */false);
|
||||
assertTrue(leaderboard.getRaceColumnByName(columnName).isMedalRace());
|
||||
}
|
||||
|
||||
@@ -119,7 +118,7 @@ public class LeaderboardOfflineTest extends AbstractLeaderboardTest {
|
||||
final String columnName = "abc";
|
||||
setupRaces(1, 0);
|
||||
leaderboard.addRaceColumn(columnName, /* medalRace */ true);
|
||||
leaderboard.addRace(testRaces.iterator().next(), columnName, /* medalRace */ false, defaultFleet);
|
||||
leaderboard.addRace(testRaces.iterator().next(), columnName, /* medalRace */false);
|
||||
final Set<String> emptySet = Collections.emptySet();
|
||||
final MillisecondsTimePoint now = MillisecondsTimePoint.now();
|
||||
final TrackedRegattaRegistry trackedRegattaRegistry = new TrackedRegattaRegistry() {
|
||||
@@ -180,7 +179,7 @@ public class LeaderboardOfflineTest extends AbstractLeaderboardTest {
|
||||
RaceColumn bestScoringRaceColumn = null;
|
||||
for (TrackedRace race : testRaces) {
|
||||
i++;
|
||||
RaceColumn raceColumn = leaderboard.addRace(race, "Test Race " + i, /* medalRace */ false, defaultFleet);
|
||||
RaceColumn raceColumn = leaderboard.addRace(race, "Test Race " + i, /* medalRace */false);
|
||||
raceColumnsInLeaderboard.put(race, raceColumn);
|
||||
if (race.getRank(competitor) < bestScore) {
|
||||
bestScore = race.getRank(competitor);
|
||||
@@ -207,8 +206,7 @@ public class LeaderboardOfflineTest extends AbstractLeaderboardTest {
|
||||
testRace.addCompetitor(c3); // this makes maxPoints==4
|
||||
FlexibleLeaderboard leaderboard = new FlexibleLeaderboardImpl("Test Leaderboard", new ThresholdBasedResultDiscardingRuleImpl(
|
||||
new int[] { 2 }), new LowPoint(), null);
|
||||
Fleet defaultFleet = leaderboard.getFleet(null);
|
||||
leaderboard.addRace(testRace, "R1", /* medalRace */ false, defaultFleet);
|
||||
leaderboard.addRace(testRace, "R1", /* medalRace */false);
|
||||
assertEquals(1., leaderboard.getTotalPoints(competitor, now), 0.00000001);
|
||||
assertEquals(1., leaderboard.getTotalPoints(c2, now), 0.00000001);
|
||||
assertEquals(1., leaderboard.getTotalPoints(c3, now), 0.00000001);
|
||||
@@ -235,10 +233,9 @@ public class LeaderboardOfflineTest extends AbstractLeaderboardTest {
|
||||
testRace.addCompetitor(c3); // this makes maxPoints==4
|
||||
FlexibleLeaderboard leaderboard = new FlexibleLeaderboardImpl("Test Leaderboard", new ThresholdBasedResultDiscardingRuleImpl(
|
||||
new int[] { 2 }), new LowPoint(), null);
|
||||
Fleet defaultFleet = leaderboard.getFleet(null);
|
||||
RaceColumn r1 = leaderboard.addRace(testRace, "R1", /* medalRace */ false, defaultFleet);
|
||||
RaceColumn r1 = leaderboard.addRace(testRace, "R1", /* medalRace */false);
|
||||
raceColumnsInLeaderboard.put(testRace, r1);
|
||||
RaceColumn r2 = leaderboard.addRaceColumn("R2", /* medalRace */ false, defaultFleet);
|
||||
RaceColumn r2 = leaderboard.addRaceColumn("R2", /* medalRace */false);
|
||||
leaderboard.getScoreCorrection().setMaxPointsReason(competitor, r2, MaxPointsReason.DND); // non-discardable disqualification
|
||||
// assert that max points were given before discarding...
|
||||
assertEquals(4.0, leaderboard.getEntry(competitor, r2, MillisecondsTimePoint.now()).getNetPoints(), 0.000000001);
|
||||
@@ -268,7 +265,7 @@ public class LeaderboardOfflineTest extends AbstractLeaderboardTest {
|
||||
RaceColumn bestScoringRaceColumn = null;
|
||||
for (TrackedRace race : testRaces) {
|
||||
i++;
|
||||
RaceColumn raceColumn = leaderboard.addRace(race, "Test Race " + i, /* medalRace */ false, defaultFleet);
|
||||
RaceColumn raceColumn = leaderboard.addRace(race, "Test Race " + i, /* medalRace */false);
|
||||
raceColumnsInLeaderboard.put(race, raceColumn);
|
||||
if (race.getRank(competitor) < bestScore) {
|
||||
bestScore = race.getRank(competitor);
|
||||
@@ -294,8 +291,7 @@ public class LeaderboardOfflineTest extends AbstractLeaderboardTest {
|
||||
for (TrackedRace race : testRaces) {
|
||||
i++;
|
||||
raceColumnsInLeaderboard.put(race, leaderboard.addRace(race, "Test Race "+i,
|
||||
/* medalRace */ numberOfUntrackedRaces == 0 && addOneMedalRace && i == testRaces.size(),
|
||||
defaultFleet));
|
||||
/* medalRace */numberOfUntrackedRaces == 0 && addOneMedalRace && i == testRaces.size()));
|
||||
}
|
||||
// add a few race columns not yet connected to a tracked race
|
||||
for (int j=0; j<numberOfUntrackedRaces; j++) {
|
||||
|
||||
+33
-35
@@ -798,8 +798,9 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest {
|
||||
TimePoint later = new MillisecondsTimePoint(now.asMillis()+1000);
|
||||
FlexibleLeaderboard leaderboardHighPoint10LastBreaksTie = new FlexibleLeaderboardImpl("Test ESS Highpoint 10LastBreaksTie", new ThresholdBasedResultDiscardingRuleImpl(/* discarding thresholds */ new int[0]),
|
||||
new HighPointFirstGets10LastBreaksTie(), null);
|
||||
leaderboardHighPoint10LastBreaksTie.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitors)), "R1", /* medalRace */ false,
|
||||
leaderboardHighPoint10LastBreaksTie.getFleet(null));
|
||||
leaderboardHighPoint10LastBreaksTie
|
||||
.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitors)), "R1", /* medalRace */
|
||||
false);
|
||||
assertTrue(leaderboardHighPoint10LastBreaksTie.getScoringScheme().getScoreComparator(/* nullScoresAreBetter */ false).compare(
|
||||
leaderboardHighPoint10LastBreaksTie.getTotalPoints(competitors[0], later), leaderboardHighPoint10LastBreaksTie.getTotalPoints(competitors[3], later)) < 0); // c0 better than c3
|
||||
assertEquals(16, leaderboardHighPoint10LastBreaksTie.getCompetitorsFromBestToWorst(later).size());
|
||||
@@ -808,8 +809,8 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest {
|
||||
// Normal HighPoint leaderboard has no max so that winner gets as many points as there are competitors
|
||||
FlexibleLeaderboard leaderboardHighPoint = new FlexibleLeaderboardImpl("Test ESS Highpoint", new ThresholdBasedResultDiscardingRuleImpl(/* discarding thresholds */ new int[0]),
|
||||
new HighPoint(), null);
|
||||
leaderboardHighPoint.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitors)), "R1", /* medalRace */ false,
|
||||
leaderboardHighPoint.getFleet(null));
|
||||
leaderboardHighPoint.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitors)), "R1", /* medalRace */
|
||||
false);
|
||||
assertEquals(16, leaderboardHighPoint.getCompetitorsFromBestToWorst(later).size());
|
||||
assertEquals(new Double(16), leaderboardHighPoint.getTotalPoints(competitors[0], later));
|
||||
LeaderboardGroup leaderboardGroup = new LeaderboardGroupImpl("ESS", "ESS", /* displayGroupsInReverseOrder */ false,
|
||||
@@ -847,26 +848,26 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest {
|
||||
TimePoint later = new MillisecondsTimePoint(now.asMillis()+1000);
|
||||
FlexibleLeaderboard leaderboard1 = new FlexibleLeaderboardImpl("Leaderboard 1", new ThresholdBasedResultDiscardingRuleImpl(/* discarding thresholds */ new int[0]),
|
||||
new HighPoint(), null);
|
||||
leaderboard1.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(f1)), "R1", /* medalRace */ false,
|
||||
leaderboard1.getFleet(null));
|
||||
leaderboard1.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(f2)), "R2", /* medalRace */ false,
|
||||
leaderboard1.getFleet(null));
|
||||
leaderboard1.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(f1)), "R1", /* medalRace */
|
||||
false);
|
||||
leaderboard1.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(f2)), "R2", /* medalRace */
|
||||
false);
|
||||
assertTrue(leaderboard1.getScoringScheme().getScoreComparator(/* nullScoresAreBetter */ false).compare(
|
||||
leaderboard1.getTotalPoints(c[0], later), leaderboard1.getTotalPoints(c[3], later)) < 0); // c0 better than c3
|
||||
FlexibleLeaderboard leaderboard2 = new FlexibleLeaderboardImpl("Leaderboard 3", new ThresholdBasedResultDiscardingRuleImpl(/* discarding thresholds */ new int[0]),
|
||||
new HighPoint(), null);
|
||||
leaderboard2.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(f3)), "R1", /* medalRace */ false,
|
||||
leaderboard1.getFleet(null));
|
||||
leaderboard2.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(f4)), "R2", /* medalRace */ false,
|
||||
leaderboard1.getFleet(null));
|
||||
leaderboard2.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(f3)), "R1", /* medalRace */
|
||||
false);
|
||||
leaderboard2.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(f4)), "R2", /* medalRace */
|
||||
false);
|
||||
assertTrue(leaderboard2.getScoringScheme().getScoreComparator(/* nullScoresAreBetter */ false).compare(
|
||||
leaderboard2.getTotalPoints(c[3], later), leaderboard2.getTotalPoints(c[0], later)) < 0); // c3 better than c0
|
||||
FlexibleLeaderboard leaderboard3 = new FlexibleLeaderboardImpl("Leaderboard 3", new ThresholdBasedResultDiscardingRuleImpl(/* discarding thresholds */ new int[0]),
|
||||
new HighPoint(), null);
|
||||
leaderboard3.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(f5)), "R1", /* medalRace */ false,
|
||||
leaderboard1.getFleet(null));
|
||||
leaderboard3.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(f6)), "R2", /* medalRace */ false,
|
||||
leaderboard1.getFleet(null));
|
||||
leaderboard3.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(f5)), "R1", /* medalRace */
|
||||
false);
|
||||
leaderboard3.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(f6)), "R2", /* medalRace */
|
||||
false);
|
||||
assertTrue(leaderboard3.getCompetitorsFromBestToWorst(later).indexOf(c[3]) <
|
||||
leaderboard3.getCompetitorsFromBestToWorst(later).indexOf(c[0])); // c3 better than c0; won last race
|
||||
LeaderboardGroup leaderboardGroup = new LeaderboardGroupImpl("Leaderboard Group", "Leaderboard Group", false, Arrays.asList(leaderboard1,
|
||||
@@ -896,18 +897,18 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest {
|
||||
TimePoint later = new MillisecondsTimePoint(now.asMillis()+1000);
|
||||
FlexibleLeaderboard leaderboard1 = new FlexibleLeaderboardImpl("Leaderboard 1", new ThresholdBasedResultDiscardingRuleImpl(/* discarding thresholds */ new int[0]),
|
||||
new HighPointFirstGets10LastBreaksTie(), null);
|
||||
leaderboard1.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(regattaRace1_1)), "R1", /* medalRace */ false,
|
||||
leaderboard1.getFleet(null));
|
||||
leaderboard1.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(regattaRace2_1)), "R2", /* medalRace */ false,
|
||||
leaderboard1.getFleet(null));
|
||||
leaderboard1.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(regattaRace1_1)), "R1", /* medalRace */
|
||||
false);
|
||||
leaderboard1.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(regattaRace2_1)), "R2", /* medalRace */
|
||||
false);
|
||||
FlexibleLeaderboard leaderboard2 = new FlexibleLeaderboardImpl("Leaderboard 2", new ThresholdBasedResultDiscardingRuleImpl(/* discarding thresholds */ new int[0]),
|
||||
new HighPointFirstGets10LastBreaksTie(), null);
|
||||
leaderboard2.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(regattaRace1_2)), "R1", /* medalRace */ false,
|
||||
leaderboard2.getFleet(null));
|
||||
leaderboard2.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(regattaRace2_2)), "R2", /* medalRace */ false,
|
||||
leaderboard2.getFleet(null));
|
||||
leaderboard2.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(regattaRace3_2)), "R3", /* medalRace */ false,
|
||||
leaderboard2.getFleet(null));
|
||||
leaderboard2.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(regattaRace1_2)), "R1", /* medalRace */
|
||||
false);
|
||||
leaderboard2.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(regattaRace2_2)), "R2", /* medalRace */
|
||||
false);
|
||||
leaderboard2.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(regattaRace3_2)), "R3", /* medalRace */
|
||||
false);
|
||||
// [C2, C3, C4, C5, C6, C7, C1, C11, C8, C9, C10, C12]
|
||||
// [29, 25, 22, 19, 16, 12, 13, 11, 10, 7, 4, 3]
|
||||
List<Competitor> rankedCompetitorsInLeaderboard = leaderboard2.getCompetitorsFromBestToWorst(later);
|
||||
@@ -958,9 +959,8 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest {
|
||||
public TimePoint getEndOfRace() {
|
||||
return endOfR1;
|
||||
}
|
||||
}, "R1", /* medalRace */ false,
|
||||
leaderboard1.getFleet(null));
|
||||
RaceColumn r2 = leaderboard1.addRaceColumn("R2", /* medalRace */ false, new Fleet[] { new FleetImpl("Default Fleet") });
|
||||
}, "R1", /* medalRace */false);
|
||||
RaceColumn r2 = leaderboard1.addRaceColumn("R2", /* medalRace */false);
|
||||
leaderboard1.getScoreCorrection().correctScore(c[0], r2, 123.);
|
||||
assertEquals(2. + 123., leaderboard1.getTotalPoints(c[0], afterEndOfR1), 0.00000001); // correction expected to apply after end of last tracked race before the corrected column
|
||||
assertEquals(2. + 0., leaderboard1.getTotalPoints(c[0], withinR1), 0.00000001); // correction expected NOT to apply before end of last tracked race before the corrected column
|
||||
@@ -986,15 +986,13 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest {
|
||||
public TimePoint getEndOfRace() {
|
||||
return endOfR1;
|
||||
}
|
||||
}, "R1", /* medalRace */ false,
|
||||
leaderboard1.getFleet(null));
|
||||
}, "R1", /* medalRace */false);
|
||||
RaceColumn r2 = leaderboard1.addRace(new MockedTrackedRaceWithStartTimeAndRanks(afterEndOfR1, Arrays.asList(c2)) {
|
||||
private static final long serialVersionUID = 8705622361027154428L;
|
||||
public TimePoint getEndOfRace() {
|
||||
return afterEndOfR1;
|
||||
}
|
||||
}, "R2", /* medalRace */ false,
|
||||
leaderboard1.getFleet(null));
|
||||
}, "R2", /* medalRace */false);
|
||||
leaderboard1.getScoreCorrection().correctScore(c1[0], r2, .9);
|
||||
assertEquals(1. + .9, leaderboard1.getTotalPoints(c1[0], afterEndOfR1), 0.00000001);
|
||||
assertEquals(1, leaderboard1.getTotalRankOfCompetitor(c1[0], afterEndOfR1));
|
||||
@@ -1034,8 +1032,8 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest {
|
||||
};
|
||||
trackedRace.getRace().getCourse().addWaypoint(0, start);
|
||||
trackedRace.getRace().getCourse().addWaypoint(1, finish);
|
||||
RaceColumn r1 = leaderboard1.addRace(trackedRace, "R1", /* medalRace */ false, leaderboard1.getFleet(null));
|
||||
RaceColumn r2 = leaderboard1.addRaceColumn("R2", /* medalRace */ false, new Fleet[] { new FleetImpl("Default Fleet") });
|
||||
RaceColumn r1 = leaderboard1.addRace(trackedRace, "R1", /* medalRace */false);
|
||||
RaceColumn r2 = leaderboard1.addRaceColumn("R2", /* medalRace */false);
|
||||
leaderboard1.getScoreCorrection().correctScore(c[0], r2, 123.);
|
||||
assertEquals(2. + 123., leaderboard1.getTotalPoints(c[0], afterEndOfR1), 0.00000001); // correction expected to apply after end of last tracked race before the corrected column
|
||||
assertEquals(2. + 0., leaderboard1.getTotalPoints(c[0], withinR1), 0.00000001); // correction expected NOT to apply before end of last tracked race before the corrected column
|
||||
|
||||
+5
-10
@@ -1,6 +1,5 @@
|
||||
package com.sap.sailing.domain.test;
|
||||
|
||||
import static org.junit.Assert.assertNotSame;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
|
||||
@@ -11,7 +10,6 @@ import org.junit.Test;
|
||||
|
||||
import com.sap.sailing.domain.base.Fleet;
|
||||
import com.sap.sailing.domain.base.RaceColumn;
|
||||
import com.sap.sailing.domain.base.impl.FleetImpl;
|
||||
import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard;
|
||||
import com.sap.sailing.domain.leaderboard.impl.FlexibleLeaderboardImpl;
|
||||
import com.sap.sailing.domain.leaderboard.impl.LowPoint;
|
||||
@@ -76,18 +74,15 @@ public class RaceLogAttachOnTrackedRaceTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAttachSpecificFleets() {
|
||||
Fleet[] fleets = new Fleet[] { new FleetImpl("A"), new FleetImpl("B") };
|
||||
Fleet aFleet = fleets[0];
|
||||
Fleet bFleet = fleets[1];
|
||||
RaceColumn column = leaderboard.addRaceColumn("R1", false, fleets);
|
||||
public void testAttachToDefaultFleet() {
|
||||
RaceColumn column = leaderboard.addRaceColumn("R1", false);
|
||||
|
||||
TrackedRace trackedRace = new MyMockedTrackedRace();
|
||||
|
||||
column.setTrackedRace(aFleet, trackedRace);
|
||||
Fleet defaultFleet = leaderboard.getFleet(null);
|
||||
column.setTrackedRace(defaultFleet, trackedRace);
|
||||
|
||||
assertSame(column.getRaceLog(aFleet), trackedRace.getRaceLog(column.getRaceLogIdentifier(aFleet)));
|
||||
assertNotSame(column.getRaceLog(bFleet), trackedRace.getRaceLog(column.getRaceIdentifier(bFleet)));
|
||||
assertSame(column.getRaceLog(defaultFleet), trackedRace.getRaceLog(column.getRaceLogIdentifier(defaultFleet)));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
package com.sap.sailing.domain.test;
|
||||
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.Timeout;
|
||||
|
||||
/**
|
||||
* Tests around the Timer class; e.g., does it kill a timer if a task throws an exception?
|
||||
*
|
||||
* @author Axel Uhl (d043530)
|
||||
*
|
||||
*/
|
||||
public class TimerTest {
|
||||
@Rule
|
||||
public Timeout AbstractTracTracLiveTestTimeout = new Timeout(10 * 1000);
|
||||
|
||||
@Test
|
||||
public void exceptionInTask() throws InterruptedException {
|
||||
Timer t = new Timer("Test");
|
||||
final TimerTask taskThrowingException = new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
throw new RuntimeException("An exception that may or may not kill the timer");
|
||||
}
|
||||
};
|
||||
t.schedule(taskThrowingException, 1l);
|
||||
try {
|
||||
for (int i = 0; i < 100; i++) {
|
||||
Thread.sleep(10);
|
||||
final boolean[] result = new boolean[1];
|
||||
t.schedule(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
synchronized (result) {
|
||||
result[0] = true;
|
||||
result.notifyAll();
|
||||
}
|
||||
}
|
||||
}, 1l);
|
||||
}
|
||||
fail("Expected IllegalStateException because task terminating abnormally cancels the timer");
|
||||
} catch (IllegalStateException e) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -4,6 +4,7 @@ import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.base.ControlPoint;
|
||||
@@ -59,7 +60,7 @@ public abstract class TrackBasedTest {
|
||||
}
|
||||
|
||||
public static CompetitorImpl createCompetitor(String competitorName) {
|
||||
return new CompetitorImpl(123, competitorName, Color.RED, new TeamImpl("STG", Collections.singleton(
|
||||
return new CompetitorImpl(UUID.randomUUID(), competitorName, Color.RED, new TeamImpl("STG", Collections.singleton(
|
||||
new PersonImpl(competitorName, new NationalityImpl("GER"),
|
||||
/* dateOfBirth */null, "This is famous " + competitorName)), new PersonImpl("Rigo van Maas",
|
||||
new NationalityImpl("NED"),
|
||||
|
||||
+9
@@ -66,6 +66,15 @@ public class MockedTrackedRaceWithFixedRank extends MockedTrackedRace {
|
||||
public Serializable getId() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Competitor getCompetitorById(Serializable competitorID) {
|
||||
if (competitorID.equals(MockedTrackedRaceWithFixedRank.this.competitor.getId())) {
|
||||
return MockedTrackedRaceWithFixedRank.this.competitor;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+10
@@ -32,6 +32,16 @@ public class MockedTrackedRaceWithFixedRankAndManyCompetitors extends MockedTrac
|
||||
public Iterable<Competitor> getCompetitors() {
|
||||
return competitors;
|
||||
}
|
||||
@Override
|
||||
public Competitor getCompetitorById(Serializable competitorID) {
|
||||
for (Competitor competitor : competitors) {
|
||||
if (competitorID.equals(competitor.getId())) {
|
||||
return competitor;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BoatClass getBoatClass() {
|
||||
return null;
|
||||
|
||||
+1
@@ -57,6 +57,7 @@ public class UpdateHandler {
|
||||
this.raceId = raceId;
|
||||
this.updateDeserializer = new UpdateResponseDeserializer();
|
||||
if (updateURI != null && !updateURI.toString().equals("")) {
|
||||
logger.info("Activating TracTrac update handler "+this+" for race with ID "+raceId);
|
||||
this.active = true;
|
||||
} else {
|
||||
this.active = false;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.sap.sailing.domain.base;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import com.sap.sailing.domain.common.WithID;
|
||||
import com.sap.sse.common.Named;
|
||||
|
||||
@@ -21,4 +23,6 @@ public interface RaceDefinition extends Named, WithID {
|
||||
Course getCourse();
|
||||
|
||||
Iterable<Competitor> getCompetitors();
|
||||
|
||||
Competitor getCompetitorById(Serializable competitorID);
|
||||
}
|
||||
+3
@@ -35,6 +35,7 @@ import com.sap.sailing.domain.leaderboard.impl.HighPointFirstGets10LastBreaksTie
|
||||
import com.sap.sailing.domain.leaderboard.impl.HighPointFirstGets1LastBreaksTie;
|
||||
import com.sap.sailing.domain.leaderboard.impl.HighPointLastBreaksTie;
|
||||
import com.sap.sailing.domain.leaderboard.impl.HighPointWinnerGetsFive;
|
||||
import com.sap.sailing.domain.leaderboard.impl.HighPointWinnerGetsFiveIgnoringRaceCount;
|
||||
import com.sap.sailing.domain.leaderboard.impl.HighPointWinnerGetsSix;
|
||||
import com.sap.sailing.domain.leaderboard.impl.LowPoint;
|
||||
import com.sap.sailing.domain.leaderboard.impl.LowPointWinnerGetsZero;
|
||||
@@ -90,6 +91,8 @@ public class DomainFactoryImpl extends SharedDomainFactoryImpl implements Domain
|
||||
return new LowPointWinnerGetsZero();
|
||||
case HIGH_POINT_WINNER_GETS_FIVE:
|
||||
return new HighPointWinnerGetsFive();
|
||||
case HIGH_POINT_WINNER_GETS_FIVE_IGNORING_RACE_COUNT:
|
||||
return new HighPointWinnerGetsFiveIgnoringRaceCount();
|
||||
case HIGH_POINT_WINNER_GETS_SIX:
|
||||
return new HighPointWinnerGetsSix();
|
||||
}
|
||||
|
||||
+13
-7
@@ -1,8 +1,7 @@
|
||||
package com.sap.sailing.domain.base.impl;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
import com.sap.sailing.domain.base.BoatClass;
|
||||
import com.sap.sailing.domain.base.Competitor;
|
||||
@@ -13,7 +12,7 @@ import com.sap.sailing.domain.common.impl.NamedImpl;
|
||||
public class RaceDefinitionImpl extends NamedImpl implements RaceDefinition {
|
||||
private static final long serialVersionUID = -1900955198751393727L;
|
||||
private final Course course;
|
||||
private final Iterable<Competitor> competitors;
|
||||
private final LinkedHashMap<Serializable, Competitor> competitorsById;
|
||||
private final BoatClass boatClass;
|
||||
private final Serializable id;
|
||||
|
||||
@@ -26,11 +25,13 @@ public class RaceDefinitionImpl extends NamedImpl implements RaceDefinition {
|
||||
assert name != null;
|
||||
|
||||
this.course = course;
|
||||
Set<Competitor> competitorsAsLinkedHashSet = new LinkedHashSet<Competitor>();
|
||||
this.competitorsById = new LinkedHashMap<>();
|
||||
for (Competitor competitor : competitors) {
|
||||
competitorsAsLinkedHashSet.add(competitor);
|
||||
Competitor competitorWithEqualID = competitorsById.put(competitor.getId(), competitor);
|
||||
if (competitorWithEqualID != null && competitorWithEqualID != competitor) {
|
||||
throw new IllegalArgumentException("Two distinct competitors with equal ID "+competitor.getId()+" are not allowed within the single race "+name);
|
||||
}
|
||||
}
|
||||
this.competitors = competitorsAsLinkedHashSet;
|
||||
this.boatClass = boatClass;
|
||||
this.id = id;
|
||||
}
|
||||
@@ -47,7 +48,12 @@ public class RaceDefinitionImpl extends NamedImpl implements RaceDefinition {
|
||||
|
||||
@Override
|
||||
public Iterable<Competitor> getCompetitors() {
|
||||
return competitors;
|
||||
return competitorsById.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Competitor getCompetitorById(Serializable competitorID) {
|
||||
return competitorsById.get(competitorID);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+9
-13
@@ -35,36 +35,32 @@ public interface FlexibleLeaderboard extends Leaderboard, Renamable {
|
||||
void moveRaceColumnDown(String name);
|
||||
|
||||
/**
|
||||
* Adds a new {@link RaceColumn} that has no {@link TrackedRace} associated yet to this leaderboard.
|
||||
* Adds a new {@link RaceColumn} that has no {@link TrackedRace} associated yet to this leaderboard. The default
|
||||
* fleet will be used.
|
||||
*
|
||||
* @param name
|
||||
* the name for the new race column such that none of the columns in {@link #getRaceColumns()}
|
||||
* has that name yet; otherwise, an message will be logged, no race column will be added and
|
||||
* the existing column will be returned for robustness reasons
|
||||
* the name for the new race column such that none of the columns in {@link #getRaceColumns()} has that
|
||||
* name yet; otherwise, an message will be logged, no race column will be added and the existing column
|
||||
* will be returned for robustness reasons
|
||||
* @param medalRace
|
||||
* tells if the column to add represents a medal race which has double score and cannot be discarded
|
||||
* @param fleets
|
||||
* the fleets to add to the {@link RaceColumn} created. If no fleets are specified, a single default
|
||||
* fleet will be assigned to the race column created.
|
||||
* @return the race column in the leaderboard used to represent the tracked <code>race</code>
|
||||
*/
|
||||
RaceColumn addRaceColumn(String name, boolean medalRace, Fleet... fleets);
|
||||
RaceColumn addRaceColumn(String name, boolean medalRace);
|
||||
|
||||
/**
|
||||
* Adds a tracked race to this leaderboard. If a {@link RaceColumn} with name <code>columnName</code> already exists
|
||||
* in this leaderboard, <code>race</code> is {@link RaceColumn#setTrackedRace(Fleet, TrackedRace) set as its tracked
|
||||
* race} and <code>medalRace</code> is ignored. Otherwise, a new {@link RaceColumn} column, with <code>race</code>
|
||||
* as its tracked race, is created and added to this leaderboard.
|
||||
* as its tracked race, is created and added to this leaderboard. The default fleet will be used.
|
||||
*
|
||||
* @param medalRace
|
||||
* tells if the column to add represents a medal race which has double score and cannot be discarded;
|
||||
* ignored if the column named <code>columnName</code> already exists
|
||||
* @param fleets
|
||||
* the fleets to add to the {@link RaceColumn} created. If no fleets are specified, a single default
|
||||
* fleet will be assigned to the race column created.
|
||||
*
|
||||
* @return the race column in the leaderboard used to represent the tracked <code>race</code>
|
||||
*/
|
||||
RaceColumn addRace(TrackedRace race, String columnName, boolean medalRace, Fleet fleet);
|
||||
RaceColumn addRace(TrackedRace race, String columnName, boolean medalRace);
|
||||
|
||||
void removeRaceColumn(String columnName);
|
||||
|
||||
|
||||
+1
-1
@@ -70,7 +70,7 @@ public class LeaderboardCacheManager {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void markPositionChanged(GPSFix fix, Mark mark) {
|
||||
public void markPositionChanged(GPSFix fix, Mark mark, boolean firstInTrack) {
|
||||
removeFromCache(leaderboard);
|
||||
}
|
||||
|
||||
|
||||
+5
-1
@@ -128,7 +128,6 @@ public abstract class AbstractScoringSchemeImpl implements ScoringScheme {
|
||||
*/
|
||||
@Override
|
||||
public int compareByBetterScore(List<com.sap.sse.common.Util.Pair<RaceColumn, Double>> o1Scores, List<com.sap.sse.common.Util.Pair<RaceColumn, Double>> o2Scores, boolean nullScoresAreBetter) {
|
||||
assert o1Scores.size() == o2Scores.size();
|
||||
final Comparator<Double> pureScoreComparator = getScoreComparator(nullScoresAreBetter);
|
||||
// needs to compare net points; therefore, divide the total points by the column factor for comparison:
|
||||
List<Double> o1NetScores = new ArrayList<>();
|
||||
@@ -148,6 +147,11 @@ public abstract class AbstractScoringSchemeImpl implements ScoringScheme {
|
||||
while (result == 0 && o1Iter.hasNext() && o2Iter.hasNext()) {
|
||||
result = pureScoreComparator.compare(o1Iter.next(), o2Iter.next());
|
||||
}
|
||||
if (o1Iter.hasNext() != o2Iter.hasNext()) {
|
||||
// if, as may be allowed by some scoring scheme variants, competitors with different numbers of scored races are compared
|
||||
// and are equal for all races of the competitor who scored fewer races, the competitor who scored more races is preferred
|
||||
result = o1Iter.hasNext() ? -1 : 1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -312,7 +312,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
|
||||
}
|
||||
|
||||
@Override
|
||||
public void markPositionChanged(GPSFix fix, Mark mark) {
|
||||
public void markPositionChanged(GPSFix fix, Mark mark, boolean firstInTrack) {
|
||||
invalidateCacheAndRemoveThisListenerFromTrackedRace();
|
||||
}
|
||||
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package com.sap.sailing.domain.leaderboard.impl;
|
||||
|
||||
import com.sap.sailing.domain.base.IsManagedBySharedDomainFactory;
|
||||
import com.sap.sailing.domain.base.SharedDomainFactory;
|
||||
import com.sap.sailing.domain.base.impl.FleetImpl;
|
||||
import com.sap.sailing.domain.common.LeaderboardNameConstants;
|
||||
|
||||
public class DefaultFleetImpl extends FleetImpl implements IsManagedBySharedDomainFactory {
|
||||
|
||||
private static final long serialVersionUID = 6233489616955255401L;
|
||||
|
||||
public DefaultFleetImpl() {
|
||||
super(LeaderboardNameConstants.DEFAULT_FLEET_NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IsManagedBySharedDomainFactory resolve(SharedDomainFactory domainFactory) {
|
||||
return FlexibleLeaderboardImpl.defaultFleet;
|
||||
}
|
||||
|
||||
}
|
||||
+13
-16
@@ -13,8 +13,6 @@ import com.sap.sailing.domain.base.CourseArea;
|
||||
import com.sap.sailing.domain.base.Fleet;
|
||||
import com.sap.sailing.domain.base.RaceColumn;
|
||||
import com.sap.sailing.domain.base.RaceColumnListener;
|
||||
import com.sap.sailing.domain.base.impl.FleetImpl;
|
||||
import com.sap.sailing.domain.common.LeaderboardNameConstants;
|
||||
import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard;
|
||||
import com.sap.sailing.domain.leaderboard.FlexibleRaceColumn;
|
||||
import com.sap.sailing.domain.leaderboard.ScoringScheme;
|
||||
@@ -39,7 +37,7 @@ import com.sap.sailing.domain.tracking.TrackedRace;
|
||||
public class FlexibleLeaderboardImpl extends AbstractLeaderboardImpl implements FlexibleLeaderboard {
|
||||
private static Logger logger = Logger.getLogger(FlexibleLeaderboardImpl.class.getName());
|
||||
|
||||
protected static final Fleet defaultFleet = new FleetImpl(LeaderboardNameConstants.DEFAULT_FLEET_NAME);
|
||||
protected static final DefaultFleetImpl defaultFleet = new DefaultFleetImpl();
|
||||
private static final long serialVersionUID = -5708971849158747846L;
|
||||
private final List<FlexibleRaceColumn> races;
|
||||
private final ScoringScheme scoringScheme;
|
||||
@@ -96,24 +94,25 @@ public class FlexibleLeaderboardImpl extends AbstractLeaderboardImpl implements
|
||||
}
|
||||
|
||||
@Override
|
||||
public RaceColumn addRace(TrackedRace race, String columnName, boolean medalRace, Fleet fleet) {
|
||||
FlexibleRaceColumn column = addRaceColumn(columnName, medalRace, /* logAlreadyExistingColumn */ false, fleet);
|
||||
column.setTrackedRace(fleet, race); // triggers listeners because this object was registered above as race column listener on the column
|
||||
public RaceColumn addRace(TrackedRace race, String columnName, boolean medalRace) {
|
||||
FlexibleRaceColumn column = addRaceColumn(columnName, medalRace, /* logAlreadyExistingColumn */false);
|
||||
column.setTrackedRace(defaultFleet, race); // triggers listeners because this object was registered above as
|
||||
// race column listener on the column
|
||||
return column;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FlexibleRaceColumn addRaceColumn(String name, boolean medalRace, Fleet... fleets) {
|
||||
return addRaceColumn(name, medalRace, /* logAlreadyExistingColumn */ true, fleets);
|
||||
public FlexibleRaceColumn addRaceColumn(String name, boolean medalRace) {
|
||||
return addRaceColumn(name, medalRace, /* logAlreadyExistingColumn */true);
|
||||
}
|
||||
|
||||
private FlexibleRaceColumn addRaceColumn(String name, boolean medalRace, boolean logAlreadyExistingColumn, Fleet... fleets) {
|
||||
private FlexibleRaceColumn addRaceColumn(String name, boolean medalRace, boolean logAlreadyExistingColumn) {
|
||||
FlexibleRaceColumn column = getRaceColumnByName(name);
|
||||
if (column != null) {
|
||||
final String msg = "Trying to create race column with duplicate name "+name+" in leaderboard +"+getName();
|
||||
logger.severe(msg);
|
||||
} else {
|
||||
column = createRaceColumn(name, medalRace, fleets);
|
||||
column = createRaceColumn(name, medalRace);
|
||||
column.addRaceColumnListener(this);
|
||||
races.add(column);
|
||||
column.setRaceLogInformation(
|
||||
@@ -159,17 +158,15 @@ public class FlexibleLeaderboardImpl extends AbstractLeaderboardImpl implements
|
||||
return Collections.unmodifiableCollection(new ArrayList<RaceColumn>(races));
|
||||
}
|
||||
|
||||
protected RaceColumnImpl createRaceColumn(String column, boolean medalRace, Fleet... fleets) {
|
||||
return new RaceColumnImpl(
|
||||
column,
|
||||
medalRace,
|
||||
turnNullOrEmptyFleetsIntoDefaultFleet(fleets));
|
||||
protected RaceColumnImpl createRaceColumn(String column, boolean medalRace) {
|
||||
return new RaceColumnImpl(column, medalRace);
|
||||
}
|
||||
|
||||
protected Iterable<Fleet> turnNullOrEmptyFleetsIntoDefaultFleet(Fleet... fleets) {
|
||||
Iterable<Fleet> theFleets;
|
||||
if (fleets == null || fleets.length == 0) {
|
||||
theFleets = Collections.singleton(defaultFleet);
|
||||
Fleet defaultfleetCasted = defaultFleet;
|
||||
theFleets = Collections.singleton(defaultfleetCasted);
|
||||
} else {
|
||||
theFleets = Arrays.asList(fleets);
|
||||
}
|
||||
|
||||
+6
@@ -1,5 +1,7 @@
|
||||
package com.sap.sailing.domain.leaderboard.impl;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
@@ -32,6 +34,10 @@ public class LeaderboardGroupImpl extends LeaderboardGroupBaseImpl implements Le
|
||||
public LeaderboardGroupImpl(String name, String description, boolean displayGroupsInReverseOrder, List<? extends Leaderboard> leaderboards) {
|
||||
this(UUID.randomUUID(), name, description, displayGroupsInReverseOrder, leaderboards);
|
||||
}
|
||||
|
||||
private synchronized void writeObject(ObjectOutputStream oos) throws IOException {
|
||||
oos.defaultWriteObject();
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this constructor when loading or deserializing a leaderboard group and the ID is known and is provided to the constructor.
|
||||
|
||||
+3
-16
@@ -1,8 +1,6 @@
|
||||
package com.sap.sailing.domain.leaderboard.impl;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import com.sap.sailing.domain.base.Fleet;
|
||||
import com.sap.sailing.domain.base.impl.AbstractRaceColumn;
|
||||
@@ -12,23 +10,12 @@ public class RaceColumnImpl extends AbstractRaceColumn implements FlexibleRaceCo
|
||||
private static final long serialVersionUID = -7801617988982540470L;
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* All fleets for which this column can contain a race. This is the maximum set of keys possible for
|
||||
* {@link #trackedRaces} and {@link #raceIdentifiers}.
|
||||
*/
|
||||
private final Iterable<Fleet> fleets;
|
||||
private boolean medalRace;
|
||||
|
||||
public RaceColumnImpl(String name, boolean medalRace, Iterable<Fleet> fleets) {
|
||||
public RaceColumnImpl(String name, boolean medalRace) {
|
||||
super();
|
||||
this.name = name;
|
||||
this.medalRace = medalRace;
|
||||
List<Fleet> myFleets = new ArrayList<Fleet>();
|
||||
for (Fleet fleet : fleets) {
|
||||
myFleets.add(fleet);
|
||||
}
|
||||
Collections.sort(myFleets);
|
||||
this.fleets = myFleets;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -53,8 +40,8 @@ public class RaceColumnImpl extends AbstractRaceColumn implements FlexibleRaceCo
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<Fleet> getFleets() {
|
||||
return fleets;
|
||||
public Iterable<? extends Fleet> getFleets() {
|
||||
return Collections.singleton(FlexibleLeaderboardImpl.defaultFleet);
|
||||
}
|
||||
|
||||
}
|
||||
+1
-1
@@ -40,7 +40,7 @@ public class MarkPassingUpdateListener extends AbstractRaceChangeListener {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void markPositionChanged(GPSFix fix, Mark mark) {
|
||||
public void markPositionChanged(GPSFix fix, Mark mark, boolean firstInTrack) {
|
||||
queue.add(new Util.Pair<Object, GPSFix>(mark, fix));
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ package com.sap.sailing.domain.tracking;
|
||||
import java.io.Serializable;
|
||||
|
||||
public interface GPSTrackListener<ItemType, FixType extends GPSFix> extends Serializable {
|
||||
void gpsFixReceived(FixType fix, ItemType item);
|
||||
void gpsFixReceived(FixType fix, ItemType item, boolean firstFixInTrack);
|
||||
|
||||
void speedAveragingChanged(long oldMillisecondsOverWhichToAverage, long newMillisecondsOverWhichToAverage);
|
||||
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ import com.sap.sailing.domain.common.WindSource;
|
||||
public interface RaceChangeListener extends CourseListener {
|
||||
void competitorPositionChanged(GPSFixMoving fix, Competitor competitor);
|
||||
|
||||
void markPositionChanged(GPSFix fix, Mark mark);
|
||||
void markPositionChanged(GPSFix fix, Mark mark, boolean firstInTrack);
|
||||
|
||||
/**
|
||||
* Invoked after the mark passings have been updated in the {@link TrackedRace}.
|
||||
|
||||
@@ -102,4 +102,10 @@ public interface TrackedLeg extends Serializable {
|
||||
*/
|
||||
Distance getWindwardDistance(Position pos1, Position pos2, TimePoint at, WindPositionMode windPositionMode) throws NoWindException;
|
||||
|
||||
/**
|
||||
* The middle (traveling half the length) of the course middle line, connecting the center of gravity of the leg's start
|
||||
* waypoint's position at time point <code>at</code> and the position of the leg's end waypoint at time point <code>at</code>.
|
||||
*/
|
||||
Position getMiddleOfLeg(TimePoint at);
|
||||
|
||||
}
|
||||
+1
-1
@@ -33,7 +33,7 @@ public abstract class AbstractRaceChangeListener implements RaceChangeListener {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void markPositionChanged(GPSFix fix, Mark mark) {
|
||||
public void markPositionChanged(GPSFix fix, Mark mark, boolean firstInTrack) {
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+1
-1
@@ -397,7 +397,7 @@ public class CrossTrackErrorCache extends AbstractRaceChangeListener {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void markPositionChanged(GPSFix fix, Mark mark) {
|
||||
public void markPositionChanged(GPSFix fix, Mark mark, boolean firstInTrack) {
|
||||
TimePoint from = owner.getOrCreateTrack(mark).getEstimatedPositionTimePeriodAffectedBy(fix).getA();
|
||||
final List<Competitor> shuffledCompetitors = new ArrayList<>(cachePerCompetitor.keySet());
|
||||
Collections.shuffle(shuffledCompetitors);
|
||||
|
||||
+100
-88
@@ -212,20 +212,20 @@ DynamicTrackedRace, GPSTrackListener<Competitor, GPSFixMoving> {
|
||||
|
||||
/**
|
||||
* In addition to creating the track which is performed by the superclass implementation, this implementation registers
|
||||
* a {@link GPSTrackListener} with the mark's track and {@link #notifyListeners(GPSFix, Mark) notifies the listeners}
|
||||
* a {@link GPSTrackListener} with the mark's track and {@link #notifyListeners(GPSFix, Mark, boolean) notifies the listeners}
|
||||
* about updates. The {@link #updated(TimePoint)} method is <em>not</em> called with the mark fix's time point because
|
||||
* mark fixes may be received also from marks that don't belong to this race.
|
||||
*/
|
||||
@Override
|
||||
protected DynamicGPSFixTrackImpl<Mark> createMarkTrack(Mark mark) {
|
||||
DynamicGPSFixTrackImpl<Mark> result = super.createMarkTrack(mark);
|
||||
final DynamicGPSFixTrackImpl<Mark> result = super.createMarkTrack(mark);
|
||||
result.addListener(new GPSTrackListener<Mark, GPSFix>() {
|
||||
private static final long serialVersionUID = -2855787105725103732L;
|
||||
|
||||
@Override
|
||||
public void gpsFixReceived(GPSFix fix, Mark mark) {
|
||||
public void gpsFixReceived(GPSFix fix, Mark mark, boolean firstFixInTrack) {
|
||||
triggerManeuverCacheRecalculationForAllCompetitors();
|
||||
notifyListeners(fix, mark);
|
||||
notifyListeners(fix, mark, firstFixInTrack);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -306,8 +306,10 @@ DynamicTrackedRace, GPSTrackListener<Competitor, GPSFixMoving> {
|
||||
GPSFixTrack<Mark, GPSFix> markTrack = getOrCreateTrack(mark);
|
||||
markTrack.lockForRead();
|
||||
try {
|
||||
boolean firstInTrack = true;
|
||||
for (GPSFix fix : markTrack.getRawFixes()) {
|
||||
listener.markPositionChanged(fix, mark);
|
||||
listener.markPositionChanged(fix, mark, firstInTrack);
|
||||
firstInTrack = false;
|
||||
}
|
||||
} finally {
|
||||
markTrack.unlockAfterRead();
|
||||
@@ -437,14 +439,14 @@ DynamicTrackedRace, GPSTrackListener<Competitor, GPSFixMoving> {
|
||||
notifyListenersWaypointRemoved(zeroBasedIndex, waypointThatGotRemoved);
|
||||
}
|
||||
|
||||
private void notifyListeners(GPSFix fix, Mark mark) {
|
||||
private void notifyListeners(GPSFix fix, Mark mark, boolean firstInTrack) {
|
||||
RaceChangeListener[] listeners;
|
||||
synchronized (getListeners()) {
|
||||
listeners = getListeners().toArray(new RaceChangeListener[getListeners().size()]);
|
||||
}
|
||||
for (RaceChangeListener listener : listeners) {
|
||||
try {
|
||||
listener.markPositionChanged(fix, mark);
|
||||
listener.markPositionChanged(fix, mark, firstInTrack);
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.SEVERE, "RaceChangeListener " + listener + " threw exception " + e.getMessage());
|
||||
logger.log(Level.SEVERE, "notifyListeners(GPSFix, Competitor)", e);
|
||||
@@ -579,96 +581,106 @@ DynamicTrackedRace, GPSTrackListener<Competitor, GPSFixMoving> {
|
||||
|
||||
@Override
|
||||
public void updateMarkPassings(Competitor competitor, Iterable<MarkPassing> markPassings) {
|
||||
Map<Waypoint, MarkPassing> oldMarkPassings = new HashMap<Waypoint, MarkPassing>();
|
||||
MarkPassing oldStartMarkPassing = null;
|
||||
TimePoint oldStartOfRace = getStartOfRace(); // getStartOfRace() may respond with a new result already after updating the mark passings
|
||||
boolean requiresStartTimeUpdate = true;
|
||||
final NavigableSet<MarkPassing> markPassingsForCompetitor = getMarkPassings(competitor);
|
||||
lockForRead(markPassingsForCompetitor);
|
||||
LockUtil.lockForRead(getSerializationLock()); // keep serializer from reading the mark passings collections
|
||||
try {
|
||||
for (MarkPassing oldMarkPassing : markPassingsForCompetitor) {
|
||||
if (oldStartMarkPassing == null) {
|
||||
oldStartMarkPassing = oldMarkPassing;
|
||||
}
|
||||
oldMarkPassings.put(oldMarkPassing.getWaypoint(), oldMarkPassing);
|
||||
}
|
||||
} finally {
|
||||
unlockAfterRead(markPassingsForCompetitor);
|
||||
}
|
||||
final NamedReentrantReadWriteLock markPassingsLock = getMarkPassingsLock(markPassingsForCompetitor);
|
||||
TimePoint timePointOfLatestEvent = new MillisecondsTimePoint(0);
|
||||
// Make sure that clearMarkPassings and the re-adding of the mark passings are non-interruptible by readers.
|
||||
// Note that the write lock for the mark passings in order per waypoint is obtained inside clearMarkPassings(...)
|
||||
// as well as inside the subsequent for-loop. It is important to always first obtain the mark passings lock for the competitor
|
||||
// mark passings before obtaining the lock for the mark passings in order for the waypoint to avoid deadlocks.
|
||||
LockUtil.lockForWrite(markPassingsLock);
|
||||
try {
|
||||
clearMarkPassings(competitor);
|
||||
for (MarkPassing markPassing : markPassings) {
|
||||
// try to find corresponding old start mark passing
|
||||
if (oldStartMarkPassing != null
|
||||
&& markPassing.getWaypoint().equals(oldStartMarkPassing.getWaypoint())) {
|
||||
if (markPassing.getTimePoint() != null && oldStartMarkPassing.getTimePoint() != null
|
||||
&& markPassing.getTimePoint().equals(oldStartMarkPassing.getTimePoint())) {
|
||||
requiresStartTimeUpdate = false;
|
||||
Map<Waypoint, MarkPassing> oldMarkPassings = new HashMap<Waypoint, MarkPassing>();
|
||||
MarkPassing oldStartMarkPassing = null;
|
||||
TimePoint oldStartOfRace = getStartOfRace(); // getStartOfRace() may respond with a new result already after
|
||||
// updating the mark passings
|
||||
boolean requiresStartTimeUpdate = true;
|
||||
final NavigableSet<MarkPassing> markPassingsForCompetitor = getMarkPassings(competitor);
|
||||
lockForRead(markPassingsForCompetitor);
|
||||
try {
|
||||
for (MarkPassing oldMarkPassing : markPassingsForCompetitor) {
|
||||
if (oldStartMarkPassing == null) {
|
||||
oldStartMarkPassing = oldMarkPassing;
|
||||
}
|
||||
oldMarkPassings.put(oldMarkPassing.getWaypoint(), oldMarkPassing);
|
||||
}
|
||||
if (!Util.contains(getRace().getCourse().getWaypoints(), markPassing.getWaypoint())) {
|
||||
StringBuilder courseWaypointsWithID = new StringBuilder();
|
||||
boolean first = true;
|
||||
for (Waypoint courseWaypoint : getRace().getCourse().getWaypoints()) {
|
||||
if (first) {
|
||||
first = false;
|
||||
} else {
|
||||
courseWaypointsWithID.append(" -> ");
|
||||
} finally {
|
||||
unlockAfterRead(markPassingsForCompetitor);
|
||||
}
|
||||
final NamedReentrantReadWriteLock markPassingsLock = getMarkPassingsLock(markPassingsForCompetitor);
|
||||
TimePoint timePointOfLatestEvent = new MillisecondsTimePoint(0);
|
||||
// Make sure that clearMarkPassings and the re-adding of the mark passings are non-interruptible by readers.
|
||||
// Note that the write lock for the mark passings in order per waypoint is obtained inside
|
||||
// clearMarkPassings(...) as well as inside the subsequent for-loop. It is important to always first obtain the mark passings lock
|
||||
// for the competitor mark passings before obtaining the lock for the mark passings in order for the waypoint to avoid
|
||||
// deadlocks.
|
||||
LockUtil.lockForWrite(markPassingsLock);
|
||||
try {
|
||||
clearMarkPassings(competitor);
|
||||
for (MarkPassing markPassing : markPassings) {
|
||||
// try to find corresponding old start mark passing
|
||||
if (oldStartMarkPassing != null
|
||||
&& markPassing.getWaypoint().equals(oldStartMarkPassing.getWaypoint())) {
|
||||
if (markPassing.getTimePoint() != null && oldStartMarkPassing.getTimePoint() != null
|
||||
&& markPassing.getTimePoint().equals(oldStartMarkPassing.getTimePoint())) {
|
||||
requiresStartTimeUpdate = false;
|
||||
}
|
||||
courseWaypointsWithID.append(courseWaypoint.toString());
|
||||
courseWaypointsWithID.append(" (ID=");
|
||||
courseWaypointsWithID.append(courseWaypoint.getId());
|
||||
courseWaypointsWithID.append(")");
|
||||
}
|
||||
logger.severe("Received mark passing " + markPassing + " for race " + getRace()
|
||||
+ " for waypoint ID" + markPassing.getWaypoint().getId()
|
||||
+ " but the waypoint does not exist in course " + courseWaypointsWithID);
|
||||
} else {
|
||||
markPassingsForCompetitor.add(markPassing);
|
||||
if (!Util.contains(getRace().getCourse().getWaypoints(), markPassing.getWaypoint())) {
|
||||
StringBuilder courseWaypointsWithID = new StringBuilder();
|
||||
boolean first = true;
|
||||
for (Waypoint courseWaypoint : getRace().getCourse().getWaypoints()) {
|
||||
if (first) {
|
||||
first = false;
|
||||
} else {
|
||||
courseWaypointsWithID.append(" -> ");
|
||||
}
|
||||
courseWaypointsWithID.append(courseWaypoint.toString());
|
||||
courseWaypointsWithID.append(" (ID=");
|
||||
courseWaypointsWithID.append(courseWaypoint.getId());
|
||||
courseWaypointsWithID.append(")");
|
||||
}
|
||||
logger.severe("Received mark passing " + markPassing + " for race " + getRace()
|
||||
+ " for waypoint ID" + markPassing.getWaypoint().getId()
|
||||
+ " but the waypoint does not exist in course " + courseWaypointsWithID);
|
||||
} else {
|
||||
markPassingsForCompetitor.add(markPassing);
|
||||
}
|
||||
Collection<MarkPassing> markPassingsInOrderForWaypoint = getOrCreateMarkPassingsInOrderAsNavigableSet(markPassing
|
||||
.getWaypoint());
|
||||
final NamedReentrantReadWriteLock markPassingsLock2 = getMarkPassingsLock(markPassingsInOrderForWaypoint);
|
||||
LockUtil.lockForWrite(markPassingsLock2);
|
||||
try {
|
||||
// The mark passings of competitor have been removed by the call to
|
||||
// clearMarkPassings(competitor) above
|
||||
// from both, the collection that holds the mark passings by waypoint and the one that holds the
|
||||
// mark passings per competitor; so we can simply add here:
|
||||
markPassingsInOrderForWaypoint.add(markPassing);
|
||||
} finally {
|
||||
LockUtil.unlockAfterWrite(markPassingsLock2);
|
||||
}
|
||||
if (markPassing.getTimePoint().compareTo(timePointOfLatestEvent) > 0) {
|
||||
timePointOfLatestEvent = markPassing.getTimePoint();
|
||||
}
|
||||
}
|
||||
Collection<MarkPassing> markPassingsInOrderForWaypoint = getOrCreateMarkPassingsInOrderAsNavigableSet(markPassing
|
||||
.getWaypoint());
|
||||
final NamedReentrantReadWriteLock markPassingsLock2 = getMarkPassingsLock(markPassingsInOrderForWaypoint);
|
||||
LockUtil.lockForWrite(markPassingsLock2);
|
||||
try {
|
||||
// The mark passings of competitor have been removed by the call to clearMarkPassings(competitor) above
|
||||
// from both, the collection that holds the mark passings by waypoint and the one that holds the
|
||||
// mark passings per competitor; so we can simply add here:
|
||||
markPassingsInOrderForWaypoint.add(markPassing);
|
||||
} finally {
|
||||
LockUtil.unlockAfterWrite(markPassingsLock2);
|
||||
}
|
||||
if (markPassing.getTimePoint().compareTo(timePointOfLatestEvent) > 0) {
|
||||
timePointOfLatestEvent = markPassing.getTimePoint();
|
||||
} finally {
|
||||
LockUtil.unlockAfterWrite(markPassingsLock);
|
||||
}
|
||||
updated(timePointOfLatestEvent);
|
||||
triggerManeuverCacheRecalculation(competitor);
|
||||
// update the race times like start, end and the leg times
|
||||
if (requiresStartTimeUpdate) {
|
||||
TimePoint interimsStartOfRace = getStartOfRace();
|
||||
invalidateStartTime();
|
||||
TimePoint newStartOfRace = getStartOfRace();
|
||||
if (Util.equalsWithNull(interimsStartOfRace, newStartOfRace)
|
||||
&& !Util.equalsWithNull(oldStartOfRace, newStartOfRace)) {
|
||||
// invalidateStartTime() will not have thrown a startOfRaceChanged event notification because it
|
||||
// already saw the new
|
||||
// start of race time; we have to throw the notification here:
|
||||
notifyListenersStartOfRaceChanged(oldStartOfRace, newStartOfRace);
|
||||
}
|
||||
}
|
||||
invalidateMarkPassingTimes();
|
||||
invalidateEndTime();
|
||||
// notify *after* all mark passings have been re-established; should avoid flicker
|
||||
notifyListeners(competitor, oldMarkPassings, markPassings);
|
||||
} finally {
|
||||
LockUtil.unlockAfterWrite(markPassingsLock);
|
||||
LockUtil.unlockAfterRead(getSerializationLock());
|
||||
}
|
||||
updated(timePointOfLatestEvent);
|
||||
triggerManeuverCacheRecalculation(competitor);
|
||||
// update the race times like start, end and the leg times
|
||||
if (requiresStartTimeUpdate) {
|
||||
TimePoint interimsStartOfRace = getStartOfRace();
|
||||
invalidateStartTime();
|
||||
TimePoint newStartOfRace = getStartOfRace();
|
||||
if (Util.equalsWithNull(interimsStartOfRace, newStartOfRace) && !Util.equalsWithNull(oldStartOfRace, newStartOfRace)) {
|
||||
// invalidateStartTime() will not have thrown a startOfRaceChanged event notification because it already saw the new
|
||||
// start of race time; we have to throw the notification here:
|
||||
notifyListenersStartOfRaceChanged(oldStartOfRace, newStartOfRace);
|
||||
}
|
||||
}
|
||||
invalidateMarkPassingTimes();
|
||||
invalidateEndTime();
|
||||
// notify *after* all mark passings have been re-established; should avoid flicker
|
||||
notifyListeners(competitor, oldMarkPassings, markPassings);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -800,7 +812,7 @@ DynamicTrackedRace, GPSTrackListener<Competitor, GPSFixMoving> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void gpsFixReceived(GPSFixMoving fix, Competitor competitor) {
|
||||
public void gpsFixReceived(GPSFixMoving fix, Competitor competitor, boolean firstFixInTrack) {
|
||||
updated(fix.getTimePoint());
|
||||
triggerManeuverCacheRecalculation(competitor);
|
||||
notifyListeners(fix, competitor);
|
||||
|
||||
+3
-1
@@ -1009,8 +1009,10 @@ public class GPSFixTrackImpl<ItemType, FixType extends GPSFix> extends TrackImpl
|
||||
@Override
|
||||
protected boolean add(FixType fix) {
|
||||
final boolean result;
|
||||
final boolean firstFixInTrack;
|
||||
lockForWrite();
|
||||
try {
|
||||
firstFixInTrack = getRawFixes().isEmpty();
|
||||
result = addWithoutLocking(fix);
|
||||
invalidateValidityAndDistanceCaches(fix);
|
||||
} finally {
|
||||
@@ -1034,7 +1036,7 @@ public class GPSFixTrackImpl<ItemType, FixType extends GPSFix> extends TrackImpl
|
||||
}
|
||||
}
|
||||
for (GPSTrackListener<ItemType, FixType> listener : getListeners()) {
|
||||
listener.gpsFixReceived(fix, getTrackedItem());
|
||||
listener.gpsFixReceived(fix, getTrackedItem(), firstFixInTrack);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
+1
-1
@@ -79,7 +79,7 @@ public class MaxSpeedCache<ItemType, FixType extends GPSFix> implements GPSTrack
|
||||
* outside of the invalidation interval, crop cache entry's interval such that it's overlap-free, otherwise remove.
|
||||
*/
|
||||
@Override
|
||||
public void gpsFixReceived(FixType fix, ItemType item) {
|
||||
public void gpsFixReceived(FixType fix, ItemType item, boolean firstFixInTrack) {
|
||||
// find the invalidation interval such that getFixesRelevantForSpeedEstimation, when passed any time point from that interval, produces "fix"
|
||||
Util.Pair<TimePoint, TimePoint> invalidationInterval = track.getTimeIntervalWhoseEstimatedSpeedMayHaveChangedAfterAddingFix(fix);
|
||||
LockUtil.lockForWrite(lock);
|
||||
|
||||
+1
-1
@@ -693,7 +693,7 @@ public class TrackBasedEstimationWindTrackImpl extends VirtualWindTrackImpl impl
|
||||
}
|
||||
|
||||
@Override
|
||||
public void markPositionChanged(GPSFix fix, Mark mark) {
|
||||
public void markPositionChanged(GPSFix fix, Mark mark, boolean firstInTrack) {
|
||||
assert fix != null && fix.getTimePoint() != null;
|
||||
// A mark position change can mean a leg type change. The interval over which the wind estimation is affected
|
||||
// depends on how the GPS track computes the estimated mark position. Ask it:
|
||||
|
||||
+16
-11
@@ -1,5 +1,7 @@
|
||||
package com.sap.sailing.domain.tracking.impl;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
@@ -31,8 +33,8 @@ import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor;
|
||||
import com.sap.sailing.domain.tracking.TrackedRace;
|
||||
import com.sap.sailing.domain.tracking.TrackedRaceStatus;
|
||||
import com.sap.sailing.domain.tracking.Wind;
|
||||
import com.sap.sailing.domain.tracking.WindPositionMode;
|
||||
import com.sap.sailing.domain.tracking.WindLegTypeAndLegBearingCache;
|
||||
import com.sap.sailing.domain.tracking.WindPositionMode;
|
||||
import com.sap.sse.common.Util;
|
||||
|
||||
public class TrackedLegImpl implements TrackedLeg, RaceChangeListener {
|
||||
@@ -45,7 +47,7 @@ public class TrackedLegImpl implements TrackedLeg, RaceChangeListener {
|
||||
private final Leg leg;
|
||||
private final Map<Competitor, TrackedLegOfCompetitor> trackedLegsOfCompetitors;
|
||||
private TrackedRaceImpl trackedRace;
|
||||
private final Map<TimePoint, List<TrackedLegOfCompetitor>> competitorTracksOrderedByRank;
|
||||
private transient Map<TimePoint, List<TrackedLegOfCompetitor>> competitorTracksOrderedByRank;
|
||||
|
||||
public TrackedLegImpl(DynamicTrackedRaceImpl trackedRace, Leg leg, Iterable<Competitor> competitors) {
|
||||
super();
|
||||
@@ -56,7 +58,12 @@ public class TrackedLegImpl implements TrackedLeg, RaceChangeListener {
|
||||
trackedLegsOfCompetitors.put(competitor, new TrackedLegOfCompetitorImpl(this, competitor));
|
||||
}
|
||||
trackedRace.addListener(this);
|
||||
competitorTracksOrderedByRank = new HashMap<TimePoint, List<TrackedLegOfCompetitor>>();
|
||||
competitorTracksOrderedByRank = new HashMap<>();
|
||||
}
|
||||
|
||||
private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException {
|
||||
ois.defaultReadObject();
|
||||
competitorTracksOrderedByRank = new HashMap<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -114,7 +121,6 @@ public class TrackedLegImpl implements TrackedLeg, RaceChangeListener {
|
||||
// race may be updated while calculation is going on, but each individual calculation is properly
|
||||
// synchronized, usually by read-write locks, so there is no major difference in synchronization issues
|
||||
// an the asynchronous nature of how the data is being received
|
||||
// TODO See bug 469; competitors already disqualified may need to be ranked worst
|
||||
Collections.sort(rankedCompetitorList, new WindwardToGoComparator(this, timePoint, cache));
|
||||
rankedCompetitorList = Collections.unmodifiableList(rankedCompetitorList);
|
||||
synchronized (competitorTracksOrderedByRank) {
|
||||
@@ -198,11 +204,10 @@ public class TrackedLegImpl implements TrackedLeg, RaceChangeListener {
|
||||
* of the first mark at the beginning of the leg and moving half way to the first mark of leg's end. If either of
|
||||
* the mark positions cannot be determined, <code>null</code> is returned.
|
||||
*/
|
||||
@Override
|
||||
public Position getMiddleOfLeg(TimePoint at) {
|
||||
Position approximateLegStartPosition = getTrackedRace().getOrCreateTrack(
|
||||
getLeg().getFrom().getMarks().iterator().next()).getEstimatedPosition(at, false);
|
||||
Position approximateLegEndPosition = getTrackedRace().getOrCreateTrack(
|
||||
getLeg().getTo().getMarks().iterator().next()).getEstimatedPosition(at, false);
|
||||
Position approximateLegStartPosition = getTrackedRace().getApproximatePosition(getLeg().getFrom(), at);
|
||||
Position approximateLegEndPosition = getTrackedRace().getApproximatePosition(getLeg().getTo(), at);
|
||||
final Position middleOfLeg;
|
||||
if (approximateLegStartPosition == null || approximateLegEndPosition == null) {
|
||||
middleOfLeg = null;
|
||||
@@ -217,12 +222,12 @@ public class TrackedLegImpl implements TrackedLeg, RaceChangeListener {
|
||||
return middleOfLeg;
|
||||
}
|
||||
|
||||
public Position getEffectiveWindPosition(Callable<Position> positionProvider, TimePoint at, WindPositionMode mode) {
|
||||
public Position getEffectiveWindPosition(Callable<Position> exactPositionProvider, TimePoint at, WindPositionMode mode) {
|
||||
final Position effectivePosition;
|
||||
switch (mode) {
|
||||
case EXACT:
|
||||
try {
|
||||
effectivePosition = positionProvider.call();
|
||||
effectivePosition = exactPositionProvider.call();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
@@ -285,7 +290,7 @@ public class TrackedLegImpl implements TrackedLeg, RaceChangeListener {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void markPositionChanged(GPSFix fix, Mark mark) {
|
||||
public void markPositionChanged(GPSFix fix, Mark mark, boolean firstInTrack) {
|
||||
clearCaches();
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -2487,7 +2487,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
|
||||
}
|
||||
|
||||
@Override
|
||||
public void gpsFixReceived(GPSFix fix, Mark mark) {
|
||||
public void gpsFixReceived(GPSFix fix, Mark mark, boolean firstFixInTrack) {
|
||||
clearDirectionFromStartToNextMarkCache();
|
||||
}
|
||||
|
||||
|
||||
Executable
+9
@@ -0,0 +1,9 @@
|
||||
package com.sap.sailing.util;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
|
||||
public class ByteArrayOutputStreamWithVisibleBuffer extends ByteArrayOutputStream {
|
||||
public byte[] getBuffer() {
|
||||
return buf;
|
||||
}
|
||||
}
|
||||
+14
-2
@@ -1,4 +1,4 @@
|
||||
package com.sap.sailing.server.replication.impl;
|
||||
package com.sap.sailing.util.impl;
|
||||
|
||||
import java.io.FilterOutputStream;
|
||||
import java.io.IOException;
|
||||
@@ -6,6 +6,12 @@ import java.io.OutputStream;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Stream decorator that counts the bytes sent through an {@link OutputStream}. Logs
|
||||
* that number in configurable way using a {@link Logger}.
|
||||
*
|
||||
* @author Axel Uhl
|
||||
*/
|
||||
public class CountingOutputStream extends FilterOutputStream {
|
||||
private static final Logger logger = Logger.getLogger(CountingOutputStream.class.getName());
|
||||
|
||||
@@ -37,7 +43,7 @@ public class CountingOutputStream extends FilterOutputStream {
|
||||
}
|
||||
|
||||
private void log() {
|
||||
logger.log(level, "wrote "+count+" bytes to "+name);
|
||||
logger.log(level, "wrote "+count+" bytes "+(count>1000?"("+count/1000+" kB)":"")+" to "+name);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -51,4 +57,10 @@ public class CountingOutputStream extends FilterOutputStream {
|
||||
super.write(b, off, len);
|
||||
count(len);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
super.close();
|
||||
log();
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,8 @@ Require-Bundle: com.sap.sailing.server,
|
||||
org.objenesis;bundle-version="1.3.0",
|
||||
org.hamcrest;bundle-version="1.1.0",
|
||||
com.sap.sailing.domain.igtimiadapter.persistence;bundle-version="1.0.0",
|
||||
com.sap.sailing.domain.racelogtrackingadapter.testsupport;bundle-version="1.0.0"
|
||||
com.sap.sailing.domain.racelogtrackingadapter.testsupport;bundle-version="1.0.0",
|
||||
com.rabbitmq.client;bundle-version="2.8.4"
|
||||
Bundle-ClassPath: .
|
||||
Import-Package: com.sap.sailing.domain.racelogtracking.impl,
|
||||
javax.jms;version="1.1.0",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
eclipse.preferences.version=1
|
||||
entryPointModules=
|
||||
filesCopiedToWebInfLib=gwt-servlet.jar
|
||||
gwtCompileSettings=PGd3dC1jb21waWxlLXNldHRpbmdzPjxsb2ctbGV2ZWw+SU5GTzwvbG9nLWxldmVsPjxvdXRwdXQtc3R5bGU+T0JGVVNDQVRFRDwvb3V0cHV0LXN0eWxlPjxleHRyYS1hcmdzPjwhW0NEQVRBWy13YXIgLiAtbG9jYWxXb3JrZXJzIDMgLXN0cmljdCAtbG9nTGV2ZWwgVFJBQ0UgLWNvbXBpbGVSZXBvcnQgLVhzb3ljRGV0YWlsZWRdXT48L2V4dHJhLWFyZ3M+PHZtLWFyZ3M+PCFbQ0RBVEFbLVhteDIwMjRtIC1EZ3d0LnVzZWFyY2hpdmVzPWZhbHNlXV0+PC92bS1hcmdzPjxlbnRyeS1wb2ludC1tb2R1bGU+Y29tLnNhcC5zYWlsaW5nLmd3dC51aS5BZG1pbkNvbnNvbGU8L2VudHJ5LXBvaW50LW1vZHVsZT48ZW50cnktcG9pbnQtbW9kdWxlPmNvbS5zYXAuc2FpbGluZy5nd3QudWkuUmFjZUJvYXJkPC9lbnRyeS1wb2ludC1tb2R1bGU+PGVudHJ5LXBvaW50LW1vZHVsZT5jb20uc2FwLnNhaWxpbmcuZ3d0LnVpLkxlYWRlcmJvYXJkPC9lbnRyeS1wb2ludC1tb2R1bGU+PC9nd3QtY29tcGlsZS1zZXR0aW5ncz4\=
|
||||
gwtCompileSettings=PGd3dC1jb21waWxlLXNldHRpbmdzPjxsb2ctbGV2ZWw+SU5GTzwvbG9nLWxldmVsPjxvdXRwdXQtc3R5bGU+T0JGVVNDQVRFRDwvb3V0cHV0LXN0eWxlPjxleHRyYS1hcmdzPjwhW0NEQVRBWy13YXIgLiAtbG9jYWxXb3JrZXJzIDMgLXN0cmljdCAtbG9nTGV2ZWwgVFJBQ0UgLWNvbXBpbGVSZXBvcnQgLVhzb3ljRGV0YWlsZWRdXT48L2V4dHJhLWFyZ3M+PHZtLWFyZ3M+PCFbQ0RBVEFbLVhteDIwMjRtIC1EZ3d0LnVzZWFyY2hpdmVzPWZhbHNlXV0+PC92bS1hcmdzPjxlbnRyeS1wb2ludC1tb2R1bGU+Y29tLnNhcC5zYWlsaW5nLmd3dC51aS5BZG1pbkNvbnNvbGU8L2VudHJ5LXBvaW50LW1vZHVsZT48ZW50cnktcG9pbnQtbW9kdWxlPmNvbS5zYXAuc2FpbGluZy5nd3QudWkuUmFjZUJvYXJkPC9lbnRyeS1wb2ludC1tb2R1bGU+PGVudHJ5LXBvaW50LW1vZHVsZT5jb20uc2FwLnNhaWxpbmcuZ3d0LnVpLkxlYWRlcmJvYXJkPC9lbnRyeS1wb2ludC1tb2R1bGU+PGVudHJ5LXBvaW50LW1vZHVsZT5jb20uc2FwLnNhaWxpbmcuZ3d0LnVpLlNwZWN0YXRvcjwvZW50cnktcG9pbnQtbW9kdWxlPjwvZ3d0LWNvbXBpbGUtc2V0dGluZ3M+
|
||||
Regular → Executable
File mode changed.
Regular → Executable
+1
@@ -19,6 +19,7 @@ public class EventRegattaRacesRaceday extends Composite {
|
||||
@UiField HTMLPanel racesPanel;
|
||||
|
||||
public EventRegattaRacesRaceday(RaceGroupSeriesDTO series) {
|
||||
// private final EventPageNavigator pageNavigator;
|
||||
EventRegattaRacesResources.INSTANCE.css().ensureInjected();
|
||||
initWidget(uiBinder.createAndBindUi(this));
|
||||
|
||||
|
||||
+1
-1
@@ -60,7 +60,7 @@ public class GetWindInfoAction implements AsyncAction<WindInfoForRaceDTO> {
|
||||
switch (callVariant) {
|
||||
case Variant1:
|
||||
sailingService.getAveragedWindInfo(raceIdentifier, from, millisecondsStepWidth, numberOfFixes, windSourceTypeNames,
|
||||
onlyUpToNewestEvent, callback);
|
||||
onlyUpToNewestEvent, /* includeCombinedWindForAllLegMiddles */ true, callback);
|
||||
break;
|
||||
case Variant2:
|
||||
sailingService.getAveragedWindInfo(raceIdentifier, fromDate, toDate, resolutionInMilliseconds, windSourceTypeNames,
|
||||
|
||||
+1
-1
@@ -45,7 +45,7 @@ public class MarkTableWrapper<S extends SelectionModel<MarkDTO>> extends TableWr
|
||||
TextColumn<MarkDTO> markTypeColumn = new TextColumn<MarkDTO>() {
|
||||
@Override
|
||||
public String getValue(MarkDTO mark) {
|
||||
return mark.type.toString();
|
||||
return mark.type == null ? "" : mark.type.toString();
|
||||
}
|
||||
};
|
||||
table.addColumn(markTypeColumn, stringMessages.type());
|
||||
|
||||
+3
-1
@@ -96,7 +96,9 @@ public class RegattaListComposite extends Composite implements RegattasDisplayer
|
||||
public Iterable<String> getSearchableStrings(RegattaDTO t) {
|
||||
List<String> string = new ArrayList<String>();
|
||||
string.add(t.getName());
|
||||
string.add(t.boatClass.getName());
|
||||
if (t.boatClass != null) {
|
||||
string.add(t.boatClass.getName());
|
||||
}
|
||||
return string;
|
||||
}
|
||||
};
|
||||
|
||||
+30
-2
@@ -147,6 +147,10 @@ public class ReplicationPanel extends FlowPanel {
|
||||
AddReplicationDialog dialog = new AddReplicationDialog(null,
|
||||
new DialogCallback<Util.Triple<Util.Triple<String, String, String>, Integer, Integer>>() {
|
||||
@Override
|
||||
/**
|
||||
* @param masterNameAndExchangeNameAndMessagingPortNumberAndServletPortNumber a triple containing the RabbitMQ exchange hostname, the servlet hostname and the RabbitMQ exchange name,
|
||||
* followed by the RabbitMQ messaging port and the servlet port
|
||||
*/
|
||||
public void ok(final Util.Triple<Util.Triple<String, String, String>, Integer, Integer> masterNameAndExchangeNameAndMessagingPortNumberAndServletPortNumber) {
|
||||
registeredMasters.removeRow(0);
|
||||
registeredMasters.insertRow(0);
|
||||
@@ -163,7 +167,7 @@ public class ReplicationPanel extends FlowPanel {
|
||||
addButton.setEnabled(true);
|
||||
errorReporter.reportError(stringMessages.errorStartingReplication(
|
||||
masterNameAndExchangeNameAndMessagingPortNumberAndServletPortNumber.getA().getB(),
|
||||
masterNameAndExchangeNameAndMessagingPortNumberAndServletPortNumber.getA().getA(), e.getMessage()));
|
||||
masterNameAndExchangeNameAndMessagingPortNumberAndServletPortNumber.getA().getC(), e.getMessage()));
|
||||
updateReplicaList();
|
||||
}
|
||||
|
||||
@@ -216,12 +220,34 @@ public class ReplicationPanel extends FlowPanel {
|
||||
});
|
||||
registeredReplicas.setWidget(i, 2, removeReplicaButton);
|
||||
i++;
|
||||
registeredReplicas.insertRow(i);
|
||||
registeredReplicas.setWidget(i, 1, new Label(stringMessages.averageNumberOfOperationsPerMessage()));
|
||||
registeredReplicas.setWidget(i, 2, new Label(""+replica.getAverageNumberOfOperationsPerMessage()));
|
||||
i++;
|
||||
registeredReplicas.insertRow(i);
|
||||
registeredReplicas.setWidget(i, 1, new Label(stringMessages.numberOfQueueMessagesSent()));
|
||||
registeredReplicas.setWidget(i, 2, new Label(""+replica.getNumberOfMessagesSent()));
|
||||
i++;
|
||||
registeredReplicas.insertRow(i);
|
||||
registeredReplicas.setWidget(i, 1, new Label(stringMessages.averageMessageSize()));
|
||||
registeredReplicas.setWidget(i, 2, new Label(""+replica.getAverageMessageSizeInBytes()));
|
||||
i++;
|
||||
registeredReplicas.insertRow(i);
|
||||
registeredReplicas.setWidget(i, 1, new Label(stringMessages.totalSize()));
|
||||
registeredReplicas.setWidget(i, 2, new Label(""+replica.getNumberOfBytesSent()+"B ("+replica.getNumberOfBytesSent()/1024.0/1024.0+"MB)"));
|
||||
i++;
|
||||
long totalNumberOfOperations = 0;
|
||||
for (Map.Entry<String, Integer> e : replica.getOperationCountByOperationClassName().entrySet()) {
|
||||
registeredReplicas.insertRow(i);
|
||||
registeredReplicas.setWidget(i, 1, new Label(e.getKey()));
|
||||
registeredReplicas.setWidget(i, 2, new Label(e.getValue().toString()));
|
||||
totalNumberOfOperations += e.getValue();
|
||||
i++;
|
||||
}
|
||||
registeredReplicas.insertRow(i);
|
||||
registeredReplicas.setWidget(i, 1, new Label(stringMessages.totalNumberOfOperations()));
|
||||
registeredReplicas.setWidget(i, 2, new Label(""+totalNumberOfOperations));
|
||||
i++;
|
||||
replicaRegistered = true;
|
||||
}
|
||||
|
||||
@@ -270,7 +296,9 @@ public class ReplicationPanel extends FlowPanel {
|
||||
|
||||
/**
|
||||
* A text entry dialog with ok/cancel button and configurable validation rule. Subclasses may provide a redefinition for
|
||||
* {@link #getAdditionalWidget()} to add a widget below the text field, e.g., for capturing additional data.
|
||||
* {@link #getAdditionalWidget()} to add a widget below the text field, e.g., for capturing additional data. The result of
|
||||
* this dialog is a triple containing the RabbitMQ exchange hostname, the servlet hostname and the RabbitMQ exchange name,
|
||||
* followed by the RabbitMQ messaging port and the servlet port.
|
||||
*
|
||||
* @author Axel Uhl (d043530)
|
||||
*
|
||||
|
||||
+1
-1
@@ -576,7 +576,7 @@ public class WindPanel extends FormPanel implements RegattasDisplayer, WindShowe
|
||||
List<String> windSourceTypeNames = new ArrayList<String>();
|
||||
windSourceTypeNames.add(WindSourceType.COMBINED.name());
|
||||
sailingService.getAveragedWindInfo(selectedRace, raceDTO.startOfRace, 30000L, 100, windSourceTypeNames,
|
||||
/* onlyUpToNewestEvent==true means to only use data "based on facts" */ true, new AsyncCallback<WindInfoForRaceDTO>() {
|
||||
/* onlyUpToNewestEvent==true means to only use data "based on facts" */ true, /* includeCombinedWindForAllLegMiddles */ false, new AsyncCallback<WindInfoForRaceDTO>() {
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable caught) {
|
||||
|
||||
+2
-1
@@ -33,6 +33,7 @@ public class DetailTypeFormatter {
|
||||
case NUMBER_OF_MANEUVERS:
|
||||
return stringMessages.numberOfManeuvers();
|
||||
case CURRENT_SPEED_OVER_GROUND_IN_KNOTS:
|
||||
return stringMessages.currentOrAverageSpeedOverGroundInKnots();
|
||||
case RACE_CURRENT_SPEED_OVER_GROUND_IN_KNOTS:
|
||||
return stringMessages.currentSpeedOverGroundInKnots();
|
||||
case ESTIMATED_TIME_TO_NEXT_WAYPOINT_IN_SECONDS:
|
||||
@@ -265,7 +266,7 @@ public class DetailTypeFormatter {
|
||||
case RACE_AVERAGE_SPEED_OVER_GROUND_IN_KNOTS:
|
||||
return stringMessages.raceAverageSpeedInKnotsTooltip();
|
||||
case CURRENT_SPEED_OVER_GROUND_IN_KNOTS:
|
||||
return stringMessages.currentSpeedOverGroundInKnotsTooltip();
|
||||
return stringMessages.currentOrAverageSpeedOverGroundInKnotsTooltip();
|
||||
case RACE_CURRENT_SPEED_OVER_GROUND_IN_KNOTS:
|
||||
return stringMessages.currentSpeedOverGroundInKnotsTooltip();
|
||||
case RACE_DISTANCE_TO_LEADER_IN_METERS:
|
||||
|
||||
+2
-1
@@ -127,7 +127,8 @@ public interface SailingService extends RemoteService {
|
||||
void setWind(RegattaAndRaceIdentifier raceIdentifier, WindDTO wind);
|
||||
|
||||
WindInfoForRaceDTO getAveragedWindInfo(RegattaAndRaceIdentifier raceIdentifier, Date from, long millisecondsStepWidth,
|
||||
int numberOfFixes, Collection<String> windSourceTypeNames, boolean onlyUpToNewestEvent) throws NoWindException;
|
||||
int numberOfFixes, Collection<String> windSourceTypeNames, boolean onlyUpToNewestEvent,
|
||||
boolean includeCombinedWindForAllLegMiddles) throws NoWindException;
|
||||
|
||||
WindInfoForRaceDTO getAveragedWindInfo(RegattaAndRaceIdentifier raceIdentifier, Date from, Date to, long resolutionInMilliseconds,
|
||||
Collection<String> windSourceTypeNames, boolean onlyUpToNewestEvent);
|
||||
|
||||
+4
-1
@@ -174,10 +174,13 @@ public interface SailingServiceAsync {
|
||||
* "best effort" readings are provided for the time interval requested, no matter if based on any sensor
|
||||
* evidence or not, regardless of {@link TrackedRace#getTimePointOfNewestEvent()
|
||||
* trackedRace.getTimePointOfNewestEvent()}.
|
||||
* @param includeCombinedWindForAllLegMiddles
|
||||
* if <code>true</code>, the result will return non-<code>null</code> results for calls to
|
||||
* {@link WindInfoForRaceDTO#getCombinedWindOnLegMiddle(int)}.
|
||||
*/
|
||||
void getAveragedWindInfo(RegattaAndRaceIdentifier raceIdentifier, Date from, long millisecondsStepWidth,
|
||||
int numberOfFixes, Collection<String> windSourceTypeNames, boolean onlyUpToNewestEvent,
|
||||
AsyncCallback<WindInfoForRaceDTO> callback);
|
||||
boolean includeCombinedWindForAllLegMiddles, AsyncCallback<WindInfoForRaceDTO> callback);
|
||||
|
||||
void setWind(RegattaAndRaceIdentifier raceIdentifier, WindDTO wind, AsyncCallback<Void> callback);
|
||||
|
||||
|
||||
+9
-1
@@ -380,7 +380,8 @@ public interface StringMessages extends Messages {
|
||||
String scoringSchemeLowPointSystem();
|
||||
String scoringSchemeHighPointSystem();
|
||||
String scoringSchemeLowPointWinnerGetsZero();
|
||||
String scoringSchemeWinnerGetsFive();
|
||||
String scoringSchemeWinnerGetsFive();
|
||||
String scoringSchemeWinnerGetsFiveIgnoringRaceCount();
|
||||
String scoringSchemeWinnerGetsSix();
|
||||
String scoringSystem();
|
||||
String createFlexibleLeaderboard();
|
||||
@@ -1049,6 +1050,7 @@ public interface StringMessages extends Messages {
|
||||
String showNumberOfRacesScored();
|
||||
String racesScored();
|
||||
String racesScoredTooltip();
|
||||
String averageNumberOfOperationsPerMessage();
|
||||
String showUncorrectedNetPoints();
|
||||
String setStartTimeReceived();
|
||||
String setStartTimeReceivedDescription();
|
||||
@@ -1056,4 +1058,10 @@ public interface StringMessages extends Messages {
|
||||
String lastScoreCorrectionsComment();
|
||||
String setTimeToNow();
|
||||
String useStartTimeInference();
|
||||
String numberOfQueueMessagesSent();
|
||||
String totalSize();
|
||||
String averageMessageSize();
|
||||
String totalNumberOfOperations();
|
||||
String currentOrAverageSpeedOverGroundInKnotsTooltip();
|
||||
String currentOrAverageSpeedOverGroundInKnots();
|
||||
}
|
||||
+8
@@ -408,6 +408,7 @@ scoringSchemeHighPointEssOverall=High Point System ESS Overall
|
||||
scoringSchemeHighPointLastBreaksTie=High Point System, Last Race Breaks Tie
|
||||
scoringSchemeLowPointWinnerGetsZero=Low Point System, Winner Gets Zero
|
||||
scoringSchemeWinnerGetsFive=High Point System, Winner Gets 5 Points
|
||||
scoringSchemeWinnerGetsFiveIgnoringRaceCount=High Point System, Winner Gets 5 Points, Ignoring Race Count
|
||||
scoringSchemeWinnerGetsSix=High Point System, Winner Gets 6 Points
|
||||
scoringSystem=Scoring system
|
||||
createFlexibleLeaderboard=Create flexible leaderboard
|
||||
@@ -1033,6 +1034,7 @@ errorFetchingWindStreamletData=Error fetching wind streamlet data: {0}
|
||||
showWindStreamletOverlay=Show wind streamlets overlay
|
||||
racesScored=Races scored
|
||||
racesScoredTooltip=Number of races the competitor has completed or gotten a score for in this leaderboard.
|
||||
averageNumberOfOperationsPerMessage=Average number of operations per message
|
||||
showUncorrectedNetPoints=Show uncorrected net points
|
||||
setStartTimeReceived=Set start time received
|
||||
setStartTimeReceivedDescription=This sets the startTimeReceived of the selected TrackedRace that isn''t persistent. This means that the new value will be forgotten after the server has been restarted.
|
||||
@@ -1040,3 +1042,9 @@ lastScoreCorrectionsTime=Last score correction time
|
||||
lastScoreCorrectionsComment=Last score correction comment
|
||||
setTimeToNow=Set time to ''now''
|
||||
useStartTimeInference=Use start time inference from start mark passings
|
||||
numberOfQueueMessagesSent=Number of queue messages sent
|
||||
totalSize=Total Size
|
||||
averageMessageSize=Average size per message in bytes
|
||||
totalNumberOfOperations=Total number of operations
|
||||
currentOrAverageSpeedOverGroundInKnotsTooltip=Current speed over ground in knots while leg is still running; average speed over ground when leg is completed
|
||||
currentOrAverageSpeedOverGroundInKnots=SOG (\u2205 at end)
|
||||
+12
-4
@@ -143,7 +143,7 @@ overallDetailsToShow=Regatta-Details
|
||||
legDetailsToShow=Schenkeldetails
|
||||
columnMoveUp=Hoch
|
||||
columnMoveDown=Runter
|
||||
port=Port
|
||||
port=Backbord
|
||||
raceStartTimeColumn=Rennstart
|
||||
showOnlySelectedCompetitors=Nur ausgewählte Teilnehmer anzeigen
|
||||
showSelectedCompetitorsInfo=Infobox für ausgewählte Teilnehmer anzeigen
|
||||
@@ -409,6 +409,7 @@ scoringSchemeHighPointEssOverall=High Point System ESS Gesamtwertung
|
||||
scoringSchemeHighPointLastBreaksTie=High Point System, letztes Rennen entscheidet bei Gleichstand
|
||||
scoringSchemeLowPointWinnerGetsZero=Low Point System, Gewinner erhält 0 Punkte
|
||||
scoringSchemeWinnerGetsFive=High Point System, Gewinner erhält 5 Punkte
|
||||
scoringSchemeWinnerGetsFiveIgnoringRaceCount=High Point System, Gewinner erhält 5 Punkte, Anzahl Wettfahrten egal
|
||||
scoringSchemeWinnerGetsSix=High Point System, Gewinner erhält 6 Punkte
|
||||
scoringSystem=Bepunktungssystem
|
||||
createFlexibleLeaderboard=Flexible Rangliste anlegen
|
||||
@@ -860,7 +861,7 @@ errorRunningDataMiningQuery=Es ist ein Felher bei der Datenabfrage aufgetreten
|
||||
hideToolbar=Toolbar ausblenden
|
||||
showSeriesLeaderboards=Ergebnisse der Serie anzeigen
|
||||
showOverallLeaderboard=Gesamtergebnisse anzeigen
|
||||
lineAngleToWindAndAdvantage=LängeÖ {0}m; Winkel zum Wind: {1}°; {2}-Seite ist {3}m bevorteilt
|
||||
lineAngleToWindAndAdvantage=Länge: {0}m; Winkel zum Wind: {1}°; {2}-Seite ist {3}m bevorteilt
|
||||
doYouReallyWantToRemoveEvents=Möchten Sie diese Veranstaltungen wirklich entfernen?
|
||||
doYouReallyWantToRemoveEvent=Möchten Sie wirklich die Veranstaltung {0} entfernen?
|
||||
line=Linie
|
||||
@@ -1027,11 +1028,18 @@ regattaNameMustNotContainSlashes=Regattaname darf keine Schrägstriche enthalten
|
||||
showNumberOfRacesScored=Anzahl bepunkteter Rennen
|
||||
racesScored=Bepunktete Rennen
|
||||
racesScoredTooltip=Anzahl von Rennen, die der Segler vollendet oder für die er Punkte bekommen hat
|
||||
averageNumberOfOperationsPerMessage=Durchschnittliche Anzahl Operationen pro Nachricht
|
||||
showUncorrectedNetPoints=Unkorrigierte Netto-Punkte anzeigen
|
||||
setStartTimeReceived=Setze die erhaltene Startzeit
|
||||
setStartTimeReceivedDescription=This sets the startTimeReceived of the selected TrackedRace, that isn''t persistent. This means, that the new value will be forgotten after the server has been restarted.
|
||||
setStartTimeReceivedDescription=Dies setzt die startTimeReceived des selektierten TrackedRaces, welches nicht persistent ist. Das beudeutet, dass der neue Wert vergessen wird, sobald der Server neu gestartet wurde.
|
||||
lastScoreCorrectionsTime=Letzter Zeitpunkt der Punktzahl-Korrektur
|
||||
lastScoreCorrectionsComment=Letzter Kommentar der Punktzahl-Korrektur
|
||||
setTimeToNow=Setze den Zeitpunkt auf ''Jetzt''
|
||||
useStartTimeInference=Startzeit aus Startlinienüberquerungen automatisch ableiten
|
||||
setTimeToNow=Setze den Zeitpunkt auf ''Jetzt''
|
||||
useStartTimeInference=Startzeit aus Startlinienüberquerungen automatisch ableiten
|
||||
numberOfQueueMessagesSent=Anzahl verschickter Queue Nachrichten
|
||||
totalSize=Gesamtgröße
|
||||
averageMessageSize=Durchschnittsgrösse einer Nachricht
|
||||
totalNumberOfOperations=Gesamtanzahl gesendete Operationen
|
||||
currentOrAverageSpeedOverGroundInKnotsTooltip=Momentane Fahrt über Grund während der Teilnehmer sich im Bahnabschnitt befindet; Durchschnitt, nachdem der Teilnehmer den Abschnitt beendet hat.
|
||||
currentOrAverageSpeedOverGroundInKnots=FüG (\u2205 nach Abschluss)
|
||||
+9
-1
@@ -399,6 +399,7 @@ scoringSchemeHighPointEssOverall=Высокобальная система по
|
||||
scoringSchemeHighPointLastBreaksTie=Высокобальная система подсчета, тай-брейк на последней гонке
|
||||
scoringSchemeLowPointWinnerGetsZero=Низкобальная система подсчета очков, победитель получает ноль очков
|
||||
scoringSchemeWinnerGetsFive=Высокобальная система подсчета, победитель получает 5 очков
|
||||
scoringSchemeWinnerGetsFiveIgnoringRaceCount=Высокобальная система подсчета, победитель получает 5 очков, Ignoring Race Count
|
||||
scoringSchemeWinnerGetsSix=Высокобальная система подсчета, победитель получает 6 очков
|
||||
scoringSystem=Система подсчета очков
|
||||
createFlexibleLeaderboard=Создание адаптивной таблицы лидеров
|
||||
@@ -988,5 +989,12 @@ showNumberOfRacesScored=Показать количество завершенн
|
||||
racesScored=Завершенные гонки
|
||||
racesScoredTooltip=Количество гонок завершенное участником в данной таблице лидеров
|
||||
regattaNameMustNotContainSlashes=Regatta name must not contain slashes
|
||||
averageNumberOfOperationsPerMessage=Average number of operations per message
|
||||
showUncorrectedNetPoints=Show uncorrected net points
|
||||
useStartTimeInference=Use start time inference from start mark passings
|
||||
useStartTimeInference=Use start time inference from start mark passings
|
||||
numberOfQueueMessagesSent=Number of queue messages sent
|
||||
totalSize=Total Size
|
||||
averageMessageSize=Average Size per message
|
||||
totalNumberOfOperations=Total number of operations
|
||||
currentOrAverageSpeedOverGroundInKnotsTooltip=Current speed over ground in knots while leg is still running; average speed over ground when leg is completed
|
||||
currentOrAverageSpeedOverGroundInKnots=SOG (\u2205 at end)
|
||||
+1
-1
@@ -28,7 +28,7 @@ public class MultiCompetitorRaceChartSettingsComponent extends AbstractChartSett
|
||||
availableDetailsTypes.add(DetailType.DISTANCE_TRAVELED);
|
||||
availableDetailsTypes.add(DetailType.VELOCITY_MADE_GOOD_IN_KNOTS);
|
||||
availableDetailsTypes.add(DetailType.GAP_TO_LEADER_IN_SECONDS);
|
||||
availableDetailsTypes.add(DetailType.CURRENT_SPEED_OVER_GROUND_IN_KNOTS);
|
||||
availableDetailsTypes.add(DetailType.RACE_CURRENT_SPEED_OVER_GROUND_IN_KNOTS);
|
||||
availableDetailsTypes.add(DetailType.RACE_RANK);
|
||||
availableDetailsTypes.add(DetailType.REGATTA_RANK);
|
||||
availableDetailsTypes.add(DetailType.DISTANCE_TO_START_LINE);
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ public class BoatClassVectorGraphicsResolver {
|
||||
compatibleBoatVectorGraphicsMap = new HashMap<String, BoatClassVectorGraphics>();
|
||||
|
||||
BoatClassVectorGraphics laser = new LaserVectorGraphics("470", "470er");
|
||||
BoatClassVectorGraphics _49er = new _49erVectorGraphics("49erFX", "49FX");
|
||||
BoatClassVectorGraphics _49er = new _49erVectorGraphics("49erFX", "49FX", "29er");
|
||||
BoatClassVectorGraphics extreme40 = new Extreme40VectorGraphics("Extreme");
|
||||
BoatClassVectorGraphics smallMultihull = new SmallMultihullVectorGraphics("Nacra 17", "Formula 16", "Formula 18",
|
||||
"Hobie Wild Cat", "Hobie Tiger", "A-Catamaran", "Tornado");
|
||||
|
||||
+1
-1
@@ -63,7 +63,7 @@ public class CompetitorInfoOverlay extends CanvasOverlayV3 {
|
||||
|
||||
ctx.setLineWidth(1.0);
|
||||
ctx.setFillStyle(grayTransparentColor);
|
||||
if(competitorColor != null) {
|
||||
if (competitorColor != null) {
|
||||
ctx.setStrokeStyle(competitorColor.getAsHtml());
|
||||
} else {
|
||||
ctx.setStrokeStyle("#888888");
|
||||
|
||||
+14
-9
@@ -2,6 +2,7 @@ package com.sap.sailing.gwt.ui.client.shared.racemap;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
@@ -133,6 +134,7 @@ public class FixesAndTails {
|
||||
int indexOfFirst = -1;
|
||||
int indexOfLast = -1;
|
||||
int i = 0;
|
||||
// TODO consider binary search to find beginning of interesting segment faster
|
||||
for (Iterator<GPSFixDTO> fixIter = fixesForCompetitor.iterator(); fixIter.hasNext() && indexOfLast == -1;) {
|
||||
GPSFixDTO fix = fixIter.next();
|
||||
if (!fix.timepoint.before(to)) {
|
||||
@@ -175,13 +177,13 @@ public class FixesAndTails {
|
||||
* not trimmed according to the specification for the tail length. This has to happen elsewhere (see also
|
||||
* {@link #updateTail}).
|
||||
*
|
||||
* @param result
|
||||
* @param fixesForCompetitors
|
||||
* For each list the invariant must hold that an {@link GPSFixDTO#extrapolated extrapolated} fix must be
|
||||
* the last one in the list
|
||||
*/
|
||||
protected void updateFixes(Map<CompetitorDTO, List<GPSFixDTO>> result,
|
||||
protected void updateFixes(Map<CompetitorDTO, List<GPSFixDTO>> fixesForCompetitors,
|
||||
Map<CompetitorDTO, Boolean> overlapsWithKnownFixes, TailFactory tailFactory, long timeForPositionTransitionMillis) {
|
||||
for (Map.Entry<CompetitorDTO, List<GPSFixDTO>> e : result.entrySet()) {
|
||||
for (Map.Entry<CompetitorDTO, List<GPSFixDTO>> e : fixesForCompetitors.entrySet()) {
|
||||
if (e.getValue() != null && !e.getValue().isEmpty()) {
|
||||
List<GPSFixDTO> fixesForCompetitor = fixes.get(e.getKey());
|
||||
if (fixesForCompetitor == null) {
|
||||
@@ -238,10 +240,14 @@ public class FixesAndTails {
|
||||
final Polyline tail = getTail(competitorDTO);
|
||||
int intoThisIndex = 0;
|
||||
for (GPSFixDTO mergeThisFix : mergeThis) {
|
||||
while (intoThisIndex < intoThis.size()
|
||||
&& intoThis.get(intoThisIndex).timepoint.before(mergeThisFix.timepoint)) {
|
||||
// TODO how about using Collections.binarySearch
|
||||
intoThisIndex++;
|
||||
intoThisIndex = Collections.binarySearch(intoThis, mergeThisFix, new Comparator<GPSFixDTO>() {
|
||||
@Override
|
||||
public int compare(GPSFixDTO o1, GPSFixDTO o2) {
|
||||
return o1.timepoint.compareTo(o2.timepoint);
|
||||
}
|
||||
});
|
||||
if (intoThisIndex < 0) {
|
||||
intoThisIndex = -intoThisIndex-1;
|
||||
}
|
||||
if (intoThisIndex < intoThis.size() && intoThis.get(intoThisIndex).timepoint.equals(mergeThisFix.timepoint)) {
|
||||
// exactly same time point; replace with fix from mergeThis unless the new fix is extrapolated and there is a later fix in intoThis;
|
||||
@@ -451,8 +457,7 @@ public class FixesAndTails {
|
||||
&& !tailstart.before(timepointOfFirstKnownFix) && timepointOfLastKnownFix != null
|
||||
&& !tailstart.after(timepointOfLastKnownFix)) {
|
||||
// the beginning of what we need is contained in the interval we already have; skip what we already have
|
||||
// FIXME requests the lastKnownFix again because "from" is *inclusive*; could lead to bug 319
|
||||
fromDate = timepointOfLastKnownFix;
|
||||
fromDate = new Date(timepointOfLastKnownFix.getTime()+1l); // "from" is "inclusive", so add 1ms to also skip the last fix we have
|
||||
overlap = true;
|
||||
} else {
|
||||
fromDate = tailstart;
|
||||
|
||||
+153
-78
@@ -101,6 +101,7 @@ import com.sap.sailing.gwt.ui.shared.racemap.GoogleMapAPIKey;
|
||||
import com.sap.sailing.gwt.ui.shared.racemap.GoogleMapStyleHelper;
|
||||
import com.sap.sailing.gwt.ui.shared.racemap.WindStreamletsRaceboardOverlay;
|
||||
import com.sap.sse.common.Util;
|
||||
import com.sap.sse.common.Util.Triple;
|
||||
import com.sap.sse.gwt.client.async.AsyncActionsExecutor;
|
||||
import com.sap.sse.gwt.client.player.TimeListener;
|
||||
import com.sap.sse.gwt.client.player.Timer;
|
||||
@@ -164,7 +165,11 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
|
||||
|
||||
private Map<SidelineDTO, Polygon> courseSidelines;
|
||||
|
||||
private WindTrackInfoDTO lastCombinedWindTrackInfoDTO;
|
||||
/**
|
||||
* Wind data used to display the advantage line. Retrieved by a {@link GetWindInfoAction} execution and used in
|
||||
* {@link #showAdvantageLine(Iterable, Date)}.
|
||||
*/
|
||||
private WindInfoForRaceDTO lastCombinedWindTrackInfoDTO;
|
||||
|
||||
/**
|
||||
* Manages the cached set of {@link GPSFixDTO}s for the boat positions as well as their graphical counterpart in the
|
||||
@@ -445,71 +450,28 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
|
||||
if (race != null) {
|
||||
final com.sap.sse.common.Util.Triple<Map<CompetitorDTO, Date>, Map<CompetitorDTO, Date>, Map<CompetitorDTO, Boolean>> fromAndToAndOverlap =
|
||||
fixesAndTails.computeFromAndTo(newTime, competitorsToShow, settings.getEffectiveTailLengthInMilliseconds());
|
||||
final int requestID = ++boatPositionRequestIDCounter;
|
||||
|
||||
int requestID = ++boatPositionRequestIDCounter;
|
||||
// For those competitors for which the tails don't overlap (and therefore will be replaced by the new tail coming from the server)
|
||||
// we expect some potential delay in computing the full tail. Therefore, in those cases we fire two requests: one fetching only the
|
||||
// boat positions at newTime with zero tail length; and another one fetching everything else.
|
||||
GetRaceMapDataAction getRaceMapDataForAllOverlappingAndTipsOfNonOverlapping = getRaceMapDataForAllOverlappingAndTipsOfNonOverlapping(fromAndToAndOverlap, race, newTime);
|
||||
if (getRaceMapDataForAllOverlappingAndTipsOfNonOverlapping != null) {
|
||||
asyncActionsExecutor.execute(getRaceMapDataForAllOverlappingAndTipsOfNonOverlapping, GET_RACE_MAP_DATA_CATEGORY,
|
||||
getRaceMapDataCallback(oldTime, newTime, fromAndToAndOverlap.getC(), competitorsToShow, requestID));
|
||||
requestID = ++boatPositionRequestIDCounter;
|
||||
}
|
||||
// next, do the full thing; being the later call, if request throttling kicks in, the later call
|
||||
// supersedes the earlier call which may get dropped then
|
||||
GetRaceMapDataAction getRaceMapDataAction = new GetRaceMapDataAction(sailingService, competitorSelection.getAllCompetitors(), race,
|
||||
newTime, fromAndToAndOverlap.getA(), fromAndToAndOverlap.getB(), /* extrapolate */ true);
|
||||
asyncActionsExecutor.execute(getRaceMapDataAction, GET_RACE_MAP_DATA_CATEGORY, new AsyncCallback<RaceMapDataDTO>() {
|
||||
@Override
|
||||
public void onFailure(Throwable caught) {
|
||||
errorReporter.reportError("Error obtaining racemap data: " + caught.getMessage(), true /*silentMode */);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(RaceMapDataDTO raceMapDataDTO) {
|
||||
if (map != null && raceMapDataDTO != null) {
|
||||
quickRanks = raceMapDataDTO.quickRanks;
|
||||
// process response only if not received out of order
|
||||
if (startedProcessingRequestID < requestID) {
|
||||
startedProcessingRequestID = requestID;
|
||||
// Do boat specific actions
|
||||
Map<CompetitorDTO, List<GPSFixDTO>> boatData = raceMapDataDTO.boatPositions;
|
||||
long timeForPositionTransitionMillis = calculateTimeForPositionTransition(newTime, oldTime);
|
||||
fixesAndTails.updateFixes(boatData, fromAndToAndOverlap.getC(), RaceMap.this, timeForPositionTransitionMillis);
|
||||
showBoatsOnMap(newTime, timeForPositionTransitionMillis, getCompetitorsToShow());
|
||||
showCompetitorInfoOnMap(newTime, timeForPositionTransitionMillis, competitorSelection.getSelectedCompetitors());
|
||||
if (douglasMarkers != null) {
|
||||
removeAllMarkDouglasPeuckerpoints();
|
||||
}
|
||||
if (maneuverMarkers != null) {
|
||||
removeAllManeuverMarkers();
|
||||
}
|
||||
|
||||
// Do mark specific actions
|
||||
showCourseMarksOnMap(raceMapDataDTO.coursePositions);
|
||||
showCourseSidelinesOnMap(raceMapDataDTO.courseSidelines);
|
||||
showStartAndFinishLines(raceMapDataDTO.coursePositions);
|
||||
showAdvantageLine(competitorsToShow, newTime);
|
||||
|
||||
// Rezoom the map
|
||||
// TODO make this a loop across the LatLngBoundsCalculators, pulling them from a collection updated in updateSettings
|
||||
if (!settings.getZoomSettings().containsZoomType(ZoomTypes.NONE)) { // Auto zoom if setting is not manual
|
||||
LatLngBounds bounds = settings.getZoomSettings().getNewBounds(RaceMap.this);
|
||||
zoomMapToNewBounds(bounds);
|
||||
mapFirstZoomDone = true;
|
||||
} else if (!mapFirstZoomDone) { // Zoom once to the marks
|
||||
zoomMapToNewBounds(new CourseMarksBoundsCalculator().calculateNewBounds(RaceMap.this));
|
||||
mapFirstZoomDone = true;
|
||||
/*
|
||||
* Reset the mapZoomedOrPannedSinceLastRaceSelection: In spite of the fact that
|
||||
* the map was just zoomed to the bounds of the marks, it was not a zoom or pan
|
||||
* triggered by the user. As a consequence the
|
||||
* mapZoomedOrPannedSinceLastRaceSelection option has to reset again.
|
||||
*/
|
||||
// TODO bug 494: consider initial user-specific zoom settings
|
||||
}
|
||||
}
|
||||
} else {
|
||||
lastTimeChangeBeforeInitialization = newTime;
|
||||
}
|
||||
}
|
||||
});
|
||||
asyncActionsExecutor.execute(getRaceMapDataAction, GET_RACE_MAP_DATA_CATEGORY,
|
||||
getRaceMapDataCallback(oldTime, newTime, fromAndToAndOverlap.getC(), competitorsToShow, requestID));
|
||||
|
||||
// draw the wind into the map, get the combined wind
|
||||
// TODO bug2057 also fetch wind for LEG_MIDDLE for all legs because this needs to be the basis for the advantage line display
|
||||
List<String> windSourceTypeNames = new ArrayList<String>();
|
||||
windSourceTypeNames.add(WindSourceType.EXPEDITION.name());
|
||||
windSourceTypeNames.add(WindSourceType.COMBINED.name());
|
||||
|
||||
GetWindInfoAction getWindInfoAction = new GetWindInfoAction(sailingService, race, newTime, 1000L, 1, windSourceTypeNames,
|
||||
/* onlyUpToNewestEvent==false means get us any data we can get by a best effort */ false);
|
||||
asyncActionsExecutor.execute(getWindInfoAction, GET_WIND_DATA_CATEGORY, new AsyncCallback<WindInfoForRaceDTO>() {
|
||||
@@ -522,6 +484,8 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
|
||||
public void onSuccess(WindInfoForRaceDTO windInfo) {
|
||||
List<com.sap.sse.common.Util.Pair<WindSource, WindTrackInfoDTO>> windSourcesToShow = new ArrayList<com.sap.sse.common.Util.Pair<WindSource, WindTrackInfoDTO>>();
|
||||
if (windInfo != null) {
|
||||
lastCombinedWindTrackInfoDTO = windInfo;
|
||||
showAdvantageLine(competitorsToShow, newTime);
|
||||
for (WindSource windSource: windInfo.windTrackInfoByWindSource.keySet()) {
|
||||
WindTrackInfoDTO windTrackInfoDTO = windInfo.windTrackInfoByWindSource.get(windSource);
|
||||
switch (windSource.getType()) {
|
||||
@@ -533,10 +497,6 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
|
||||
break;
|
||||
case COMBINED:
|
||||
showCombinedWindOnMap(windSource, windTrackInfoDTO);
|
||||
if (windTrackInfoDTO != null) {
|
||||
lastCombinedWindTrackInfoDTO = windTrackInfoDTO;
|
||||
showAdvantageLine(competitorsToShow, newTime);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// Which wind sources are requested is defined in a list above this
|
||||
@@ -555,6 +515,103 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We assume that overlapping segments usually don't require a lot of loading time as the most typical case will be to update a longer
|
||||
* tail with a few new fixes that were received since the last time tick. Non-overlapping position requests typically occur for the
|
||||
* first request when no fix at all is known for the competitor yet, and when the user has radically moved the time slider to some
|
||||
* other time such that given the current tail length setting the new tail segment does not overlap with the old one, requiring a full
|
||||
* load of the entire tail data for that competitor.<p>
|
||||
*
|
||||
* For the non-overlapping requests, this method creates a separate request which only loads boat positions, quick ranks, sidelines and
|
||||
* mark positions for the zero-length interval at <code>newTime</code>, assuming that this will work fairly fast and in particular in
|
||||
* O(1) time regardless of tail length, compared to fetching the entire tail for all competitors.
|
||||
*/
|
||||
private GetRaceMapDataAction getRaceMapDataForAllOverlappingAndTipsOfNonOverlapping(
|
||||
Triple<Map<CompetitorDTO, Date>, Map<CompetitorDTO, Date>, Map<CompetitorDTO, Boolean>> fromAndToAndOverlap,
|
||||
RegattaAndRaceIdentifier race, Date newTime) {
|
||||
Map<CompetitorDTO, Date> fromTimes = new HashMap<>();
|
||||
Map<CompetitorDTO, Date> toTimes = new HashMap<>();
|
||||
for (Map.Entry<CompetitorDTO, Boolean> e : fromAndToAndOverlap.getC().entrySet()) {
|
||||
if (!e.getValue()) {
|
||||
// no overlap; add competitor to request
|
||||
fromTimes.put(e.getKey(), newTime);
|
||||
toTimes.put(e.getKey(), newTime);
|
||||
}
|
||||
}
|
||||
final GetRaceMapDataAction result;
|
||||
if (!fromTimes.isEmpty()) {
|
||||
result = new GetRaceMapDataAction(sailingService, competitorSelection.getAllCompetitors(),
|
||||
race, newTime, fromTimes, toTimes, /* extrapolate */true);
|
||||
} else {
|
||||
result = null;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private AsyncCallback<RaceMapDataDTO> getRaceMapDataCallback(
|
||||
final Date oldTime,
|
||||
final Date newTime,
|
||||
final Map<CompetitorDTO, Boolean> hasTailOverlapForCompetitor,
|
||||
final Iterable<CompetitorDTO> competitorsToShow, final int requestID) {
|
||||
return new AsyncCallback<RaceMapDataDTO>() {
|
||||
@Override
|
||||
public void onFailure(Throwable caught) {
|
||||
errorReporter.reportError("Error obtaining racemap data: " + caught.getMessage(), true /*silentMode */);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(RaceMapDataDTO raceMapDataDTO) {
|
||||
if (map != null && raceMapDataDTO != null) {
|
||||
quickRanks = raceMapDataDTO.quickRanks;
|
||||
// process response only if not received out of order
|
||||
if (startedProcessingRequestID < requestID) {
|
||||
startedProcessingRequestID = requestID;
|
||||
// Do boat specific actions
|
||||
Map<CompetitorDTO, List<GPSFixDTO>> boatData = raceMapDataDTO.boatPositions;
|
||||
long timeForPositionTransitionMillis = calculateTimeForPositionTransition(newTime, oldTime);
|
||||
fixesAndTails.updateFixes(boatData, hasTailOverlapForCompetitor, RaceMap.this, timeForPositionTransitionMillis);
|
||||
showBoatsOnMap(newTime, timeForPositionTransitionMillis, getCompetitorsToShow());
|
||||
showCompetitorInfoOnMap(newTime, timeForPositionTransitionMillis, competitorSelection.getSelectedCompetitors());
|
||||
if (douglasMarkers != null) {
|
||||
removeAllMarkDouglasPeuckerpoints();
|
||||
}
|
||||
if (maneuverMarkers != null) {
|
||||
removeAllManeuverMarkers();
|
||||
}
|
||||
|
||||
// Do mark specific actions
|
||||
showCourseMarksOnMap(raceMapDataDTO.coursePositions);
|
||||
showCourseSidelinesOnMap(raceMapDataDTO.courseSidelines);
|
||||
showStartAndFinishLines(raceMapDataDTO.coursePositions);
|
||||
// even though the wind data is retrieved by a separate call, re-draw the advantage line because it needs to
|
||||
// adjust to new boat positions
|
||||
showAdvantageLine(competitorsToShow, newTime);
|
||||
|
||||
// Rezoom the map
|
||||
// TODO make this a loop across the LatLngBoundsCalculators, pulling them from a collection updated in updateSettings
|
||||
if (!settings.getZoomSettings().containsZoomType(ZoomTypes.NONE)) { // Auto zoom if setting is not manual
|
||||
LatLngBounds bounds = settings.getZoomSettings().getNewBounds(RaceMap.this);
|
||||
zoomMapToNewBounds(bounds);
|
||||
mapFirstZoomDone = true;
|
||||
} else if (!mapFirstZoomDone) { // Zoom once to the marks
|
||||
zoomMapToNewBounds(new CourseMarksBoundsCalculator().calculateNewBounds(RaceMap.this));
|
||||
mapFirstZoomDone = true;
|
||||
/*
|
||||
* Reset the mapZoomedOrPannedSinceLastRaceSelection: In spite of the fact that
|
||||
* the map was just zoomed to the bounds of the marks, it was not a zoom or pan
|
||||
* triggered by the user. As a consequence the
|
||||
* mapZoomedOrPannedSinceLastRaceSelection option has to reset again.
|
||||
*/
|
||||
// TODO bug 494: consider initial user-specific zoom settings
|
||||
}
|
||||
}
|
||||
} else {
|
||||
lastTimeChangeBeforeInitialization = newTime;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void showCourseSidelinesOnMap(List<SidelineDTO> sidelinesDTOs) {
|
||||
if (map != null && sidelinesDTOs != null ) {
|
||||
Map<SidelineDTO, Polygon> toRemoveSidelines = new HashMap<SidelineDTO, Polygon>(courseSidelines);
|
||||
@@ -785,7 +842,8 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
|
||||
/**
|
||||
* Returns a pair whose first component is the leg number (one-based) of the competitor returned as the second component.
|
||||
*/
|
||||
private com.sap.sse.common.Util.Pair<Integer, CompetitorDTO> getLeadingVisibleCompetitorInfo(Iterable<CompetitorDTO> competitorsToShow) {
|
||||
private com.sap.sse.common.Util.Pair<Integer, CompetitorDTO> getLeadingVisibleCompetitorWithOneBasedLegNumber(
|
||||
Iterable<CompetitorDTO> competitorsToShow) {
|
||||
CompetitorDTO leadingCompetitorDTO = null;
|
||||
int legOfLeaderCompetitor = -1;
|
||||
// this only works because the quickRanks are sorted
|
||||
@@ -800,19 +858,24 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
|
||||
}
|
||||
|
||||
private void showAdvantageLine(Iterable<CompetitorDTO> competitorsToShow, Date date) {
|
||||
if (map != null && lastRaceTimesInfo != null && quickRanks != null && lastCombinedWindTrackInfoDTO != null
|
||||
&& !lastCombinedWindTrackInfoDTO.windFixes.isEmpty()) {
|
||||
if (map != null && lastRaceTimesInfo != null && quickRanks != null && lastCombinedWindTrackInfoDTO != null) {
|
||||
boolean drawAdvantageLine = false;
|
||||
if (settings.getHelpLinesSettings().isVisible(HelpLineTypes.ADVANTAGELINE)) {
|
||||
// find competitor with highest rank
|
||||
com.sap.sse.common.Util.Pair<Integer, CompetitorDTO> visibleLeaderInfo = getLeadingVisibleCompetitorInfo(competitorsToShow);
|
||||
com.sap.sse.common.Util.Pair<Integer, CompetitorDTO> visibleLeaderInfo = getLeadingVisibleCompetitorWithOneBasedLegNumber(competitorsToShow);
|
||||
// the boat fix may be null; may mean that no positions were loaded yet for the leading visible boat;
|
||||
// don't show anything
|
||||
GPSFixDTO lastBoatFix = null;
|
||||
boolean isVisibleLeaderInfoComplete = false;
|
||||
boolean isLegTypeKnown = false;
|
||||
if (visibleLeaderInfo != null && visibleLeaderInfo.getA() > 0
|
||||
&& visibleLeaderInfo.getA() <= lastRaceTimesInfo.getLegInfos().size()) {
|
||||
WindTrackInfoDTO windDataForLegMiddle = null;
|
||||
if (visibleLeaderInfo != null
|
||||
&& visibleLeaderInfo.getA() > 0
|
||||
&& visibleLeaderInfo.getA() <= lastRaceTimesInfo.getLegInfos().size()
|
||||
// get wind at middle of leg for leading visible competitor
|
||||
&& (windDataForLegMiddle = lastCombinedWindTrackInfoDTO
|
||||
.getCombinedWindOnLegMiddle(visibleLeaderInfo.getA() - 1)) != null
|
||||
&& !windDataForLegMiddle.windFixes.isEmpty()) {
|
||||
isVisibleLeaderInfoComplete = true;
|
||||
LegInfoDTO legInfoDTO = lastRaceTimesInfo.getLegInfos().get(visibleLeaderInfo.getA() - 1);
|
||||
if (legInfoDTO.legType != null) {
|
||||
@@ -824,13 +887,14 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
|
||||
LegInfoDTO legInfoDTO = lastRaceTimesInfo.getLegInfos().get(visibleLeaderInfo.getA() - 1);
|
||||
double advantageLineLengthInKm = 1.0; // TODO this should probably rather scale with the visible
|
||||
// area of the map; bug 616
|
||||
double distanceFromBoatPositionInKm = visibleLeaderInfo.getB().getBoatClass().getHullLengthInMeters() / 1000.; // one hull length
|
||||
double distanceFromBoatPositionInKm = visibleLeaderInfo.getB().getBoatClass()
|
||||
.getHullLengthInMeters() / 1000.; // one hull length
|
||||
// implement and use Position.translateRhumb()
|
||||
double bearingOfBoatInDeg = lastBoatFix.speedWithBearing.bearingInDegrees;
|
||||
LatLng boatPosition = LatLng.newInstance(lastBoatFix.position.latDeg, lastBoatFix.position.lngDeg);
|
||||
LatLng posAheadOfFirstBoat = calculatePositionAlongRhumbline(boatPosition, bearingOfBoatInDeg,
|
||||
distanceFromBoatPositionInKm);
|
||||
final WindDTO windFix = lastCombinedWindTrackInfoDTO.windFixes.get(0);
|
||||
final WindDTO windFix = windDataForLegMiddle.windFixes.get(0);
|
||||
double bearingOfCombinedWindInDeg = windFix.trueWindBearingDeg;
|
||||
double rotatedBearingDeg1 = 0.0;
|
||||
double rotatedBearingDeg2 = 0.0;
|
||||
@@ -877,9 +941,9 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
|
||||
pointsAsArray.insertAt(0, advantageLinePos1);
|
||||
pointsAsArray.insertAt(1, advantageLinePos2);
|
||||
advantageLine.setPath(pointsAsArray);
|
||||
|
||||
advantageLineMouseOverHandler = new AdvantageLineMouseOverMapHandler(bearingOfCombinedWindInDeg,
|
||||
new Date(windFix.measureTimepoint));
|
||||
|
||||
advantageLineMouseOverHandler = new AdvantageLineMouseOverMapHandler(
|
||||
bearingOfCombinedWindInDeg, new Date(windFix.measureTimepoint));
|
||||
advantageLine.addMouseOverHandler(advantageLineMouseOverHandler);
|
||||
advantageLine.addMouseOutMoveHandler(new MouseOutMapHandler() {
|
||||
@Override
|
||||
@@ -919,7 +983,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
|
||||
|
||||
private void showStartAndFinishLines(final CoursePositionsDTO courseDTO) {
|
||||
if (map != null && courseDTO != null && lastRaceTimesInfo != null) {
|
||||
com.sap.sse.common.Util.Pair<Integer, CompetitorDTO> leadingVisibleCompetitorInfo = getLeadingVisibleCompetitorInfo(getCompetitorsToShow());
|
||||
com.sap.sse.common.Util.Pair<Integer, CompetitorDTO> leadingVisibleCompetitorInfo = getLeadingVisibleCompetitorWithOneBasedLegNumber(getCompetitorsToShow());
|
||||
int legOfLeadingCompetitor = leadingVisibleCompetitorInfo == null ? -1 : leadingVisibleCompetitorInfo.getA();
|
||||
int numberOfLegs = lastRaceTimesInfo.legInfos.size();
|
||||
// draw the start line
|
||||
@@ -1508,9 +1572,20 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
|
||||
double factorForBefore = 1-factorForAfter;
|
||||
PositionDTO betweenPosition = new PositionDTO(factorForBefore*fixBefore.position.latDeg + factorForAfter*fixAfter.position.latDeg,
|
||||
factorForBefore*fixBefore.position.lngDeg + factorForAfter*fixAfter.position.lngDeg);
|
||||
double betweenBearing = new ScalableBearing(new DegreeBearingImpl(fixBefore.speedWithBearing.bearingInDegrees)).
|
||||
multiply(factorForBefore).add(new ScalableBearing(new DegreeBearingImpl(fixAfter.speedWithBearing.bearingInDegrees)).
|
||||
multiply(factorForAfter)).divide(1).getDegrees();
|
||||
final double betweenBearing;
|
||||
if (fixBefore.speedWithBearing == null) {
|
||||
if (fixAfter.speedWithBearing == null) {
|
||||
betweenBearing = 0;
|
||||
} else {
|
||||
betweenBearing = fixAfter.speedWithBearing.bearingInDegrees;
|
||||
}
|
||||
} else if (fixAfter.speedWithBearing == null) {
|
||||
betweenBearing = fixBefore.speedWithBearing.bearingInDegrees;
|
||||
} else {
|
||||
betweenBearing = new ScalableBearing(new DegreeBearingImpl(fixBefore.speedWithBearing.bearingInDegrees)).
|
||||
multiply(factorForBefore).add(new ScalableBearing(new DegreeBearingImpl(fixAfter.speedWithBearing.bearingInDegrees)).
|
||||
multiply(factorForAfter)).divide(1).getDegrees();
|
||||
}
|
||||
SpeedWithBearingDTO betweenSpeed = new SpeedWithBearingDTO(
|
||||
factorForBefore*fixBefore.speedWithBearing.speedInKnots + factorForAfter*fixAfter.speedWithBearing.speedInKnots,
|
||||
betweenBearing);
|
||||
|
||||
+2
@@ -22,6 +22,8 @@ public class ScoringSchemeTypeFormatter {
|
||||
return stringMessages.scoringSchemeLowPointWinnerGetsZero();
|
||||
case HIGH_POINT_WINNER_GETS_FIVE:
|
||||
return stringMessages.scoringSchemeWinnerGetsFive();
|
||||
case HIGH_POINT_WINNER_GETS_FIVE_IGNORING_RACE_COUNT:
|
||||
return stringMessages.scoringSchemeWinnerGetsFiveIgnoringRaceCount();
|
||||
case HIGH_POINT_WINNER_GETS_SIX:
|
||||
return stringMessages.scoringSchemeWinnerGetsSix();
|
||||
}
|
||||
|
||||
+7
-1
@@ -271,9 +271,15 @@ public class RaceBoardPanel extends SimplePanel implements RegattasDisplayer, Ra
|
||||
return filterSets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the media selector drop down and fills it with media for the current race.
|
||||
* NOTE:
|
||||
* Media selector will NOT listen and react to changes of the start time during a race board session.
|
||||
* This case and its consequences are considered negligible.
|
||||
* Consequently, media selector will also NOT react on media added newly during a race board session.
|
||||
*/
|
||||
private void addMediaSelectorToNavigationMenu() {
|
||||
MediaSelector mediaSelector = new MediaSelector(selectedRaceIdentifier, raceTimesInfoProvider, timer, mediaService, stringMessages, errorReporter, this.user, getConfiguration().isAutoSelectMedia());
|
||||
// raceTimesInfoProvider.addRaceTimesInfoProviderListener(mediaSelector);
|
||||
timer.addPlayStateListener(mediaSelector);
|
||||
timer.addTimeListener(mediaSelector);
|
||||
mediaService.getMediaTracksForRace(selectedRaceIdentifier, mediaSelector);
|
||||
|
||||
+77
-46
@@ -1281,12 +1281,12 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
*/
|
||||
@Override
|
||||
public WindInfoForRaceDTO getAveragedWindInfo(RegattaAndRaceIdentifier raceIdentifier, Date from, long millisecondsStepWidth,
|
||||
int numberOfFixes, Collection<String> windSourceTypeNames, boolean onlyUpToNewestEvent)
|
||||
int numberOfFixes, Collection<String> windSourceTypeNames, boolean onlyUpToNewestEvent, boolean includeCombinedWindForAllLegMiddles)
|
||||
throws NoWindException {
|
||||
assert from != null;
|
||||
TrackedRace trackedRace = getExistingTrackedRace(raceIdentifier);
|
||||
WindInfoForRaceDTO result = getAveragedWindInfo(new MillisecondsTimePoint(from), millisecondsStepWidth, numberOfFixes,
|
||||
windSourceTypeNames, trackedRace, /* onlyUpToNewestEvent */ true);
|
||||
windSourceTypeNames, trackedRace, /* onlyUpToNewestEvent */ true, includeCombinedWindForAllLegMiddles);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1298,9 +1298,13 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
* "best effort" readings are provided for the time interval requested, no matter if based on any sensor
|
||||
* evidence or not, regardless of {@link TrackedRace#getTimePointOfNewestEvent()
|
||||
* trackedRace.getTimePointOfNewestEvent()}.
|
||||
* @param includeCombinedWindForAllLegMiddles
|
||||
* if <code>true</code>, the result will return non-<code>null</code> results for calls to
|
||||
* {@link WindInfoForRaceDTO#getCombinedWindOnLegMiddle(int)}.
|
||||
*/
|
||||
private WindInfoForRaceDTO getAveragedWindInfo(TimePoint from, long millisecondsStepWidth, int numberOfFixes,
|
||||
Collection<String> windSourceTypeNames, TrackedRace trackedRace, boolean onlyUpToNewestEvent) {
|
||||
Collection<String> windSourceTypeNames, TrackedRace trackedRace, boolean onlyUpToNewestEvent,
|
||||
boolean includeCombinedWindForAllLegMiddles) {
|
||||
WindInfoForRaceDTO result = null;
|
||||
if (trackedRace != null) {
|
||||
TimePoint newestEvent = trackedRace.getTimePointOfNewestEvent();
|
||||
@@ -1315,51 +1319,75 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
result.windTrackInfoByWindSource = windTrackInfoDTOs;
|
||||
List<WindSource> windSourcesToDeliver = new ArrayList<WindSource>();
|
||||
Util.addAll(trackedRace.getWindSources(), windSourcesToDeliver);
|
||||
windSourcesToDeliver.add(new WindSourceImpl(WindSourceType.COMBINED));
|
||||
final WindSource combinedWindSource = new WindSourceImpl(WindSourceType.COMBINED);
|
||||
windSourcesToDeliver.add(combinedWindSource);
|
||||
for (WindSource windSource : windSourcesToDeliver) {
|
||||
// TODO consider parallelizing
|
||||
if (windSourceTypeNames == null || windSourceTypeNames.contains(windSource.getType().name())) {
|
||||
WindTrackInfoDTO windTrackInfoDTO = new WindTrackInfoDTO();
|
||||
WindTrackInfoDTO windTrackInfoDTO = createWindTrackInfoDTO(from, millisecondsStepWidth,
|
||||
numberOfFixes, trackedRace, onlyUpToNewestEvent, newestEvent, windSource,
|
||||
new PositionAtTimeProvider() { @Override public Position getPosition(TimePoint at) { return null; }});
|
||||
windTrackInfoDTOs.put(windSource, windTrackInfoDTO);
|
||||
windTrackInfoDTO.windFixes = new ArrayList<WindDTO>();
|
||||
WindTrack windTrack = trackedRace.getOrCreateWindTrack(windSource);
|
||||
windTrackInfoDTO.dampeningIntervalInMilliseconds = windTrack
|
||||
.getMillisecondsOverWhichToAverageWind();
|
||||
TimePoint timePoint = from;
|
||||
Double minWindConfidence = 2.0;
|
||||
Double maxWindConfidence = -1.0;
|
||||
for (int i = 0; i < numberOfFixes && (!onlyUpToNewestEvent ||
|
||||
(newestEvent != null && timePoint.before(newestEvent))); i++) {
|
||||
WindWithConfidence<com.sap.sse.common.Util.Pair<Position, TimePoint>> averagedWindWithConfidence = windTrack.getAveragedWindWithConfidence(null, timePoint);
|
||||
if (averagedWindWithConfidence != null) {
|
||||
if (logger.getLevel() != null && logger.getLevel().equals(Level.FINEST)) {
|
||||
logger.finest("Found averaged wind: " + averagedWindWithConfidence);
|
||||
}
|
||||
double confidence = averagedWindWithConfidence.getConfidence();
|
||||
WindDTO windDTO = createWindDTOFromAlreadyAveraged(averagedWindWithConfidence.getObject(), timePoint);
|
||||
windDTO.confidence = confidence;
|
||||
windTrackInfoDTO.windFixes.add(windDTO);
|
||||
if (confidence < minWindConfidence) {
|
||||
minWindConfidence = confidence;
|
||||
}
|
||||
if (confidence > maxWindConfidence) {
|
||||
maxWindConfidence = confidence;
|
||||
}
|
||||
} else {
|
||||
if (logger.getLevel() != null && logger.getLevel().equals(Level.FINEST)) {
|
||||
logger.finest("Did NOT find any averaged wind for timepoint " + timePoint + " and tracked race " + trackedRace.getRaceIdentifier().getRaceName());
|
||||
}
|
||||
}
|
||||
timePoint = new MillisecondsTimePoint(timePoint.asMillis() + millisecondsStepWidth);
|
||||
}
|
||||
windTrackInfoDTO.minWindConfidence = minWindConfidence;
|
||||
windTrackInfoDTO.maxWindConfidence = maxWindConfidence;
|
||||
}
|
||||
}
|
||||
if (includeCombinedWindForAllLegMiddles) {
|
||||
int zeroBasedLegNumber = 0;
|
||||
for (final TrackedLeg trackedLeg : trackedRace.getTrackedLegs()) {
|
||||
WindTrackInfoDTO windTrackInfoForLegMiddle = createWindTrackInfoDTO(from, millisecondsStepWidth,
|
||||
numberOfFixes, trackedRace, onlyUpToNewestEvent, newestEvent, combinedWindSource,
|
||||
new PositionAtTimeProvider() { @Override public Position getPosition(TimePoint at) { return trackedLeg.getMiddleOfLeg(at); }});
|
||||
result.addWindOnLegMiddle(zeroBasedLegNumber, windTrackInfoForLegMiddle);
|
||||
zeroBasedLegNumber++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private interface PositionAtTimeProvider {
|
||||
Position getPosition(TimePoint at);
|
||||
}
|
||||
|
||||
private WindTrackInfoDTO createWindTrackInfoDTO(TimePoint from, long millisecondsStepWidth, int numberOfFixes,
|
||||
TrackedRace trackedRace, boolean onlyUpToNewestEvent, TimePoint newestEvent, WindSource windSource,
|
||||
PositionAtTimeProvider positionProvider) {
|
||||
WindTrack windTrack = trackedRace.getOrCreateWindTrack(windSource);
|
||||
WindTrackInfoDTO windTrackInfoDTO = new WindTrackInfoDTO();
|
||||
windTrackInfoDTO.windFixes = new ArrayList<WindDTO>();
|
||||
windTrackInfoDTO.dampeningIntervalInMilliseconds = windTrack.getMillisecondsOverWhichToAverageWind();
|
||||
TimePoint timePoint = from;
|
||||
Double minWindConfidence = 2.0;
|
||||
Double maxWindConfidence = -1.0;
|
||||
for (int i = 0; i < numberOfFixes && (!onlyUpToNewestEvent ||
|
||||
(newestEvent != null && timePoint.before(newestEvent))); i++) {
|
||||
WindWithConfidence<com.sap.sse.common.Util.Pair<Position, TimePoint>> averagedWindWithConfidence =
|
||||
windTrack.getAveragedWindWithConfidence(positionProvider.getPosition(timePoint), timePoint);
|
||||
if (averagedWindWithConfidence != null) {
|
||||
if (logger.getLevel() != null && logger.getLevel().equals(Level.FINEST)) {
|
||||
logger.finest("Found averaged wind: " + averagedWindWithConfidence);
|
||||
}
|
||||
double confidence = averagedWindWithConfidence.getConfidence();
|
||||
WindDTO windDTO = createWindDTOFromAlreadyAveraged(averagedWindWithConfidence.getObject(), timePoint);
|
||||
windDTO.confidence = confidence;
|
||||
windTrackInfoDTO.windFixes.add(windDTO);
|
||||
if (confidence < minWindConfidence) {
|
||||
minWindConfidence = confidence;
|
||||
}
|
||||
if (confidence > maxWindConfidence) {
|
||||
maxWindConfidence = confidence;
|
||||
}
|
||||
} else {
|
||||
if (logger.getLevel() != null && logger.getLevel().equals(Level.FINEST)) {
|
||||
logger.finest("Did NOT find any averaged wind for timepoint " + timePoint + " and tracked race " + trackedRace.getRaceIdentifier().getRaceName());
|
||||
}
|
||||
}
|
||||
timePoint = new MillisecondsTimePoint(timePoint.asMillis() + millisecondsStepWidth);
|
||||
}
|
||||
windTrackInfoDTO.minWindConfidence = minWindConfidence;
|
||||
windTrackInfoDTO.maxWindConfidence = maxWindConfidence;
|
||||
return windTrackInfoDTO;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param to
|
||||
* if <code>null</code>, data is returned up to end of race; if the end of race is not known and
|
||||
@@ -1379,11 +1407,12 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
WindInfoForRaceDTO result = null;
|
||||
if (trackedRace != null) {
|
||||
TimePoint fromTimePoint = from == null ? trackedRace.getStartOfTracking() : new MillisecondsTimePoint(from);
|
||||
TimePoint toTimePoint = to == null ? trackedRace.getEndOfRace() : new MillisecondsTimePoint(to);
|
||||
TimePoint toTimePoint = to == null ? trackedRace.getEndOfRace() == null ?
|
||||
MillisecondsTimePoint.now().minus(trackedRace.getDelayToLiveInMillis()) : trackedRace.getEndOfRace() : new MillisecondsTimePoint(to);
|
||||
if (fromTimePoint != null && toTimePoint != null) {
|
||||
int numberOfFixes = (int) ((toTimePoint.asMillis() - fromTimePoint.asMillis())/resolutionInMilliseconds);
|
||||
result = getAveragedWindInfo(fromTimePoint, resolutionInMilliseconds, numberOfFixes,
|
||||
windSourceTypeNames, trackedRace, onlyUpToNewestEvent);
|
||||
windSourceTypeNames, trackedRace, onlyUpToNewestEvent, /* includeCombinedWindForAllLegMiddles */ false);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
@@ -1486,8 +1515,8 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
Iterator<GPSFixMoving> fixIter = track.getFixesIterator(fromTimePoint, /* inclusive */true);
|
||||
while (fixIter.hasNext()) {
|
||||
GPSFixMoving fix = fixIter.next();
|
||||
if (fix.getTimePoint().compareTo(toTimePointExcluding) < 0) {
|
||||
if (logger.getLevel() != null && logger.getLevel().equals(Level.FINEST)) {
|
||||
if (fix.getTimePoint().before(toTimePointExcluding)) {
|
||||
if (logger.isLoggable(Level.FINEST)) {
|
||||
logger.finest(""+competitor.getName()+": " + fix);
|
||||
}
|
||||
fixes.add(fix);
|
||||
@@ -1514,7 +1543,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
Iterator<GPSFixMoving> fixIter = fixes.iterator();
|
||||
if (fixIter.hasNext()) {
|
||||
GPSFixMoving fix = fixIter.next();
|
||||
while (fix != null && (fix.getTimePoint().compareTo(toTimePointExcluding) < 0 ||
|
||||
while (fix != null && (fix.getTimePoint().before(toTimePointExcluding) ||
|
||||
(fix.getTimePoint().equals(toTimePointExcluding) && toTimePointExcluding.equals(fromTimePoint)))) {
|
||||
Wind wind = trackedRace.getWind(fix.getPosition(), fix.getTimePoint());
|
||||
final SpeedWithBearing estimatedSpeed = track.getEstimatedSpeed(fix.getTimePoint());
|
||||
@@ -2491,7 +2520,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
try {
|
||||
TrackedLegOfCompetitor trackedLeg = trackedRace.getTrackedLeg(competitor, timePoint);
|
||||
switch (dataType) {
|
||||
case CURRENT_SPEED_OVER_GROUND_IN_KNOTS:
|
||||
case RACE_CURRENT_SPEED_OVER_GROUND_IN_KNOTS:
|
||||
final GPSFixTrack<Competitor, GPSFixMoving> track = trackedRace.getTrack(competitor);
|
||||
if (track != null) {
|
||||
SpeedWithBearing speedOverGround = track.getEstimatedSpeed(timePoint);
|
||||
@@ -2954,8 +2983,10 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
for (Map.Entry<Class<? extends RacingEventServiceOperation<?>>, Integer> e : statistics.entrySet()) {
|
||||
replicationCountByOperationClassName.put(e.getKey().getName(), e.getValue());
|
||||
}
|
||||
replicaDTOs.add(new ReplicaDTO(replicaDescriptor.getIpAddress().getHostName(), replicaDescriptor.getRegistrationTime().asDate(), replicaDescriptor.getUuid().toString(),
|
||||
replicationCountByOperationClassName));
|
||||
replicaDTOs.add(new ReplicaDTO(replicaDescriptor.getIpAddress().getHostName(), replicaDescriptor
|
||||
.getRegistrationTime().asDate(), replicaDescriptor.getUuid().toString(),
|
||||
replicationCountByOperationClassName, service.getAverageNumberOfOperationsPerMessage(replicaDescriptor),
|
||||
service.getNumberOfMessagesSent(replicaDescriptor), service.getNumberOfBytesSent(replicaDescriptor), service.getAverageNumberOfBytesPerMessage(replicaDescriptor)));
|
||||
}
|
||||
ReplicationMasterDTO master;
|
||||
ReplicationMasterDescriptor replicatingFromMaster = service.isReplicatingFromMaster();
|
||||
|
||||
+22
-1
@@ -10,13 +10,22 @@ public class ReplicaDTO implements IsSerializable {
|
||||
private String identifier;
|
||||
private Date registrationTime;
|
||||
private Map<String, Integer> operationCountByOperationClassName;
|
||||
private double averageNumberOfOperationsPerMessage;
|
||||
private long numberOfMessagesSent;
|
||||
private double averageMessageSizeInBytes;
|
||||
private long totalNumberOfBytesSent;
|
||||
ReplicaDTO() {}
|
||||
public ReplicaDTO(String hostname, Date registrationTime,
|
||||
String identifier, Map<String, Integer> operationCountByOperationClassName) {
|
||||
String identifier, Map<String, Integer> operationCountByOperationClassName, double averageNumberOfOperationsPerMessage,
|
||||
long numberOfMessagesSent, long totalNumberOfBytesSent, double averageMessageSizeInBytes) {
|
||||
this.hostname = hostname;
|
||||
this.identifier = identifier;
|
||||
this.registrationTime = registrationTime;
|
||||
this.operationCountByOperationClassName = operationCountByOperationClassName;
|
||||
this.averageNumberOfOperationsPerMessage = averageNumberOfOperationsPerMessage;
|
||||
this.numberOfMessagesSent = numberOfMessagesSent;
|
||||
this.averageMessageSizeInBytes = averageMessageSizeInBytes;
|
||||
this.totalNumberOfBytesSent = totalNumberOfBytesSent;
|
||||
}
|
||||
public String getHostname() {
|
||||
return hostname;
|
||||
@@ -27,6 +36,18 @@ public class ReplicaDTO implements IsSerializable {
|
||||
public Map<String, Integer> getOperationCountByOperationClassName() {
|
||||
return operationCountByOperationClassName;
|
||||
}
|
||||
public double getAverageNumberOfOperationsPerMessage() {
|
||||
return averageNumberOfOperationsPerMessage;
|
||||
}
|
||||
public long getNumberOfMessagesSent() {
|
||||
return numberOfMessagesSent;
|
||||
}
|
||||
public long getNumberOfBytesSent() {
|
||||
return totalNumberOfBytesSent;
|
||||
}
|
||||
public double getAverageMessageSizeInBytes() {
|
||||
return averageMessageSizeInBytes;
|
||||
}
|
||||
public String getIdentifier() {
|
||||
return identifier;
|
||||
}
|
||||
|
||||
+11
@@ -1,5 +1,6 @@
|
||||
package com.sap.sailing.gwt.ui.shared;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.gwt.user.client.rpc.IsSerializable;
|
||||
@@ -10,5 +11,15 @@ public class WindInfoForRaceDTO implements IsSerializable {
|
||||
public boolean raceIsKnownToStartUpwind;
|
||||
public Iterable<WindSource> windSourcesToExclude;
|
||||
|
||||
private Map<Integer, WindTrackInfoDTO> combinedWindOnLegMiddle = new HashMap<>();
|
||||
|
||||
public WindInfoForRaceDTO() {}
|
||||
|
||||
public WindTrackInfoDTO getCombinedWindOnLegMiddle(int zeroBasedLegNumber) {
|
||||
return combinedWindOnLegMiddle.get(zeroBasedLegNumber);
|
||||
}
|
||||
|
||||
public void addWindOnLegMiddle(int zeroBasedLegNumber, WindTrackInfoDTO windTrackInfoForLegMiddle) {
|
||||
combinedWindOnLegMiddle.put(zeroBasedLegNumber, windTrackInfoForLegMiddle);
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -54,7 +54,8 @@ public class MarkVectorGraphics {
|
||||
}
|
||||
|
||||
protected void drawMark(Context2d ctx, boolean isSelected, String color) {
|
||||
switch(type) {
|
||||
final MarkType markType = type==null ? MarkType.BUOY : type;
|
||||
switch(markType) {
|
||||
case BUOY:
|
||||
if(shape != null) {
|
||||
if(Shape.CYLINDER.name().equalsIgnoreCase(shape) && pattern != null && Pattern.CHECKERED.name().equalsIgnoreCase(pattern)) {
|
||||
|
||||
+2
-2
@@ -6,8 +6,8 @@ public class StreamletParameters {
|
||||
public double motionScale = 1.0;
|
||||
public double swarmScale = 1.0;
|
||||
public int detailZoom = 15;
|
||||
public double lineScale = 1.0;
|
||||
public double lineBase = 1.5;
|
||||
public double lineScale = 0.1;
|
||||
public double lineBase = 1.2;
|
||||
|
||||
public StreamletParameters() {
|
||||
}
|
||||
|
||||
+2
-1
@@ -74,7 +74,8 @@ public class WindStreamletsCanvasOverlay extends FullCanvasOverlay implements Ti
|
||||
}
|
||||
|
||||
public void setWindField(final WindFieldDTO windFieldDTO) {
|
||||
this.windFieldDTO = windFieldDTO;
|
||||
this.windFieldDTO = windFieldDTO;
|
||||
this.swarm = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+5
-5
@@ -31,9 +31,9 @@ public class SimulatorField implements VectorField {
|
||||
private final Position bdC;
|
||||
|
||||
private final double xScale;
|
||||
private double motionFactor;
|
||||
private double lineBase;
|
||||
private double lineScale;
|
||||
private double motionFactor;
|
||||
private double lineBase;
|
||||
private double lineScale;
|
||||
private final double maxLength;
|
||||
private final double particleFactor;
|
||||
|
||||
@@ -100,7 +100,7 @@ public class SimulatorField implements VectorField {
|
||||
}
|
||||
}
|
||||
this.maxLength = maxWindSpeed;
|
||||
this.particleFactor = 2.0;
|
||||
this.particleFactor = 1.0;
|
||||
final double latAvg = (this.rcEnd.getLatDeg() + this.rcStart.getLatDeg()) / 2.;
|
||||
this.lngScale = Math.cos(latAvg * Math.PI / 180.0);
|
||||
final double difLat = this.rcEnd.getLatDeg() - this.rcStart.getLatDeg();
|
||||
@@ -151,7 +151,7 @@ public class SimulatorField implements VectorField {
|
||||
+ dataAtStep[idx.yTop][2 * idx.xBot + 1] * idx.yMod * (1 - idx.xMod)
|
||||
+ dataAtStep[idx.yBot][2 * idx.xTop + 1] * (1 - idx.yMod) * idx.xMod
|
||||
+ dataAtStep[idx.yTop][2 * idx.xTop + 1] * idx.yMod * idx.xMod;
|
||||
return new Vector(avgX / this.lngScale, avgY);
|
||||
return new Vector(avgX, avgY);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+4
-7
@@ -135,8 +135,7 @@ public class TestStoringAndRetrievingLeaderboardGroups extends AbstractMongoDBTe
|
||||
FlexibleLeaderboard leaderboard = new FlexibleLeaderboardImpl(leaderboardNames[0], new ThresholdBasedResultDiscardingRuleImpl(discardIndexResultsStartingWithHowManyRaces),
|
||||
new LowPoint(), null);
|
||||
racingEventService.addLeaderboard(leaderboard);
|
||||
leaderboard.addRace(raceWithTwoCompetitors, raceColumnName1, /* medalRace */ false,
|
||||
leaderboard.getFleet(null));
|
||||
leaderboard.addRace(raceWithTwoCompetitors, raceColumnName1, /* medalRace */false);
|
||||
leaderboard = new FlexibleLeaderboardImpl(leaderboardNames[1], new ThresholdBasedResultDiscardingRuleImpl(discardIndexResultsStartingWithHowManyRaces),
|
||||
new LowPoint(), null);
|
||||
racingEventService.addLeaderboard(leaderboard);
|
||||
@@ -192,8 +191,7 @@ public class TestStoringAndRetrievingLeaderboardGroups extends AbstractMongoDBTe
|
||||
FlexibleLeaderboard leaderboard = new FlexibleLeaderboardImpl(leaderboardNames[0], new ThresholdBasedResultDiscardingRuleImpl(discardIndexResultsStartingWithHowManyRaces),
|
||||
new LowPoint(), null);
|
||||
leaderboards.add(leaderboard);
|
||||
leaderboard.addRace(raceWithTwoCompetitors, raceColumnName1, /* medalRace */ false,
|
||||
leaderboard.getFleet(null));
|
||||
leaderboard.addRace(raceWithTwoCompetitors, raceColumnName1, /* medalRace */false);
|
||||
leaderboard = new FlexibleLeaderboardImpl(leaderboardNames[1], new ThresholdBasedResultDiscardingRuleImpl(discardIndexResultsStartingWithHowManyRaces),
|
||||
new LowPoint(), null);
|
||||
leaderboards.add(leaderboard);
|
||||
@@ -249,8 +247,7 @@ public class TestStoringAndRetrievingLeaderboardGroups extends AbstractMongoDBTe
|
||||
FlexibleLeaderboard leaderboard = new FlexibleLeaderboardImpl(leaderboardNames[0], new ThresholdBasedResultDiscardingRuleImpl(discardIndexResultsStartingWithHowManyRaces),
|
||||
new LowPoint(), null);
|
||||
leaderboards.add(leaderboard);
|
||||
leaderboard.addRace(raceWithTwoCompetitors, raceColumnName1, /* medalRace */ false,
|
||||
leaderboard.getFleet(null));
|
||||
leaderboard.addRace(raceWithTwoCompetitors, raceColumnName1, /* medalRace */false);
|
||||
leaderboard = new FlexibleLeaderboardImpl(leaderboardNames[1], new ThresholdBasedResultDiscardingRuleImpl(discardIndexResultsStartingWithHowManyRaces),
|
||||
new LowPoint(), null);
|
||||
leaderboards.add(leaderboard);
|
||||
@@ -435,7 +432,7 @@ public class TestStoringAndRetrievingLeaderboardGroups extends AbstractMongoDBTe
|
||||
|
||||
final FlexibleLeaderboard leaderboard = new FlexibleLeaderboardImpl(leaderboardName, new ThresholdBasedResultDiscardingRuleImpl(discardIndexResultsStartingWithHowManyRaces), new LowPoint(), null);
|
||||
final Fleet fleet = leaderboard.getFleet(null);
|
||||
final RaceColumn race = leaderboard.addRaceColumn(columnName, false, fleet);
|
||||
final RaceColumn race = leaderboard.addRaceColumn(columnName, false);
|
||||
leaderboards.add(leaderboard);
|
||||
|
||||
final LeaderboardGroup group = new LeaderboardGroupImpl(groupName, groupDescription, false, leaderboards);
|
||||
|
||||
+29
-45
@@ -84,7 +84,7 @@ public class TestStoringAndRetrievingLeaderboards extends AbstractMongoDBTest {
|
||||
final String raceColumnName1 = "My First Race 1";
|
||||
MockedTrackedRaceWithFixedRankAndManyCompetitors raceWithTwoCompetitors = new MockedTrackedRaceWithFixedRankAndManyCompetitors(wolfgang, /* rank */ 1, /* started */ true);
|
||||
raceWithTwoCompetitors.addCompetitor(hasso);
|
||||
leaderboard.addRace(raceWithTwoCompetitors, raceColumnName1, /* medalRace */ false, leaderboard.getFleet(null));
|
||||
leaderboard.addRace(raceWithTwoCompetitors, raceColumnName1, /* medalRace */ false);
|
||||
leaderboard.setSuppressed(wolfgang, true);
|
||||
assertTrue(Util.contains(leaderboard.getSuppressedCompetitors(), wolfgang));
|
||||
assertFalse(Util.contains(leaderboard.getSuppressedCompetitors(), hasso));
|
||||
@@ -121,14 +121,14 @@ public class TestStoringAndRetrievingLeaderboards extends AbstractMongoDBTest {
|
||||
Competitor competitor = createCompetitor();
|
||||
TrackedRace raceWithOneCompetitor1 = new MockedTrackedRaceWithFixedRank(competitor, /* rank */ 1, /* started */ true);
|
||||
final String raceColumnName1 = "My First Race 1";
|
||||
leaderboard.addRace(raceWithOneCompetitor1, raceColumnName1, /* medalRace */ false, leaderboard.getFleet(null));
|
||||
leaderboard.addRace(raceWithOneCompetitor1, raceColumnName1, /* medalRace */ false);
|
||||
final String displayName = "$$$ ... The Renamed Competitor ... $$$";
|
||||
leaderboard.setDisplayName(competitor, displayName);
|
||||
new MongoObjectFactoryImpl(db).storeLeaderboard(leaderboard);
|
||||
FlexibleLeaderboard loadedLeaderboard = (FlexibleLeaderboard) new DomainObjectFactoryImpl(db, DomainFactory.INSTANCE).loadLeaderboard(
|
||||
leaderboardName, /* regattaRegistry */null);
|
||||
// attach tracked race to leaderboard to ensure that competitor object is assigned properly
|
||||
loadedLeaderboard.addRace(raceWithOneCompetitor1, raceColumnName1, /* medalRace, ignored */ false, leaderboard.getFleet(null));
|
||||
loadedLeaderboard.addRace(raceWithOneCompetitor1, raceColumnName1, /* medalRace, ignored */ false);
|
||||
Competitor loadedCompetitor = loadedLeaderboard.getCompetitorByName(competitor.getName());
|
||||
assertEquals(displayName, loadedLeaderboard.getDisplayName(loadedCompetitor));
|
||||
}
|
||||
@@ -168,12 +168,12 @@ public class TestStoringAndRetrievingLeaderboards extends AbstractMongoDBTest {
|
||||
new LowPoint(), null);
|
||||
Competitor competitor = createCompetitor();
|
||||
TrackedRace raceWithOneCompetitor = new MockedTrackedRaceWithFixedRank(competitor, /* rank */ 1, /* started */ true);
|
||||
leaderboard.addRace(raceWithOneCompetitor, raceColumnName, /* medalRace */ false, leaderboard.getFleet(null));
|
||||
leaderboard.addRace(raceWithOneCompetitor, raceColumnName, /* medalRace */ false);
|
||||
leaderboard.setCarriedPoints(competitor, carriedPointsForWolfgangHunger);
|
||||
new MongoObjectFactoryImpl(db).storeLeaderboard(leaderboard);
|
||||
FlexibleLeaderboard loadedLeaderboard = (FlexibleLeaderboard) new DomainObjectFactoryImpl(db, DomainFactory.INSTANCE).loadLeaderboard(leaderboardName, /* regattaRegistry */ null);
|
||||
// attach tracked race to leaderboard to ensure that competitor object is assigned properly
|
||||
loadedLeaderboard.addRace(raceWithOneCompetitor, raceColumnName, /* medalRace, ignored */ false, leaderboard.getFleet(null));
|
||||
loadedLeaderboard.addRace(raceWithOneCompetitor, raceColumnName, /* medalRace, ignored */ false);
|
||||
assertEquals(leaderboardName, loadedLeaderboard.getName());
|
||||
assertTrue(Arrays.equals(discardIndexResultsStartingWithHowManyRaces,
|
||||
((ThresholdBasedResultDiscardingRule) loadedLeaderboard.getResultDiscardingRule()).getDiscardIndexResultsStartingWithHowManyRaces()));
|
||||
@@ -194,16 +194,14 @@ public class TestStoringAndRetrievingLeaderboards extends AbstractMongoDBTest {
|
||||
Competitor competitor = createCompetitor();
|
||||
TrackedRace raceWithOneCompetitor1 = new MockedTrackedRaceWithFixedRank(competitor, /* rank */ 1, /* started */ true);
|
||||
TrackedRace raceWithOneCompetitor2 = new MockedTrackedRaceWithFixedRank(competitor, /* rank */ 2, /* started */ true);
|
||||
leaderboard.addRace(raceWithOneCompetitor1, raceColumnName1, /* medalRace */ false, leaderboard.getFleet(null));
|
||||
leaderboard.addRace(raceWithOneCompetitor2, raceColumnName2, /* medalRace */ true, leaderboard.getFleet(null));
|
||||
leaderboard.addRace(raceWithOneCompetitor1, raceColumnName1, /* medalRace */ false);
|
||||
leaderboard.addRace(raceWithOneCompetitor2, raceColumnName2, /* medalRace */ true);
|
||||
leaderboard.getScoreCorrection().correctScore(competitor, leaderboard.getRaceColumnByName(raceColumnName1), correctedPoints);
|
||||
new MongoObjectFactoryImpl(db).storeLeaderboard(leaderboard);
|
||||
FlexibleLeaderboard loadedLeaderboard = (FlexibleLeaderboard) new DomainObjectFactoryImpl(db, DomainFactory.INSTANCE).loadLeaderboard(leaderboardName, /* regattaRegistry */ null);
|
||||
// attach tracked race to leaderboard to ensure that competitor object is assigned properly
|
||||
RaceColumn loadedColumn1 = loadedLeaderboard.addRace(raceWithOneCompetitor1, raceColumnName1, /* medalRace, ignored */ false,
|
||||
leaderboard.getFleet(null));
|
||||
RaceColumn loadedColumn2 = loadedLeaderboard.addRace(raceWithOneCompetitor2, raceColumnName2, /* medalRace, ignored */ false,
|
||||
leaderboard.getFleet(null));
|
||||
RaceColumn loadedColumn1 = loadedLeaderboard.addRace(raceWithOneCompetitor1, raceColumnName1, /* medalRace, ignored */ false);
|
||||
RaceColumn loadedColumn2 = loadedLeaderboard.addRace(raceWithOneCompetitor2, raceColumnName2, /* medalRace, ignored */ false);
|
||||
assertEquals(leaderboardName, loadedLeaderboard.getName());
|
||||
assertTrue(loadedLeaderboard.getScoreCorrection().isScoreCorrected(competitor, loadedColumn1, MillisecondsTimePoint.now()));
|
||||
assertEquals(correctedPoints, (double) loadedLeaderboard.getScoreCorrection().getExplicitScoreCorrection(competitor, loadedColumn1), 0.00000001);
|
||||
@@ -222,14 +220,14 @@ public class TestStoringAndRetrievingLeaderboards extends AbstractMongoDBTest {
|
||||
Competitor competitor = createCompetitor();
|
||||
TrackedRace raceWithOneCompetitor1 = new MockedTrackedRaceWithFixedRank(competitor, /* rank */ 1, /* started */ true);
|
||||
TrackedRace raceWithOneCompetitor2 = new MockedTrackedRaceWithFixedRank(competitor, /* rank */ 2, /* started */ true);
|
||||
leaderboard.addRace(raceWithOneCompetitor1, raceColumnName1, /* medalRace */ false, leaderboard.getFleet(null));
|
||||
leaderboard.addRace(raceWithOneCompetitor2, raceColumnName2, /* medalRace */ true, leaderboard.getFleet(null));
|
||||
leaderboard.addRace(raceWithOneCompetitor1, raceColumnName1, /* medalRace */ false);
|
||||
leaderboard.addRace(raceWithOneCompetitor2, raceColumnName2, /* medalRace */ true);
|
||||
leaderboard.getScoreCorrection().correctScore(competitor, leaderboard.getRaceColumnByName(raceColumnName1), correctedPoints);
|
||||
new MongoObjectFactoryImpl(db).storeLeaderboard(leaderboard);
|
||||
FlexibleLeaderboard loadedLeaderboard = (FlexibleLeaderboard) new DomainObjectFactoryImpl(db, DomainFactory.INSTANCE).loadLeaderboard(leaderboardName, /* regattaRegistry */ null);
|
||||
// attach tracked race to leaderboard to ensure that competitor object is assigned properly
|
||||
RaceColumn loadedColumn1 = loadedLeaderboard.addRace(raceWithOneCompetitor1, raceColumnName1, /* medalRace, ignored */ false, leaderboard.getFleet(null));
|
||||
RaceColumn loadedColumn2 = loadedLeaderboard.addRace(raceWithOneCompetitor2, raceColumnName2, /* medalRace, ignored */ false, leaderboard.getFleet(null));
|
||||
RaceColumn loadedColumn1 = loadedLeaderboard.addRace(raceWithOneCompetitor1, raceColumnName1, /* medalRace, ignored */ false);
|
||||
RaceColumn loadedColumn2 = loadedLeaderboard.addRace(raceWithOneCompetitor2, raceColumnName2, /* medalRace, ignored */ false);
|
||||
assertEquals(leaderboardName, loadedLeaderboard.getName());
|
||||
assertTrue(loadedLeaderboard.getScoreCorrection().isScoreCorrected(competitor, loadedColumn1, MillisecondsTimePoint.now()));
|
||||
assertEquals(correctedPoints, (double) loadedLeaderboard.getScoreCorrection().getExplicitScoreCorrection(competitor, loadedColumn1), 0.00000001);
|
||||
@@ -248,16 +246,14 @@ public class TestStoringAndRetrievingLeaderboards extends AbstractMongoDBTest {
|
||||
Competitor competitor = createCompetitor();
|
||||
TrackedRace raceWithOneCompetitor1 = new MockedTrackedRaceWithFixedRank(competitor, /* rank */ 1, /* started */ true);
|
||||
TrackedRace raceWithOneCompetitor2 = new MockedTrackedRaceWithFixedRank(competitor, /* rank */ 2, /* started */ true);
|
||||
leaderboard.addRace(raceWithOneCompetitor1, raceColumnName1, /* medalRace */ false, leaderboard.getFleet(null));
|
||||
leaderboard.addRace(raceWithOneCompetitor2, raceColumnName2, /* medalRace */ true, leaderboard.getFleet(null));
|
||||
leaderboard.addRace(raceWithOneCompetitor1, raceColumnName1, /* medalRace */ false);
|
||||
leaderboard.addRace(raceWithOneCompetitor2, raceColumnName2, /* medalRace */ true);
|
||||
leaderboard.getScoreCorrection().setMaxPointsReason(competitor, leaderboard.getRaceColumnByName(raceColumnName2), maxPointsReason);
|
||||
new MongoObjectFactoryImpl(db).storeLeaderboard(leaderboard);
|
||||
FlexibleLeaderboard loadedLeaderboard = (FlexibleLeaderboard) new DomainObjectFactoryImpl(db, DomainFactory.INSTANCE).loadLeaderboard(leaderboardName, /* regattaRegistry */ null);
|
||||
// attach tracked race to leaderboard to ensure that competitor object is assigned properly
|
||||
RaceColumn loadedColumn1 = loadedLeaderboard.addRace(raceWithOneCompetitor1, raceColumnName1, /* medalRace, ignored */ false,
|
||||
leaderboard.getFleet(null));
|
||||
RaceColumn loadedColumn2 = loadedLeaderboard.addRace(raceWithOneCompetitor2, raceColumnName2, /* medalRace, ignored */ false,
|
||||
leaderboard.getFleet(null));
|
||||
RaceColumn loadedColumn1 = loadedLeaderboard.addRace(raceWithOneCompetitor1, raceColumnName1, /* medalRace, ignored */ false);
|
||||
RaceColumn loadedColumn2 = loadedLeaderboard.addRace(raceWithOneCompetitor2, raceColumnName2, /* medalRace, ignored */ false);
|
||||
assertEquals(leaderboardName, loadedLeaderboard.getName());
|
||||
assertFalse(loadedLeaderboard.getScoreCorrection().isScoreCorrected(competitor, loadedColumn1, MillisecondsTimePoint.now()));
|
||||
assertTrue(loadedLeaderboard.getScoreCorrection().isScoreCorrected(competitor, loadedColumn2, MillisecondsTimePoint.now()));
|
||||
@@ -277,19 +273,15 @@ public class TestStoringAndRetrievingLeaderboards extends AbstractMongoDBTest {
|
||||
Competitor competitor = createCompetitor();
|
||||
TrackedRace raceWithOneCompetitor1 = new MockedTrackedRaceWithFixedRank(competitor, /* rank */ 1, /* started */ true);
|
||||
TrackedRace raceWithOneCompetitor2 = new MockedTrackedRaceWithFixedRank(competitor, /* rank */ 2, /* started */ true);
|
||||
leaderboard.addRace(raceWithOneCompetitor1, raceColumnName1, /* medalRace */ false,
|
||||
leaderboard.getFleet(null));
|
||||
leaderboard.addRace(raceWithOneCompetitor2, raceColumnName2, /* medalRace */ true,
|
||||
leaderboard.getFleet(null));
|
||||
leaderboard.addRace(raceWithOneCompetitor1, raceColumnName1, /* medalRace */ false);
|
||||
leaderboard.addRace(raceWithOneCompetitor2, raceColumnName2, /* medalRace */ true);
|
||||
leaderboard.getScoreCorrection().correctScore(competitor, leaderboard.getRaceColumnByName(raceColumnName1), correctedPoints);
|
||||
leaderboard.getScoreCorrection().setMaxPointsReason(competitor, leaderboard.getRaceColumnByName(raceColumnName2), maxPointsReason);
|
||||
new MongoObjectFactoryImpl(db).storeLeaderboard(leaderboard);
|
||||
FlexibleLeaderboard loadedLeaderboard = (FlexibleLeaderboard) new DomainObjectFactoryImpl(db, DomainFactory.INSTANCE).loadLeaderboard(leaderboardName, /* regattaRegistry */ null);
|
||||
// attach tracked race to leaderboard to ensure that competitor object is assigned properly
|
||||
RaceColumn loadedColumn1 = loadedLeaderboard.addRace(raceWithOneCompetitor1, raceColumnName1, /* medalRace, ignored */ false,
|
||||
leaderboard.getFleet(null));
|
||||
RaceColumn loadedColumn2 = loadedLeaderboard.addRace(raceWithOneCompetitor2, raceColumnName2, /* medalRace, ignored */ false,
|
||||
leaderboard.getFleet(null));
|
||||
RaceColumn loadedColumn1 = loadedLeaderboard.addRace(raceWithOneCompetitor1, raceColumnName1, /* medalRace, ignored */ false);
|
||||
RaceColumn loadedColumn2 = loadedLeaderboard.addRace(raceWithOneCompetitor2, raceColumnName2, /* medalRace, ignored */ false);
|
||||
assertEquals(leaderboardName, loadedLeaderboard.getName());
|
||||
assertTrue(loadedLeaderboard.getScoreCorrection().isScoreCorrected(competitor, loadedColumn1, MillisecondsTimePoint.now()));
|
||||
assertEquals(correctedPoints, (double) loadedLeaderboard.getScoreCorrection().getExplicitScoreCorrection(competitor, loadedColumn1), 0.000000001);
|
||||
@@ -319,10 +311,8 @@ public class TestStoringAndRetrievingLeaderboards extends AbstractMongoDBTest {
|
||||
Competitor competitor = createCompetitor();
|
||||
TrackedRace raceWithOneCompetitor1 = new MockedTrackedRaceWithFixedRank(competitor, /* rank */ 1, /* started */ true);
|
||||
TrackedRace raceWithOneCompetitor2 = new MockedTrackedRaceWithFixedRank(competitor, /* rank */ 2, /* started */ true);
|
||||
RaceColumn r1 = leaderboard.addRace(raceWithOneCompetitor1, raceColumnName1, /* medalRace */ false,
|
||||
leaderboard.getFleet(null));
|
||||
RaceColumn r2 = leaderboard.addRace(raceWithOneCompetitor2, raceColumnName2, /* medalRace */ true,
|
||||
leaderboard.getFleet(null));
|
||||
RaceColumn r1 = leaderboard.addRace(raceWithOneCompetitor1, raceColumnName1, /* medalRace */ false);
|
||||
RaceColumn r2 = leaderboard.addRace(raceWithOneCompetitor2, raceColumnName2, /* medalRace */ true);
|
||||
leaderboard.getScoreCorrection().correctScore(competitor, leaderboard.getRaceColumnByName(raceColumnName1), correctedPoints);
|
||||
leaderboard.getScoreCorrection().setMaxPointsReason(competitor, leaderboard.getRaceColumnByName(raceColumnName2), maxPointsReason);
|
||||
// now release the tracked races; leaving no competitors in leaderboard.getCompetitors()
|
||||
@@ -331,10 +321,8 @@ public class TestStoringAndRetrievingLeaderboards extends AbstractMongoDBTest {
|
||||
new MongoObjectFactoryImpl(db).storeLeaderboard(leaderboard);
|
||||
FlexibleLeaderboard loadedLeaderboard = (FlexibleLeaderboard) new DomainObjectFactoryImpl(db, DomainFactory.INSTANCE).loadLeaderboard(leaderboardName, /* regattaRegistry */ null);
|
||||
// attach tracked race to leaderboard to ensure that competitor object is assigned properly
|
||||
RaceColumn loadedColumn1 = loadedLeaderboard.addRace(raceWithOneCompetitor1, raceColumnName1, /* medalRace, ignored */ false,
|
||||
leaderboard.getFleet(null));
|
||||
RaceColumn loadedColumn2 = loadedLeaderboard.addRace(raceWithOneCompetitor2, raceColumnName2, /* medalRace, ignored */ false,
|
||||
leaderboard.getFleet(null));
|
||||
RaceColumn loadedColumn1 = loadedLeaderboard.addRace(raceWithOneCompetitor1, raceColumnName1, /* medalRace, ignored */ false);
|
||||
RaceColumn loadedColumn2 = loadedLeaderboard.addRace(raceWithOneCompetitor2, raceColumnName2, /* medalRace, ignored */ false);
|
||||
assertEquals(leaderboardName, loadedLeaderboard.getName());
|
||||
assertTrue(loadedLeaderboard.getScoreCorrection().isScoreCorrected(competitor, loadedColumn1, MillisecondsTimePoint.now()));
|
||||
assertEquals(correctedPoints, (double) loadedLeaderboard.getScoreCorrection().getExplicitScoreCorrection(competitor, loadedColumn1), 0.000000001);
|
||||
@@ -368,10 +356,8 @@ public class TestStoringAndRetrievingLeaderboards extends AbstractMongoDBTest {
|
||||
/* dateOfBirth */ null, "This is Rigo, the coach")), new BoatImpl("Dr. Wolfgang Hunger's boat", new BoatClassImpl("505", /* typicallyStartsUpwind */ true), null));
|
||||
TrackedRace raceWithOneCompetitor1 = new MockedTrackedRaceWithFixedRank(competitor, /* rank */ 1, /* started */ true);
|
||||
TrackedRace raceWithOneCompetitor2 = new MockedTrackedRaceWithFixedRank(competitor, /* rank */ 2, /* started */ true);
|
||||
leaderboard.addRace(raceWithOneCompetitor1, raceColumnName1, /* medalRace */ false,
|
||||
leaderboard.getFleet(null));
|
||||
leaderboard.addRace(raceWithOneCompetitor2, raceColumnName2, /* medalRace */ true,
|
||||
leaderboard.getFleet(null));
|
||||
leaderboard.addRace(raceWithOneCompetitor1, raceColumnName1, /* medalRace */ false);
|
||||
leaderboard.addRace(raceWithOneCompetitor2, raceColumnName2, /* medalRace */ true);
|
||||
leaderboard.getScoreCorrection().correctScore(competitor, leaderboard.getRaceColumnByName(raceColumnName1), correctedPoints);
|
||||
leaderboard.getScoreCorrection().setMaxPointsReason(competitor, leaderboard.getRaceColumnByName(raceColumnName2), maxPointsReason);
|
||||
final MongoObjectFactoryImpl mongoObjectFactory = new MongoObjectFactoryImpl(db);
|
||||
@@ -381,10 +367,8 @@ public class TestStoringAndRetrievingLeaderboards extends AbstractMongoDBTest {
|
||||
// don't attach tracked race to leaderboard and immediately store again
|
||||
mongoObjectFactory.storeLeaderboard(loadedLeaderboard);
|
||||
FlexibleLeaderboard loadedLeaderboard2 = (FlexibleLeaderboard) domainObjectFactory.loadLeaderboard(leaderboardName, /* regattaRegistry */ null);
|
||||
RaceColumn loadedColumn1 = loadedLeaderboard2.addRace(raceWithOneCompetitor1, raceColumnName1, /* medalRace, ignored */ false,
|
||||
leaderboard.getFleet(null));
|
||||
RaceColumn loadedColumn2 = loadedLeaderboard2.addRace(raceWithOneCompetitor2, raceColumnName2, /* medalRace, ignored */ false,
|
||||
leaderboard.getFleet(null));
|
||||
RaceColumn loadedColumn1 = loadedLeaderboard2.addRace(raceWithOneCompetitor1, raceColumnName1, /* medalRace, ignored */ false);
|
||||
RaceColumn loadedColumn2 = loadedLeaderboard2.addRace(raceWithOneCompetitor2, raceColumnName2, /* medalRace, ignored */ false);
|
||||
assertEquals(leaderboardName, loadedLeaderboard2.getName());
|
||||
assertTrue(loadedLeaderboard2.getScoreCorrection().isScoreCorrected(competitor, loadedColumn1, MillisecondsTimePoint.now()));
|
||||
assertEquals(correctedPoints, (double) loadedLeaderboard2.getScoreCorrection().getExplicitScoreCorrection(competitor, loadedColumn1), 0.000000001);
|
||||
|
||||
+3
-4
@@ -85,8 +85,7 @@ public class TestStoringAndRetrievingRaceLogInLeaderboards extends RaceLogMongoD
|
||||
RaceLogStore raceLogStore = MongoRaceLogStoreFactory.INSTANCE.getMongoRaceLogStore(mongoObjectFactory, domainObjectFactory);
|
||||
leaderboard = new FlexibleLeaderboardImpl(raceLogStore, leaderboardName, new ThresholdBasedResultDiscardingRuleImpl(discardIndexResultsStartingWithHowManyRaces),
|
||||
new LowPoint(), null);
|
||||
Fleet defaultFleet = leaderboard.getFleet(null);
|
||||
leaderboard.addRaceColumn(raceColumnName, /* medalRace */ false, defaultFleet);
|
||||
leaderboard.addRaceColumn(raceColumnName, /* medalRace */false);
|
||||
}
|
||||
|
||||
private void addAndStoreRaceLogEvent(FlexibleLeaderboard leaderboard, String raceColumnName, RaceLogEvent event) {
|
||||
@@ -130,7 +129,7 @@ public class TestStoringAndRetrievingRaceLogInLeaderboards extends RaceLogMongoD
|
||||
db.getLastError(); // sync DB
|
||||
leaderboard.removeRaceColumn(raceColumnName);
|
||||
db.getLastError(); // sync DB
|
||||
leaderboard.addRaceColumn(raceColumnName, /* medalRace */ false, defaultFleet);
|
||||
leaderboard.addRaceColumn(raceColumnName, /* medalRace */false);
|
||||
// now assert that the race log is empty because the column was removed and so should have been the race log
|
||||
RaceLog loadedRaceLog = leaderboard.getRaceColumnByName(raceColumnName).getRaceLog(leaderboard.getRaceColumnByName(raceColumnName).getFleets().iterator().next());
|
||||
loadedRaceLog.lockForRead();
|
||||
@@ -173,7 +172,7 @@ public class TestStoringAndRetrievingRaceLogInLeaderboards extends RaceLogMongoD
|
||||
} finally {
|
||||
reloadedRenamedRaceLog.unlockAfterRead();
|
||||
}
|
||||
leaderboard.addRaceColumn(raceColumnName, /* medalRace */ false, defaultFleet);
|
||||
leaderboard.addRaceColumn(raceColumnName, /* medalRace */false);
|
||||
mongoObjectFactory.storeLeaderboard(leaderboard);
|
||||
// now assert that the race log is empty because the renamed column's persistent race log is expected to have
|
||||
// been removed
|
||||
|
||||
+2
-1
@@ -14,6 +14,7 @@ import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
@@ -88,7 +89,7 @@ public abstract class AbstractJsonExportTest {
|
||||
BoatClass boatClass = new BoatClassImpl("505", /* typicallyStartsUpwind */ true);
|
||||
for (int i = 1; i <= numberOfCompetitorsToCreate; i++) {
|
||||
String competitorName = "C" + i;
|
||||
Competitor competitor = new CompetitorImpl(123, competitorName, Color.RED, new TeamImpl("STG", Collections.singleton(
|
||||
Competitor competitor = new CompetitorImpl(UUID.randomUUID(), competitorName, Color.RED, new TeamImpl("STG", Collections.singleton(
|
||||
new PersonImpl(competitorName, new NationalityImpl("GER"),
|
||||
/* dateOfBirth */ null, "This is famous "+competitorName)),
|
||||
new PersonImpl("Rigo van Maas", new NationalityImpl("NED"),
|
||||
|
||||
+5
-7
@@ -139,7 +139,7 @@ public class LeaderboardsResource extends AbstractSailingServerResource {
|
||||
JSONObject jsonRaceColumns = new JSONObject();
|
||||
jsonCompetitor.put("raceScores", jsonRaceColumns);
|
||||
for (RaceColumnDTO raceColumn : leaderboardDTO.getRaceList()) {
|
||||
List<CompetitorDTO> rankedCompetitorsForColumn = leaderboardDTO.getCompetitorOrderingPerRaceColumnName().get(raceColumn.getName());
|
||||
List<CompetitorDTO> regattaRankedCompetitorsForColumn = leaderboardDTO.getCompetitorOrderingPerRaceColumnName().get(raceColumn.getName());
|
||||
JSONObject jsonEntry = new JSONObject();
|
||||
jsonRaceColumns.put(raceColumn.getName(), jsonEntry);
|
||||
LeaderboardEntryDTO leaderboardEntry = leaderboardRowDTO.fieldsByRaceColumnName.get(raceColumn.getName());
|
||||
@@ -151,11 +151,9 @@ public class LeaderboardsResource extends AbstractSailingServerResource {
|
||||
jsonEntry.put("totalPoints", leaderboardEntry.totalPoints);
|
||||
MaxPointsReason maxPointsReason = leaderboardEntry.reasonForMaxPoints;
|
||||
jsonEntry.put("maxPointsReason", maxPointsReason != null ? maxPointsReason.toString(): null);
|
||||
jsonEntry.put("rank", rankedCompetitorsForColumn.indexOf(competitor)+1);
|
||||
// final TrackedRace trackedRace = raceColumn.getTrackedRace(competitor);
|
||||
// if (trackedRace != null) {
|
||||
// jsonEntry.put("raceRank", trackedRace.getRank(competitor, resultTimePoint));
|
||||
// }
|
||||
jsonEntry.put("rank", regattaRankedCompetitorsForColumn.indexOf(competitor)+1);
|
||||
List<CompetitorDTO> raceRankedCompetitorsInColumn = leaderboardDTO.getCompetitorsFromBestToWorst(raceColumn);
|
||||
jsonEntry.put("raceRank", raceRankedCompetitorsInColumn.indexOf(competitor)+1);
|
||||
jsonEntry.put("isDiscarded", leaderboardEntry.discarded);
|
||||
jsonEntry.put("isCorrected", leaderboardEntry.hasScoreCorrection());
|
||||
}
|
||||
@@ -212,7 +210,7 @@ public class LeaderboardsResource extends AbstractSailingServerResource {
|
||||
jsonLeaderboard.put("resultTimepoint", resultTimePoint != null ? resultTimePoint.asMillis() : null);
|
||||
jsonLeaderboard.put("resultState", resultState.name());
|
||||
jsonLeaderboard.put("maxCompetitorsCount", maxCompetitorsCount);
|
||||
|
||||
jsonLeaderboard.put("higherScoreIsBetter", leaderboard.isHigherScoreBetter());
|
||||
jsonLeaderboard.put("scoringComment", leaderboard.getComment());
|
||||
Date lastUpdateTimepoint = leaderboard.getTimePointOfLastCorrectionsValidity();
|
||||
jsonLeaderboard.put("lastScoringUpdate", lastUpdateTimepoint != null ? lastUpdateTimepoint.getTime(): null);
|
||||
|
||||
+11
-3
@@ -1,5 +1,7 @@
|
||||
package com.sap.sailing.server.replication.test;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
@@ -23,8 +25,7 @@ import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.rules.Timeout;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
|
||||
import com.rabbitmq.client.Channel;
|
||||
import com.rabbitmq.client.QueueingConsumer;
|
||||
import com.sap.sailing.domain.base.DomainFactory;
|
||||
import com.sap.sailing.domain.base.impl.DomainFactoryImpl;
|
||||
@@ -39,6 +40,7 @@ import com.sap.sailing.server.impl.RacingEventServiceImpl;
|
||||
import com.sap.sailing.server.replication.ReplicationMasterDescriptor;
|
||||
import com.sap.sailing.server.replication.ReplicationService;
|
||||
import com.sap.sailing.server.replication.impl.Activator;
|
||||
import com.sap.sailing.server.replication.impl.RabbitOutputStream;
|
||||
import com.sap.sailing.server.replication.impl.ReplicaDescriptor;
|
||||
import com.sap.sailing.server.replication.impl.ReplicationInstancesManager;
|
||||
import com.sap.sailing.server.replication.impl.ReplicationMasterDescriptorImpl;
|
||||
@@ -165,6 +167,7 @@ public abstract class AbstractServerReplicationTest {
|
||||
}
|
||||
|
||||
static class ReplicationServiceTestImpl extends ReplicationServiceImpl {
|
||||
protected static final int INITIAL_LOAD_PACKAGE_SIZE = 1024*1024;
|
||||
private final DomainFactory resolveAgainst;
|
||||
private final RacingEventService master;
|
||||
private final ReplicaDescriptor replicaDescriptor;
|
||||
@@ -214,10 +217,15 @@ public abstract class AbstractServerReplicationTest {
|
||||
registerReplicaUuidForMaster(uuid, masterDescriptor);
|
||||
pw.print(uuid.getBytes());
|
||||
} else if (request.contains("INITIAL_LOAD")) {
|
||||
final GZIPOutputStream gzipOutputStream = new GZIPOutputStream(s.getOutputStream());
|
||||
Channel channel = masterReplicationService.createMasterChannel();
|
||||
RabbitOutputStream ros = new RabbitOutputStream(INITIAL_LOAD_PACKAGE_SIZE, channel,
|
||||
/* queueName */ "initial-load-for-TestClient-"+UUID.randomUUID(), /* syncAfterTimeout */ false);
|
||||
pw.println(ros.getQueueName());
|
||||
final GZIPOutputStream gzipOutputStream = new GZIPOutputStream(ros);
|
||||
final ObjectOutputStream oos = new ObjectOutputStream(gzipOutputStream);
|
||||
master.serializeForInitialReplication(oos);
|
||||
gzipOutputStream.finish();
|
||||
ros.close();
|
||||
} else if (request.contains("STOP")) {
|
||||
stop = true;
|
||||
}
|
||||
|
||||
+1
-1
@@ -55,7 +55,7 @@ import com.sap.sse.common.Util;
|
||||
*/
|
||||
public class CompetitorReplicationTest extends AbstractServerReplicationTest {
|
||||
/**
|
||||
* Add a tracked race to the master that includes a competitor; check that the competiotr was properly replicated to
|
||||
* Add a tracked race to the master that includes a competitor; check that the competitor was properly replicated to
|
||||
* the replica's {@link CompetitorStore}. Afterwards, use the {@link UpdateCompetitor} operation on the master to
|
||||
* perform an explicit update; ensure that the update arrived on the replica. Then execute an
|
||||
* {@link AllowCompetitorResetToDefaults} operation on the master, afterwards update the competitor on the master,
|
||||
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
package com.sap.sailing.server.replication.test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.rabbitmq.client.Channel;
|
||||
import com.rabbitmq.client.Connection;
|
||||
import com.rabbitmq.client.ConnectionFactory;
|
||||
import com.sap.sailing.server.replication.impl.RabbitInputStreamProvider;
|
||||
import com.sap.sailing.server.replication.impl.RabbitOutputStream;
|
||||
|
||||
/**
|
||||
* A number of tests for the {@link OutputStream} / {@link InputStream} implementation using an underlying RabbitMQ message queue.
|
||||
*
|
||||
* @author Axel Uhl (D043530)
|
||||
*
|
||||
*/
|
||||
public class RemoteMessageStreamTest {
|
||||
private Channel channel;
|
||||
private RabbitOutputStream outputStream;
|
||||
private RabbitInputStreamProvider inputStream;
|
||||
|
||||
@Before
|
||||
public void setUp() throws IOException {
|
||||
ConnectionFactory factory = new ConnectionFactory();
|
||||
Connection connection = factory.newConnection();
|
||||
channel = connection.createChannel();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBasicConnectivityInOnePacket() throws IOException {
|
||||
setupStreams(/* messageSizeInBytes */ 8192, /* syncAfterTimeout */ false);
|
||||
final String message = "Hello World!";
|
||||
outputStream.write(message.getBytes());
|
||||
outputStream.close();
|
||||
byte[] buf = new byte[100];
|
||||
int numberOfReadBytes = inputStream.getInputStream().read(buf);
|
||||
assertEquals(message.getBytes().length, numberOfReadBytes);
|
||||
byte[] trimmedMessage = new byte[numberOfReadBytes];
|
||||
System.arraycopy(buf, 0, trimmedMessage, 0, numberOfReadBytes);
|
||||
String messageAsString = new String(trimmedMessage);
|
||||
assertEquals(message, messageAsString);
|
||||
assertEquals(-1, inputStream.getInputStream().read());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBasicCddonnectivityInMultiplePackets() throws IOException {
|
||||
setupStreams(/* messageSizeInBytes */ 5, /* syncAfterTimeout */ false);
|
||||
final String message = "Hello World!";
|
||||
outputStream.write(message.getBytes());
|
||||
outputStream.close();
|
||||
byte[] buf = new byte[100];
|
||||
int numberOfReadBytes = inputStream.getInputStream().read(buf);
|
||||
assertEquals(message.getBytes().length, numberOfReadBytes);
|
||||
byte[] trimmedMessage = new byte[numberOfReadBytes];
|
||||
System.arraycopy(buf, 0, trimmedMessage, 0, numberOfReadBytes);
|
||||
String messageAsString = new String(trimmedMessage);
|
||||
assertEquals(message, messageAsString);
|
||||
assertEquals(-1, inputStream.getInputStream().read());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDataIsSentAfterTimeout() throws IOException, InterruptedException {
|
||||
setupStreams(/* messageSizeInBytes */ 1000, /* syncAfterTimeout */ true);
|
||||
final String message = "Hello World!";
|
||||
outputStream.write(message.getBytes());
|
||||
// wait until timeout should cause bytes to be sent, plus a bit to give Rabbit some time
|
||||
Thread.sleep(RabbitOutputStream.DURATION_AFTER_TO_SYNC_DATA_TO_CHANNEL_AS_MILLIS.plus(1000).asMillis());
|
||||
byte[] buf = new byte[100];
|
||||
int numberOfReadBytes = inputStream.getInputStream().read(buf);
|
||||
assertEquals(message.getBytes().length, numberOfReadBytes);
|
||||
byte[] trimmedMessage = new byte[numberOfReadBytes];
|
||||
System.arraycopy(buf, 0, trimmedMessage, 0, numberOfReadBytes);
|
||||
String messageAsString = new String(trimmedMessage);
|
||||
assertEquals(message, messageAsString);
|
||||
final String message2 = "Hello Back!";
|
||||
outputStream.write(message2.getBytes());
|
||||
outputStream.close();
|
||||
int numberOfReadBytes2 = inputStream.getInputStream().read(buf);
|
||||
assertEquals(message2.getBytes().length, numberOfReadBytes2);
|
||||
byte[] trimmedMessage2 = new byte[numberOfReadBytes2];
|
||||
System.arraycopy(buf, 0, trimmedMessage2, 0, numberOfReadBytes2);
|
||||
String messageAsString2 = new String(trimmedMessage2);
|
||||
assertEquals(message2, messageAsString2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSerializedObjectsCutInPieces() throws IOException, InterruptedException, ClassNotFoundException {
|
||||
setupStreams(/* messageSizeInBytes */ 10, /* syncAfterTimeout */ false);
|
||||
List<String> l = new ArrayList<>();
|
||||
final String message = "Hello World!";
|
||||
l.add(message);
|
||||
ObjectOutputStream oos = new ObjectOutputStream(outputStream);
|
||||
oos.writeObject(l);
|
||||
oos.close();
|
||||
ObjectInputStream ois = new ObjectInputStream(inputStream.getInputStream());
|
||||
assertEquals(l, ois.readObject());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEscapedTerminatorTransmission() throws IOException {
|
||||
setupStreams(/* messageSizeInBytes */ 20, /* syncAfterTimeout */ false);
|
||||
byte[] terminator = new byte[] { 2, 6, 0, 4, 1, 9, 8, 2, 0, 1, 4, 2 };
|
||||
outputStream.write(terminator);
|
||||
outputStream.close();
|
||||
byte[] buf = new byte[100];
|
||||
int numberOfReadBytes = inputStream.getInputStream().read(buf);
|
||||
assertEquals(terminator.length, numberOfReadBytes);
|
||||
byte[] trimmedMessage = new byte[numberOfReadBytes];
|
||||
System.arraycopy(buf, 0, trimmedMessage, 0, numberOfReadBytes);
|
||||
assertTrue(Arrays.equals(terminator, trimmedMessage));
|
||||
assertEquals(-1, inputStream.getInputStream().read());
|
||||
}
|
||||
|
||||
|
||||
private void setupStreams(int messageSizeInBytes, boolean syncAfterTimeout) throws IOException {
|
||||
outputStream = new RabbitOutputStream(messageSizeInBytes, channel, UUID.randomUUID().toString(), syncAfterTimeout);
|
||||
inputStream = new RabbitInputStreamProvider(channel, outputStream.getQueueName());
|
||||
}
|
||||
}
|
||||
+12
-1
@@ -5,6 +5,7 @@ import static org.junit.Assert.assertEquals;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.junit.Before;
|
||||
@@ -32,9 +33,19 @@ public class ReplicationInstancesManagerLoggingPerformanceTest {
|
||||
public void testLoggingPerformance() {
|
||||
final int count = 10000000;
|
||||
for (int i=0; i<count; i++) {
|
||||
replicationInstanceManager.log(operation);
|
||||
replicationInstanceManager.log(Collections.<Class<?>>singletonList(operation.getClass()), 0l);
|
||||
}
|
||||
assertEquals(count, replicationInstanceManager.getStatistics(replica).get(operation.getClass()).intValue());
|
||||
assertEquals(1.0, replicationInstanceManager.getAverageNumberOfOperationsPerMessage(replica), 0.0000001);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoggingAverages() {
|
||||
final int count = 1000;
|
||||
for (int i=0; i<count; i++) {
|
||||
replicationInstanceManager.log(Arrays.asList(new Class<?>[] { operation.getClass(), operation.getClass() }), 0l);
|
||||
}
|
||||
assertEquals(2*count, replicationInstanceManager.getStatistics(replica).get(operation.getClass()).intValue());
|
||||
assertEquals(2.0, replicationInstanceManager.getAverageNumberOfOperationsPerMessage(replica), 0.0000001);
|
||||
}
|
||||
}
|
||||
+6
@@ -6,6 +6,7 @@ import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.UUID;
|
||||
|
||||
import com.rabbitmq.client.Channel;
|
||||
import com.rabbitmq.client.QueueingConsumer;
|
||||
|
||||
/**
|
||||
@@ -40,4 +41,9 @@ public interface ReplicationMasterDescriptor {
|
||||
void stopConnection();
|
||||
|
||||
String getMessagingHostname();
|
||||
|
||||
/**
|
||||
* @return a RabbitMQ channel created with the replication connectivity parameters defined by this descriptor
|
||||
*/
|
||||
Channel createChannel() throws IOException;
|
||||
}
|
||||
+12
@@ -1,9 +1,11 @@
|
||||
package com.sap.sailing.server.replication;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.ConnectException;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import com.rabbitmq.client.Channel;
|
||||
import com.sap.sailing.server.RacingEventServiceOperation;
|
||||
import com.sap.sailing.server.replication.impl.ReplicaDescriptor;
|
||||
import com.sap.sailing.server.replication.impl.ReplicationServlet;
|
||||
@@ -43,6 +45,8 @@ public interface ReplicationService {
|
||||
* replica by type, where the operation type is the key, represented as the operation's class name
|
||||
*/
|
||||
Map<Class<? extends RacingEventServiceOperation<?>>, Integer> getStatistics(ReplicaDescriptor replicaDescriptor);
|
||||
|
||||
double getAverageNumberOfOperationsPerMessage(ReplicaDescriptor replicaDescriptor);
|
||||
|
||||
/**
|
||||
* Stops the currently running replication. As there can be only one replication running
|
||||
@@ -62,4 +66,12 @@ public interface ReplicationService {
|
||||
* @return
|
||||
*/
|
||||
UUID getServerIdentifier();
|
||||
|
||||
Channel createMasterChannel() throws IOException, ConnectException;
|
||||
|
||||
long getNumberOfMessagesSent(ReplicaDescriptor replica);
|
||||
|
||||
long getNumberOfBytesSent(ReplicaDescriptor replica);
|
||||
|
||||
double getAverageNumberOfBytesPerMessage(ReplicaDescriptor replica);
|
||||
}
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
package com.sap.sailing.server.replication.impl;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.PipedInputStream;
|
||||
import java.io.PipedOutputStream;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.rabbitmq.client.Channel;
|
||||
import com.rabbitmq.client.ConsumerCancelledException;
|
||||
import com.rabbitmq.client.QueueingConsumer;
|
||||
import com.rabbitmq.client.QueueingConsumer.Delivery;
|
||||
import com.rabbitmq.client.ShutdownSignalException;
|
||||
import com.sap.sailing.domain.common.impl.NamedImpl;
|
||||
|
||||
/**
|
||||
* Input stream that reads messages from a RabbitMQ queue filled by a {@link RabbitOutputStream} and lets a client read
|
||||
* this stream from {@link #getInputStream()}.
|
||||
*
|
||||
* @author Simon Marcel Pamies
|
||||
* @author Axel Uhl
|
||||
*/
|
||||
public class RabbitInputStreamProvider extends NamedImpl {
|
||||
private static final Logger logger = Logger.getLogger(RabbitInputStreamProvider.class.getName());
|
||||
private static final long serialVersionUID = 1342935135386887494L;
|
||||
|
||||
private final PipedInputStream clientReadsFromThis;
|
||||
private final PipedOutputStream messagesAreWrittenToThis;
|
||||
|
||||
public RabbitInputStreamProvider(Channel channel, String queueName) throws IOException {
|
||||
this(channel, queueName, /* name */ UUID.randomUUID().toString());
|
||||
}
|
||||
|
||||
public RabbitInputStreamProvider(Channel channel, String queueName, String name) throws IOException {
|
||||
super(name);
|
||||
assert name != null;
|
||||
messagesAreWrittenToThis = new PipedOutputStream();
|
||||
clientReadsFromThis = new PipedInputStream(messagesAreWrittenToThis);
|
||||
final QueueingConsumer messageConsumer = new QueueingConsumer(channel);
|
||||
channel.basicConsume(queueName, /* auto-ack */ true, messageConsumer);
|
||||
new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
while (true) {
|
||||
try {
|
||||
Delivery delivery = messageConsumer.nextDelivery();
|
||||
byte[] bytesFromMessage = delivery.getBody();
|
||||
if (RabbitOutputStream.startsWithTerminationCommand(bytesFromMessage, bytesFromMessage.length)) {
|
||||
if (bytesFromMessage.length == RabbitOutputStream.TERMINATION_COMMAND.length) {
|
||||
// received exactly TERMINATION_COMMAND
|
||||
messagesAreWrittenToThis.close();
|
||||
break;
|
||||
} else {
|
||||
// more to come; skip one byte which is the "escape" symbol ensuring that the message
|
||||
// sizes don't match
|
||||
byte[] newBytesFromMessage = new byte[bytesFromMessage.length - 1];
|
||||
System.arraycopy(bytesFromMessage, 0, newBytesFromMessage, 0,
|
||||
RabbitOutputStream.TERMINATION_COMMAND.length);
|
||||
System.arraycopy(bytesFromMessage, RabbitOutputStream.TERMINATION_COMMAND.length + 1,
|
||||
newBytesFromMessage, RabbitOutputStream.TERMINATION_COMMAND.length,
|
||||
bytesFromMessage.length - RabbitOutputStream.TERMINATION_COMMAND.length - 1);
|
||||
bytesFromMessage = newBytesFromMessage;
|
||||
}
|
||||
}
|
||||
messagesAreWrittenToThis.write(bytesFromMessage);
|
||||
} catch (ShutdownSignalException | ConsumerCancelledException e) {
|
||||
logger.log(Level.INFO, "Problem with message queue "+getName(), e);
|
||||
break;
|
||||
} catch (InterruptedException e) {
|
||||
logger.log(Level.WARNING, "Reading of next message in stream "+getName()+" Interrupted; continuing", e);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
|
||||
public InputStream getInputStream() {
|
||||
return clientReadsFromThis;
|
||||
}
|
||||
}
|
||||
+166
@@ -0,0 +1,166 @@
|
||||
package com.sap.sailing.server.replication.impl;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.rabbitmq.client.Channel;
|
||||
import com.sap.sailing.domain.common.Duration;
|
||||
import com.sap.sailing.domain.common.TimePoint;
|
||||
import com.sap.sailing.domain.common.impl.MillisecondsDurationImpl;
|
||||
import com.sap.sailing.domain.common.impl.MillisecondsTimePoint;
|
||||
|
||||
/**
|
||||
* Output stream that can split its contents into messages for RabbitMQ. This stream creates a RabbitMQ queue and sends
|
||||
* a RabbitMQ message for a configurable number of bytes written to that stream or after a timeout of
|
||||
* {@link #DURATION_AFTER_TO_SYNC_DATA_TO_CHANNEL_AS_MILLIS } and send it to the queue whose name was provided to the
|
||||
* constructor.
|
||||
* <p>
|
||||
*
|
||||
* Usage: call the constructor with a channel to a RabbitMQ connection. Then, extract the queue name assigned (see
|
||||
* {@link #getQueueName()} and get it to the client. On the client, use {@link RabbitInputStreamProvider}, passing the queue name
|
||||
* to the constructor. The client can then obtain an {@link InputStream} from the {@link RabbitInputStreamProvider} by calling
|
||||
* {@link RabbitInputStreamProvider#getInputStream()} from which the content can be read. The client will receive EOF at the end
|
||||
* of the stream.
|
||||
*
|
||||
* @author Simon Marcel Pamies
|
||||
* @author Axel Uhl (d043530)
|
||||
*/
|
||||
public class RabbitOutputStream extends OutputStream {
|
||||
private static final Logger logger = Logger.getLogger(RabbitOutputStream.class.getName());
|
||||
|
||||
static final byte TERMINATION_COMMAND[] = new byte[] { 2, 6, 0, 4, 1, 9, 8, 2, 0, 1, 4, 2 };
|
||||
|
||||
public static final Duration DURATION_AFTER_TO_SYNC_DATA_TO_CHANNEL_AS_MILLIS = new MillisecondsDurationImpl(5000);
|
||||
private static final long DURATION_TO_PAUSE_SYNCER_THREAD_AS_MILLIS = 1000;
|
||||
|
||||
private final Channel channel;
|
||||
private final String queueName;
|
||||
|
||||
private boolean closed;
|
||||
|
||||
private TimePoint timeLastDataHasBeenReceived;
|
||||
private int count;
|
||||
private final byte streamBuffer[];
|
||||
|
||||
public RabbitOutputStream(int messageSizeInBytes, Channel channel, String queueName, boolean syncAfterTimeout) throws IOException {
|
||||
super();
|
||||
this.streamBuffer = new byte[messageSizeInBytes];
|
||||
this.count = 0;
|
||||
this.channel = channel;
|
||||
this.queueName = channel.queueDeclare(queueName, /* durable */ false, /* exclusive */ false, /* autoDelete */ false,
|
||||
/* args */ null).getQueue();
|
||||
this.closed = false;
|
||||
|
||||
if (syncAfterTimeout) {
|
||||
new Thread("Timeout syncer for "+getClass().getSimpleName()+" on channel "+channel) {
|
||||
@Override
|
||||
public void run() {
|
||||
while (!closed) {
|
||||
synchronized (RabbitOutputStream.this) {
|
||||
if (timeLastDataHasBeenReceived != null) {
|
||||
TimePoint now = MillisecondsTimePoint.now();
|
||||
// FIXME the timing should better be managed by a Timer instance with delays based on last send and wake-up / test time point
|
||||
if (timeLastDataHasBeenReceived.until(now).compareTo(DURATION_AFTER_TO_SYNC_DATA_TO_CHANNEL_AS_MILLIS) > 0) {
|
||||
try {
|
||||
sendBuffer();
|
||||
timeLastDataHasBeenReceived = MillisecondsTimePoint.now(); // reset time to avoid unnecessary write attempt
|
||||
} catch (IOException e) {
|
||||
logger.log(Level.INFO, "Exception trying to send message. Aborting.", e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
Thread.sleep(DURATION_TO_PAUSE_SYNCER_THREAD_AS_MILLIS);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
}
|
||||
|
||||
public String getQueueName() {
|
||||
return queueName;
|
||||
}
|
||||
|
||||
/**
|
||||
* The method writes a byte to the buffer. If this fills up the buffer to its limit, the buffer is {@link #sendBuffer() sent}
|
||||
* as a message and cleared again. The {@link #timeLastDataHasBeenReceived} timestamp is updated to the current time.
|
||||
*/
|
||||
@Override
|
||||
public synchronized void write(int b) throws IOException {
|
||||
assert count < streamBuffer.length;
|
||||
if (this.closed) {
|
||||
throw new IOException("This stream has been closed by an earlier operation.");
|
||||
}
|
||||
streamBuffer[count++] = (byte) b;
|
||||
if (count == streamBuffer.length) {
|
||||
sendBuffer();
|
||||
}
|
||||
timeLastDataHasBeenReceived = MillisecondsTimePoint.now();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
try {
|
||||
finish();
|
||||
} finally {
|
||||
// make sure to always set this stream to closed
|
||||
this.closed = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() throws IOException {
|
||||
sendBuffer();
|
||||
}
|
||||
|
||||
private void finish() throws IOException {
|
||||
sendBuffer();
|
||||
this.channel.basicPublish(/* exchangeName */ "", /* routingKey */ queueName, /* properties */null, TERMINATION_COMMAND);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the buffer contents from index 0 to index {@link #count}-1 inclusive. {@link #count} is reset to 0 when done.
|
||||
* The method is synchronized as it needs exclusive and atomic access to {@link #count} and {@link #streamBuffer}.
|
||||
*/
|
||||
private synchronized void sendBuffer() throws IOException {
|
||||
if (count > 0) {
|
||||
if (this.channel != null && this.channel.isOpen()) {
|
||||
final byte[] message;
|
||||
// check for the unlikely case that a client has coincidentally submitted the TERMINATION_COMMAND; we escape by
|
||||
// appending a random byte which makes the length differ; the input stream provider will skip one byte after receiving
|
||||
// the TERMINATION_COMMAND at the beginning of a message whose length is greater than that of the TERMINATION_COMMAND.
|
||||
if (startsWithTerminationCommand(streamBuffer, count)) {
|
||||
message = new byte[count+1];
|
||||
} else {
|
||||
message = new byte[count];
|
||||
}
|
||||
System.arraycopy(streamBuffer, 0, message, 0, count);
|
||||
this.channel.basicPublish(/* exchangeName */"", /* routingKey */queueName, /* properties */null, message);
|
||||
count = 0;
|
||||
} else {
|
||||
this.closed = true;
|
||||
throw new IOException("AMPQ Channel seems to be closed!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static boolean startsWithTerminationCommand(byte[] bytes, int count) {
|
||||
boolean result = true;
|
||||
if (count < TERMINATION_COMMAND.length) {
|
||||
result = false;
|
||||
} else {
|
||||
for (int i = 0; i < TERMINATION_COMMAND.length && result; i++) {
|
||||
result = bytes[i] == TERMINATION_COMMAND[i];
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
+4
@@ -72,5 +72,9 @@ public class ReplicaDescriptor implements Serializable {
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return ""+uuid+": "+ipAddress+" ("+additionalInformation+")";
|
||||
}
|
||||
|
||||
}
|
||||
+104
-9
@@ -3,11 +3,13 @@ package com.sap.sailing.server.replication.impl;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import com.sap.sailing.server.RacingEventServiceOperation;
|
||||
import com.sap.sailing.server.replication.ReplicationMasterDescriptor;
|
||||
import com.sap.sse.common.Util;
|
||||
|
||||
public class ReplicationInstancesManager {
|
||||
|
||||
@@ -19,6 +21,25 @@ public class ReplicationInstancesManager {
|
||||
|
||||
private Map<ReplicaDescriptor, Map<Class<? extends RacingEventServiceOperation<?>>, Integer>> replicationCounts;
|
||||
|
||||
/**
|
||||
* Used to calculate the average number of operations per message sent/received for each replica.
|
||||
*
|
||||
* @see #totalMessageCount
|
||||
*/
|
||||
private final Map<ReplicaDescriptor, Long> totalNumberOfOperations;
|
||||
|
||||
/**
|
||||
* Used to calculate the average number of operations per message sent/received for each replica.
|
||||
*
|
||||
* @see #totalNumberOfOperations
|
||||
*/
|
||||
private final Map<ReplicaDescriptor, Long> totalMessageCount;
|
||||
|
||||
/**
|
||||
* Contains the size of all messages sent over
|
||||
*/
|
||||
private final Map<ReplicaDescriptor, Long> totalQueueMessagesRawSizeInBytes;
|
||||
|
||||
/**
|
||||
* The descriptor of the replication master
|
||||
*/
|
||||
@@ -27,6 +48,9 @@ public class ReplicationInstancesManager {
|
||||
public ReplicationInstancesManager() {
|
||||
replicaDescriptors = new HashSet<ReplicaDescriptor>();
|
||||
replicationCounts = new HashMap<ReplicaDescriptor, Map<Class<? extends RacingEventServiceOperation<?>>,Integer>>();
|
||||
totalMessageCount = new HashMap<>();
|
||||
totalNumberOfOperations = new HashMap<>();
|
||||
totalQueueMessagesRawSizeInBytes = new HashMap<ReplicaDescriptor, Long>();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -63,28 +87,99 @@ public class ReplicationInstancesManager {
|
||||
*
|
||||
* @see #getStatistics
|
||||
*/
|
||||
public <T> void log(RacingEventServiceOperation<T> replicatedOperation) {
|
||||
public void log(List<Class<?>> classes, long sizeOfQueueMessageInBytes) {
|
||||
for (ReplicaDescriptor replica : getReplicaDescriptors()) {
|
||||
Map<Class<? extends RacingEventServiceOperation<?>>, Integer> counts = replicationCounts.get(replica);
|
||||
if (counts == null) {
|
||||
counts = new HashMap<Class<? extends RacingEventServiceOperation<?>>, Integer>();
|
||||
replicationCounts.put(replica, counts);
|
||||
}
|
||||
@SuppressWarnings("unchecked") // safe because replicatedOperation is declared of type RacingEventserviceOperation<T>
|
||||
Class<? extends RacingEventServiceOperation<T>> operationClass = (Class<? extends RacingEventServiceOperation<T>>) replicatedOperation
|
||||
.getClass();
|
||||
Integer count = counts.get(operationClass);
|
||||
if (count == null) {
|
||||
count = 0;
|
||||
for (Class<?> replicatedOperationClass : classes) {
|
||||
// safe because replicatedOperation is declared of type RacingEventserviceOperation<T>
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<? extends RacingEventServiceOperation<?>> operationClass = (Class<? extends RacingEventServiceOperation<?>>) replicatedOperationClass;
|
||||
Integer count = counts.get(operationClass);
|
||||
if (count == null) {
|
||||
count = 0;
|
||||
}
|
||||
counts.put(operationClass, ++count);
|
||||
}
|
||||
count++;
|
||||
counts.put(operationClass, count);
|
||||
Long totalOps = totalNumberOfOperations.get(replica);
|
||||
if (totalOps == null) {
|
||||
totalOps = 0l;
|
||||
}
|
||||
totalOps += Util.size(classes);
|
||||
totalNumberOfOperations.put(replica, totalOps);
|
||||
Long totalMessages = totalMessageCount.get(replica);
|
||||
if (totalMessages == null) {
|
||||
totalMessages = 0l;
|
||||
}
|
||||
totalMessageCount.put(replica, ++totalMessages);
|
||||
Long totalQueueMessagesSizeInBytes = totalQueueMessagesRawSizeInBytes.get(replica);
|
||||
if (totalQueueMessagesSizeInBytes == null) {
|
||||
totalQueueMessagesSizeInBytes = 0l;
|
||||
}
|
||||
totalQueueMessagesRawSizeInBytes.put(replica, totalQueueMessagesSizeInBytes+sizeOfQueueMessageInBytes);
|
||||
}
|
||||
}
|
||||
|
||||
public Map<Class<? extends RacingEventServiceOperation<?>>, Integer> getStatistics(ReplicaDescriptor replica) {
|
||||
return replicationCounts.get(replica);
|
||||
}
|
||||
|
||||
public long getNumberOfBytesSent(ReplicaDescriptor replica) {
|
||||
final long result;
|
||||
Long totalQueueMessageSize = totalQueueMessagesRawSizeInBytes.get(replica);
|
||||
if (totalQueueMessageSize == null) {
|
||||
result = 0l;
|
||||
} else {
|
||||
result = totalQueueMessageSize.longValue();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public double getAverageNumberOfOperationsPerMessage(ReplicaDescriptor replica) {
|
||||
final double result;
|
||||
Long messageCount = totalMessageCount.get(replica);
|
||||
if (messageCount != null) {
|
||||
Long operationsCount = totalNumberOfOperations.get(replica);
|
||||
if (operationsCount == null) {
|
||||
result = 0;
|
||||
} else {
|
||||
result = ((double) operationsCount)/(double) messageCount;
|
||||
}
|
||||
} else {
|
||||
result = 0;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public double getAverageNumberOfBytesPerMessage(ReplicaDescriptor replica) {
|
||||
final double result;
|
||||
Long messageCount = totalMessageCount.get(replica);
|
||||
if (messageCount != null) {
|
||||
Long messageSizeInTotal = totalQueueMessagesRawSizeInBytes.get(replica);
|
||||
if (messageSizeInTotal == null) {
|
||||
result = 0;
|
||||
} else {
|
||||
result = ((double) messageSizeInTotal)/(double) messageCount;
|
||||
}
|
||||
} else {
|
||||
result = 0;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public long getNumberOfMessagesSent(ReplicaDescriptor replica) {
|
||||
final long result;
|
||||
Long messageCount = totalMessageCount.get(replica);
|
||||
if (messageCount != null) {
|
||||
result = messageCount.longValue();
|
||||
} else {
|
||||
result = 0l;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public void removeAll() {
|
||||
replicationCounts.clear();
|
||||
|
||||
+15
-9
@@ -62,15 +62,7 @@ public class ReplicationMasterDescriptorImpl implements ReplicationMasterDescrip
|
||||
|
||||
@Override
|
||||
public synchronized QueueingConsumer getConsumer() throws IOException {
|
||||
ConnectionFactory connectionFactory = new ConnectionFactory();
|
||||
connectionFactory.setHost(getMessagingHostname());
|
||||
int port = getMessagingPort();
|
||||
if (port != 0) {
|
||||
connectionFactory.setPort(port);
|
||||
}
|
||||
Connection connection = connectionFactory.newConnection();
|
||||
Channel channel = connection.createChannel();
|
||||
|
||||
Channel channel = createChannel();
|
||||
/*
|
||||
* Connect a queue to the given exchange that has already
|
||||
* been created by the master server.
|
||||
@@ -116,6 +108,19 @@ public class ReplicationMasterDescriptorImpl implements ReplicationMasterDescrip
|
||||
this.consumer = consumer;
|
||||
return consumer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Channel createChannel() throws IOException {
|
||||
ConnectionFactory connectionFactory = new ConnectionFactory();
|
||||
connectionFactory.setHost(getMessagingHostname());
|
||||
int port = getMessagingPort();
|
||||
if (port != 0) {
|
||||
connectionFactory.setPort(port);
|
||||
}
|
||||
Connection connection = connectionFactory.newConnection();
|
||||
Channel channel = connection.createChannel();
|
||||
return channel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void stopConnection() {
|
||||
@@ -129,6 +134,7 @@ public class ReplicationMasterDescriptorImpl implements ReplicationMasterDescrip
|
||||
} catch (Exception ex) {
|
||||
// ignore any exception during abort. close can yield a broad
|
||||
// number of exceptions that we don't want to know or to log.
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Regular → Executable
+254
-41
@@ -1,23 +1,31 @@
|
||||
package com.sap.sailing.server.replication.impl;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.net.ConnectException;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
|
||||
import org.osgi.util.tracker.ServiceTracker;
|
||||
|
||||
import com.rabbitmq.client.AMQP.Exchange;
|
||||
import com.rabbitmq.client.AMQP.Queue.DeleteOk;
|
||||
import com.rabbitmq.client.Channel;
|
||||
import com.rabbitmq.client.ConnectionFactory;
|
||||
import com.rabbitmq.client.QueueingConsumer;
|
||||
@@ -48,7 +56,7 @@ public class ReplicationServiceImpl implements ReplicationService, OperationExec
|
||||
|
||||
private final ReplicationInstancesManager replicationInstancesManager;
|
||||
|
||||
private ServiceTracker<RacingEventService, RacingEventService> racingEventServiceTracker;
|
||||
private final ServiceTracker<RacingEventService, RacingEventService> racingEventServiceTracker;
|
||||
|
||||
private final RacingEventService localService;
|
||||
|
||||
@@ -82,45 +90,126 @@ public class ReplicationServiceImpl implements ReplicationService, OperationExec
|
||||
private Replicator replicator;
|
||||
private Thread replicatorThread;
|
||||
|
||||
public ReplicationServiceImpl(String exchangeName, String exchangeHost, final ReplicationInstancesManager replicationInstancesManager) throws IOException {
|
||||
this.replicationInstancesManager = replicationInstancesManager;
|
||||
replicaUUIDs = new HashMap<ReplicationMasterDescriptor, String>();
|
||||
racingEventServiceTracker = getRacingEventServiceTracker();
|
||||
racingEventServiceTracker.open();
|
||||
localService = null;
|
||||
this.exchangeName = exchangeName;
|
||||
this.exchangeHost = exchangeHost;
|
||||
replicator = null;
|
||||
serverUUID = UUID.randomUUID();
|
||||
logger.info("Setting " + serverUUID.toString() + " as unique replication identifier.");
|
||||
}
|
||||
|
||||
protected ServiceTracker<RacingEventService, RacingEventService> getRacingEventServiceTracker() {
|
||||
return new ServiceTracker<RacingEventService, RacingEventService>(
|
||||
Activator.getDefaultContext(), RacingEventService.class.getName(), null);
|
||||
}
|
||||
/**
|
||||
* Used to synchronize write access and replacements of {@link #outboundBuffer}, {@link #outboundObjectBuffer} and
|
||||
* {@link #outboundBufferClasses} when the timer scoops up the messages to send.
|
||||
*/
|
||||
private final Object outboundBufferMonitor = "";
|
||||
|
||||
/**
|
||||
* Like {@link #ReplicationServiceImpl(String, ReplicationInstancesManager)}, only that instead of using
|
||||
* an OSGi service tracker to discover the {@link RacingEventService}, the service to replicate is "injected" here.
|
||||
* @param exchangeName the name of the exchange to which replicas can bind
|
||||
* Sending operations as serialized Java objects using binary RabbitMQ messages comes at an overhead. To reduce the overhead,
|
||||
* several operations can be serialized into a single message. The actual serialization of the buffer happens after a short duration
|
||||
* has passed since the last sending, managed by a {@link Timer}. Writers need to synchronize on {@link #outboundBufferMonitor}
|
||||
* which protects all of {@link #outboundBuffer}, {@link #outboundObjectBuffer} and {@link #outboundBufferClasses} which
|
||||
* are replaced or cleared when the timer scoops up the currently buffered operations to send them out.
|
||||
*/
|
||||
public ReplicationServiceImpl(String exchangeName, String exchangeHost,
|
||||
final ReplicationInstancesManager replicationInstancesManager, RacingEventService localService) throws IOException {
|
||||
private ByteArrayOutputStream outboundBuffer;
|
||||
|
||||
/**
|
||||
* An object output stream that writes to {@link #outboundBuffer}. Operations are serialized into this stream until the timer
|
||||
* acquires the {@link #outboundBufferMonitor}, closes the stream and transmits the contents of {@link #outboundBuffer} as a
|
||||
* RabbitMQ message. While still holding the monitor, the timer task creates a new {@link #outboundBuffer} and a new
|
||||
* {@link #outboundObjectBuffer} wrapping the {@link #outboundBuffer}.
|
||||
*/
|
||||
private ObjectOutputStream outboundObjectBuffer;
|
||||
|
||||
/**
|
||||
* Remembers the classes of the operations serialized into {@link #outboundObjectBuffer}. The list of classes in this list
|
||||
* matches with the sequence of objects written to {@link #outboundObjectBuffer} as long as the {@link #outboundBufferMonitor}
|
||||
* is being held.
|
||||
*/
|
||||
private List<Class<?>> outboundBufferClasses;
|
||||
|
||||
/**
|
||||
* Used to schedule the sending of all operations in {@link #outboundBuffer} using the {@link #sendingTask}.
|
||||
*/
|
||||
private final Timer timer;
|
||||
|
||||
/**
|
||||
* Sends all operations in {@link #outboundBuffer}. When holding the monitor of {@link #outboundBuffer},
|
||||
* the following rules hold:
|
||||
*
|
||||
* <ul>
|
||||
* <li>if <code>null</code>, adding an operation to {@link #outboundBuffer} needs to create and assign a new timer that
|
||||
* schedules a sending task.
|
||||
* </li>
|
||||
* <li>if not <code>null</code>, an operation added to {@link #outboundBuffer} is guaranteed to be sent by the timer
|
||||
* </li>
|
||||
* </ul>
|
||||
*
|
||||
*/
|
||||
private TimerTask sendingTask;
|
||||
|
||||
/**
|
||||
* Defines for how many milliseconds the {@link #timer} will wait since the first operation has been added to an empty
|
||||
* {@link #outboundBuffer} until it carries out the actual transmission task. The longer this duration, the more operations
|
||||
* are likely to be sent per message transmitted, reducing overhead but correspondingly increasing latency.
|
||||
*/
|
||||
private final long TRANSMISSION_DELAY_MILLIS = 100;
|
||||
|
||||
/**
|
||||
* Defines at which message size in bytes the message will be sent regardless the {@link #TRANSMISSION_DELAY_MILLIS}.
|
||||
*/
|
||||
private final int TRIGGER_MESSAGE_SIZE_IN_BYTES = 1024*1024;
|
||||
|
||||
/**
|
||||
* Counts the messages sent out by this replicator
|
||||
*/
|
||||
private long messageCount;
|
||||
|
||||
public ReplicationServiceImpl(String exchangeName, String exchangeHost, final ReplicationInstancesManager replicationInstancesManager) throws IOException {
|
||||
this(exchangeName, exchangeHost, replicationInstancesManager, /* localService */ null, /* create RacingEventServiceTracker */ true);
|
||||
}
|
||||
|
||||
private ReplicationServiceImpl(String exchangeName, String exchangeHost,
|
||||
final ReplicationInstancesManager replicationInstancesManager, RacingEventService localService,
|
||||
boolean createRacingEventServiceTracker) throws IOException {
|
||||
timer = new Timer("ReplicationServiceImpl timer for delayed task sending");
|
||||
this.replicationInstancesManager = replicationInstancesManager;
|
||||
replicaUUIDs = new HashMap<ReplicationMasterDescriptor, String>(); // XXX why is this a map? there should be only one connection to a master
|
||||
replicaUUIDs = new HashMap<ReplicationMasterDescriptor, String>();
|
||||
if (createRacingEventServiceTracker) {
|
||||
racingEventServiceTracker = getRacingEventServiceTracker();
|
||||
racingEventServiceTracker.open();
|
||||
} else {
|
||||
racingEventServiceTracker = null;
|
||||
}
|
||||
this.localService = localService;
|
||||
this.exchangeName = exchangeName;
|
||||
this.exchangeHost = exchangeHost;
|
||||
replicator = null;
|
||||
serverUUID = UUID.randomUUID();
|
||||
logger.info("Setting " + serverUUID.toString() + " as unique replication identifier.");
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Like {@link #ReplicationServiceImpl(String, ReplicationInstancesManager)}, only that instead of using an OSGi
|
||||
* service tracker to discover the {@link RacingEventService}, the service to replicate is "injected" here.
|
||||
*
|
||||
* @param exchangeName
|
||||
* the name of the exchange to which replicas can bind
|
||||
*/
|
||||
public ReplicationServiceImpl(String exchangeName, String exchangeHost,
|
||||
final ReplicationInstancesManager replicationInstancesManager, RacingEventService localService) throws IOException {
|
||||
this(exchangeName, exchangeHost, replicationInstancesManager, localService, /* create RacingEventServiceTracker */ false);
|
||||
}
|
||||
|
||||
private Channel createMasterChannel(String exchangeName, String exchangeHost) throws IOException {
|
||||
protected ServiceTracker<RacingEventService, RacingEventService> getRacingEventServiceTracker() {
|
||||
return new ServiceTracker<RacingEventService, RacingEventService>(
|
||||
Activator.getDefaultContext(), RacingEventService.class.getName(), null);
|
||||
}
|
||||
|
||||
private Channel createMasterChannelAndDeclareFanoutExchange() throws IOException {
|
||||
Channel result = createMasterChannel();
|
||||
result.exchangeDeclare(exchangeName, "fanout");
|
||||
logger.info("Created fanout exchange "+exchangeName+" successfully.");
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Channel createMasterChannel() throws IOException, ConnectException {
|
||||
final ConnectionFactory connectionFactory = new ConnectionFactory();
|
||||
connectionFactory.setHost(exchangeHost); // ...and use default port
|
||||
|
||||
Channel result = null;
|
||||
try {
|
||||
result = connectionFactory.newConnection().createChannel();
|
||||
@@ -129,9 +218,7 @@ public class ReplicationServiceImpl implements ReplicationService, OperationExec
|
||||
logger.severe("Could not connect to messaging queue on " + connectionFactory.getHost() + ":" + connectionFactory.getPort() + "/" + exchangeName);
|
||||
throw ex;
|
||||
}
|
||||
|
||||
logger.info("Connected to " + connectionFactory.getHost() + ":" + connectionFactory.getPort() + "/" + exchangeName);
|
||||
result.exchangeDeclare(exchangeName, "fanout");
|
||||
logger.info("Connected to " + connectionFactory.getHost() + ":" + connectionFactory.getPort());
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -152,11 +239,12 @@ public class ReplicationServiceImpl implements ReplicationService, OperationExec
|
||||
addAsListenerToRacingEventService();
|
||||
synchronized (this) {
|
||||
if (masterChannel == null) {
|
||||
masterChannel = createMasterChannel(exchangeName, exchangeHost);
|
||||
masterChannel = createMasterChannelAndDeclareFanoutExchange();
|
||||
}
|
||||
}
|
||||
}
|
||||
replicationInstancesManager.registerReplica(replica);
|
||||
logger.info("Registered replica " + replica);
|
||||
}
|
||||
|
||||
private void addAsListenerToRacingEventService() {
|
||||
@@ -165,6 +253,7 @@ public class ReplicationServiceImpl implements ReplicationService, OperationExec
|
||||
|
||||
@Override
|
||||
public void unregisterReplica(ReplicaDescriptor replica) throws IOException {
|
||||
logger.info("Unregistering replica " + replica);
|
||||
replicationInstancesManager.unregisterReplica(replica);
|
||||
if (!replicationInstancesManager.hasReplicas()) {
|
||||
removeAsListenerFromRacingEventService();
|
||||
@@ -181,15 +270,105 @@ public class ReplicationServiceImpl implements ReplicationService, OperationExec
|
||||
getRacingEventService().removeOperationExecutionListener(this);
|
||||
}
|
||||
|
||||
private void broadcastOperation(RacingEventServiceOperation<?> operation) throws Exception {
|
||||
// serialize operation into message
|
||||
/**
|
||||
* Schedules a single operation for broadcast. The operation is added to {@link #outboundBuffer}, and if not already scheduled,
|
||||
* a {@link #timer} is created and scheduled to send in {@link #TRANSMISSION_DELAY_MILLIS} milliseconds.
|
||||
*/
|
||||
private void broadcastOperation(RacingEventServiceOperation<?> operation) throws IOException {
|
||||
// need to write the operations one by one, making sure the ObjectOutputStream always writes
|
||||
// identical objects again if required because they may have changed state in between
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
ObjectOutputStream oos = new ObjectOutputStream(bos);
|
||||
oos.writeObject(operation);
|
||||
oos.close();
|
||||
final byte[] bytes = bos.toByteArray();
|
||||
synchronized (outboundBufferMonitor) {
|
||||
if (outboundBuffer == null) {
|
||||
outboundBuffer = new ByteArrayOutputStream();
|
||||
GZIPOutputStream zipper = new GZIPOutputStream(outboundBuffer);
|
||||
outboundObjectBuffer = new ObjectOutputStream(zipper);
|
||||
outboundBufferClasses = new ArrayList<>();
|
||||
}
|
||||
outboundObjectBuffer.writeObject(bytes);
|
||||
outboundBufferClasses.add(operation.getClass());
|
||||
if (outboundBuffer.size() > TRIGGER_MESSAGE_SIZE_IN_BYTES) {
|
||||
logger.info("Triggering replication because buffer holds " + outboundBuffer.size()
|
||||
+ " bytes which exceeds trigger size " + TRIGGER_MESSAGE_SIZE_IN_BYTES+" bytes");
|
||||
flushBufferToRabbitMQ();
|
||||
} else {
|
||||
if (sendingTask == null) {
|
||||
sendingTask = new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
sendingTask = null;
|
||||
logger.fine("Running timer task, flushing buffer");
|
||||
flushBufferToRabbitMQ();
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.SEVERE, "Exception while trying to replicate operations", e);
|
||||
}
|
||||
}
|
||||
};
|
||||
timer.schedule(sendingTask, TRANSMISSION_DELAY_MILLIS);
|
||||
}
|
||||
}
|
||||
if (++messageCount % 10000l == 0) {
|
||||
logger.info("Handled "+messageCount+" messages for replication. Current outbound replication queue size: "+outboundBufferClasses.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains the monitor on {@link #outboundBufferMonitor}, copies the references to the buffers, nulls out
|
||||
* the buffers, then releases the monitor and broadcasts the buffer.
|
||||
*/
|
||||
private void flushBufferToRabbitMQ() {
|
||||
logger.fine("Trying to acquire monitor");
|
||||
final byte[] bytesToSend;
|
||||
final List<Class<?>> classesOfOperationsToSend;
|
||||
final boolean doSend;
|
||||
synchronized (outboundBufferMonitor) {
|
||||
if (outboundBuffer != null) {
|
||||
logger.fine("Preparing " + outboundBufferClasses.size() + " operations for sending to RabbitMQ exchange");
|
||||
try {
|
||||
outboundObjectBuffer.close();
|
||||
logger.fine("Sucessfully closed ObjectOutputStream");
|
||||
} catch (IOException e) {
|
||||
logger.log(Level.SEVERE, "Error trying to replicate " + outboundBufferClasses.size()
|
||||
+ " operations", e);
|
||||
}
|
||||
bytesToSend = outboundBuffer.toByteArray();
|
||||
logger.fine("Successfully produced bytesToSend array of length " + bytesToSend.length);
|
||||
classesOfOperationsToSend = outboundBufferClasses;
|
||||
doSend = true;
|
||||
outboundBuffer = null;
|
||||
outboundObjectBuffer = null;
|
||||
outboundBufferClasses = null;
|
||||
} else {
|
||||
logger.fine("No buffer set; probably two timer tasks were scheduled concurrently. No problem, just not sending this time around.");
|
||||
doSend = false;
|
||||
bytesToSend = null;
|
||||
classesOfOperationsToSend = null;
|
||||
}
|
||||
}
|
||||
if (doSend) {
|
||||
try {
|
||||
broadcastOperations(bytesToSend, classesOfOperationsToSend);
|
||||
logger.fine("Successfully handed " + classesOfOperationsToSend.size() + " operations to broadcaster");
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.SEVERE, "Error trying to replicate " + classesOfOperationsToSend.size() + " operations", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void broadcastOperations(byte[] bytesToSend, List<Class<?>> classesOfOperationsToSend) throws IOException {
|
||||
logger.fine("broadcasting "+classesOfOperationsToSend.size()+" operations as "+bytesToSend.length+" bytes");
|
||||
if (masterChannel != null) {
|
||||
masterChannel.basicPublish(exchangeName, /* routingKey */"", /* properties */null, bos.toByteArray());
|
||||
replicationInstancesManager.log(operation);
|
||||
logger.fine("buffer to broadcast has "+bytesToSend.length+" bytes ("+(bytesToSend.length/1024/1024)+"MB)");
|
||||
long startTime = System.currentTimeMillis();
|
||||
masterChannel.basicPublish(exchangeName, /* routingKey */"", /* properties */null, bytesToSend);
|
||||
logger.fine("successfully published "+bytesToSend.length+" bytes, taking "+(System.currentTimeMillis()-startTime)+"ms");
|
||||
replicationInstancesManager.log(classesOfOperationsToSend, bytesToSend.length);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,12 +413,22 @@ public class ReplicationServiceImpl implements ReplicationService, OperationExec
|
||||
replicatorThread.start();
|
||||
logger.info("Started replicator thread");
|
||||
InputStream is = initialLoadURL.openStream();
|
||||
final RacingEventService racingEventService = getRacingEventService();
|
||||
ObjectInputStream ois = racingEventService.getBaseDomainFactory().createObjectInputStreamResolvingAgainstThisFactory(new GZIPInputStream(is));
|
||||
logger.info("Starting to receive initial load");
|
||||
racingEventService.initiallyFillFrom(ois);
|
||||
logger.info("Done receiving initial load");
|
||||
replicator.setSuspended(false); // apply queued operations
|
||||
final String queueName = new BufferedReader(new InputStreamReader(is)).readLine();
|
||||
try {
|
||||
RabbitInputStreamProvider rabbitInputStreamProvider = new RabbitInputStreamProvider(master.createChannel(), queueName);
|
||||
final RacingEventService racingEventService = getRacingEventService();
|
||||
ObjectInputStream ois = racingEventService.getBaseDomainFactory()
|
||||
.createObjectInputStreamResolvingAgainstThisFactory(
|
||||
new GZIPInputStream(rabbitInputStreamProvider.getInputStream()));
|
||||
logger.info("Starting to receive initial load");
|
||||
racingEventService.initiallyFillFrom(ois);
|
||||
logger.info("Done receiving initial load");
|
||||
replicator.setSuspended(false); // apply queued operations
|
||||
} finally {
|
||||
// delete initial load queue
|
||||
DeleteOk deleteOk = consumer.getChannel().queueDelete(queueName);
|
||||
logger.info("Deleted queue "+queueName+" used for initial load: "+deleteOk.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -283,6 +472,10 @@ public class ReplicationServiceImpl implements ReplicationService, OperationExec
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link #broadcastOperation(RacingEventServiceOperation) Broadcasts} the <code>operation</code> to all registered
|
||||
* replicas by publishing it to the fan-out exchange.
|
||||
*/
|
||||
@Override
|
||||
public <T> void executed(RacingEventServiceOperation<T> operation) {
|
||||
try {
|
||||
@@ -300,7 +493,27 @@ public class ReplicationServiceImpl implements ReplicationService, OperationExec
|
||||
public Map<Class<? extends RacingEventServiceOperation<?>>, Integer> getStatistics(ReplicaDescriptor replicaDescriptor) {
|
||||
return replicationInstancesManager.getStatistics(replicaDescriptor);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public double getAverageNumberOfOperationsPerMessage(ReplicaDescriptor replicaDescriptor) {
|
||||
return replicationInstancesManager.getAverageNumberOfOperationsPerMessage(replicaDescriptor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getNumberOfMessagesSent(ReplicaDescriptor replica) {
|
||||
return replicationInstancesManager.getNumberOfMessagesSent(replica);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getNumberOfBytesSent(ReplicaDescriptor replica) {
|
||||
return replicationInstancesManager.getNumberOfBytesSent(replica);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getAverageNumberOfBytesPerMessage(ReplicaDescriptor replica) {
|
||||
return replicationInstancesManager.getAverageNumberOfBytesPerMessage(replica);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopToReplicateFromMaster() throws IOException {
|
||||
ReplicationMasterDescriptor descriptor = isReplicatingFromMaster();
|
||||
|
||||
+23
-3
@@ -2,9 +2,12 @@ package com.sap.sailing.server.replication.impl;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.PrintWriter;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
@@ -17,9 +20,11 @@ import javax.servlet.http.HttpServletResponse;
|
||||
import org.osgi.framework.BundleContext;
|
||||
import org.osgi.util.tracker.ServiceTracker;
|
||||
|
||||
import com.rabbitmq.client.Channel;
|
||||
import com.sap.sailing.server.RacingEventService;
|
||||
import com.sap.sailing.server.gateway.SailingServerHttpServlet;
|
||||
import com.sap.sailing.server.replication.ReplicationService;
|
||||
import com.sap.sailing.util.impl.CountingOutputStream;
|
||||
|
||||
/**
|
||||
* As the response to any type of <code>GET</code> request, sends a serialized copy of the {@link RacingEventService} to
|
||||
@@ -39,6 +44,11 @@ public class ReplicationServlet extends SailingServerHttpServlet {
|
||||
public static final String SERVER_UUID = "uuid";
|
||||
public static final String ADDITIONAL_INFORMATION = "additional";
|
||||
|
||||
/**
|
||||
* The size of the packages into which the initial load is split
|
||||
*/
|
||||
private static final int INITIAL_LOAD_PACKAGE_SIZE = 1024*1024;
|
||||
|
||||
private ServiceTracker<ReplicationService, ReplicationService> replicationServiceTracker;
|
||||
|
||||
public ReplicationServlet() throws Exception {
|
||||
@@ -61,6 +71,7 @@ public class ReplicationServlet extends SailingServerHttpServlet {
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
||||
String action = req.getParameter(ACTION);
|
||||
logger.info("Received replication request, action is "+action);
|
||||
switch (Action.valueOf(action)) {
|
||||
case REGISTER:
|
||||
registerClientWithReplicationService(req, resp);
|
||||
@@ -69,9 +80,17 @@ public class ReplicationServlet extends SailingServerHttpServlet {
|
||||
deregisterClientWithReplicationService(req, resp);
|
||||
break;
|
||||
case INITIAL_LOAD:
|
||||
final GZIPOutputStream gzipOutputStream = new GZIPOutputStream(new CountingOutputStream(
|
||||
resp.getOutputStream(), /* log every megabyte */1024l * 1024l, Level.INFO,
|
||||
"HTTP output for initial load for " + req.getRemoteHost()));
|
||||
Channel channel = getReplicationService().createMasterChannel();
|
||||
RabbitOutputStream ros = new RabbitOutputStream(INITIAL_LOAD_PACKAGE_SIZE, channel,
|
||||
/* queueName */ "initialLoad-for-"+req.getRemoteHost()+"@"+new Date()+"-"+UUID.randomUUID(),
|
||||
/* syncAfterTimeout */ false);
|
||||
PrintWriter br = new PrintWriter(new OutputStreamWriter(resp.getOutputStream()));
|
||||
br.println(ros.getQueueName());
|
||||
br.flush();
|
||||
final CountingOutputStream countingOutputStream = new CountingOutputStream(
|
||||
ros, /* log every megabyte */1024l * 1024l, Level.INFO,
|
||||
"HTTP output for initial load for " + req.getRemoteHost());
|
||||
final GZIPOutputStream gzipOutputStream = new GZIPOutputStream(countingOutputStream);
|
||||
ObjectOutputStream oos = new ObjectOutputStream(gzipOutputStream);
|
||||
try {
|
||||
getService().serializeForInitialReplication(oos);
|
||||
@@ -82,6 +101,7 @@ public class ReplicationServlet extends SailingServerHttpServlet {
|
||||
resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
|
||||
e.printStackTrace(resp.getWriter());
|
||||
}
|
||||
countingOutputStream.close();
|
||||
break;
|
||||
default:
|
||||
resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "Action " + action + " not understood. Must be one of "
|
||||
|
||||
+40
-18
@@ -1,6 +1,7 @@
|
||||
package com.sap.sailing.server.replication.impl;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.lang.reflect.Field;
|
||||
@@ -14,6 +15,7 @@ import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
import com.rabbitmq.client.ConsumerCancelledException;
|
||||
import com.rabbitmq.client.QueueingConsumer;
|
||||
@@ -87,7 +89,6 @@ public class Replicator implements Runnable {
|
||||
* them directly.
|
||||
* @param consumer
|
||||
* the RabbitMQ consumer from which to load messages
|
||||
* @param baseDomainFactory TODO
|
||||
*/
|
||||
public Replicator(ReplicationMasterDescriptor master, HasRacingEventService racingEventServiceTracker, boolean startSuspended, QueueingConsumer consumer, DomainFactory baseDomainFactory) {
|
||||
this.queue = new ArrayList<RacingEventServiceOperation<?>>();
|
||||
@@ -98,14 +99,18 @@ public class Replicator implements Runnable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts fetching messages from the {@link #consumer}. After receiving a single message, assumes it's a serialized
|
||||
* {@link RacingEventServiceOperation}, and applies it to the {@link RacingEventService} which is obtained from the
|
||||
* service tracker passed to this replicator at construction time.
|
||||
* Starts fetching messages from the {@link #consumer}. After receiving a single message, assumes it's an
|
||||
* {@link Iterable} of serialized {@link RacingEventServiceOperation} objects, and applies it to the
|
||||
* {@link RacingEventService} which is obtained from the service tracker passed to this replicator at construction
|
||||
* time.
|
||||
*
|
||||
* @see ReplicationServiceImpl#executed(RacingEventServiceOperation)
|
||||
*/
|
||||
@Override
|
||||
public void run() {
|
||||
ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader();
|
||||
long messageCount = 0;
|
||||
long operationCount = 0;
|
||||
Field _queue;
|
||||
try {
|
||||
_queue = QueueingConsumer.class.getDeclaredField("_queue");
|
||||
@@ -113,17 +118,16 @@ public class Replicator implements Runnable {
|
||||
} catch (Exception e) {
|
||||
_queue = null;
|
||||
}
|
||||
while (true) {
|
||||
if (isBeingStopped()) {
|
||||
break;
|
||||
}
|
||||
try {
|
||||
final boolean logsFine = logger.isLoggable(Level.FINE);
|
||||
while (!isBeingStopped()) {
|
||||
try {
|
||||
Delivery delivery = consumer.nextDelivery();
|
||||
messageCount++;
|
||||
if (_queue != null) {
|
||||
if (messageCount % 10000l == 0) {
|
||||
if (logsFine || messageCount % 10l == 0) {
|
||||
try {
|
||||
logger.info("Inbound replication queue size: "+((BlockingQueue<?>) _queue.get(consumer)).size());
|
||||
logger.log(messageCount%10l==0 ? Level.INFO : Level.FINE,
|
||||
"Inbound replication queue size: "+((BlockingQueue<?>) _queue.get(consumer)).size());
|
||||
} catch (Exception e) {
|
||||
// it didn't work; but it's a log message only...
|
||||
logger.info("Received another 10000 replication messages");
|
||||
@@ -136,9 +140,26 @@ public class Replicator implements Runnable {
|
||||
// of all required bundles/packages can be deserialized at least
|
||||
Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
|
||||
ObjectInputStream ois = racingEventServiceTracker.getRacingEventService().getBaseDomainFactory()
|
||||
.createObjectInputStreamResolvingAgainstThisFactory(new ByteArrayInputStream(bytesFromMessage));
|
||||
RacingEventServiceOperation<?> operation = (RacingEventServiceOperation<?>) ois.readObject();
|
||||
applyOrQueue(operation);
|
||||
.createObjectInputStreamResolvingAgainstThisFactory(
|
||||
new GZIPInputStream(new ByteArrayInputStream(bytesFromMessage)));
|
||||
int operationsInMessage = 0;
|
||||
try {
|
||||
while (true) {
|
||||
byte[] serializedOperation = (byte[]) ois.readObject();
|
||||
ObjectInputStream operationOIS = racingEventServiceTracker.getRacingEventService().getBaseDomainFactory()
|
||||
.createObjectInputStreamResolvingAgainstThisFactory(new ByteArrayInputStream(serializedOperation));
|
||||
RacingEventServiceOperation<?> operation = (RacingEventServiceOperation<?>) operationOIS.readObject();
|
||||
operationCount++;
|
||||
operationsInMessage++;
|
||||
if (operationCount % 10000l == 0) {
|
||||
logger.info("Received " + operationCount + " operations so far");
|
||||
}
|
||||
applyOrQueue(operation);
|
||||
}
|
||||
} catch (EOFException eof) {
|
||||
logger.fine("Reached EOF on replication message after having read "+operationsInMessage+" operations");
|
||||
// reached EOF; expected
|
||||
}
|
||||
} catch (ConsumerCancelledException cce) {
|
||||
logger.info("Consumer has been shut down properly.");
|
||||
break;
|
||||
@@ -150,12 +171,10 @@ public class Replicator implements Runnable {
|
||||
if (isBeingStopped()) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (sse.isInitiatedByApplication()) {
|
||||
logger.severe("Application shut down messaging queue for " + this.toString());
|
||||
break;
|
||||
}
|
||||
|
||||
logger.info(sse.getMessage());
|
||||
if (checksPerformed <= CHECK_COUNT) {
|
||||
try {
|
||||
@@ -193,7 +212,6 @@ public class Replicator implements Runnable {
|
||||
Thread.currentThread().setContextClassLoader(oldClassLoader);
|
||||
}
|
||||
}
|
||||
|
||||
logger.info("Stopped replicator thread. This server will no longer receive events from a master.");
|
||||
}
|
||||
|
||||
@@ -248,7 +266,11 @@ public class Replicator implements Runnable {
|
||||
for (Iterator<RacingEventServiceOperation<?>> i=queue.iterator(); i.hasNext(); ) {
|
||||
RacingEventServiceOperation<?> operation = i.next();
|
||||
i.remove();
|
||||
apply(operation);
|
||||
try {
|
||||
apply(operation);
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.SEVERE, "Error applying queued, replicated operation "+operation+". Continuing with next queued operation.", e);
|
||||
}
|
||||
}
|
||||
assert queue.isEmpty();
|
||||
notifyAll();
|
||||
|
||||
+13
-19
@@ -10,9 +10,6 @@ import org.junit.Test;
|
||||
import com.sap.sailing.domain.base.BoatClass;
|
||||
import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.base.DomainFactory;
|
||||
import com.sap.sailing.domain.base.Fleet;
|
||||
import com.sap.sailing.domain.base.impl.FleetImpl;
|
||||
import com.sap.sailing.domain.common.LeaderboardNameConstants;
|
||||
import com.sap.sailing.domain.common.MaxPointsReason;
|
||||
import com.sap.sailing.domain.common.NoWindException;
|
||||
import com.sap.sailing.domain.common.TimePoint;
|
||||
@@ -68,19 +65,18 @@ public class LeaderboardDiscardingRulesTest {
|
||||
FlexibleLeaderboard leaderboard = (FlexibleLeaderboard) racingEventService.getLeaderboardByName(LEADERBOARDNAME);
|
||||
assertNotNull(leaderboard);
|
||||
BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("29erXX", /* typicallyStartsUpwind */ true);
|
||||
Fleet fleet = new FleetImpl(LeaderboardNameConstants.DEFAULT_FLEET_NAME);
|
||||
|
||||
Competitor hasso = AbstractLeaderboardTest.createCompetitor("Dr. Hasso Plattner");
|
||||
final TrackedRace race1 = new MockedTrackedRaceWithFixedRank(hasso, /* rank */ 123, /* started */ true, boatClass);
|
||||
leaderboard.addRace(race1, "R1", /* medalRace */ false, fleet);
|
||||
leaderboard.addRace(race1, "R1", /* medalRace */false);
|
||||
final TrackedRace race2 = new MockedTrackedRaceWithFixedRank(hasso, /* rank */ 123, /* started */ true, boatClass);
|
||||
leaderboard.addRace(race2, "R2", /* medalRace */ false, fleet);
|
||||
leaderboard.addRace(race2, "R2", /* medalRace */false);
|
||||
|
||||
Competitor wolfgang = AbstractLeaderboardTest.createCompetitor("Wolfgang Hunger");
|
||||
final TrackedRace race3 = new MockedTrackedRaceWithFixedRank(wolfgang, /* rank */ 123, /* started */ true, boatClass);
|
||||
leaderboard.addRace(race3, "R3", /* medalRace */ false, fleet);
|
||||
leaderboard.addRace(race3, "R3", /* medalRace */false);
|
||||
final TrackedRace race4 = new MockedTrackedRaceWithFixedRank(wolfgang, /* rank */ 124, /* started */ true, boatClass);
|
||||
leaderboard.addRace(race4, "R4", /* medalRace */ false, fleet);
|
||||
leaderboard.addRace(race4, "R4", /* medalRace */false);
|
||||
|
||||
leaderboard.getScoreCorrection().correctScore(hasso, leaderboard.getRaceColumnByName("R3"), 123.);
|
||||
leaderboard.getScoreCorrection().correctScore(hasso, leaderboard.getRaceColumnByName("R4"), 123.);
|
||||
@@ -108,23 +104,22 @@ public class LeaderboardDiscardingRulesTest {
|
||||
FlexibleLeaderboard leaderboard = (FlexibleLeaderboard) racingEventService.getLeaderboardByName(LEADERBOARDNAME);
|
||||
assertNotNull(leaderboard);
|
||||
BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("ESS40", /* typicallyStartsUpwind */ true);
|
||||
Fleet fleet = new FleetImpl(LeaderboardNameConstants.DEFAULT_FLEET_NAME);
|
||||
|
||||
Competitor rasmus = AbstractLeaderboardTest.createCompetitor("Rasmus");
|
||||
final TrackedRace race1 = new MockedTrackedRaceWithFixedRank(rasmus, /* rank */ 2, /* started */ true, boatClass); // score 2.0
|
||||
leaderboard.addRace(race1, "R1", /* medalRace */ false, fleet);
|
||||
leaderboard.addRace(race1, "R1", /* medalRace */false);
|
||||
final TrackedRace race2 = new MockedTrackedRaceWithFixedRank(rasmus, /* rank */ 2, /* started */ true, boatClass); // score 2.0
|
||||
leaderboard.addRace(race2, "R2", /* medalRace */ false, fleet);
|
||||
leaderboard.addRace(race2, "R2", /* medalRace */false);
|
||||
|
||||
Competitor jes = AbstractLeaderboardTest.createCompetitor("Jes");
|
||||
final TrackedRace race3 = new MockedTrackedRaceWithFixedRank(jes, /* rank */ 2, /* started */ true, boatClass); // score 2.0
|
||||
leaderboard.addRace(race3, "R3", /* medalRace */ false, fleet);
|
||||
leaderboard.addRace(race3, "R3", /* medalRace */false);
|
||||
final TrackedRace race4 = new MockedTrackedRaceWithFixedRank(jes, /* rank */ 3, /* started */ true, boatClass); // score 1.0
|
||||
leaderboard.addRace(race4, "R4", /* medalRace */ false, fleet);
|
||||
leaderboard.addRace(race4, "R4", /* medalRace */false);
|
||||
|
||||
Competitor nobody = AbstractLeaderboardTest.createCompetitor("Nobody");
|
||||
final TrackedRace race5 = new MockedTrackedRaceWithFixedRank(nobody, /* rank */ 3, /* started */ true, boatClass); // score 1.0
|
||||
leaderboard.addRace(race5, "R5", /* medalRace */ false, fleet);
|
||||
leaderboard.addRace(race5, "R5", /* medalRace */false);
|
||||
|
||||
leaderboard.getScoreCorrection().correctScore(rasmus, leaderboard.getRaceColumnByName("R3"), 2.0);
|
||||
leaderboard.getScoreCorrection().correctScore(rasmus, leaderboard.getRaceColumnByName("R4"), 2.0);
|
||||
@@ -153,19 +148,18 @@ public class LeaderboardDiscardingRulesTest {
|
||||
FlexibleLeaderboard leaderboard = (FlexibleLeaderboard) racingEventService.getLeaderboardByName(LEADERBOARDNAME);
|
||||
assertNotNull(leaderboard);
|
||||
BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("29erXX", /* typicallyStartsUpwind */ true);
|
||||
Fleet fleet = new FleetImpl(LeaderboardNameConstants.DEFAULT_FLEET_NAME);
|
||||
|
||||
Competitor hasso = AbstractLeaderboardTest.createCompetitor("Dr. Hasso Plattner");
|
||||
final TrackedRace race1 = new MockedTrackedRaceWithFixedRank(hasso, /* rank */ 123, /* started */ true, boatClass);
|
||||
leaderboard.addRace(race1, "R1", /* medalRace */ false, fleet);
|
||||
leaderboard.addRace(race1, "R1", /* medalRace */false);
|
||||
final TrackedRace race2 = new MockedTrackedRaceWithFixedRank(hasso, /* rank */ 123, /* started */ true, boatClass);
|
||||
leaderboard.addRace(race2, "R2", /* medalRace */ false, fleet);
|
||||
leaderboard.addRace(race2, "R2", /* medalRace */false);
|
||||
|
||||
Competitor wolfgang = AbstractLeaderboardTest.createCompetitor("Wolfgang Hunger");
|
||||
final TrackedRace race3 = new MockedTrackedRaceWithFixedRank(wolfgang, /* rank */ 123, /* started */ true, boatClass);
|
||||
leaderboard.addRace(race3, "R3", /* medalRace */ false, fleet);
|
||||
leaderboard.addRace(race3, "R3", /* medalRace */false);
|
||||
final TrackedRace race4 = new MockedTrackedRaceWithFixedRank(wolfgang, /* rank */ 124, /* started */ true, boatClass);
|
||||
leaderboard.addRace(race4, "R4", /* medalRace */ false, fleet);
|
||||
leaderboard.addRace(race4, "R4", /* medalRace */false);
|
||||
|
||||
leaderboard.getScoreCorrection().correctScore(hasso, leaderboard.getRaceColumnByName("R3"), 123.);
|
||||
leaderboard.getScoreCorrection().correctScore(hasso, leaderboard.getRaceColumnByName("R4"), 123.);
|
||||
|
||||
+212
-2
@@ -573,9 +573,8 @@ public class MasterDataImportTest {
|
||||
|
||||
RaceColumn raceColumn = leaderboard.getRaceColumnByName(raceColumnName);
|
||||
TrackedRace trackedRace = new DummyTrackedRace(competitors, regatta, null);
|
||||
|
||||
regatta.addRace(trackedRace.getRace()); // make sure the competitor is serialized together with the regatta
|
||||
raceColumn.setTrackedRace(testFleet1, trackedRace);
|
||||
|
||||
// Serialize
|
||||
List<String> groupNamesToExport = new ArrayList<String>();
|
||||
groupNamesToExport.add(group.getName());
|
||||
@@ -1845,4 +1844,215 @@ public class MasterDataImportTest {
|
||||
Assert.assertEquals(factor, metaColumn.getFactor());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMasterDataImportWithFlexibleLeaderboard() throws MalformedURLException, IOException,
|
||||
InterruptedException, ClassNotFoundException {
|
||||
// Setup source service
|
||||
RacingEventService sourceService = new RacingEventServiceImpl();
|
||||
UUID courseAreaUUID = UUID.randomUUID();
|
||||
|
||||
List<String> raceColumnNames = new ArrayList<String>();
|
||||
String raceColumnName = "T1";
|
||||
raceColumnNames.add(raceColumnName);
|
||||
raceColumnNames.add("T2");
|
||||
final List<String> emptyRaceColumnNamesList = Collections.emptyList();
|
||||
|
||||
List<Series> series = new ArrayList<Series>();
|
||||
List<Fleet> fleets = new ArrayList<Fleet>();
|
||||
String testFleet1Name = "testFleet1";
|
||||
FleetImpl testFleet1 = new FleetImpl(testFleet1Name);
|
||||
fleets.add(testFleet1);
|
||||
fleets.add(new FleetImpl("testFleet2"));
|
||||
series.add(new SeriesImpl("testSeries", false, fleets, emptyRaceColumnNamesList, sourceService));
|
||||
UUID regattaUUID = UUID.randomUUID();
|
||||
Regatta regatta = sourceService.createRegatta(
|
||||
RegattaImpl.getDefaultName(TEST_REGATTA_NAME, TEST_BOAT_CLASS_NAME), TEST_BOAT_CLASS_NAME, regattaUUID,
|
||||
series, true, new LowPoint(), courseAreaUUID, /* useStartTimeInference */true);
|
||||
for (String name : raceColumnNames) {
|
||||
series.get(0).addRaceColumn(name, sourceService);
|
||||
}
|
||||
double factor = 3.0;
|
||||
series.get(0).getRaceColumnByName(raceColumnName).setFactor(factor);
|
||||
|
||||
int[] discardRule = { 1, 2, 3, 4 };
|
||||
ScoringScheme scoringScheme = new LowPoint();
|
||||
String flexLeaderboardName = "FlexName";
|
||||
FlexibleLeaderboard leaderboard = sourceService.addFlexibleLeaderboard(flexLeaderboardName, "TestFlex",
|
||||
discardRule,
|
||||
scoringScheme, courseAreaUUID);
|
||||
List<String> leaderboardNames = new ArrayList<String>();
|
||||
leaderboardNames.add(leaderboard.getName());
|
||||
LeaderboardGroup group = sourceService.addLeaderboardGroup(UUID.randomUUID(), TEST_GROUP_NAME, "testGroupDesc",
|
||||
false, leaderboardNames, null, null);
|
||||
|
||||
|
||||
// Set tracked Race with competitors
|
||||
List<Competitor> competitors = new ArrayList<Competitor>();
|
||||
UUID competitorUUID = UUID.randomUUID();
|
||||
Set<DynamicPerson> sailors = new HashSet<DynamicPerson>();
|
||||
sailors.add(new PersonImpl("Froderik Poterson", new NationalityImpl("GER"), new Date(645487200000L),
|
||||
"Oberhoschy"));
|
||||
DynamicPerson coach = new PersonImpl("Lennart Hensler", new NationalityImpl("GER"), new Date(645487200000L),
|
||||
"Der Lennart halt");
|
||||
DynamicTeam team = new TeamImpl("Pros", sailors, coach);
|
||||
BoatClass boatClass = new BoatClassImpl("H16", true);
|
||||
DynamicBoat boat = new BoatImpl("Wingy", boatClass, "GER70133");
|
||||
CompetitorImpl competitor = new CompetitorImpl(competitorUUID, "Froderik", Color.RED, team, boat);
|
||||
competitors.add(competitor);
|
||||
UUID competitorToSuppressUUID = UUID.randomUUID();
|
||||
Set<DynamicPerson> sailors2 = new HashSet<DynamicPerson>();
|
||||
sailors2.add(new PersonImpl("Angela Merkel", new NationalityImpl("GER"), new Date(645487200000L),
|
||||
"segelt auch mit"));
|
||||
DynamicPerson coach2 = new PersonImpl("Peer Steinbrueck", new NationalityImpl("GER"), new Date(645487200000L),
|
||||
"Bester Coach");
|
||||
DynamicTeam team2 = new TeamImpl("Noobs", sailors2, coach2);
|
||||
DynamicBoat boat2 = new BoatImpl("LahmeEnte", boatClass, "GER1337");
|
||||
CompetitorImpl competitorToSuppress = new CompetitorImpl(competitorToSuppressUUID, "Merkel", Color.RED, team2,
|
||||
boat2);
|
||||
competitors.add(competitorToSuppress);
|
||||
TrackedRace trackedRace = new DummyTrackedRace(competitors, regatta, null);
|
||||
|
||||
leaderboard.addRace(trackedRace, raceColumnName, false);
|
||||
RaceColumn raceColumnOnLeaderboard = leaderboard.getRaceColumnByName(raceColumnName);
|
||||
raceColumnOnLeaderboard.setFactor(3.0);
|
||||
|
||||
// Set log event
|
||||
RaceLogEventFactory factory = new RaceLogEventFactoryImpl();
|
||||
TimePoint logTimePoint = new MillisecondsTimePoint(1372489200000L);
|
||||
TimePoint logTimePoint2 = new MillisecondsTimePoint(1372489201000L);
|
||||
RaceLogStartTimeEvent logEvent = factory.createStartTimeEvent(logTimePoint, author, 1, logTimePoint);
|
||||
Fleet defaultFleet = leaderboard.getFleet(null);
|
||||
raceColumnOnLeaderboard.getRaceLog(defaultFleet).add(logEvent);
|
||||
Position p = new DegreePosition(3, 3);
|
||||
Wind wind = new WindImpl(p, logTimePoint2, new KnotSpeedWithBearingImpl(5, new DegreeBearingImpl(12)));
|
||||
RaceLogWindFixEvent windEvent = factory.createWindFixEvent(logTimePoint2, author, UUID.randomUUID(),
|
||||
new ArrayList<Competitor>(), 2, wind);
|
||||
raceColumnOnLeaderboard.getRaceLog(defaultFleet).add(windEvent);
|
||||
storedLogUUIDs.add(logEvent.getId());
|
||||
storedLogUUIDs.add(windEvent.getId());
|
||||
|
||||
// Set score correction
|
||||
double scoreCorrection = 12.0;
|
||||
leaderboard.getScoreCorrection().correctScore(competitor, raceColumnOnLeaderboard, scoreCorrection);
|
||||
MaxPointsReason maxPointsReason = MaxPointsReason.DNS;
|
||||
leaderboard.getScoreCorrection().setMaxPointsReason(competitor, raceColumnOnLeaderboard, maxPointsReason);
|
||||
|
||||
// Set carried Points
|
||||
double carriedPoints = 2.0;
|
||||
leaderboard.setCarriedPoints(competitor, carriedPoints);
|
||||
|
||||
// Set suppressed competitor
|
||||
leaderboard.setSuppressed(competitorToSuppress, true);
|
||||
|
||||
// Set display name
|
||||
String nickName = "Angie";
|
||||
leaderboard.setDisplayName(competitorToSuppress, nickName);
|
||||
|
||||
// Serialize
|
||||
List<String> groupNamesToExport = new ArrayList<String>();
|
||||
groupNamesToExport.add(group.getName());
|
||||
|
||||
RacingEventService destService;
|
||||
DomainFactory domainFactory;
|
||||
MasterDataResource resource = new MasterDataResource();
|
||||
MasterDataResource spyResource = spyResource(resource, sourceService);
|
||||
Response response = spyResource.getMasterDataByLeaderboardGroups(groupNamesToExport, false, true);
|
||||
StreamingOutput streamingOutput = (StreamingOutput) response.getEntity();
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
UUID randomUUID = UUID.randomUUID();
|
||||
InputStream inputStream = null;
|
||||
try {
|
||||
streamingOutput.write(os);
|
||||
os.flush();
|
||||
// Delete all data above from the database, to allow recreating all of it on target server
|
||||
deleteAllDataFromDatabase();
|
||||
// Import in new service
|
||||
destService = new RacingEventServiceImplMock(new DataImportProgressImpl(randomUUID));
|
||||
domainFactory = destService.getBaseDomainFactory();
|
||||
inputStream = new ByteArrayInputStream(os.toByteArray());
|
||||
|
||||
MasterDataImporter importer = new MasterDataImporter(domainFactory, destService);
|
||||
importer.importFromStream(inputStream, randomUUID, false);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException(e);
|
||||
} finally {
|
||||
os.close();
|
||||
inputStream.close();
|
||||
}
|
||||
|
||||
MasterDataImportObjectCreationCount creationCount = destService.getDataImportLock().getProgress(randomUUID)
|
||||
.getResult();
|
||||
|
||||
Assert.assertNotNull(creationCount);
|
||||
LeaderboardGroup leaderboardGroupOnTarget = destService.getLeaderboardGroupByName(TEST_GROUP_NAME);
|
||||
Assert.assertNotNull(leaderboardGroupOnTarget);
|
||||
Leaderboard leaderboardOnTarget = destService.getLeaderboardByName(flexLeaderboardName);
|
||||
Assert.assertNotNull(leaderboardOnTarget);
|
||||
|
||||
RaceColumn raceColumnOnTarget = leaderboardOnTarget.getRaceColumns().iterator().next();
|
||||
Fleet defaultFleetOnTarget = raceColumnOnTarget.getFleets().iterator().next();
|
||||
Assert.assertNotNull(raceColumnOnTarget);
|
||||
Assert.assertNull(raceColumnOnTarget.getTrackedRace(defaultFleetOnTarget));
|
||||
|
||||
Regatta regattaOnTarget = destService.createRegatta(
|
||||
RegattaImpl.getDefaultName(TEST_REGATTA_NAME, TEST_BOAT_CLASS_NAME), TEST_BOAT_CLASS_NAME, regattaUUID,
|
||||
series, true, new LowPoint(), courseAreaUUID, /* useStartTimeInference */true);
|
||||
|
||||
raceColumnOnTarget.setTrackedRace(defaultFleetOnTarget,
|
||||
new DummyTrackedRace(
|
||||
competitors, regattaOnTarget, null));
|
||||
|
||||
Assert.assertTrue(leaderboardOnTarget.getScoreCorrection().hasCorrectionFor(raceColumnOnTarget));
|
||||
Competitor competitorOnTarget = domainFactory.getExistingCompetitorById(competitorUUID);
|
||||
Set<Competitor> competitorsCreatedOnTarget = new HashSet<Competitor>();
|
||||
competitorsCreatedOnTarget.add(competitorOnTarget);
|
||||
|
||||
Assert.assertEquals(defaultFleetOnTarget, leaderboardOnTarget.getFleet(null));
|
||||
TrackedRace trackedRaceForTarget = new DummyTrackedRace(competitorsCreatedOnTarget, regattaOnTarget, null);
|
||||
|
||||
raceColumnOnTarget.setTrackedRace(defaultFleetOnTarget, trackedRaceForTarget);
|
||||
|
||||
Assert.assertEquals(factor, raceColumnOnTarget.getFactor());
|
||||
|
||||
// Check for score corrections
|
||||
Assert.assertEquals(
|
||||
scoreCorrection,
|
||||
leaderboardOnTarget.getScoreCorrection().getExplicitScoreCorrection(competitorOnTarget,
|
||||
raceColumnOnTarget));
|
||||
Assert.assertEquals(
|
||||
maxPointsReason,
|
||||
leaderboardOnTarget.getScoreCorrection().getMaxPointsReason(competitorOnTarget, raceColumnOnTarget,
|
||||
MillisecondsTimePoint.now()));
|
||||
|
||||
// Check for carried points
|
||||
Assert.assertEquals(carriedPoints, leaderboardOnTarget.getCarriedPoints(competitorOnTarget));
|
||||
|
||||
// Check for suppressed competitor
|
||||
Assert.assertTrue(leaderboardOnTarget.getSuppressedCompetitors().iterator().hasNext());
|
||||
Competitor suppressedCompetitorOnTarget = domainFactory.getCompetitorStore().getExistingCompetitorById(
|
||||
competitorToSuppressUUID);
|
||||
Assert.assertEquals(suppressedCompetitorOnTarget, leaderboardOnTarget.getSuppressedCompetitors().iterator()
|
||||
.next());
|
||||
|
||||
// Check for competitor display name
|
||||
Assert.assertEquals(nickName, leaderboardOnTarget.getDisplayName(suppressedCompetitorOnTarget));
|
||||
|
||||
// Check for race log event
|
||||
Assert.assertNotNull(raceColumnOnTarget.getRaceLog(defaultFleetOnTarget).getFirstRawFixAtOrAfter(logTimePoint));
|
||||
Assert.assertEquals(logEvent.getId(), raceColumnOnTarget.getRaceLog(defaultFleetOnTarget)
|
||||
.getFirstRawFixAtOrAfter(logTimePoint).getId());
|
||||
Assert.assertNotNull(raceColumnOnTarget.getRaceLog(defaultFleetOnTarget).getFirstFixAtOrAfter(logTimePoint2));
|
||||
Assert.assertEquals(wind, ((RaceLogWindFixEvent) raceColumnOnTarget.getRaceLog(defaultFleetOnTarget)
|
||||
.getFirstFixAtOrAfter(logTimePoint2)).getWindFix());
|
||||
|
||||
// Check for persisting of race log events:
|
||||
RacingEventService dest2 = new RacingEventServiceImplMock();
|
||||
Leaderboard lb2 = dest2.getLeaderboardByName(flexLeaderboardName);
|
||||
RaceColumn raceColumn2 = lb2.getRaceColumns().iterator().next();
|
||||
RaceLog raceLog2 = raceColumn2.getRaceLog(raceColumn2.getFleets().iterator().next());
|
||||
Assert.assertEquals(logEvent.getId(), raceLog2.getFirstRawFixAtOrAfter(logTimePoint).getId());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -19,7 +19,7 @@
|
||||
<stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-os ${target.os} -ws ${target.ws} -arch ${target.arch} -nl ${target.nl} -consoleLog -console -clean"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.SOURCE_PATH_PROVIDER" value="org.eclipse.pde.ui.workbenchClasspathProvider"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-ea -Declipse.ignoreApp=true -Dosgi.noShutdown=true -Dexpedition.udp.port=2010 -Xmx4096m -Djetty.home=${project_loc:com.sap.sailing.server}/../target/configuration/jetty -Djava.util.logging.config.file=${project_loc:com.sap.sailing.server}/../target/configuration/logging_debug.properties -Dkiwo.results=${project_loc:com.sap.sailing.kiworesultimport.test}/resources -Dpersistentcompetitors.clear=false -XX:+UseMembar -Digtimi.client.id=7fcdd217e0aa16090edb4ad55b09ec43b2021090e209541fc9b7003c2a2b70c6 -Digtimi.client.secret=aa569cf4909bdc7b0e04b11873f3c4ea20687421e010fcc25b771cca9e6f3f9a -Digtimi.client.redirecturi=http://127.0.0.1:8888/igtimi/oauth/v1/authorizationcallback -DspatialWind=true"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-ea -Declipse.ignoreApp=true -Dosgi.noShutdown=true -Dexpedition.udp.port=2010 -Xmx6000m -XX:+UseG1GC -Djetty.home=${project_loc:com.sap.sailing.server}/../target/configuration/jetty -Djava.util.logging.config.file=${project_loc:com.sap.sailing.server}/../target/configuration/logging_debug.properties -Dkiwo.results=${project_loc:com.sap.sailing.kiworesultimport.test}/resources -Dpersistentcompetitors.clear=false -XX:+UseMembar -Digtimi.client.id=7fcdd217e0aa16090edb4ad55b09ec43b2021090e209541fc9b7003c2a2b70c6 -Digtimi.client.secret=aa569cf4909bdc7b0e04b11873f3c4ea20687421e010fcc25b771cca9e6f3f9a -Digtimi.client.redirecturi=http://127.0.0.1:8888/igtimi/oauth/v1/authorizationcallback -DspatialWind=true"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.WORKING_DIRECTORY" value="${workspace_loc}"/>
|
||||
<stringAttribute key="pde.version" value="3.3"/>
|
||||
<stringAttribute key="profilingTraceType-ALLOCATION_TRACE" value="KEY_APPLICATION_FILTER%CTX_KEY%*%CTX_ENTRY%INCREASE_COUNT%CTX_KEY%8192%CTX_ENTRY%KEY_MIN_SIZE%CTX_KEY%32%CTX_ENTRY%KEY_MAX_SIZE%CTX_KEY%65536%CTX_ENTRY%KEY_INC_LINE_NRS%CTX_KEY%true%CTX_ENTRY%KEY_SESSION_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_ENABLEMENT%CTX_KEY%false%CTX_ENTRY%CLASS_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_USER_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_REQUEST_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_ADAPTIVE%CTX_KEY%false%CTX_ENTRY%"/>
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-os ${target.os} -ws ${target.ws} -arch ${target.arch} -nl ${target.nl} -consoleLog -console -clean"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.SOURCE_PATH_PROVIDER" value="org.eclipse.pde.ui.workbenchClasspathProvider"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Djetty.port=8889 -Dreplication.exchangeName=sapsailinganalytics-replica -Declipse.ignoreApp=true -Dosgi.noShutdown=true -Dexpedition.udp.port=2011 -Xmx6000m -Djetty.home=${project_loc:com.sap.sailing.server}/../target/configuration/jetty -Djava.util.logging.config.file=${project_loc:com.sap.sailing.server}/../target/configuration/logging_debug.properties -XX:+UseMembar"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Djetty.port=8889 -Dreplication.exchangeName=sapsailinganalytics-replica -Declipse.ignoreApp=true -Dosgi.noShutdown=true -Dexpedition.udp.port=2011 -Xmx6000m -XX:+UseG1GC -Djetty.home=${project_loc:com.sap.sailing.server}/../target/configuration/jetty -Djava.util.logging.config.file=${project_loc:com.sap.sailing.server}/../target/configuration/logging_debug.properties -XX:+UseMembar"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.WORKING_DIRECTORY" value="${workspace_loc}"/>
|
||||
<stringAttribute key="pde.version" value="3.3"/>
|
||||
<booleanAttribute key="show_selected_only" value="false"/>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-os ${target.os} -ws ${target.ws} -arch ${target.arch} -nl ${target.nl} -consoleLog -console -clean"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.SOURCE_PATH_PROVIDER" value="org.eclipse.pde.ui.workbenchClasspathProvider"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-ea -Declipse.ignoreApp=true -Dosgi.noShutdown=true -Dfile.encoding=cp1252 -Dexpedition.udp.port=2010 -Xmx6000m -Dhttp.proxyHost=proxy.pal.sap.corp -Dhttp.proxyPort=8080 -Dhttps.proxyHost=proxy.pal.sap.corp -Dhttps.proxyPort=8080 -Djetty.home=${project_loc:com.sap.sailing.server}/../target/configuration/jetty -Djava.util.logging.config.file=${project_loc:com.sap.sailing.server}/../target/configuration/logging_debug.properties -Dkiwo.results=${project_loc:com.sap.sailing.kiworesultimport.test}/resources -Dpersistentcompetitors.clear=false -XX:+UseMembar -Digtimi.client.id=7fcdd217e0aa16090edb4ad55b09ec43b2021090e209541fc9b7003c2a2b70c6 -Digtimi.client.secret=aa569cf4909bdc7b0e04b11873f3c4ea20687421e010fcc25b771cca9e6f3f9a -Digtimi.client.redirecturi=http://127.0.0.1:8888/igtimi/oauth/v1/authorizationcallback"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-ea -Declipse.ignoreApp=true -Dosgi.noShutdown=true -Dfile.encoding=cp1252 -Dexpedition.udp.port=2010 -Xmx6000m -XX:+UseG1GC -Dhttp.proxyHost=proxy.pal.sap.corp -Dhttp.proxyPort=8080 -Dhttps.proxyHost=proxy.pal.sap.corp -Dhttps.proxyPort=8080 -Djetty.home=${project_loc:com.sap.sailing.server}/../target/configuration/jetty -Djava.util.logging.config.file=${project_loc:com.sap.sailing.server}/../target/configuration/logging_debug.properties -Dkiwo.results=${project_loc:com.sap.sailing.kiworesultimport.test}/resources -Dpersistentcompetitors.clear=false -XX:+UseMembar -Digtimi.client.id=7fcdd217e0aa16090edb4ad55b09ec43b2021090e209541fc9b7003c2a2b70c6 -Digtimi.client.secret=aa569cf4909bdc7b0e04b11873f3c4ea20687421e010fcc25b771cca9e6f3f9a -Digtimi.client.redirecturi=http://127.0.0.1:8888/igtimi/oauth/v1/authorizationcallback"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.WORKING_DIRECTORY" value="${workspace_loc}"/>
|
||||
<stringAttribute key="pde.version" value="3.3"/>
|
||||
<stringAttribute key="profilingTraceType-ALLOCATION_TRACE" value="KEY_APPLICATION_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_INC_LINE_NRS%CTX_KEY%true%CTX_ENTRY%KEY_MAX_SIZE%CTX_KEY%65536%CTX_ENTRY%INCREASE_COUNT%CTX_KEY%8192%CTX_ENTRY%KEY_USER_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_ID%CTX_KEY%false%CTX_ENTRY%KEY_MIN_SIZE%CTX_KEY%32%CTX_ENTRY%KEY_SESSION_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_ENABLEMENT%CTX_KEY%false%CTX_ENTRY%CLASS_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_REQUEST_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_ADAPTIVE%CTX_KEY%false%CTX_ENTRY%KEY_JRUBY_SUPPORT%CTX_KEY%false%CTX_ENTRY%"/>
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-os ${target.os} -ws ${target.ws} -arch ${target.arch} -nl ${target.nl} -consoleLog -console -clean"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.SOURCE_PATH_PROVIDER" value="org.eclipse.pde.ui.workbenchClasspathProvider"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Djetty.port=8889 -Dreplication.exchangeName=sapsailinganalytics-replica -Declipse.ignoreApp=true -Dosgi.noShutdown=true -Dexpedition.udp.port=2011 -Xmx6000m -Dhttp.proxyHost=proxy.wdf.sap.corp -Dhttp.proxyPort=8080 -Dhttps.proxyHost=proxy -Dhttps.proxyPort=8080 -Djetty.home=${project_loc:com.sap.sailing.server}/../target/configuration/jetty -Djava.util.logging.config.file=${project_loc:com.sap.sailing.server}/../target/configuration/logging_debug.properties -XX:+UseMembar"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Djetty.port=8889 -Dreplication.exchangeName=sapsailinganalytics-replica -Declipse.ignoreApp=true -Dosgi.noShutdown=true -Dexpedition.udp.port=2011 -Xmx6000m -XX:+UseG1GC -Dhttp.proxyHost=proxy.wdf.sap.corp -Dhttp.proxyPort=8080 -Dhttps.proxyHost=proxy -Dhttps.proxyPort=8080 -Djetty.home=${project_loc:com.sap.sailing.server}/../target/configuration/jetty -Djava.util.logging.config.file=${project_loc:com.sap.sailing.server}/../target/configuration/logging_debug.properties -XX:+UseMembar"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.WORKING_DIRECTORY" value="${workspace_loc}"/>
|
||||
<stringAttribute key="pde.version" value="3.3"/>
|
||||
<booleanAttribute key="show_selected_only" value="false"/>
|
||||
|
||||
@@ -91,7 +91,7 @@ class MediaLibrary {
|
||||
* performance gain.
|
||||
*
|
||||
* Actually, efficient lookup of overlapping intervals is supposed to be performed using an interval tree, e.g.
|
||||
* http://thekevindolan.com/2010/02/interval-tree/
|
||||
* http://en.wikipedia.org/wiki/Interval_tree.
|
||||
*
|
||||
* However, considering the expected low number of media entries and the expected high rate of cache hits doesn't
|
||||
* justify providing a dedicated interval tree implementation (given that there's none readily available).
|
||||
|
||||
Loaded 100 of 142 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user