leaderboardNames, int[] overallLeaderboardDiscardThresholds,
ScoringSchemeType overallLeaderboardScoringSchemeType) {
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);
}
}
Leaderboard overallLeaderboard = group.getOverallLeaderboard();
if (overallLeaderboard != null) {
if (overallLeaderboardScoringSchemeType == null) {
group.setOverallLeaderboard(null);
removeLeaderboard(overallLeaderboard.getName());
} else {
// update existing overall leaderboard's discards settings; scoring scheme cannot be updated in-place
overallLeaderboard.setCrossLeaderboardResultDiscardingRule(new ThresholdBasedResultDiscardingRuleImpl(overallLeaderboardDiscardThresholds));
updateStoredLeaderboard(overallLeaderboard);
}
} else if (overallLeaderboard == null && overallLeaderboardScoringSchemeType != null) {
addOverallLeaderboardToLeaderboardGroup(group,
getBaseDomainFactory().createScoringScheme(overallLeaderboardScoringSchemeType),
overallLeaderboardDiscardThresholds);
}
updateStoredLeaderboardGroup(group);
}
private void addOverallLeaderboardToLeaderboardGroup(LeaderboardGroup leaderboardGroup,
ScoringScheme scoringScheme, int[] discardThresholds) {
Leaderboard overallLeaderboard = new LeaderboardGroupMetaLeaderboard(leaderboardGroup, scoringScheme,
new ThresholdBasedResultDiscardingRuleImpl(discardThresholds));
leaderboardGroup.setOverallLeaderboard(overallLeaderboard);
addLeaderboard(overallLeaderboard);
updateStoredLeaderboard(overallLeaderboard);
}
@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.
*
* 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 apply(RacingEventServiceOperation operation) {
try {
T result = operation.internalApplyTo(this);
replicate(operation);
return result;
} catch (Exception e) {
logger.log(Level.SEVERE, "apply", e);
throw new RuntimeException(e);
}
}
@Override
public void replicate(RacingEventServiceOperation operation) {
for (OperationExecutionListener listener : operationExecutionListeners.keySet()) {
try {
listener.executed(operation);
} catch (Exception e) {
// don't risk the master's operation only because replication to a listener/replica doesn't work
logger.severe("Error replicating operation "+operation+" to replication listener "+listener);
logger.log(Level.SEVERE, "replicate", e);
}
}
}
@Override
public void addOperationExecutionListener(OperationExecutionListener listener) {
operationExecutionListeners.put(listener, listener);
}
@Override
public void removeOperationExecutionListener(OperationExecutionListener listener) {
operationExecutionListeners.remove(listener);
}
@Override
public void serializeForInitialReplication(ObjectOutputStream oos) throws IOException {
logger.info("serializing eventsById");
oos.writeObject(eventsById);
logger.info("serializing regattasByName");
oos.writeObject(regattasByName);
logger.info("serializing regattasObservedForDefaultLeaderboard");
oos.writeObject(regattasObservedForDefaultLeaderboard);
logger.info("serializing regattaTrackingCache");
oos.writeObject(regattaTrackingCache);
logger.info("serializing leaderboardGroupsByName");
oos.writeObject(leaderboardGroupsByName);
logger.info("serializing leaderboardsByName");
oos.writeObject(leaderboardsByName);
}
@SuppressWarnings("unchecked") // the type-parameters in the casts of the de-serialized collection objects can't be checked
@Override
public void initiallyFillFrom(ObjectInputStream ois) throws IOException, ClassNotFoundException, InterruptedException {
logger.info("Performing initial replication load on "+this);
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();
regattasObservedForDefaultLeaderboard.clear();
for (DynamicTrackedRegatta regatta: regattaTrackingCache.values()) {
for (RaceTracker tracker : raceTrackersByRegatta.get(regatta)) {
tracker.stop();
}
}
regattaTrackingCache.clear();
leaderboardGroupsByName.clear();
leaderboardsByName.clear();
eventsById.clear();
logger.info("receiving eventsById");
eventsById.putAll((Map) ois.readObject());
logger.info("Recieved " + eventsById.size() + " NEW events");
logger.info("receiving regattasByName");
regattasByName.putAll((Map) ois.readObject());
logger.info("Recieved " + regattasByName.size() + " NEW regattas");
// it is important that the leaderboards and tracked regattas are cleared before auto-linking to
// old leaderboards takes place which then don't match the new ones
for (DynamicTrackedRegatta trackedRegattaToObserve : (Set) ois.readObject()) {
ensureRegattaIsObservedForDefaultLeaderboardAndAutoLeaderboardLinking(trackedRegattaToObserve);
}
logger.info("receiving regattaTrackingCache");
regattaTrackingCache.putAll((Map) ois.readObject());
logger.info("Recieved " + regattaTrackingCache.size() + " NEW regatta tracking cache entries");
logger.info("receiving leaderboardGroupsByName");
leaderboardGroupsByName.putAll((Map) ois.readObject());
logger.info("Received " + leaderboardGroupsByName.size() + " NEW leaderboard groups");
logger.info("receiving leaderboardsByName");
leaderboardsByName.putAll((Map) ois.readObject());
logger.info("Recieved " + leaderboardsByName.size() + " NEW leaderboards");
// now fix ScoreCorrectionListener setup for LeaderboardGroupMetaLeaderboard instances:
for (Leaderboard leaderboard : leaderboardsByName.values()) {
if (leaderboard instanceof LeaderboardGroupMetaLeaderboard) {
((LeaderboardGroupMetaLeaderboard) leaderboard).registerAsScoreCorrectionChangeForwarderAndRaceColumnListenerOnAllLeaderboards();
}
}
logger.info("Done with initial replication on "+this);
} finally {
Thread.currentThread().setContextClassLoader(oldContextClassloader);
}
}
@Override
public long getDelayToLiveInMillis() {
return delayToLiveInMillis;
}
@Override
public void setDelayToLiveInMillis(long delayToLiveInMillis) {
this.delayToLiveInMillis = delayToLiveInMillis;
}
@Override
public Event addEvent(String eventName, String venue, String publicationUrl, boolean isPublic, Serializable id, List courseAreaNames) {
Event result = new EventImpl(eventName, venue, publicationUrl, isPublic, id);
synchronized (eventsById) {
if (eventsById.containsKey(result.getId())) {
throw new IllegalArgumentException("Event with ID " + result.getId() + " already exists which is pretty surprising...");
}
eventsById.put(result.getId(), result);
replicate(new CreateEvent(eventName, venue, publicationUrl, isPublic, id, courseAreaNames));
}
mongoObjectFactory.storeEvent(result);
return result;
}
@Override
public void updateEvent(Serializable id, String eventName, String venueName, String publicationUrl, boolean isPublic, List regattaNames) {
synchronized (eventsById) {
if (!eventsById.containsKey(id)) {
throw new IllegalArgumentException("Sailing event with ID " + id + " does not exist.");
}
Event event = eventsById.get(id);
event.setName(eventName);
event.setPublicationUrl(publicationUrl);
event.setPublic(isPublic);
event.getVenue().setName(venueName);
// TODO need to update regattas if they are once linked to event objects
mongoObjectFactory.storeEvent(event);
replicate(new UpdateEvent(id, eventName, venueName, publicationUrl, isPublic, regattaNames));
}
}
@Override
public void renameEvent(Serializable id, String newName) {
synchronized (eventsById) {
if (!eventsById.containsKey(id)) {
throw new IllegalArgumentException("No sailing event with ID "+id+" found.");
}
Event toRename = eventsById.get(id);
toRename.setName(newName);
mongoObjectFactory.renameEvent(id, newName);
replicate(new RenameEvent(id, newName));
}
}
@Override
public void removeEvent(Serializable id) {
removeEventFromEventsById(id);
mongoObjectFactory.removeEvent(id);
replicate(new RemoveEvent(id));
}
protected void removeEventFromEventsById(Serializable id) {
synchronized (eventsById) {
eventsById.remove(id);
}
}
@Override
public Regatta getRememberedRegattaForRace(Serializable raceID) {
return persistentRegattasForRaceIDs.get(raceID.toString());
}
/**
* Persistently remembers the association of the race with its {@link RaceDefinition#getId()} to the
* regatta with its {@link Regatta#getRegattaIdentifier() identifier} so that the next time
* {@link #getRememberedRegattaForRace(RaceDefinition)} is called with race as argument,
* regatta will be returned.
*/
private void setRegattaForRace(Regatta regatta, RaceDefinition race) {
persistentRegattasForRaceIDs.put(race.getId().toString(), regatta);
mongoObjectFactory.storeRegattaForRaceID(race.getId().toString(), regatta);
}
@Override
public CourseArea addCourseArea(Serializable eventId, String courseAreaName, Serializable courseAreaId) {
CourseArea courseArea = new CourseAreaImpl(courseAreaName, courseAreaId);
synchronized (eventsById) {
if (!eventsById.containsKey(eventId)) {
throw new IllegalArgumentException("No sailing event with ID " + eventId + " found.");
}
Event event = eventsById.get(eventId);
event.getVenue().addCourseArea(courseArea);
replicate(new AddCourseArea(eventId, courseAreaName, courseAreaId));
mongoObjectFactory.storeEvent(event);
}
return courseArea;
}
}