apply competitor listener pattern to leaderboard caching infrastructure; competitor change will invalidate leaderboard cache

This commit is contained in:
Axel Uhl committed 2014-05-07 23:42:23 +02:00
1 parent dc688e857e
commit 48cb36ab60
1 file changed
+77 -10
@@ -7,11 +7,15 @@ import java.util.Set;
import java.util.WeakHashMap;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.CompetitorChangeListener;
import com.sap.sailing.domain.base.Fleet;
import com.sap.sailing.domain.base.Mark;
import com.sap.sailing.domain.base.Nationality;
import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.base.RaceColumnListener;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.base.WithNationality;
import com.sap.sailing.domain.common.Color;
import com.sap.sailing.domain.common.MaxPointsReason;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.WindSource;
@@ -39,7 +43,8 @@ import com.sap.sailing.util.impl.NamedReentrantReadWriteLock;
*/
public class LeaderboardCacheManager {
private final WeakHashMap<Leaderboard, CacheInvalidationUponScoreCorrectionListener> scoreCorrectionListeners;
private final NamedReentrantReadWriteLock scoreCorrectionListenersLock;
private final NamedReentrantReadWriteLock scoreCorrectionAndCompetitorChangeListenersLock;
private final WeakHashMap<Leaderboard, CacheInvalidationUponCompetitorChangeListener> competitorChangeListeners;
private final WeakHashMap<Leaderboard, Map<TrackedRace, Set<CacheInvalidationListener>>> invalidationListenersPerLeaderboard;
private final WeakHashMap<Leaderboard, RaceColumnListener> raceColumnListeners;
@@ -113,6 +118,53 @@ public class LeaderboardCacheManager {
}
}
private class CacheInvalidationUponCompetitorChangeListener implements CompetitorChangeListener {
private static final long serialVersionUID = 8308312509904366143L;
private final Leaderboard leaderboard;
private final Set<Competitor> observedCompetitors;
public CacheInvalidationUponCompetitorChangeListener(Leaderboard leaderboard) {
this.leaderboard = leaderboard;
observedCompetitors = new HashSet<>();
}
@Override
public void sailIdChanged(String oldSailId, String newSailId) {
removeFromCache(leaderboard);
}
@Override
public void nationalityChanged(WithNationality what, Nationality oldNationality, Nationality newNationality) {
removeFromCache(leaderboard);
}
@Override
public void colorChanged(Color oldColor, Color newColor) {
removeFromCache(leaderboard);
}
@Override
public void nameChanged(String oldName, String newName) {
removeFromCache(leaderboard);
}
public synchronized void updateCompetitorListeners() {
Set<Competitor> competitorsToStopObserving = new HashSet<>(observedCompetitors);
for (Competitor competitor : leaderboard.getCompetitors()) {
if (!observedCompetitors.contains(competitor)) {
competitor.addCompetitorChangeListener(this); // start observing competitor
} else {
competitorsToStopObserving.remove(competitor); // keep observing competitor
}
}
for (Competitor competitorToStopObserving : competitorsToStopObserving) {
competitorToStopObserving.removeCompetitorChangeListener(this);
observedCompetitors.remove(competitorToStopObserving);
}
}
}
private class CacheInvalidationUponScoreCorrectionListener implements ScoreCorrectionListener {
private final Leaderboard leaderboard;
@@ -146,10 +198,12 @@ public class LeaderboardCacheManager {
public LeaderboardCacheManager(LeaderboardCache leaderboardCache) {
this.leaderboardCache = leaderboardCache;
this.invalidationListenersPerLeaderboard = new WeakHashMap<Leaderboard, Map<TrackedRace, Set<CacheInvalidationListener>>>();
this.invalidationListenersPerLeaderboard = new WeakHashMap<>();
this.raceColumnListeners = new WeakHashMap<Leaderboard, RaceColumnListener>();
this.scoreCorrectionListeners = new WeakHashMap<Leaderboard, CacheInvalidationUponScoreCorrectionListener>();
this.scoreCorrectionListenersLock = new NamedReentrantReadWriteLock("Score correction listeners", /* fair */ false);
this.scoreCorrectionListeners = new WeakHashMap<>();
this.scoreCorrectionAndCompetitorChangeListenersLock = new NamedReentrantReadWriteLock(
"Score correction and competitor change listeners", /* fair */false);
this.competitorChangeListeners = new WeakHashMap<>();
}
private void removeFromCache(Leaderboard leaderboard) {
@@ -167,14 +221,19 @@ public class LeaderboardCacheManager {
synchronized (raceColumnListeners) {
leaderboard.removeRaceColumnListener(raceColumnListeners.remove(leaderboard));
}
LockUtil.lockForWrite(scoreCorrectionListenersLock);
LockUtil.lockForWrite(scoreCorrectionAndCompetitorChangeListenersLock);
final CacheInvalidationUponScoreCorrectionListener removedScoreCorrectionListener;
final CacheInvalidationUponCompetitorChangeListener removedCompetitorChangeListener;
try {
removedScoreCorrectionListener = scoreCorrectionListeners.remove(leaderboard);
removedCompetitorChangeListener = competitorChangeListeners.remove(leaderboard);
} finally {
LockUtil.unlockAfterWrite(scoreCorrectionListenersLock);
LockUtil.unlockAfterWrite(scoreCorrectionAndCompetitorChangeListenersLock);
}
leaderboard.getScoreCorrection().removeScoreCorrectionListener(removedScoreCorrectionListener);
for (Competitor competitor : leaderboard.getCompetitors()) {
competitor.removeCompetitorChangeListener(removedCompetitorChangeListener);
}
// invalidate after the listeners have been removed; this is important because invalidation may trigger a
// re-calculation which then in turn may call add(leaderboard) asynchronously again which may be executed
// before the listener removal happens here. This could lead to a race condition where listeners are
@@ -195,22 +254,28 @@ public class LeaderboardCacheManager {
private void registerAsListener(final Leaderboard leaderboard) {
// only add as listener again if not yet added
final boolean containsKey;
LockUtil.lockForRead(scoreCorrectionListenersLock);
LockUtil.lockForRead(scoreCorrectionAndCompetitorChangeListenersLock);
try {
containsKey = scoreCorrectionListeners.containsKey(leaderboard);
} finally {
LockUtil.unlockAfterRead(scoreCorrectionListenersLock);
LockUtil.unlockAfterRead(scoreCorrectionAndCompetitorChangeListenersLock);
}
if (!containsKey) {
LockUtil.lockForWrite(scoreCorrectionListenersLock);
LockUtil.lockForWrite(scoreCorrectionAndCompetitorChangeListenersLock);
try {
for (TrackedRace trackedRace : leaderboard.getTrackedRaces()) {
registerListener(leaderboard, trackedRace);
}
final CacheInvalidationUponScoreCorrectionListener scoreCorrectionListener = new CacheInvalidationUponScoreCorrectionListener(
leaderboard);
final CacheInvalidationUponCompetitorChangeListener competitorChangeListener = new CacheInvalidationUponCompetitorChangeListener(
leaderboard);
leaderboard.getScoreCorrection().addScoreCorrectionListener(scoreCorrectionListener);
for (Competitor competitor : leaderboard.getCompetitors()) {
competitor.addCompetitorChangeListener(competitorChangeListener);
}
scoreCorrectionListeners.put(leaderboard, scoreCorrectionListener);
competitorChangeListeners.put(leaderboard, competitorChangeListener);
final RaceColumnListener raceColumnListener = new RaceColumnListener() {
private static final long serialVersionUID = 8165124797028386317L;
@@ -218,6 +283,7 @@ public class LeaderboardCacheManager {
public void trackedRaceLinked(RaceColumn raceColumn, Fleet fleet, TrackedRace trackedRace) {
removeFromCache(leaderboard);
registerListener(leaderboard, trackedRace);
competitorChangeListeners.get(leaderboard).updateCompetitorListeners();
}
/**
@@ -231,6 +297,7 @@ public class LeaderboardCacheManager {
@Override
public void trackedRaceUnlinked(RaceColumn raceColumn, Fleet fleet, TrackedRace trackedRace) {
removeFromCache(leaderboard);
competitorChangeListeners.get(leaderboard).updateCompetitorListeners();
Map<TrackedRace, Set<CacheInvalidationListener>> listenersMap = invalidationListenersPerLeaderboard
.get(leaderboard);
if (listenersMap != null) {
@@ -312,7 +379,7 @@ public class LeaderboardCacheManager {
raceColumnListeners.put(leaderboard, raceColumnListener);
}
} finally {
LockUtil.unlockAfterWrite(scoreCorrectionListenersLock);
LockUtil.unlockAfterWrite(scoreCorrectionAndCompetitorChangeListenersLock);
}
}
}