Merge branch 'bug1666'

This commit is contained in:
Axel Uhl committed 2014-03-12 15:27:13 +01:00
commit 18d3935ba6
21 files changed
+375 -198

No files matched your search

@@ -2,6 +2,8 @@ package com.sap.sailing.domain.persistence;
import com.sap.sailing.domain.persistence.impl.MongoRaceLogStoreFactoryImpl;
import com.sap.sailing.domain.racelog.RaceLogEventVisitor;
import com.sap.sailing.domain.racelog.RaceLogIdentifier;
import com.sap.sailing.domain.racelog.RaceLogStore;
public interface MongoRaceLogStoreFactory {
@@ -12,4 +14,6 @@ public interface MongoRaceLogStoreFactory {
* and mongo.dbName[=<contents of constant {@link #DEFAULT_DB_NAME}>] specified in the bundle context
*/
RaceLogStore getMongoRaceLogStore(MongoObjectFactory mongoObjectFactory, DomainObjectFactory domainObjectFactory);
RaceLogEventVisitor getMongoRaceLogStoreVisitor(RaceLogIdentifier identifier, MongoObjectFactory mongoObjectFactory);
}
@@ -1,9 +1,10 @@
package com.sap.sailing.domain.persistence.impl;
import com.sap.sailing.domain.persistence.DomainObjectFactory;
import com.sap.sailing.domain.persistence.MongoObjectFactory;
import com.sap.sailing.domain.persistence.MongoRaceLogStoreFactory;
import com.sap.sailing.domain.racelog.RaceLogEventVisitor;
import com.sap.sailing.domain.racelog.RaceLogIdentifier;
import com.sap.sailing.domain.racelog.RaceLogStore;
public class MongoRaceLogStoreFactoryImpl implements MongoRaceLogStoreFactory {
@@ -11,4 +12,9 @@ public class MongoRaceLogStoreFactoryImpl implements MongoRaceLogStoreFactory {
public RaceLogStore getMongoRaceLogStore(MongoObjectFactory mongoObjectFactory, DomainObjectFactory domainObjectFactory) {
return new MongoRaceLogStoreImpl(mongoObjectFactory, domainObjectFactory);
}
@Override
public RaceLogEventVisitor getMongoRaceLogStoreVisitor(RaceLogIdentifier identifier, MongoObjectFactory mongoObjectFactory) {
return new MongoRaceLogStoreVisitor(identifier, mongoObjectFactory);
}
}
@@ -98,4 +98,9 @@ public interface RaceLog extends Track<RaceLogEvent>, WithID {
* in this race log yet
*/
boolean load(RaceLogEvent event);
/**
* Merges all events from the <code>other</code> race log into this.
*/
void merge(RaceLog other);
}
@@ -210,4 +210,9 @@ public class NoAddingRaceLogWrapper implements RaceLog {
public Duration getAverageIntervalBetweenRawFixes() {
return innerRaceLog.getAverageIntervalBetweenRawFixes();
}
@Override
public void merge(RaceLog other) {
innerRaceLog.merge(other);
}
}
@@ -5,6 +5,7 @@ import java.io.ObjectInputStream;
import java.io.Serializable;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.NavigableSet;
@@ -278,4 +279,48 @@ public class RaceLogImpl extends TrackImpl<RaceLogEvent> implements RaceLog {
edtc.addAll(result);
return result;
}
@Override
public void merge(RaceLog other) {
lockForWrite();
other.lockForRead();
try {
RaceLogEventComparator comparator = RaceLogEventComparator.INSTANCE;
Iterator<RaceLogEvent> thisIter = getRawFixes().iterator();
Iterator<RaceLogEvent> otherIter = other.getRawFixes().iterator();
RaceLogEvent thisEvent = null;
RaceLogEvent otherEvent = null;
while (otherIter.hasNext() || otherEvent != null) {
if (thisEvent == null && thisIter.hasNext()) {
thisEvent = thisIter.next();
}
if (otherEvent == null) {
otherEvent = otherIter.next();
}
if (thisEvent == null) {
// All events of this race log have been consumed; simply keep adding the events
// from the other race log to this race log.
// otherEvent has to be non-null because if thisIter didn't have a next, otherIter must have had a next
add(otherEvent);
otherEvent = null; // "consumed" otherEvent; try to grab next if a next element exists in otherIter
} else {
final int comparison = comparator.compare(thisEvent, otherEvent);
if (comparison < 0) {
thisEvent = null; // skip the "lesser" race log event on this race log
} else if (comparison == 0) {
// the race log event from the other log is already contained in this log; skip both
thisEvent = null;
otherEvent = null;
} else {
// comparison > 0; we skipped on this race log until we found a "greater" event on this race log; insert otherEvent
add(otherEvent);
otherEvent = null; // "consumed"
}
}
}
} finally {
other.unlockAfterRead();
unlockAfterWrite();
}
}
}
@@ -186,11 +186,8 @@ public interface RaceColumn extends Named {
/**
* Sets (or reloads) {@link RaceLog} for this column with the given fleet
*
* @param raceLogInformation
* @param fleetImpl
*/
void setOrReloadRaceLogInformation(RaceLogInformation raceLogInformation, Fleet fleetImpl);
void setOrReloadRaceLogInformation(RaceLogInformation raceLogInformation, Fleet fleet);
/**
* Remove the association between a race and a column. This is different from
@@ -4,7 +4,6 @@ import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
@@ -14,7 +13,6 @@ import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.common.RaceIdentifier;
import com.sap.sailing.domain.common.impl.Util;
import com.sap.sailing.domain.racelog.RaceLog;
import com.sap.sailing.domain.racelog.RaceLogEventVisitor;
import com.sap.sailing.domain.racelog.RaceLogIdentifier;
import com.sap.sailing.domain.racelog.RaceLogIdentifierTemplate;
import com.sap.sailing.domain.racelog.RaceLogInformation;
@@ -176,29 +174,22 @@ public abstract class AbstractRaceColumn extends SimpleAbstractRaceColumn implem
}
@Override
public void setOrReloadRaceLogInformation(RaceLogInformation information, Fleet fleetImpl) {
public void setOrReloadRaceLogInformation(RaceLogInformation information, Fleet fleet) {
synchronized(raceLogs) {
raceLogInformation = information;
RaceLogStore store = information.getStore();
HashSet<RaceLogEventVisitor> listeners = new HashSet<RaceLogEventVisitor>();
RaceLog raceLogAvailable = raceLogs.get(fleetImpl);
if (raceLogAvailable != null) {
store.removeListenersAddedByStoreFrom(raceLogAvailable);
listeners = raceLogAvailable.removeAllListeners();
raceLogs.remove(fleetImpl);
}
raceLogIdentifierTemplate = raceLogInformation.getIdentifierTemplate();
RaceLogIdentifier identifier = raceLogIdentifierTemplate.compileRaceLogIdentifier(fleetImpl);
RaceLog raceLog = store.getRaceLog(identifier, /*ignoreCache*/ true);
if (listeners.isEmpty()) {
RaceLogIdentifier identifier = raceLogIdentifierTemplate.compileRaceLogIdentifier(fleet);
RaceLog newOrLoadedRaceLog = store.getRaceLog(identifier, /*ignoreCache*/ true);
RaceLog raceLogAvailable = raceLogs.get(fleet);
if (raceLogAvailable == null) {
RaceColumnRaceLogReplicator listener = new RaceColumnRaceLogReplicator(this, identifier);
raceLog.addListener(listener);
newOrLoadedRaceLog.addListener(listener);
raceLogs.put(fleet, newOrLoadedRaceLog);
} else {
raceLog.addAllListeners(listeners);
// now add all race log events from newOrLoadedRaceLog that are not already in raceLogAvailable
raceLogAvailable.merge(newOrLoadedRaceLog);
}
raceLogs.put(fleetImpl, raceLog);
}
}
@@ -9,29 +9,34 @@ import com.sap.sailing.domain.base.Fleet;
* <p>
* {LeaderboardName / RegattaName} + RaceColumnName + FleetName
* <p>
* As the components of the RaceLogIdentifier are not available on RaceColumn level, we have to propagate the
* As the components of the RaceLogIdentifier are not available on RaceColumn level, we have to propagate the
* information in this template data object.
*
*/
public interface RaceLogIdentifierTemplate extends Serializable {
/**
* This method returns the name of parent object of the {@link RaceColumn}. This can be a {@link FlexibleLeaderboard} or a {@link Regatta}.
* This method returns the name of parent object of the {@link RaceColumn}. This can be a
* {@link FlexibleLeaderboard} or a {@link Regatta}.
*
* @return the name of the parent object
*/
String getParentObjectName();
/**
* Compiles the template with giving the last component, the {@link Fleet}, to a RaceLogIdentifier that is used to retrieve the
* {@link RaceLogEvent}s from a RaceLogStore
* @param fleet the fleet of the race
* Compiles the template with giving the last component, the {@link Fleet}, to a RaceLogIdentifier that is used to
* retrieve the {@link RaceLogEvent}s from a RaceLogStore
*
* @param fleet
* the fleet of the race
* @return the RaceLogIdentifier
*/
RaceLogIdentifier compileRaceLogIdentifier(Fleet fleet);
/**
* Resolves this RaceLogIdentifier template for replication with the help of the given resolver.
* @param resolver the resolver that resolves this template, either for {@link Leaderboard} or for {@link Regatta}.
*
* @param resolver
* the resolver that resolves this template, either for {@link Leaderboard} or for {@link Regatta}.
*/
void resolve(RaceLogIdentifierTemplateResolver resolver);
@@ -151,11 +151,9 @@ public class RaceColumnListeners implements Serializable {
}
}
public void notifyListenersAboutRaceLogEventAdded(RaceColumn raceColumn, RaceLogIdentifier raceLogIdentifier,
RaceLogEvent event) {
public void notifyListenersAboutRaceLogEventAdded(RaceColumn raceColumn, RaceLogIdentifier raceLogIdentifier, RaceLogEvent event) {
for (RaceColumnListener listener : getRaceColumnListeners()) {
listener.raceLogEventAdded(raceColumn, raceLogIdentifier, event);
}
}
}
@@ -27,6 +27,7 @@ import com.rabbitmq.client.QueueingConsumer;
import com.sap.sailing.domain.base.DomainFactory;
import com.sap.sailing.domain.base.impl.DomainFactoryImpl;
import com.sap.sailing.domain.common.impl.Util.Pair;
import com.sap.sailing.domain.persistence.MongoObjectFactory;
import com.sap.sailing.domain.persistence.PersistenceFactory;
import com.sap.sailing.domain.persistence.media.MediaDBFactory;
import com.sap.sailing.domain.tracking.impl.EmptyWindStore;
@@ -93,19 +94,19 @@ public abstract class AbstractServerReplicationTest {
mongoDBService.getDB().dropDatabase();
}
resolveAgainst = DomainFactory.INSTANCE;
final MongoObjectFactory mongoObjectFactory = PersistenceFactory.INSTANCE.getMongoObjectFactory(mongoDBService);
mongoObjectFactory.getDatabase().requestStart();
if (master != null) {
this.master = master;
} else {
this.master = new RacingEventServiceImpl(PersistenceFactory.INSTANCE.getDomainObjectFactory(mongoDBService, DomainFactory.INSTANCE), PersistenceFactory.INSTANCE
.getMongoObjectFactory(mongoDBService), MediaDBFactory.INSTANCE.getMediaDB(mongoDBService), EmptyWindStore.INSTANCE);
this.master = createNewMaster(mongoDBService, mongoObjectFactory);
}
if (replica != null) {
this.replica = replica;
} else {
this.replica = new RacingEventServiceImpl(PersistenceFactory.INSTANCE.getDomainObjectFactory(mongoDBService,
// replica gets its own base DomainFactory:
new DomainFactoryImpl()), PersistenceFactory.INSTANCE
.getMongoObjectFactory(mongoDBService), MediaDBFactory.INSTANCE.getMediaDB(mongoDBService), EmptyWindStore.INSTANCE);
new DomainFactoryImpl()), mongoObjectFactory, MediaDBFactory.INSTANCE.getMediaDB(mongoDBService), EmptyWindStore.INSTANCE);
}
ReplicationInstancesManager rim = new ReplicationInstancesManager();
masterReplicator = new ReplicationServiceImpl(exchangeName, exchangeHost, rim, this.master);
@@ -121,9 +122,19 @@ public abstract class AbstractServerReplicationTest {
this.replicaReplicator = replicaReplicator;
return result;
}
protected RacingEventServiceImpl createNewMaster(final MongoDBService mongoDBService,
final MongoObjectFactory mongoObjectFactory) {
return new RacingEventServiceImpl(PersistenceFactory.INSTANCE.getDomainObjectFactory(mongoDBService,
DomainFactory.INSTANCE), mongoObjectFactory, MediaDBFactory.INSTANCE.getMediaDB(mongoDBService),
EmptyWindStore.INSTANCE);
}
@After
public void tearDown() throws Exception {
final MongoDBService mongoDBService = MongoDBService.INSTANCE;
final MongoObjectFactory mongoObjectFactory = PersistenceFactory.INSTANCE.getMongoObjectFactory(mongoDBService);
mongoObjectFactory.getDatabase().requestDone();
masterReplicator.unregisterReplica(replicaDescriptor);
masterDescriptor.stopConnection();
try {
@@ -1,9 +1,12 @@
package com.sap.sailing.server.replication.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.UUID;
@@ -19,33 +22,49 @@ import com.sap.sailing.domain.base.Fleet;
import com.sap.sailing.domain.base.Mark;
import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.base.Series;
import com.sap.sailing.domain.base.impl.ControlPointWithTwoMarksImpl;
import com.sap.sailing.domain.base.impl.CourseDataImpl;
import com.sap.sailing.domain.base.impl.MarkImpl;
import com.sap.sailing.domain.base.impl.WaypointImpl;
import com.sap.sailing.domain.common.Color;
import com.sap.sailing.domain.common.MarkType;
import com.sap.sailing.domain.common.PassingInstruction;
import com.sap.sailing.domain.common.RegattaName;
import com.sap.sailing.domain.common.dto.FleetDTO;
import com.sap.sailing.domain.common.dto.RegattaCreationParametersDTO;
import com.sap.sailing.domain.common.dto.SeriesCreationParametersDTO;
import com.sap.sailing.domain.common.impl.MillisecondsTimePoint;
import com.sap.sailing.domain.common.impl.Util;
import com.sap.sailing.domain.common.impl.Util.Pair;
import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus;
import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.leaderboard.RegattaLeaderboard;
import com.sap.sailing.domain.leaderboard.impl.LowPoint;
import com.sap.sailing.domain.persistence.MongoObjectFactory;
import com.sap.sailing.domain.persistence.PersistenceFactory;
import com.sap.sailing.domain.racelog.RaceLog;
import com.sap.sailing.domain.racelog.RaceLogCourseDesignChangedEvent;
import com.sap.sailing.domain.racelog.RaceLogEvent;
import com.sap.sailing.domain.racelog.RaceLogEventAuthor;
import com.sap.sailing.domain.racelog.RaceLogEventFactory;
import com.sap.sailing.domain.racelog.RaceLogIdentifier;
import com.sap.sailing.domain.racelog.RaceLogRaceStatusEvent;
import com.sap.sailing.domain.racelog.impl.RaceLogEventAuthorImpl;
import com.sap.sailing.mongodb.MongoDBService;
import com.sap.sailing.server.impl.RacingEventServiceImpl;
import com.sap.sailing.server.operationaltransformation.AddColumnToLeaderboard;
import com.sap.sailing.server.operationaltransformation.AddColumnToSeries;
import com.sap.sailing.server.operationaltransformation.AddDefaultRegatta;
import com.sap.sailing.server.operationaltransformation.AddSpecificRegatta;
import com.sap.sailing.server.operationaltransformation.CreateFlexibleLeaderboard;
import com.sap.sailing.server.operationaltransformation.CreateRegattaLeaderboard;
import com.sap.sailing.server.operationaltransformation.RenameLeaderboard;
import com.sap.sailing.server.replication.ReplicationMasterDescriptor;
public class RaceLogReplicationTest extends AbstractServerReplicationTest {
private static final String BOAT_CLASS_NAME_49er = "49er";
private Pair<ReplicationServiceTestImpl, ReplicationMasterDescriptor> replicationDescriptorPair;
private RaceLogEvent raceLogEvent;
@@ -70,12 +89,9 @@ public class RaceLogReplicationTest extends AbstractServerReplicationTest {
final String seriesName = "Default";
final String fleetName = "Default";
final String raceColumnName = "R1";
Regatta regatta = setupRegatta(regattaName);
Regatta regatta = setupRegatta(regattaName, seriesName, fleetName, BOAT_CLASS_NAME_49er);
RaceLog masterLog = setupRaceColumn(regatta, seriesName, raceColumnName, fleetName);
replicationDescriptorPair.getA().startToReplicateFrom(replicationDescriptorPair.getB());
RaceLog replicaLog = getReplicaLog(seriesName, fleetName, raceColumnName, regatta);
addAndValidateEventIds(masterLog, replicaLog);
}
@@ -86,13 +102,10 @@ public class RaceLogReplicationTest extends AbstractServerReplicationTest {
final String seriesName = "Default";
final String fleetName = "Default";
final String raceColumnName = "R1";
Regatta masterRegatta = setupRegatta(regattaName);
Regatta masterRegatta = setupRegatta(regattaName, seriesName, fleetName, BOAT_CLASS_NAME_49er);
RaceLog masterLog = setupRaceColumn(masterRegatta, seriesName, raceColumnName, fleetName);
masterLog.add(raceLogEvent);
replicationDescriptorPair.getA().startToReplicateFrom(replicationDescriptorPair.getB());
RaceLog replicaLog = getReplicaLog(seriesName, fleetName, raceColumnName, masterRegatta);
addAndValidateEventIds(masterLog, replicaLog);
}
@@ -103,12 +116,9 @@ public class RaceLogReplicationTest extends AbstractServerReplicationTest {
final String seriesName = "Default";
final String fleetName = "Default";
final String raceColumnName = "R1";
Regatta masterRegatta = setupRegatta(regattaName);
Regatta masterRegatta = setupRegatta(regattaName, seriesName, fleetName, BOAT_CLASS_NAME_49er);
RaceLog masterLog = setupRaceColumn(masterRegatta, seriesName, raceColumnName, fleetName);
replicationDescriptorPair.getA().startToReplicateFrom(replicationDescriptorPair.getB());
RaceLog replicaLog = getReplicaLog(seriesName, fleetName, raceColumnName, masterRegatta);
addAndValidateEventIds(masterLog, replicaLog, raceLogEvent);
}
@@ -118,12 +128,9 @@ public class RaceLogReplicationTest extends AbstractServerReplicationTest {
final String leaderboardName = "Test";
final String fleetName = "Default";
final String raceColumnName = "R1";
FlexibleLeaderboard masterLeaderboard = setupFlexibleLeaderboard(leaderboardName);
RaceLog masterLog = setupRaceColumn(leaderboardName, fleetName, raceColumnName);
replicationDescriptorPair.getA().startToReplicateFrom(replicationDescriptorPair.getB());
RaceLog replicaLog = getReplicaLog(fleetName, raceColumnName, masterLeaderboard);
addAndValidateEventIds(masterLog, replicaLog, raceLogEvent);
}
@@ -134,13 +141,10 @@ public class RaceLogReplicationTest extends AbstractServerReplicationTest {
final String seriesName = "Default";
final String fleetName = "Default";
final String raceColumnName = "R1";
Regatta masterRegatta = setupRegatta(regattaName);
Regatta masterRegatta = setupRegatta(regattaName, seriesName, fleetName, BOAT_CLASS_NAME_49er);
RaceLog masterLog = setupRaceColumn(masterRegatta, seriesName, raceColumnName, fleetName);
masterLog.add(raceLogEvent);
replicationDescriptorPair.getA().startToReplicateFrom(replicationDescriptorPair.getB());
RaceLog replicaLog = getReplicaLog(seriesName, fleetName, raceColumnName, masterRegatta);
addAndValidateEventIds(masterLog, replicaLog, anotherRaceLogEvent);
}
@@ -151,20 +155,14 @@ public class RaceLogReplicationTest extends AbstractServerReplicationTest {
final String seriesName = "Default";
final String fleetName = "Default";
final String raceColumnName = "R1";
Regatta masterRegatta = setupRegatta(regattaName);
Regatta masterRegatta = setupRegatta(regattaName, seriesName, fleetName, BOAT_CLASS_NAME_49er);
RaceLog masterLog = setupRaceColumn(masterRegatta, seriesName, raceColumnName, fleetName);
raceLogEvent = RaceLogEventFactory.INSTANCE.createCourseDesignChangedEvent(MillisecondsTimePoint.now(), author, 43, createCourseData());
masterLog.add(raceLogEvent);
replicationDescriptorPair.getA().startToReplicateFrom(replicationDescriptorPair.getB());
RaceLog replicaLog = getReplicaLog(seriesName, fleetName, raceColumnName, masterRegatta);
anotherRaceLogEvent = RaceLogEventFactory.INSTANCE.createCourseDesignChangedEvent(MillisecondsTimePoint.now(), author, 43, createCourseData());
addAndValidateEventIds(masterLog, replicaLog, anotherRaceLogEvent);
compareReplicatedCourseDesignEvent(replicaLog, (RaceLogCourseDesignChangedEvent) anotherRaceLogEvent);
}
@@ -173,13 +171,10 @@ public class RaceLogReplicationTest extends AbstractServerReplicationTest {
final String leaderboardName = "Test";
final String fleetName = "Default";
final String raceColumnName = "R1";
FlexibleLeaderboard masterLeaderboard = setupFlexibleLeaderboard(leaderboardName);
RaceLog masterLog = setupRaceColumn(leaderboardName, fleetName, raceColumnName);
masterLog.add(raceLogEvent);
replicationDescriptorPair.getA().startToReplicateFrom(replicationDescriptorPair.getB());
RaceLog replicaLog = getReplicaLog(fleetName, raceColumnName, masterLeaderboard);
addAndValidateEventIds(masterLog, replicaLog, anotherRaceLogEvent);
}
@@ -189,15 +184,11 @@ public class RaceLogReplicationTest extends AbstractServerReplicationTest {
final String leaderboardName = "Test";
final String fleetName = "Default";
final String raceColumnName = "R1";
FlexibleLeaderboard masterLeaderboard = setupFlexibleLeaderboard(leaderboardName);
RaceLog masterLog = setupRaceColumn(leaderboardName, fleetName, raceColumnName);
raceLogEvent = RaceLogEventFactory.INSTANCE.createCourseDesignChangedEvent(MillisecondsTimePoint.now(), author, 43, createCourseData());
masterLog.add(raceLogEvent);
replicationDescriptorPair.getA().startToReplicateFrom(replicationDescriptorPair.getB());
RaceLog replicaLog = getReplicaLog(fleetName, raceColumnName, masterLeaderboard);
anotherRaceLogEvent = RaceLogEventFactory.INSTANCE.createCourseDesignChangedEvent(MillisecondsTimePoint.now(), author, 43, createCourseData());
addAndValidateEventIds(masterLog, replicaLog, anotherRaceLogEvent);
@@ -224,36 +215,76 @@ public class RaceLogReplicationTest extends AbstractServerReplicationTest {
final String leaderboardName = "Test";
final String fleetName = "Default";
final String raceColumnName = "R1";
FlexibleLeaderboard masterLeaderboard = setupFlexibleLeaderboard(leaderboardName);
RaceLog masterLog = setupRaceColumn(leaderboardName, fleetName, raceColumnName);
replicationDescriptorPair.getA().startToReplicateFrom(replicationDescriptorPair.getB());
masterLog.add(raceLogEvent);
RenameLeaderboard renameOperation = new RenameLeaderboard(leaderboardName, leaderboardName + "new");
master.apply(renameOperation);
Thread.sleep(3000);
RaceLog replicaLog = getReplicaLog(fleetName, raceColumnName, masterLeaderboard);
addAndValidateEventIds(masterLog, replicaLog, anotherRaceLogEvent);
}
/**
* See bug 1666; a race log reload operation may not properly have been replicating. This tests asserts that when a race log event
* has been added to the DB and then race log is re-loaded on the master, the events added through the DB also show up on the replica.
*/
@Test
public void testRaceLogReloadReplication() throws ClassNotFoundException, IOException, InterruptedException {
final String regattaName = "Test";
final String seriesName = "Default";
final String fleetName = "Default";
final String raceColumnName = "R1";
Regatta masterRegatta = setupRegatta(regattaName, seriesName, fleetName, BOAT_CLASS_NAME_49er);
RaceLog masterLog = setupRaceColumn(masterRegatta, seriesName, raceColumnName, fleetName);
masterLog.add(raceLogEvent);
replicationDescriptorPair.getA().startToReplicateFrom(replicationDescriptorPair.getB());
RaceLog replicaLog = getReplicaLog(seriesName, fleetName, raceColumnName, masterRegatta);
addAndValidateEventIds(masterLog, replicaLog);
final Series series = masterRegatta.getSeries().iterator().next();
final MillisecondsTimePoint approximateRaceStatusEventCreationTimePoint = MillisecondsTimePoint.now();
final RaceLogRaceStatusEvent raceStatusEvent = RaceLogEventFactory.INSTANCE.createRaceStatusEvent(approximateRaceStatusEventCreationTimePoint, author, 42,
RaceLogRaceStatus.UNKNOWN);
addEventToDB(series.getRaceColumnByName(raceColumnName).getRaceLogIdentifier(series.getFleetByName(fleetName)), raceStatusEvent, regattaName, raceColumnName, fleetName);
RegattaLeaderboard leaderboard = master.apply(new CreateRegattaLeaderboard(
masterRegatta.getRegattaIdentifier(), /* leaderboardDisplayName */ null, new int[0]));
master.reloadRaceLog(leaderboard.getName(), raceColumnName, fleetName);
Thread.sleep(3000);
final RaceLog reloadedMasterLog = leaderboard.getRaceColumnByName(raceColumnName).getRaceLog(series.getFleetByName(fleetName));
final RaceLogEvent lastEventReadFromMasterLog = reloadedMasterLog.getFirstRawFixAtOrAfter(approximateRaceStatusEventCreationTimePoint);
assertNotNull(lastEventReadFromMasterLog);
assertEqualsOnId(reloadedMasterLog, getReplicaLog(seriesName, fleetName, raceColumnName, masterRegatta));
}
/**
* Uses a new master that loads the existing regatta and race log to append an event to the race log. This will store it to the DB so that if the original
* master re-loads the race log it should see the new race log event.
*/
private void addEventToDB(RaceLogIdentifier raceLogIdentifier, RaceLogRaceStatusEvent createRaceStatusEvent, String regattaName, String raceColumnName, String fleetName) {
final MongoObjectFactory defaultMongoObjectFactory = PersistenceFactory.INSTANCE.getDefaultMongoObjectFactory();
defaultMongoObjectFactory.getDatabase().getLastError(); // wait for regatta write operation to complete
final RacingEventServiceImpl temporaryMaster = createNewMaster(MongoDBService.INSTANCE, defaultMongoObjectFactory);
Regatta regatta = temporaryMaster.getRegatta(new RegattaName(regattaName+" ("+BOAT_CLASS_NAME_49er+")"));
Series series = regatta.getSeries().iterator().next();
RaceLog masterLog = series.getRaceColumnByName(raceColumnName).getRaceLog(series.getFleetByName(fleetName));
masterLog.add(createRaceStatusEvent);
defaultMongoObjectFactory.getDatabase().getLastError(); // wait for write operation to complete
}
/**
* Validation is done based only on the identifier and type of the {@link RaceLogEvent}s.
*/
private void addAndValidateEventIds(RaceLog masterLog, RaceLog replicaLog, RaceLogEvent... addedEvents) throws InterruptedException {
// 1. Check state of replica after initial load...
assertEqualsOnId(masterLog, replicaLog);
// 2. ... add all incoming events...
for (RaceLogEvent event : addedEvents) {
masterLog.add(event);
}
// 3. ... and give replication some time to deliver messages.
Thread.sleep(3000);
assertEqualsOnId(masterLog, replicaLog);
}
@@ -280,10 +311,8 @@ public class RaceLogReplicationTest extends AbstractServerReplicationTest {
private void assertEqualsOnId(Iterable<RaceLogEvent> expectedEvents, Iterable<RaceLogEvent> actualEvents) {
List<RaceLogEvent> expectedCollection = new ArrayList<>();
Util.addAll(expectedEvents, expectedCollection);
List<RaceLogEvent> actualCollection = new ArrayList<>();
Util.addAll(actualEvents, actualCollection);
assertEquals(Util.size(expectedEvents), Util.size(actualEvents));
for (int i = 0; i < expectedCollection.size(); i++) {
assertEquals(expectedCollection.get(i).getClass(), actualCollection.get(i).getClass());
@@ -306,9 +335,17 @@ public class RaceLogReplicationTest extends AbstractServerReplicationTest {
return replicaColumn.getRaceLog(replicaFleet);
}
private Regatta setupRegatta(final String regattaName) {
private Regatta setupRegatta(final String regattaName, String seriesName, String fleetName, String boatClassName) {
LinkedHashMap<String, SeriesCreationParametersDTO> seriesCreationParameters = new LinkedHashMap<>();
SeriesCreationParametersDTO creationParametersForDefaultSeries = new SeriesCreationParametersDTO(
Arrays.asList(new FleetDTO[] { new FleetDTO(fleetName, 0, Color.BLACK), }), /* medal */false, /* startsWithZero */
false, /* firstColumnIsNonDiscardableCarryForward */false, /* discardingThresholds */new int[0], /* hasSplitFleetContiguousScoring */
false);
seriesCreationParameters.put(seriesName, creationParametersForDefaultSeries);
// 1. Install some race column on master...
AddDefaultRegatta addRegattaOperation = new AddDefaultRegatta(regattaName, "49er", UUID.randomUUID());
RegattaCreationParametersDTO regattaCreationParams = new RegattaCreationParametersDTO(seriesCreationParameters);
AddSpecificRegatta addRegattaOperation = new AddSpecificRegatta(regattaName, boatClassName, /* regatta ID */ UUID.randomUUID(), regattaCreationParams, /* persistent */ true,
new LowPoint(), /* default course area ID */ UUID.randomUUID());
return master.apply(addRegattaOperation);
}
@@ -336,7 +373,6 @@ public class RaceLogReplicationTest extends AbstractServerReplicationTest {
protected CourseBase createCourseData() {
CourseBase course = new CourseDataImpl("Test Course");
course.addWaypoint(0, new WaypointImpl(new ControlPointWithTwoMarksImpl(UUID.randomUUID(),
new MarkImpl(UUID.randomUUID(), "Black", MarkType.BUOY, "black", "round", "circle"),
new MarkImpl(UUID.randomUUID(), "Green", MarkType.BUOY, "green", "round", "circle"),
@@ -28,7 +28,6 @@ public class RaceLogReplicator implements RaceColumnListener {
@Override
public void raceLogEventAdded(final RaceColumn raceColumn, final RaceLogIdentifier identifier, final RaceLogEvent event) {
identifier.getTemplate().resolve(new RaceLogIdentifierTemplateResolver() {
@Override
public void resolveOnRegattaIdentifierAndReplicate(RaceLogOnRegattaIdentifier identifierTemplate) {
RacingEventServiceOperation<?> operation = new RecordRaceLogEventOnRegatta(
@@ -0,0 +1,17 @@
package com.sap.sailing.server.operationaltransformation;
import com.sap.sailing.domain.racelog.RaceLogEvent;
public abstract class AbstractRaceLogEventOperation extends AbstractRaceLogOperation<RaceLogEvent> {
private static final long serialVersionUID = -8559301422783375526L;
private final RaceLogEvent event;
protected AbstractRaceLogEventOperation(String raceColumnName, String fleetName, RaceLogEvent event) {
super(raceColumnName, fleetName);
this.event = event;
}
protected RaceLogEvent getEvent() {
return event;
}
}
@@ -0,0 +1,22 @@
package com.sap.sailing.server.operationaltransformation;
import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.server.RacingEventService;
public abstract class AbstractRaceLogOnLeaderboardOperation<T> extends AbstractRaceLogOperation<T> {
private static final long serialVersionUID = -5649159437859782663L;
private final String leaderboardName;
public AbstractRaceLogOnLeaderboardOperation(String leaderboardName, String raceColumnName, String fleetName) {
super(raceColumnName, fleetName);
this.leaderboardName = leaderboardName;
}
protected RaceColumn getRaceColumn(RacingEventService toState) {
Leaderboard leaderboard = toState.getLeaderboardByName(leaderboardName);
RaceColumn raceColumn = leaderboard.getRaceColumnByName(getRaceColumnName());
return raceColumn;
}
}
@@ -0,0 +1,35 @@
package com.sap.sailing.server.operationaltransformation;
import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.base.Series;
import com.sap.sailing.server.RacingEventService;
public abstract class AbstractRaceLogOnRegattaOperation<T> extends AbstractRaceLogOperation<T> {
private static final long serialVersionUID = -7174569054572099191L;
private final String regattaName;
public AbstractRaceLogOnRegattaOperation(String regattaName, String raceColumnName,
String fleetName) {
super(raceColumnName, fleetName);
this.regattaName = regattaName;
}
protected String getRegattaName() {
return regattaName;
}
@Override
protected RaceColumn getRaceColumn(RacingEventService toState) {
Regatta regatta = toState.getRegattaByName(regattaName);
RaceColumn raceColumn = null;
for (Series series : regatta.getSeries()) {
raceColumn = series.getRaceColumnByName(getRaceColumnName());
if (raceColumn != null) {
break;
}
}
return raceColumn;
}
}
@@ -0,0 +1,37 @@
package com.sap.sailing.server.operationaltransformation;
import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.server.RacingEventService;
import com.sap.sailing.server.RacingEventServiceOperation;
public abstract class AbstractRaceLogOperation<T> extends AbstractRacingEventServiceOperation<T> {
private static final long serialVersionUID = 2140858355670664173L;
private final String raceColumnName;
private final String fleetName;
public AbstractRaceLogOperation(String raceColumnName, String fleetName) {
this.raceColumnName = raceColumnName;
this.fleetName = fleetName;
}
protected String getRaceColumnName() {
return raceColumnName;
}
protected String getFleetName() {
return fleetName;
}
abstract protected RaceColumn getRaceColumn(RacingEventService toState);
@Override
public RacingEventServiceOperation<?> transformClientOp(RacingEventServiceOperation<?> serverOp) {
return null;
}
@Override
public RacingEventServiceOperation<?> transformServerOp(RacingEventServiceOperation<?> clientOp) {
return null;
}
}
@@ -1,36 +0,0 @@
package com.sap.sailing.server.operationaltransformation;
import com.sap.sailing.domain.base.Fleet;
import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.racelog.RaceLog;
import com.sap.sailing.domain.racelog.RaceLogEvent;
public abstract class AbstractRecordRaceLogEvent extends AbstractRacingEventServiceOperation<RaceLogEvent> {
private static final long serialVersionUID = 2140858355670664173L;
protected final String raceColumnName;
private final String fleetName;
private final RaceLogEvent event;
public AbstractRecordRaceLogEvent(String raceColumnName, String fleetName, RaceLogEvent event) {
this.raceColumnName = raceColumnName;
this.fleetName = fleetName;
this.event = event;
}
/**
* Adds the stored event to the race column's race log.
* @param raceColumn to add to.
* @param fleetName to resolve correct {@link RaceLog}.
* @return <code>true</code> if add was successful.
*/
protected RaceLogEvent addEventTo(RaceColumn raceColumn) {
if (raceColumn != null) {
Fleet fleet = raceColumn.getFleetByName(fleetName);
RaceLog raceLog = raceColumn.getRaceLog(fleet);
raceLog.add(event);
}
return event;
}
}
@@ -3,7 +3,6 @@ package com.sap.sailing.server.operationaltransformation;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -35,8 +34,12 @@ import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule;
import com.sap.sailing.domain.leaderboard.meta.LeaderboardGroupMetaLeaderboard;
import com.sap.sailing.domain.masterdataimport.TopLevelMasterData;
import com.sap.sailing.domain.masterdataimport.WindTrackMasterData;
import com.sap.sailing.domain.persistence.MongoObjectFactory;
import com.sap.sailing.domain.persistence.MongoRaceLogStoreFactory;
import com.sap.sailing.domain.racelog.RaceLog;
import com.sap.sailing.domain.racelog.RaceLogEvent;
import com.sap.sailing.domain.racelog.RaceLogEventVisitor;
import com.sap.sailing.domain.racelog.RaceLogIdentifier;
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.TrackedRegatta;
@@ -103,7 +106,6 @@ public class ImportMasterDataOperation extends
private void createLeaderboardGroupWithAllRelatedObjects(final RacingEventService toState,
LeaderboardGroup leaderboardGroup) {
Map<RaceColumn, Map<Fleet, Iterable<RaceLogEvent>>> raceLogEvents = extractRaceLogEvents(leaderboardGroup);
Map<String, Leaderboard> existingLeaderboards = toState.getLeaderboards();
List<String> leaderboardNames = new ArrayList<String>();
createCourseAreasAndEvents(toState, leaderboardGroup);
@@ -136,7 +138,7 @@ public class ImportMasterDataOperation extends
}
if (leaderboard != null) {
toState.addLeaderboard(leaderboard);
addRaceLogEvents(leaderboard, raceLogEvents);
storeRaceLogEvents(leaderboard, toState.getMongoObjectFactory());
creationCount.addOneLeaderboard(leaderboard.getName());
relinkTrackedRacesIfPossible(toState, leaderboard);
}
@@ -189,39 +191,28 @@ public class ImportMasterDataOperation extends
}
}
private void addRaceLogEvents(Leaderboard leaderboard,
Map<RaceColumn, Map<Fleet, Iterable<RaceLogEvent>>> raceLogEvents) {
/**
* Ensures that the race log events are stored to the receiving instance's database. The race logs have been received
* in serialized form on the {@link RaceColumn} objects, but the database doesn't yet know about them. This method uses
* a <code>MongoRaceLogStoreVisitor</code> to store all race log events to the database.
*/
private void storeRaceLogEvents(Leaderboard leaderboard, MongoObjectFactory mongoObjectFactory) {
for (RaceColumn raceColumn : leaderboard.getRaceColumns()) {
for (Fleet fleet : raceColumn.getFleets()) {
RaceLog log = raceColumn.getRaceLog(fleet);
Iterable<RaceLogEvent> raceLogsForColumnAndFleet = raceLogEvents.get(raceColumn).get(fleet);
for (RaceLogEvent event : raceLogsForColumnAndFleet) {
log.add(event);
}
}
}
}
private Map<RaceColumn, Map<Fleet, Iterable<RaceLogEvent>>> extractRaceLogEvents(LeaderboardGroup group) {
Map<RaceColumn, Map<Fleet, Iterable<RaceLogEvent>>> raceLogEvents = new HashMap<RaceColumn, Map<Fleet, Iterable<RaceLogEvent>>>();
for (Leaderboard leaderboard : group.getLeaderboards()) {
for (RaceColumn raceColumn : leaderboard.getRaceColumns()) {
HashMap<Fleet, Iterable<RaceLogEvent>> raceLogEventsForRaceColumn = new HashMap<Fleet, Iterable<RaceLogEvent>>();
raceLogEvents.put(raceColumn, raceLogEventsForRaceColumn);
for (Fleet fleet : raceColumn.getFleets()) {
RaceLog raceLog = raceColumn.getRaceLog(fleet);
raceLog.lockForRead();
try {
Iterable<RaceLogEvent> fixes = raceLog.getRawFixes();
raceLogEventsForRaceColumn.put(fleet, fixes);
} finally {
raceLog.unlockAfterRead();
RaceLogIdentifier identifier = raceColumn.getRaceLogIdentifier(fleet);
RaceLogEventVisitor storeVisitor = MongoRaceLogStoreFactory.INSTANCE.getMongoRaceLogStoreVisitor(identifier, mongoObjectFactory);
log.lockForRead();
try {
for (RaceLogEvent event : log.getRawFixes()) {
event.accept(storeVisitor);
}
} finally {
log.unlockAfterRead();
}
}
}
return raceLogEvents;
}
private void relinkTrackedRacesIfPossible(RacingEventService toState, Leaderboard newLeaderboard) {
@@ -0,0 +1,45 @@
package com.sap.sailing.server.operationaltransformation;
import java.io.Serializable;
import com.sap.sailing.domain.base.Fleet;
import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.racelog.RaceLog;
import com.sap.sailing.domain.racelog.RaceLogEvent;
public class RaceLogEventRecorder implements Serializable {
private static final long serialVersionUID = -5426128599743917274L;
private final String fleetName;
private final RaceLogEvent event;
public RaceLogEventRecorder(String fleetName, RaceLogEvent event) {
this.fleetName = fleetName;
this.event = event;
}
private String getFleetName() {
return fleetName;
}
private RaceLogEvent getEvent() {
return event;
}
/**
* Adds the stored event to the race column's race log.
*
* @param raceColumn
* to add to.
* @param fleetName
* to resolve correct {@link RaceLog}.
* @return <code>true</code> if add was successful.
*/
RaceLogEvent addEventTo(RaceColumn raceColumn) {
if (raceColumn != null) {
Fleet fleet = raceColumn.getFleetByName(getFleetName());
RaceLog raceLog = raceColumn.getRaceLog(fleet);
raceLog.add(getEvent());
}
return getEvent();
}
}
@@ -1,37 +1,22 @@
package com.sap.sailing.server.operationaltransformation;
import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.racelog.RaceLogEvent;
import com.sap.sailing.server.RacingEventService;
import com.sap.sailing.server.RacingEventServiceOperation;
public class RecordRaceLogEventOnLeaderboard extends AbstractRecordRaceLogEvent {
public class RecordRaceLogEventOnLeaderboard extends AbstractRaceLogOnLeaderboardOperation<RaceLogEvent> {
private static final long serialVersionUID = 1008913415922358665L;
private final RaceLogEvent event;
private final String leaderboardName;
public RecordRaceLogEventOnLeaderboard(String leaderboardName, String raceColumnName,
String fleetName, RaceLogEvent event) {
super(raceColumnName, fleetName, event);
this.leaderboardName = leaderboardName;
super(leaderboardName, raceColumnName, fleetName);
this.event = event;
}
@Override
public RaceLogEvent internalApplyTo(RacingEventService toState) throws Exception {
Leaderboard leaderboard = toState.getLeaderboardByName(leaderboardName);
RaceColumn raceColumn = leaderboard.getRaceColumnByName(raceColumnName);
return addEventTo(raceColumn);
RaceColumn raceColumn = getRaceColumn(toState);
return new RaceLogEventRecorder(getFleetName(), event).addEventTo(raceColumn);
}
@Override
public RacingEventServiceOperation<?> transformClientOp(RacingEventServiceOperation<?> serverOp) {
return null;
}
@Override
public RacingEventServiceOperation<?> transformServerOp(RacingEventServiceOperation<?> clientOp) {
return null;
}
}
@@ -1,44 +1,23 @@
package com.sap.sailing.server.operationaltransformation;
import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.base.Series;
import com.sap.sailing.domain.racelog.RaceLogEvent;
import com.sap.sailing.server.RacingEventService;
import com.sap.sailing.server.RacingEventServiceOperation;
public class RecordRaceLogEventOnRegatta extends AbstractRecordRaceLogEvent {
public class RecordRaceLogEventOnRegatta extends AbstractRaceLogOnRegattaOperation<RaceLogEvent> {
private static final long serialVersionUID = 8092146834280389864L;
private final String regattaName;
private final RaceLogEvent event;
public RecordRaceLogEventOnRegatta(String regattaName, String raceColumnName,
String fleetName, RaceLogEvent event) {
super(raceColumnName, fleetName, event);
this.regattaName = regattaName;
super(regattaName, raceColumnName, fleetName);
this.event = event;
}
@Override
public RaceLogEvent internalApplyTo(RacingEventService toState) throws Exception {
Regatta regatta = toState.getRegattaByName(regattaName);
RaceColumn raceColumn = null;
for (Series series : regatta.getSeries()) {
raceColumn = series.getRaceColumnByName(raceColumnName);
if (raceColumn != null) {
break;
}
}
return addEventTo(raceColumn);
}
@Override
public RacingEventServiceOperation<?> transformClientOp(RacingEventServiceOperation<?> serverOp) {
return null;
}
@Override
public RacingEventServiceOperation<?> transformServerOp(RacingEventServiceOperation<?> clientOp) {
return null;
RaceColumn raceColumn = getRaceColumn(toState);
return new RaceLogEventRecorder(getFleetName(), event).addEventTo(raceColumn);
}
}