Files
sailing-analytics/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RacingEventServiceImpl.java
T

1222 lines
58 KiB
Java
Executable File

package com.sap.sailing.server.impl;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.net.MalformedURLException;
import java.net.SocketException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.UnknownHostException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sap.sailing.domain.base.Buoy;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.ControlPoint;
import com.sap.sailing.domain.base.Fleet;
import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.base.RaceDefinition;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.base.RegattaListener;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.base.impl.RegattaImpl;
import com.sap.sailing.domain.common.DefaultLeaderboardName;
import com.sap.sailing.domain.common.RaceIdentifier;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.RegattaIdentifier;
import com.sap.sailing.domain.common.RegattaName;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.WindSource;
import com.sap.sailing.domain.common.impl.Util;
import com.sap.sailing.domain.common.impl.Util.Pair;
import com.sap.sailing.domain.common.impl.Util.Triple;
import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.leaderboard.LeaderboardGroup;
import com.sap.sailing.domain.leaderboard.impl.FlexibleLeaderboardImpl;
import com.sap.sailing.domain.leaderboard.impl.LeaderboardGroupImpl;
import com.sap.sailing.domain.leaderboard.impl.LowerScoreIsBetter;
import com.sap.sailing.domain.leaderboard.impl.ResultDiscardingRuleImpl;
import com.sap.sailing.domain.leaderboard.impl.ScoreCorrectionImpl;
import com.sap.sailing.domain.persistence.DomainObjectFactory;
import com.sap.sailing.domain.persistence.MongoFactory;
import com.sap.sailing.domain.persistence.MongoObjectFactory;
import com.sap.sailing.domain.swisstimingadapter.Race;
import com.sap.sailing.domain.swisstimingadapter.SailMasterConnector;
import com.sap.sailing.domain.swisstimingadapter.SailMasterMessage;
import com.sap.sailing.domain.swisstimingadapter.SwissTimingFactory;
import com.sap.sailing.domain.swisstimingadapter.persistence.SwissTimingAdapterPersistence;
import com.sap.sailing.domain.tracking.DynamicTrackedRegatta;
import com.sap.sailing.domain.tracking.GPSFix;
import com.sap.sailing.domain.tracking.GPSFixMoving;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.RaceChangeListener;
import com.sap.sailing.domain.tracking.RaceListener;
import com.sap.sailing.domain.tracking.RaceTracker;
import com.sap.sailing.domain.tracking.RaceTrackingConnectivityParameters;
import com.sap.sailing.domain.tracking.RacesHandle;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.TrackedRegatta;
import com.sap.sailing.domain.tracking.Wind;
import com.sap.sailing.domain.tracking.WindStore;
import com.sap.sailing.domain.tracking.WindTracker;
import com.sap.sailing.domain.tracking.impl.DynamicTrackedRegattaImpl;
import com.sap.sailing.domain.tractracadapter.DomainFactory;
import com.sap.sailing.domain.tractracadapter.JSONService;
import com.sap.sailing.domain.tractracadapter.RaceRecord;
import com.sap.sailing.domain.tractracadapter.Receiver;
import com.sap.sailing.expeditionconnector.ExpeditionListener;
import com.sap.sailing.expeditionconnector.ExpeditionWindTrackerFactory;
import com.sap.sailing.expeditionconnector.UDPExpeditionReceiver;
import com.sap.sailing.mongodb.MongoDBService;
import com.sap.sailing.operationaltransformation.Operation;
import com.sap.sailing.server.OperationExecutionListener;
import com.sap.sailing.server.RacingEventService;
import com.sap.sailing.server.RacingEventServiceOperation;
import com.sap.sailing.server.operationaltransformation.AddRaceDefinition;
import com.sap.sailing.server.operationaltransformation.AddRegatta;
import com.sap.sailing.server.operationaltransformation.ConnectTrackedRaceToLeaderboardColumn;
import com.sap.sailing.server.operationaltransformation.CreateTrackedRace;
import com.sap.sailing.server.operationaltransformation.RecordBuoyGPSFix;
import com.sap.sailing.server.operationaltransformation.RecordCompetitorGPSFix;
import com.sap.sailing.server.operationaltransformation.RecordWindFix;
import com.sap.sailing.server.operationaltransformation.RemoveWindFix;
import com.sap.sailing.server.operationaltransformation.TrackRegatta;
import com.sap.sailing.server.operationaltransformation.UpdateMarkPassings;
import com.sap.sailing.server.operationaltransformation.UpdateRaceDelayToLive;
import com.sap.sailing.server.operationaltransformation.UpdateRaceTimes;
import com.sap.sailing.server.operationaltransformation.UpdateWindAveragingTime;
import com.sap.sailing.server.operationaltransformation.UpdateWindSourcesToExclude;
public class RacingEventServiceImpl implements RacingEventService, RegattaListener {
private static final Logger logger = Logger.getLogger(RacingEventServiceImpl.class.getName());
/**
* A scheduler for the periodic checks of the paramURL documents for the advent of {@link ControlPoint}s
* with static position information otherwise not available through <code>MarkPassingReceiver</code>'s events.
*/
private static final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
private final DomainFactory tractracDomainFactory;
private final com.sap.sailing.domain.swisstimingadapter.DomainFactory swissTimingDomainFactory;
private final ExpeditionWindTrackerFactory windTrackerFactory;
/**
* Holds the {@link Regatta} objects for those races registered with this service. Note that there may be {@link Regatta}
* objects that exist outside this service for races not (yet) registered here.
*/
protected final Map<String, Regatta> regattasByName;
private final Map<RaceDefinition, CourseChangeReplicator> courseListeners;
protected final Map<Regatta, Set<RaceTracker>> raceTrackersByRegatta;
/**
* Remembers the trackers by paramURL/liveURI/storedURI to avoid duplication
*/
protected final Map<Object, RaceTracker> raceTrackersByID;
/**
* Leaderboards managed by this racing event service
*/
private final Map<String, Leaderboard> leaderboardsByName;
private final Map<String, LeaderboardGroup> leaderboardGroupsByName;
private Set<DynamicTrackedRegatta> regattasObservedForDefaultLeaderboard = new HashSet<DynamicTrackedRegatta>();
private final MongoObjectFactory mongoObjectFactory;
private final DomainObjectFactory domainObjectFactory;
private final SwissTimingFactory swissTimingFactory;
private final SwissTimingAdapterPersistence swissTimingAdapterPersistence;
private final Map<Regatta, DynamicTrackedRegatta> regattaTrackingCache;
private final Set<OperationExecutionListener> operationExecutionListeners;
/**
* The globally used configuration of the time delay (in milliseconds) to the 'live' timepoint used for each new tracked race.
*/
private long delayToLiveInMillis;
public RacingEventServiceImpl() {
this(MongoFactory.INSTANCE.getDefaultDomainObjectFactory(), MongoFactory.INSTANCE.getDefaultMongoObjectFactory());
}
private RacingEventServiceImpl(DomainObjectFactory domainObjectFactory, MongoObjectFactory mongoObjectFactory) {
tractracDomainFactory = DomainFactory.INSTANCE;
this.domainObjectFactory = domainObjectFactory;
this.mongoObjectFactory = mongoObjectFactory;
swissTimingFactory = SwissTimingFactory.INSTANCE;
swissTimingDomainFactory = com.sap.sailing.domain.swisstimingadapter.DomainFactory.INSTANCE;
swissTimingAdapterPersistence = SwissTimingAdapterPersistence.INSTANCE;
windTrackerFactory = ExpeditionWindTrackerFactory.getInstance();
regattasByName = new HashMap<String, Regatta>();
regattaTrackingCache = new HashMap<Regatta, DynamicTrackedRegatta>();
raceTrackersByRegatta = new HashMap<Regatta, Set<RaceTracker>>();
raceTrackersByID = new HashMap<Object, RaceTracker>();
leaderboardGroupsByName = new HashMap<String, LeaderboardGroup>();
leaderboardsByName = new HashMap<String, Leaderboard>();
operationExecutionListeners = new HashSet<OperationExecutionListener>();
courseListeners = new HashMap<RaceDefinition, CourseChangeReplicator>();
delayToLiveInMillis = TrackedRace.DEFAULT_LIVE_DELAY_IN_MILLISECONDS;
// Add one default leaderboard that aggregates all races currently tracked by this service.
// This is more for debugging purposes than for anything else.
addFlexibleLeaderboard(DefaultLeaderboardName.DEFAULT_LEADERBOARD_NAME, new int[] { 5, 8 });
loadStoredLeaderboardsAndGroups();
}
public RacingEventServiceImpl(MongoDBService mongoDBService) {
this(MongoFactory.INSTANCE.getDomainObjectFactory(mongoDBService), MongoFactory.INSTANCE.getMongoObjectFactory(mongoDBService));
}
private void loadStoredLeaderboardsAndGroups() {
// Loading all leaderboard groups and putting the contained leaderboards
for (LeaderboardGroup leaderboardGroup : domainObjectFactory.getAllLeaderboardGroups()) {
leaderboardGroupsByName.put(leaderboardGroup.getName(), leaderboardGroup);
for (Leaderboard leaderboard : leaderboardGroup.getLeaderboards()) {
leaderboardsByName.put(leaderboard.getName(), leaderboard);
}
}
// Loading the remaining leaderboards
for (Leaderboard leaderboard : domainObjectFactory.getLeaderboardsNotInGroup()) {
leaderboardsByName.put(leaderboard.getName(), leaderboard);
}
}
@Override
public Leaderboard addFlexibleLeaderboard(String name, int[] discardThresholds) {
Leaderboard result = new FlexibleLeaderboardImpl(name, new ScoreCorrectionImpl(), new ResultDiscardingRuleImpl(
discardThresholds), new LowerScoreIsBetter());
synchronized (leaderboardsByName) {
if (leaderboardsByName.containsKey(name)) {
throw new IllegalArgumentException("Leaderboard with name "+name+" already exists");
}
leaderboardsByName.put(name, result);
}
mongoObjectFactory.storeLeaderboard(result);
return result;
}
@Override
public RaceColumn addColumnToLeaderboard(String columnName, String leaderboardName, boolean medalRace) {
Leaderboard leaderboard = getLeaderboardByName(leaderboardName);
if (leaderboard != null && leaderboard instanceof FlexibleLeaderboard) {
// uses the default fleet as the single fleet for the new column
RaceColumn result = ((FlexibleLeaderboard) leaderboard).addRaceColumn(columnName, medalRace, leaderboard.getFleet(null));
updateStoredLeaderboard(leaderboard);
return result;
} else {
throw new IllegalArgumentException("Leaderboard named " + leaderboardName + " not found");
}
}
@Override
public void moveLeaderboardColumnUp(String leaderboardName, String columnName) {
Leaderboard leaderboard = getLeaderboardByName(leaderboardName);
if (leaderboard != null && leaderboard instanceof FlexibleLeaderboard) {
((FlexibleLeaderboard) leaderboard).moveRaceColumnUp(columnName);
updateStoredLeaderboard(leaderboard);
} else {
throw new IllegalArgumentException("Leaderboard named " + leaderboardName + " not found");
}
}
@Override
public void moveLeaderboardColumnDown(String leaderboardName, String columnName) {
Leaderboard leaderboard = getLeaderboardByName(leaderboardName);
if (leaderboard != null && leaderboard instanceof FlexibleLeaderboard) {
((FlexibleLeaderboard) leaderboard).moveRaceColumnDown(columnName);
updateStoredLeaderboard(leaderboard);
} else {
throw new IllegalArgumentException("Leaderboard named " + leaderboardName + " not found");
}
}
@Override
public void removeLeaderboardColumn(String leaderboardName, String columnName) {
Leaderboard leaderboard = getLeaderboardByName(leaderboardName);
if (leaderboard != null && leaderboard instanceof FlexibleLeaderboard) {
((FlexibleLeaderboard) leaderboard).removeRaceColumn(columnName);
updateStoredLeaderboard(leaderboard);
} else {
throw new IllegalArgumentException("Leaderboard named "+leaderboardName+" not found");
}
}
@Override
public void renameLeaderboardColumn(String leaderboardName, String oldColumnName, String newColumnName) {
Leaderboard leaderboard = getLeaderboardByName(leaderboardName);
if (leaderboard != null) {
leaderboard.getRaceColumnByName(oldColumnName).setName(newColumnName);
updateStoredLeaderboard(leaderboard);
} else {
throw new IllegalArgumentException("Leaderboard named "+leaderboardName+" not found");
}
}
@Override
public void renameLeaderboard(String oldName, String newName) {
synchronized (leaderboardsByName) {
if (!leaderboardsByName.containsKey(oldName)) {
throw new IllegalArgumentException("No leaderboard with name "+oldName+" found");
}
if (leaderboardsByName.containsKey(newName)) {
throw new IllegalArgumentException("Leaderboard with name "+newName+" already exists");
}
Leaderboard toRename = leaderboardsByName.remove(oldName);
if (toRename instanceof FlexibleLeaderboard) {
((FlexibleLeaderboard) toRename).setName(newName);
leaderboardsByName.put(newName, toRename);
mongoObjectFactory.renameLeaderboard(oldName, newName);
} else {
throw new IllegalArgumentException("Leaderboard with name "+newName+" is not a FlexibleLeaderboard and therefore cannot be renamed");
}
}
}
@Override
public void updateStoredLeaderboard(Leaderboard leaderboard) {
mongoObjectFactory.storeLeaderboard(leaderboard);
syncGroupsAfterLeaderboardChange(leaderboard, true);
}
/**
* Checks all groups, if they contain a leaderboard with the name of the <code>updatedLeaderboard</code> and
* replaces the one in the group with the updated one.<br />
* This synchronizes things like the RaceIdentifier in the leaderboard columns.
*
* @param updatedLeaderboard
*/
private void syncGroupsAfterLeaderboardChange(Leaderboard updatedLeaderboard, boolean doDatabaseUpdate) {
boolean groupNeedsUpdate = false;
synchronized (leaderboardGroupsByName) {
for (LeaderboardGroup leaderboardGroup : leaderboardGroupsByName.values()) {
for (Leaderboard leaderboard : leaderboardGroup.getLeaderboards()) {
if (leaderboard.getName().equals(updatedLeaderboard.getName())) {
int index = leaderboardGroup.getIndexOf(leaderboard);
leaderboardGroup.removeLeaderboard(leaderboard);
leaderboardGroup.addLeaderboardAt(updatedLeaderboard, index);
groupNeedsUpdate = true;
// TODO we assume that the leaderboard names are unique, so we can break the inner loop here
break;
}
}
if (doDatabaseUpdate && groupNeedsUpdate) {
mongoObjectFactory.storeLeaderboardGroup(leaderboardGroup);
}
groupNeedsUpdate = false;
}
}
}
@Override
public void removeLeaderboard(String leaderboardName) {
synchronized (leaderboardsByName) {
leaderboardsByName.remove(leaderboardName);
}
mongoObjectFactory.removeLeaderboard(leaderboardName);
syncGroupsAfterLeaderboardRemove(leaderboardName, true);
}
/**
* Checks all groups, if they contain a leaderboard with the <code>removedLeaderboardName</code> and removes it from the group.
* @param removedLeaderboardName
*/
private void syncGroupsAfterLeaderboardRemove(String removedLeaderboardName, boolean doDatabaseUpdate) {
boolean groupNeedsUpdate = false;
synchronized (leaderboardGroupsByName) {
for (LeaderboardGroup leaderboardGroup : leaderboardGroupsByName.values()) {
for (Leaderboard leaderboard : leaderboardGroup.getLeaderboards()) {
if (leaderboard.getName().equals(removedLeaderboardName)) {
leaderboardGroup.removeLeaderboard(leaderboard);
groupNeedsUpdate = true;
// TODO we assume that the leaderboard names are unique, so we can break the inner loop here
break;
}
}
if (doDatabaseUpdate && groupNeedsUpdate) {
mongoObjectFactory.storeLeaderboardGroup(leaderboardGroup);
}
groupNeedsUpdate = false;
}
}
}
@Override
public Leaderboard getLeaderboardByName(String name) {
synchronized (leaderboardsByName) {
return leaderboardsByName.get(name);
}
}
@Override
public Map<String, Leaderboard> getLeaderboards() {
synchronized (leaderboardsByName) {
return Collections.unmodifiableMap(new HashMap<String, Leaderboard>(leaderboardsByName));
}
}
private DomainFactory getDomainFactory() {
return tractracDomainFactory;
}
@Override
public SwissTimingFactory getSwissTimingFactory() {
return swissTimingFactory;
}
@Override
public synchronized Iterable<Regatta> getAllRegattas() {
return Collections.unmodifiableCollection(new ArrayList<Regatta>(regattasByName.values()));
}
@Override
public boolean isRaceBeingTracked(RaceDefinition r) {
for (Set<RaceTracker> trackers : raceTrackersByRegatta.values()) {
for (RaceTracker tracker : trackers) {
if (tracker.getRaces() != null && tracker.getRaces().contains(r)) {
return true;
}
}
}
return false;
}
@Override
public Regatta getRegattaByName(String name) {
return regattasByName.get(name);
}
@Override
public synchronized Regatta addRegatta(URL jsonURL, URI liveURI, URI storedURI, WindStore windStore, long timeoutInMilliseconds) throws Exception {
JSONService jsonService = getDomainFactory().parseJSONURL(jsonURL);
Regatta regatta = null;
for (RaceRecord rr : jsonService.getRaceRecords()) {
URL paramURL = rr.getParamURL();
regatta = addTracTracRace(paramURL, liveURI, storedURI, windStore, timeoutInMilliseconds).getRegatta();
}
return regatta;
}
@Override
public Regatta getOrCreateRegatta(String baseEventName, String boatClassName, boolean boatClassTypicallyStartsUpwind) {
Regatta regatta = new RegattaImpl(baseEventName, com.sap.sailing.domain.base.DomainFactory.INSTANCE.getOrCreateBoatClass(
boatClassName, boatClassTypicallyStartsUpwind));
Regatta result = regattasByName.get(regatta.getName());
if (result == null) {
result = regatta;
logger.info("Created regatta "+result.getName()+" ("+hashCode()+")");
cacheAndReplicateRegatta(result);
}
return result;
}
@Override
public Pair<String, List<RaceRecord>> getTracTracRaceRecords(URL jsonURL) throws IOException, ParseException, org.json.simple.parser.ParseException, URISyntaxException {
JSONService jsonService = getDomainFactory().parseJSONURL(jsonURL);
return new Pair<String, List<RaceRecord>>(jsonService.getEventName(), jsonService.getRaceRecords());
}
@Override
public List<com.sap.sailing.domain.swisstimingadapter.RaceRecord> getSwissTimingRaceRecords(String hostname,
int port, boolean canSendRequests) throws InterruptedException, UnknownHostException, IOException, ParseException {
List<com.sap.sailing.domain.swisstimingadapter.RaceRecord> result = new ArrayList<com.sap.sailing.domain.swisstimingadapter.RaceRecord>();
// SailMasterConnector swissTimingConnector = swissTimingFactory.getOrCreateSailMasterConnector(hostname, port, swissTimingAdapterPersistence,
// canSendRequests);
//TODO remove after testing and enable the upper code instead
SailMasterConnector swissTimingConnector = swissTimingFactory.getOrCreateSailMasterLiveSimulatorConnector(hostname, port, swissTimingAdapterPersistence,
canSendRequests);
//
for (Race race : swissTimingConnector.getRaces()) {
TimePoint startTime = swissTimingConnector.getStartTime(race.getRaceID());
result.add(new com.sap.sailing.domain.swisstimingadapter.RaceRecord(race.getRaceID(), race.getDescription(),
startTime==null?null:startTime.asDate()));
}
return result;
}
@Override
public synchronized RacesHandle addSwissTimingRace(RegattaIdentifier regattaToAddTo, String raceID, String hostname,
int port, boolean canSendRequests, WindStore windStore, long timeoutInMilliseconds) throws Exception {
return addRace(
regattaToAddTo,
swissTimingDomainFactory.createTrackingConnectivityParameters(hostname, port, raceID, canSendRequests, delayToLiveInMillis,
swissTimingFactory, swissTimingDomainFactory, windStore, swissTimingAdapterPersistence), windStore, timeoutInMilliseconds);
}
@Override
public synchronized RacesHandle addTracTracRace(URL paramURL, URI liveURI, URI storedURI, WindStore windStore,
long timeoutInMilliseconds) throws Exception {
return addRace(
/* regattaToAddTo */ null,
getDomainFactory().createTrackingConnectivityParameters(paramURL, liveURI, storedURI,
/* startOfTracking */ null,
/* endOfTracking */null, delayToLiveInMillis, windStore), windStore, timeoutInMilliseconds);
}
@Override
public void addRace(RegattaIdentifier addToRegatta, RaceDefinition raceDefinition) {
Regatta regatta = getRegatta(addToRegatta);
regatta.addRace(raceDefinition); // will trigger the raceAdded operation because this service is listening on all its regattass
}
@Override
public void raceAdded(Regatta regatta, RaceDefinition raceDefinition) {
final CourseChangeReplicator listener = new CourseChangeReplicator(this, regatta, raceDefinition);
courseListeners.put(raceDefinition, listener);
raceDefinition.getCourse().addCourseListener(listener);
replicate(new AddRaceDefinition(regatta.getRegattaIdentifier(), raceDefinition));
}
@Override
public void raceRemoved(Regatta regatta, RaceDefinition raceDefinition) {
raceDefinition.getCourse().removeCourseListener(courseListeners.remove(raceDefinition));
}
@Override
public synchronized RacesHandle addRace(RegattaIdentifier regattaToAddTo, RaceTrackingConnectivityParameters params,
WindStore windStore, long timeoutInMilliseconds) throws Exception {
RaceTracker tracker = raceTrackersByID.get(params.getTrackerID());
if (tracker == null) {
Regatta regatta = regattaToAddTo == null ? null : getRegatta(regattaToAddTo);
if (regatta == null) {
// create tracker and use an existing or create a default regatta
tracker = params.createRaceTracker(this);
} else {
// use the regatta selected by the RaceIdentifier regattaToAddTo
tracker = params.createRaceTracker(regatta, this);
assert tracker.getRegatta() == regatta;
}
raceTrackersByID.put(params.getTrackerID(), tracker);
Set<RaceTracker> trackers = raceTrackersByRegatta.get(tracker.getRegatta());
if (trackers == null) {
trackers = new HashSet<RaceTracker>();
raceTrackersByRegatta.put(tracker.getRegatta(), trackers);
}
trackers.add(tracker);
// TODO we assume here that the event name is unique which necessitates adding the boat class name to it in EventImpl constructor
String regattaName = tracker.getRegatta().getName();
Regatta regattaWithName = regattasByName.get(regattaName);
// TODO we assume here that the event name is unique which necessitates adding the boat class name to it in EventImpl constructor
if (regattaWithName != null) {
if (regattaWithName != tracker.getRegatta()) {
if (Util.isEmpty(regattaWithName.getAllRaces())) {
// probably, tracker removed the last races from the old regatta and created a new one
cacheAndReplicateRegatta(tracker.getRegatta());
} else {
throw new RuntimeException("Internal error. Two Event objects with equal name "+regattaName);
}
}
} else {
cacheAndReplicateRegatta(tracker.getRegatta());
}
} else {
WindStore existingTrackersWindStore = tracker.getWindStore();
if (!existingTrackersWindStore.equals(windStore)) {
logger.warning("Wind store mismatch. Requested wind store: "+windStore+
". Wind store in use by existing tracker: "+existingTrackersWindStore);
}
}
if (timeoutInMilliseconds != -1) {
scheduleAbortTrackerAfterInitialTimeout(tracker, timeoutInMilliseconds);
}
return tracker.getRacesHandle();
}
/**
* If <code>regatta</code> is not yet in {@link #regattasByName}, it is added, this service is
* {@link Regatta#addRegattaListener(RegattaListener) added} as regatta listener, and the regatta and all its contained
* {@link Regatta#getAllRaces() races} are replicated to all replica.
*/
private void cacheAndReplicateRegatta(Regatta regatta) {
if (!regattasByName.containsKey(regatta.getName())) {
regattasByName.put(regatta.getName(), regatta);
regatta.addRegattaListener(this);
replicate(new AddRegatta(regatta.getBaseName(), regatta.getBoatClass() == null ? null : regatta.getBoatClass().getName(),
regatta.getBoatClass() == null ? false : regatta.getBoatClass().typicallyStartsUpwind()));
RegattaIdentifier regattaIdentifier = regatta.getRegattaIdentifier();
for (RaceDefinition race : regatta.getAllRaces()) {
replicate(new AddRaceDefinition(regattaIdentifier, race));
}
}
}
@Override
public synchronized TrackedRace createTrackedRace(RegattaAndRaceIdentifier raceIdentifier, WindStore windStore,
long delayToLiveInMillis, long millisecondsOverWhichToAverageWind, long millisecondsOverWhichToAverageSpeed) {
DynamicTrackedRegatta trackedRegatta = getTrackedRegatta(getRegatta(raceIdentifier));
RaceDefinition race = getRace(raceIdentifier);
return trackedRegatta.createTrackedRace(race, windStore, delayToLiveInMillis, millisecondsOverWhichToAverageWind, millisecondsOverWhichToAverageSpeed,
/* raceDefinitionSetToUpdate */ null);
}
@Override
public synchronized RacesHandle addTracTracRace(RegattaIdentifier regattaToAddTo, URL paramURL, URI liveURI,
URI storedURI, TimePoint startOfTracking, TimePoint endOfTracking,
WindStore windStore, long timeoutInMilliseconds) throws Exception {
return addRace(regattaToAddTo, getDomainFactory().createTrackingConnectivityParameters(paramURL, liveURI, storedURI, startOfTracking,
endOfTracking, delayToLiveInMillis, windStore), windStore, timeoutInMilliseconds);
}
private void ensureRegattaIsObservedForDefaultLeaderboardAndAutoLeaderboardLinking(DynamicTrackedRegatta trackedRegatta) {
synchronized (regattasObservedForDefaultLeaderboard) {
if (!regattasObservedForDefaultLeaderboard.contains(trackedRegatta)) {
trackedRegatta.addRaceListener(new RaceAdditionListener());
regattasObservedForDefaultLeaderboard.add(trackedRegatta);
}
}
}
/**
* A listener class used to ensure that when a tracked race is added to any {@link TrackedRegatta} managed by this
* service, the service adds the tracked race to the default leaderboard and links it to the leaderboard columns
* that were previously connected to it. Additionally, a {@link RaceChangeListener} is added to the {@link TrackedRace}
* which is responsible for triggering the replication of all relevant changes to the tracked race. When a tracked
* race is removed, the {@link TrackedRaceReplicator} that was added as listener to that tracked race is removed again.
*
* @author Axel Uhl (d043530)
*
*/
private class RaceAdditionListener implements RaceListener, Serializable {
private static final long serialVersionUID = 1036955460477000265L;
private final Map<TrackedRace, TrackedRaceReplicator> trackedRaceReplicators;
public RaceAdditionListener() {
this.trackedRaceReplicators = new HashMap<TrackedRace, TrackedRaceReplicator>();
}
@Override
public void raceRemoved(TrackedRace trackedRace) {
TrackedRaceReplicator trackedRaceReplicator = trackedRaceReplicators.remove(trackedRace);
if (trackedRaceReplicator != null) {
trackedRace.removeListener(trackedRaceReplicator);
}
}
@Override
public void raceAdded(TrackedRace trackedRace) {
// replicate the addition of the tracked race:
CreateTrackedRace op = new CreateTrackedRace(trackedRace.getRaceIdentifier(), trackedRace.getWindStore(),
trackedRace.getDelayToLiveInMillis(),
trackedRace.getMillisecondsOverWhichToAverageWind(), trackedRace.getMillisecondsOverWhichToAverageSpeed());
replicate(op);
linkRaceToConfiguredLeaderboardColumns(trackedRace);
final FlexibleLeaderboard defaultLeaderboard = (FlexibleLeaderboard) leaderboardsByName.get(DefaultLeaderboardName.DEFAULT_LEADERBOARD_NAME);
defaultLeaderboard.addRace(trackedRace,
trackedRace.getRace().getName(), /* medalRace */false, defaultLeaderboard.getFleet(null));
TrackedRaceReplicator trackedRaceReplicator = new TrackedRaceReplicator(trackedRace);
trackedRaceReplicators.put(trackedRace, trackedRaceReplicator);
trackedRace.addListener(trackedRaceReplicator);
}
}
private class TrackedRaceReplicator implements RaceChangeListener {
private final TrackedRace trackedRace;
public TrackedRaceReplicator(TrackedRace trackedRace) {
this.trackedRace = trackedRace;
}
@Override
public void windSourcesToExcludeChanged(Iterable<? extends WindSource> windSourcesToExclude) {
replicate(new UpdateWindSourcesToExclude(getRaceIdentifier(), windSourcesToExclude));
}
@Override
public void raceTimesChanged(TimePoint startOfTracking, TimePoint endOfTracking, TimePoint startTimeReceived) {
replicate(new UpdateRaceTimes(getRaceIdentifier(), startOfTracking, endOfTracking, startTimeReceived));
}
@Override
public void delayToLiveChanged(long delayToLiveInMillis) {
replicate(new UpdateRaceDelayToLive(getRaceIdentifier(), delayToLiveInMillis));
}
@Override
public void windDataReceived(Wind wind, WindSource windSource) {
replicate(new RecordWindFix(getRaceIdentifier(), windSource, wind));
}
@Override
public void windDataRemoved(Wind wind, WindSource windSource) {
replicate(new RemoveWindFix(getRaceIdentifier(), windSource, wind));
}
@Override
public void windAveragingChanged(long oldMillisecondsOverWhichToAverage, long newMillisecondsOverWhichToAverage) {
replicate(new UpdateWindAveragingTime(getRaceIdentifier(), newMillisecondsOverWhichToAverage));
}
@Override
public void competitorPositionChanged(GPSFixMoving fix, Competitor competitor) {
replicate(new RecordCompetitorGPSFix(getRaceIdentifier(), competitor, fix));
}
@Override
public void buoyPositionChanged(GPSFix fix, Buoy buoy) {
replicate(new RecordBuoyGPSFix(getRaceIdentifier(), buoy, fix));
}
@Override
public void markPassingReceived(Map<Waypoint, MarkPassing> oldMarkPassings, Iterable<MarkPassing> markPassings) {
replicate(new UpdateMarkPassings(getRaceIdentifier(), markPassings.iterator().next().getCompetitor(),
markPassings));
}
@Override
public void speedAveragingChanged(long oldMillisecondsOverWhichToAverage, long newMillisecondsOverWhichToAverage) {
replicate(new UpdateWindAveragingTime(getRaceIdentifier(), newMillisecondsOverWhichToAverage));
}
private RegattaAndRaceIdentifier getRaceIdentifier() {
return trackedRace.getRaceIdentifier();
}
}
/**
* Based on the <code>trackedRace</code>'s {@link TrackedRace#getRaceIdentifier() race identifier}, the tracked race
* is (re-)associated to all {@link RaceColumn race columns} that currently have no
* {@link RaceColumn#getTrackedRace(Fleet) tracked race assigned} and whose
* {@link RaceColumn#getRaceIdentifier(Fleet) race identifier} equals that of <code>trackedRace</code>.
*/
private void linkRaceToConfiguredLeaderboardColumns(TrackedRace trackedRace) {
boolean leaderboardHasChanged = false;
RaceIdentifier trackedRaceIdentifier = trackedRace.getRaceIdentifier();
for (Leaderboard leaderboard : getLeaderboards().values()) {
for (RaceColumn column : leaderboard.getRaceColumns()) {
for (Fleet fleet : column.getFleets()) {
if (trackedRaceIdentifier.equals(column.getRaceIdentifier(fleet)) && column.getTrackedRace(fleet) == null) {
column.setTrackedRace(fleet, trackedRace);
leaderboardHasChanged = true;
replicate(new ConnectTrackedRaceToLeaderboardColumn(leaderboard.getName(), column.getName(),
fleet.getName(), trackedRaceIdentifier));
}
}
}
if (leaderboardHasChanged) {
//Update the corresponding groups, to keep them in sync
syncGroupsAfterLeaderboardChange(leaderboard, /*doDatabaseUpdate*/ false);
}
}
}
@Override
public synchronized void stopTracking(Regatta regatta) throws MalformedURLException, IOException, InterruptedException {
if (raceTrackersByRegatta.containsKey(regatta)) {
for (RaceTracker raceTracker : raceTrackersByRegatta.get(regatta)) {
for (RaceDefinition race : raceTracker.getRaces()) {
stopTrackingWind(regatta, race);
}
raceTracker.stop(); // this also removes the TrackedRace from trackedRegatta
raceTrackersByID.remove(raceTracker.getID());
}
raceTrackersByRegatta.remove(regatta);
}
}
@Override
public synchronized void stopTrackingAndRemove(Regatta regatta) throws MalformedURLException, IOException, InterruptedException {
stopTracking(regatta);
if (regatta != null) {
if (regatta.getName() != null) {
logger.info("Removing regatta "+regatta.getName()+" ("+regatta.hashCode()+") from RacingEventServiceImpl");
regattasByName.remove(regatta.getName());
regattaTrackingCache.remove(regatta);
regatta.removeRegattaListener(this);
}
for (RaceDefinition race : regatta.getAllRaces()) {
stopTrackingWind(regatta, race);
// remove from default leaderboard
FlexibleLeaderboard defaultLeaderboard = (FlexibleLeaderboard) getLeaderboardByName(DefaultLeaderboardName.DEFAULT_LEADERBOARD_NAME);
defaultLeaderboard.removeRaceColumn(race.getName());
}
}
}
/**
* The tracker will initially try to connect to the TracTrac infrastructure to obtain basic race master data. If
* this fails after some timeout, to avoid garbage and lingering threads, the task scheduled by this method will
* check after the timeout expires if race master data was successfully received. If so, the tracker continues
* normally. Otherwise, the tracker is shut down orderly by {@link Receiver#stopPreemptively() stopping} all
* receivers and {@link DataController#stop(boolean) stopping} the TracTrac controller for this tracker.
*
* @return the scheduled task, in case the caller wants to {@link ScheduledFuture#cancel(boolean) cancel} it, e.g.,
* when the tracker is stopped or has successfully received the race
*/
private ScheduledFuture<?> scheduleAbortTrackerAfterInitialTimeout(final RaceTracker tracker, final long timeoutInMilliseconds) {
ScheduledFuture<?> task = getScheduler().schedule(new Runnable() {
@Override public void run() {
if (tracker.getRaces() == null || tracker.getRaces().isEmpty()) {
try {
Regatta regatta = tracker.getRegatta();
logger.log(Level.SEVERE, "RaceDefinition for a race in regatta "+regatta.getName()+" not obtained within "+
timeoutInMilliseconds+"ms. Aborting tracker for this race.");
Set<RaceTracker> trackersForRegatta = raceTrackersByRegatta.get(regatta);
if (trackersForRegatta != null) {
trackersForRegatta.remove(tracker);
}
tracker.stop();
raceTrackersByID.remove(tracker.getID());
if (trackersForRegatta == null || trackersForRegatta.isEmpty()) {
stopTracking(regatta);
}
} catch (Exception e) {
logger.throwing(RacingEventServiceImpl.class.getName(), "scheduleAbortTrackerAfterInitialTimeout", e);
e.printStackTrace();
}
}
}
}, /* delay */ timeoutInMilliseconds, /* unit */ TimeUnit.MILLISECONDS);
return task;
}
@Override
public synchronized void stopTracking(Regatta regatta, RaceDefinition race) throws MalformedURLException, IOException, InterruptedException {
logger.info("Stopping tracking for "+race+"...");
if (raceTrackersByRegatta.containsKey(regatta)) {
Iterator<RaceTracker> trackerIter = raceTrackersByRegatta.get(regatta).iterator();
while (trackerIter.hasNext()) {
RaceTracker raceTracker = trackerIter.next();
if (raceTracker.getRaces() != null && raceTracker.getRaces().contains(race)) {
logger.info("Found tracker to stop for races "+raceTracker.getRaces());
raceTracker.stop(); // this also removes the TrackedRace from trackedRegatta
// do not remove the tracker from raceTrackersByRegatta, because it should still exist there, but with the state "non-tracked"
trackerIter.remove();
raceTrackersByID.remove(raceTracker.getID());
}
}
} else {
logger.warning("Didn't find any trackers for regatta "+regatta);
}
stopTrackingWind(regatta, race);
// if the last tracked race was removed, remove the entire regatta
if (raceTrackersByRegatta.get(regatta).isEmpty()) {
stopTracking(regatta);
}
}
@Override
public synchronized void removeRegatta(Regatta regatta) throws MalformedURLException, IOException, InterruptedException {
for (RaceDefinition race : regatta.getAllRaces()) {
removeRace(regatta, race);
}
}
@Override
public synchronized void removeRace(Regatta regatta, RaceDefinition race) throws MalformedURLException,
IOException, InterruptedException {
logger.info("Removing the race " + race + "...");
stopAllTrackersForWhichRaceIsLastReachable(regatta, race);
stopTrackingWind(regatta, race);
TrackedRace trackedRace = getExistingTrackedRace(regatta, race);
if (trackedRace != null) {
TrackedRegatta trackedRegatta = getTrackedRegatta(regatta);
if (trackedRegatta != null) {
trackedRegatta.removeTrackedRace(trackedRace);
}
if (Util.isEmpty(trackedRegatta.getTrackedRaces())) {
removeTrackedRegatta(regatta);
}
for (Leaderboard leaderboard : getLeaderboards().values()) {
boolean changed = false;
for (RaceColumn raceColumn : leaderboard.getRaceColumns()) {
for (Fleet fleet : raceColumn.getFleets()) {
if (raceColumn.getTrackedRace(fleet) == trackedRace) {
raceColumn.releaseTrackedRace(fleet); // but leave the RaceIdentifier on the race column
changed = true; // untouched, e.g., for later re-load
}
}
}
if (changed) {
updateStoredLeaderboard(leaderboard);
}
}
}
// remove the race from the regatta
regatta.removeRace(race);
if (Util.isEmpty(regatta.getAllRaces())) {
logger.info("Removing regatta "+regatta.getName()+" from service "+this);
regattasByName.remove(regatta.getName());
regatta.removeRegattaListener(this);
}
}
/**
* Doesn't stop any wind trackers
*/
private void stopAllTrackersForWhichRaceIsLastReachable(Regatta regatta, RaceDefinition race)
throws MalformedURLException, IOException, InterruptedException {
if (raceTrackersByRegatta.containsKey(regatta)) {
Iterator<RaceTracker> trackerIter = raceTrackersByRegatta.get(regatta).iterator();
while (trackerIter.hasNext()) {
RaceTracker raceTracker = trackerIter.next();
if (raceTracker.getRaces() != null && raceTracker.getRaces().contains(race)) {
boolean foundReachableRace = false;
for (RaceDefinition raceTrackedByTracker : raceTracker.getRaces()) {
if (raceTrackedByTracker != race && isReachable(regatta, raceTrackedByTracker)) {
foundReachableRace = true;
break;
}
}
if (!foundReachableRace) {
// firstly stop the tracker
raceTracker.stop();
// remove it from the raceTrackers by Regatta
trackerIter.remove();
raceTrackersByID.remove(raceTracker.getID());
// if the last tracked race was removed, remove the entire regatta
if (raceTrackersByRegatta.get(regatta).isEmpty()) {
stopTracking(regatta);
}
}
}
}
}
}
private boolean isReachable(Regatta regatta, RaceDefinition race) {
return Util.contains(regatta.getAllRaces(), race);
}
@Override
public void startTrackingWind(Regatta regatta, RaceDefinition race,
boolean correctByDeclination) throws SocketException {
windTrackerFactory.createWindTracker(getOrCreateTrackedRegatta(regatta), race, correctByDeclination);
}
@Override
public synchronized void stopTrackingWind(Regatta regatta, RaceDefinition race) throws SocketException, IOException {
WindTracker windTracker = windTrackerFactory.getExistingWindTracker(race);
if (windTracker != null) {
windTracker.stop();
}
}
@Override
public synchronized Iterable<Triple<Regatta, RaceDefinition, String>> getWindTrackedRaces() {
List<Triple<Regatta, RaceDefinition, String>> result = new ArrayList<Triple<Regatta, RaceDefinition, String>>();
for (Regatta regatta : getAllRegattas()) {
for (RaceDefinition race : regatta.getAllRaces()) {
WindTracker windTracker = windTrackerFactory.getExistingWindTracker(race);
if (windTracker != null) {
result.add(new Triple<Regatta, RaceDefinition, String>(regatta, race, windTracker.toString()));
}
}
}
return result;
}
@Override
public TrackedRace getTrackedRace(Regatta regatta, RaceDefinition race) {
return getOrCreateTrackedRegatta(regatta).getTrackedRace(race);
}
private TrackedRace getExistingTrackedRace(Regatta regatta, RaceDefinition race) {
return getOrCreateTrackedRegatta(regatta).getExistingTrackedRace(race);
}
@Override
public DynamicTrackedRegatta getOrCreateTrackedRegatta(Regatta regatta) {
cacheAndReplicateRegatta(regatta);
synchronized (regattaTrackingCache) {
DynamicTrackedRegatta result = regattaTrackingCache.get(regatta);
if (result == null) {
logger.info("Creating DynamicTrackedRegattaImpl for regatta "+regatta.getName()+
" with hashCode "+regatta.hashCode());
result = new DynamicTrackedRegattaImpl(regatta);
replicate(new TrackRegatta(regatta.getRegattaIdentifier()));
regattaTrackingCache.put(regatta, result);
ensureRegattaIsObservedForDefaultLeaderboardAndAutoLeaderboardLinking(result);
}
return result;
}
}
@Override
public DynamicTrackedRegatta getTrackedRegatta(com.sap.sailing.domain.base.Regatta regatta) {
return regattaTrackingCache.get(regatta);
}
@Override
public void removeTrackedRegatta(Regatta regatta) {
logger.info("Removing regatta "+regatta.getName()+" from regattaTrackingCache");
regattaTrackingCache.remove(regatta);
}
@Override
public void storeSwissTimingDummyRace(String racMessage, String stlMessage, String ccgMessage){
SailMasterMessage racSMMessage = swissTimingFactory.createMessage(racMessage, null);
SailMasterMessage stlSMMessage = swissTimingFactory.createMessage(stlMessage, null);
SailMasterMessage ccgSMMessage = swissTimingFactory.createMessage(ccgMessage, null);
if (swissTimingAdapterPersistence.getRace(stlSMMessage.getRaceID()) != null) {
throw new IllegalArgumentException("Race with raceID \"" + stlSMMessage.getRaceID() + "\" already exists.");
}
else {
swissTimingAdapterPersistence.storeSailMasterMessage(racSMMessage);
swissTimingAdapterPersistence.storeSailMasterMessage(stlSMMessage);
swissTimingAdapterPersistence.storeSailMasterMessage(ccgSMMessage);
}
}
@Override
public Regatta getRegatta(RegattaName regattaName) {
return (Regatta) regattasByName.get(regattaName.getRegattaName());
}
@Override
public Regatta getRegatta(RegattaIdentifier regattaIdentifier) {
return (Regatta) regattaIdentifier.getRegatta(this);
}
@Override
public TrackedRace getTrackedRace(RegattaAndRaceIdentifier raceIdentifier) {
TrackedRace result = null;
Regatta regatta = regattasByName.get(raceIdentifier.getRegattaName());
if (regatta != null) {
DynamicTrackedRegatta trackedRegatta = regattaTrackingCache.get(regatta);
if (trackedRegatta != null) {
RaceDefinition race = getRace(raceIdentifier);
if (race != null) {
result = trackedRegatta.getTrackedRace(race);
}
}
}
return result;
}
@Override
public TrackedRace getExistingTrackedRace(RaceIdentifier raceIdentifier) {
Regatta regatta = getRegattaByName(raceIdentifier.getRegattaName());
TrackedRace trackedRace = null;
if (regatta != null) {
RaceDefinition race = regatta.getRaceByName(raceIdentifier.getRaceName());
trackedRace = getOrCreateTrackedRegatta(regatta).getExistingTrackedRace(race);
}
return trackedRace;
}
@Override
public RaceDefinition getRace(RegattaAndRaceIdentifier regattaNameAndRaceName) {
RaceDefinition result = null;
Regatta regatta = getRegatta(regattaNameAndRaceName);
if (regatta != null) {
result = regatta.getRaceByName(regattaNameAndRaceName.getRaceName());
}
return result;
}
@Override
public Map<String, LeaderboardGroup> getLeaderboardGroups() {
synchronized (leaderboardGroupsByName) {
return Collections.unmodifiableMap(new HashMap<String, LeaderboardGroup>(leaderboardGroupsByName));
}
}
@Override
public LeaderboardGroup getLeaderboardGroupByName(String groupName) {
synchronized (leaderboardGroupsByName) {
return leaderboardGroupsByName.get(groupName);
}
}
@Override
public LeaderboardGroup addLeaderboardGroup(String groupName, String description, List<String> leaderboardNames) {
ArrayList<Leaderboard> leaderboards = new ArrayList<>();
synchronized (leaderboardsByName) {
for (String leaderboardName : leaderboardNames) {
Leaderboard leaderboard = leaderboardsByName.get(leaderboardName);
if (leaderboard == null) {
throw new IllegalArgumentException("No leaderboard with name " + leaderboardName + " found");
} else {
leaderboards.add(leaderboard);
}
}
}
LeaderboardGroup result = new LeaderboardGroupImpl(groupName, description, leaderboards);
synchronized (leaderboardGroupsByName) {
if (leaderboardGroupsByName.containsKey(groupName)) {
throw new IllegalArgumentException("Leaderboard group with name " + groupName + " already exists");
}
leaderboardGroupsByName.put(groupName, result);
}
mongoObjectFactory.storeLeaderboardGroup(result);
return result;
}
@Override
public void removeLeaderboardGroup(String groupName) {
synchronized (leaderboardGroupsByName) {
leaderboardGroupsByName.remove(groupName);
}
mongoObjectFactory.removeLeaderboardGroup(groupName);
}
@Override
public void renameLeaderboardGroup(String oldName, String newName) {
synchronized (leaderboardGroupsByName) {
if (!leaderboardGroupsByName.containsKey(oldName)) {
throw new IllegalArgumentException("No leaderboard group with name " + oldName + " found");
}
if (leaderboardGroupsByName.containsKey(newName)) {
throw new IllegalArgumentException("Leaderboard group with name " + newName + " already exists");
}
LeaderboardGroup toRename = leaderboardGroupsByName.remove(oldName);
toRename.setName(newName);
leaderboardGroupsByName.put(newName, toRename);
mongoObjectFactory.renameLeaderboardGroup(oldName, newName);
}
}
@Override
public void updateLeaderboardGroup(String oldName, String newName, String description, List<String> leaderboardNames) {
if (!oldName.equals(newName)) {
renameLeaderboardGroup(oldName, newName);
}
LeaderboardGroup group = getLeaderboardGroupByName(newName);
if (!description.equals(group.getDescription())) {
group.setDescriptiom(description);
}
group.clearLeaderboards();
for (String leaderboardName : leaderboardNames) {
Leaderboard leaderboard = getLeaderboardByName(leaderboardName);
if (leaderboard != null) {
group.addLeaderboard(leaderboard);
}
}
mongoObjectFactory.storeLeaderboardGroup(group);
}
@Override
public void updateStoredLeaderboardGroup(LeaderboardGroup leaderboardGroup) {
mongoObjectFactory.storeLeaderboardGroup(leaderboardGroup);
}
@Override
public void addExpeditionListener(ExpeditionListener listener, boolean validMessagesOnly) throws SocketException {
UDPExpeditionReceiver receiver = windTrackerFactory.getOrCreateWindReceiverOnDefaultPort();
receiver.addListener(listener, validMessagesOnly);
}
@Override
public void removeExpeditionListener(ExpeditionListener listener) {
UDPExpeditionReceiver receiver;
try {
receiver = windTrackerFactory.getOrCreateWindReceiverOnDefaultPort();
receiver.removeListener(listener);
} catch (SocketException e) {
logger.info("Failed to remove expedition listener "+listener+
"; exception while trying to retrieve wind receiver: "+e.getMessage());
}
}
private ScheduledExecutorService getScheduler() {
return scheduler;
}
/**
* Currently, the operation is executed by immediately {@link Operation#internalApplyTo(Object) applying} it to this
* service object.<p>
*
* Future implementations of this method will need to also replicate the effects of the operation to all replica
* of this service known.
*/
@Override
public <T> T apply(RacingEventServiceOperation<T> operation) {
try {
T result = operation.internalApplyTo(this);
replicate(operation);
return result;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
<T> void replicate(RacingEventServiceOperation<T> operation) {
for (OperationExecutionListener listener : operationExecutionListeners) {
listener.executed(operation); // TODO consider exception handling
}
}
@Override
public void addOperationExecutionListener(OperationExecutionListener listener) {
operationExecutionListeners.add(listener);
}
@Override
public void removeOperationExecutionListener(OperationExecutionListener listener) {
operationExecutionListeners.remove(listener);
}
@Override
public void serializeForInitialReplication(ObjectOutputStream oos) throws IOException {
oos.writeObject(regattasByName);
oos.writeObject(regattasObservedForDefaultLeaderboard);
oos.writeObject(regattaTrackingCache);
oos.writeObject(leaderboardGroupsByName);
oos.writeObject(leaderboardsByName);
}
@SuppressWarnings("unchecked") // the type-parameters in the casts of the de-serialized collection objects can't be checked
@Override
public synchronized void initiallyFillFrom(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ClassLoader oldContextClassloader = Thread.currentThread().getContextClassLoader();
try {
// Use this object's class's class loader as the context class loader which will then be used for
// de-serialization; this will cause all classes to be visible that this bundle
// (com.sap.sailing.server) can see
Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
regattasByName.clear();
regattasByName.putAll((Map<String, Regatta>) ois.readObject());
regattasObservedForDefaultLeaderboard.clear();
for (DynamicTrackedRegatta trackedRegattaToObserve : (Set<DynamicTrackedRegatta>) ois.readObject()) {
ensureRegattaIsObservedForDefaultLeaderboardAndAutoLeaderboardLinking(trackedRegattaToObserve);
}
regattaTrackingCache.clear();
regattaTrackingCache.putAll((Map<Regatta, DynamicTrackedRegatta>) ois.readObject());
leaderboardGroupsByName.clear();
leaderboardGroupsByName.putAll((Map<String, LeaderboardGroup>) ois.readObject());
leaderboardsByName.clear();
leaderboardsByName.putAll((Map<String, Leaderboard>) ois.readObject());
} finally {
Thread.currentThread().setContextClassLoader(oldContextClassloader);
}
}
@Override
public long getDelayToLiveInMillis() {
return delayToLiveInMillis;
}
@Override
public void setDelayToLiveInMillis(long delayToLiveInMillis) {
this.delayToLiveInMillis = delayToLiveInMillis;
}
}