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

This commit is contained in:
fmittag committed 2014-07-21 18:16:35 +02:00
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,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
}
@@ -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;
}
@@ -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());
}
}
@@ -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);
}
}
@@ -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);
}
@@ -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,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
@@ -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);
@@ -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++) {
@@ -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
@@ -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
@@ -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
}
}
}
@@ -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"),
@@ -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
@@ -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;
@@ -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);
}
@@ -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();
}
@@ -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
@@ -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);
@@ -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);
}
@@ -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;
}
@@ -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();
}
@@ -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,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);
}
@@ -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.
@@ -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);
}
}
@@ -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));
}
@@ -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);
@@ -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);
}
@@ -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
@@ -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);
@@ -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);
@@ -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;
}
@@ -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);
@@ -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:
@@ -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();
}
@@ -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();
}
@@ -0,0 +1,9 @@
package com.sap.sailing.util;
import java.io.ByteArrayOutputStream;
public class ByteArrayOutputStreamWithVisibleBuffer extends ByteArrayOutputStream {
public byte[] getBuffer() {
return buf;
}
}
@@ -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+
@@ -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));
@@ -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,
@@ -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());
@@ -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;
}
};
@@ -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)
*
@@ -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) {
@@ -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:
@@ -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);
@@ -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);
@@ -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();
}
@@ -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)
@@ -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)
@@ -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)
@@ -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);
@@ -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");
@@ -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");
@@ -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;
@@ -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);
@@ -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();
}
@@ -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);
@@ -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();
@@ -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;
}
@@ -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);
}
}
@@ -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)) {
@@ -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() {
}
@@ -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
@@ -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
@@ -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);
@@ -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);
@@ -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
@@ -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"),
@@ -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);
@@ -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;
}
@@ -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,
@@ -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());
}
}
@@ -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,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;
}
@@ -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);
}
@@ -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;
}
}
@@ -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;
}
}
@@ -72,5 +72,9 @@ public class ReplicaDescriptor implements Serializable {
return false;
return true;
}
public String toString() {
return ""+uuid+": "+ipAddress+" ("+additionalInformation+")";
}
}
@@ -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();
@@ -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();
}
}
@@ -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();
@@ -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 "
@@ -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();
@@ -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.);
@@ -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&#13;&#10;-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&#13;&#10;-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&#13;&#10;-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&#13;&#10;-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