mirror of
https://github.com/eclipse-sailing-analytics/sailing-analytics.git
synced 2026-11-06 11:02:25 +00:00
Merge remote-tracking branch 'external/master' into jens--media-synch
umn from SettableScoreCorrection to ScoreCorrection
This commit is contained in:
commit
6eea85c5a3
360 files changed
+36932
-843
No files matched your search
@@ -274,6 +274,13 @@ if [[ "$@" == "install" ]] || [[ "$@" == "all" ]]; then
|
||||
mkdir $ACDIR/configuration
|
||||
fi
|
||||
|
||||
# seems that initial scripts not there
|
||||
if [ ! -f "$ACDIR/start" ]; then
|
||||
cp -v $PROJECT_HOME/java/target/start $ACDIR
|
||||
cp -v $PROJECT_HOME/java/target/http2udpmirror $ACDIR
|
||||
cp -v $PROJECT_HOME/java/target/configuration/logging.properties $ACDIR/configuration
|
||||
fi
|
||||
|
||||
cd $ACDIR
|
||||
|
||||
rm -rf $ACDIR/plugins/*.*
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
port = 27017
|
||||
master = true
|
||||
dbpath = /opt/mongodb/data//mongodb-data
|
||||
logpath = /opt/mongodb/data/logs/mongodb-data/mongodb.log
|
||||
dbpath = /opt/mongodb/data/mongodb-data
|
||||
logappend = true
|
||||
directoryperdb = true
|
||||
rest = true
|
||||
|
||||
Executable
+5622
File diff suppressed because it is too large.
Load diff
Executable
BIN
Binary file not shown.
@@ -8,13 +8,19 @@ Like businesses, sailors need the latest information to make strategic decisions
|
||||
|
||||
### Table of Contents
|
||||
|
||||
* [[How to work with this wiki|wiki/howto]]
|
||||
* [[Information about this Wiki and HowTo|wiki/howto]]
|
||||
* [[General Project Information|wiki/general-information]]
|
||||
* [[OnBoarding Information|wiki/onboarding]]
|
||||
* [[Architecture and Infrastructure|wiki/architecture-and-infrastructure]]
|
||||
* [[Production Environment|wiki/production-environment]]
|
||||
* [[Development Environment|wiki/development-environment]]
|
||||
* [[Server Replication|wiki/server-replication]]
|
||||
* [[Configure Races on Server|wiki/configure-races-on-server]]
|
||||
* [[Cook Book|wiki/cook-book]]
|
||||
* [[Planning|wiki/planning]]
|
||||
* Event Specifica
|
||||
* [[General Planning|wiki/general-event-planning]]
|
||||
* [[Extreme Sailing Series|wiki/extreme-sailing-series]]
|
||||
|
||||
### Internal services (not related to wiki but useful)
|
||||
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<launchConfiguration type="org.eclipse.jdt.launching.localJavaApplication">
|
||||
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
|
||||
<listEntry value="/com.sap.sailing.declination/src/com/sap/sailing/declination/impl/DeclinationStore.java"/>
|
||||
</listAttribute>
|
||||
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
|
||||
<listEntry value="1"/>
|
||||
</listAttribute>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.sap.sailing.declination.impl.DeclinationStore"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="2013 2013 1"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="com.sap.sailing.declination"/>
|
||||
</launchConfiguration>
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<launchConfiguration type="org.eclipse.jdt.launching.localJavaApplication">
|
||||
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
|
||||
<listEntry value="/com.sap.sailing.declination/src/com/sap/sailing/declination/impl/DeclinationStore.java"/>
|
||||
</listAttribute>
|
||||
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
|
||||
<listEntry value="1"/>
|
||||
</listAttribute>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.sap.sailing.declination.impl.DeclinationStore"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="2012 2012 1"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="com.sap.sailing.declination"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Dhttp.proxyHost=proxy.wdf.sap.corp -Dhttp.proxyPort=8080"/>
|
||||
</launchConfiguration>
|
||||
+4
@@ -4,4 +4,8 @@ public interface LeaderboardNameConstants {
|
||||
static final String DEFAULT_LEADERBOARD_NAME = "Default Leaderboard";
|
||||
|
||||
static final String OVERALL = "Overall";
|
||||
|
||||
static final String DEFAULT_FLEET_NAME = "Default";
|
||||
|
||||
static final String DEFAULT_SERIES_NAME = "Default";
|
||||
}
|
||||
+1
-1
@@ -14,7 +14,7 @@ import com.sap.sailing.domain.common.impl.Util.Pair;
|
||||
*/
|
||||
public interface ScoreCorrectionProvider extends Named {
|
||||
/**
|
||||
* Returns a valid map whose keys are event names such as "Kieler Woche 2011" or "IDM Travemünde 2011" and whose
|
||||
* Returns a valid map whose keys are event names such as "Kieler Woche 2011" or "IDM Travemuende 2011" and whose
|
||||
* values are sets of pairs of boat class names and the times for which it has score corrections. If there are multiple
|
||||
* score corrections taken at different times. Later score corrections are expected to be cumulative, meaning they
|
||||
* also contain all previous corrections.
|
||||
|
||||
+2
-1
@@ -1,5 +1,6 @@
|
||||
package com.sap.sailing.domain.common;
|
||||
|
||||
public enum ScoringSchemeType {
|
||||
LOW_POINT, HIGH_POINT, HIGH_POINT_ESS_OVERALL, HIGH_POINT_LAST_BREAKS_TIE, HIGH_POINT_FIRST_GETS_TEN
|
||||
LOW_POINT, HIGH_POINT, HIGH_POINT_ESS_OVERALL, HIGH_POINT_LAST_BREAKS_TIE,
|
||||
HIGH_POINT_FIRST_GETS_TEN, LOW_POINT_WINNER_GETS_ZERO
|
||||
}
|
||||
Executable
+8
@@ -0,0 +1,8 @@
|
||||
package com.sap.sailing.domain.common;
|
||||
|
||||
public interface TimingConstants {
|
||||
/**
|
||||
* The time after the end of a race during which it is still considered and displayed as "live"
|
||||
*/
|
||||
final long IS_LIVE_GRACE_PERIOD_IN_MILLIS = 3 * 60 * 1000; // three minutes
|
||||
}
|
||||
+5
@@ -64,4 +64,9 @@ public class CountryCodeImpl implements CountryCode {
|
||||
public String getITUCallPrefix() {
|
||||
return ituCallPrefix;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getThreeLetterIOCCode();
|
||||
}
|
||||
}
|
||||
+34
-25
@@ -161,7 +161,7 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
|
||||
} catch (Exception e) {
|
||||
// something went wrong during DB access; report, then use empty new wind track
|
||||
logger.log(Level.SEVERE, "Error connecting to MongoDB, unable to load leaderboard "+name+".");
|
||||
logger.throwing(DomainObjectFactoryImpl.class.getName(), "loadLeaderboard", e);
|
||||
logger.log(Level.SEVERE, "loadLeaderboard", e);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -180,7 +180,7 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
|
||||
} catch (Exception e) {
|
||||
// something went wrong during DB access; report, then use empty new wind track
|
||||
logger.log(Level.SEVERE, "Error connecting to MongoDB, unable to load leaderboards.");
|
||||
logger.throwing(DomainObjectFactoryImpl.class.getName(), "getAllLeaderboards", e);
|
||||
logger.log(Level.SEVERE, "getAllLeaderboards", e);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -341,21 +341,22 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
|
||||
|
||||
private FlexibleLeaderboard loadFlexibleLeaderboard(DBObject dbLeaderboard,
|
||||
SettableScoreCorrection scoreCorrection, ThresholdBasedResultDiscardingRule resultDiscardingRule) {
|
||||
final ScoringScheme scoringScheme = loadScoringScheme(dbLeaderboard);
|
||||
|
||||
RaceLogStore raceLogStore = MongoRaceLogStoreFactory.INSTANCE.getMongoRaceLogStore(
|
||||
new MongoObjectFactoryImpl(database),
|
||||
this);
|
||||
CourseArea courseArea = loadCourseAreaFromEvents(dbLeaderboard);
|
||||
FlexibleLeaderboardImpl result = new FlexibleLeaderboardImpl(
|
||||
raceLogStore,
|
||||
(String) dbLeaderboard.get(FieldNames.LEADERBOARD_NAME.name()),
|
||||
scoreCorrection,
|
||||
resultDiscardingRule,
|
||||
scoringScheme,
|
||||
courseArea);
|
||||
final FlexibleLeaderboardImpl result;
|
||||
BasicDBList dbRaceColumns = (BasicDBList) dbLeaderboard.get(FieldNames.LEADERBOARD_COLUMNS.name());
|
||||
// For a FlexibleLeaderboard, fleets are owned by the leaderboard's RaceColumn objects. We need to manage them here:
|
||||
if (dbRaceColumns == null) {
|
||||
// this was probably an orphaned overall leaderboard
|
||||
logger.warning("Probably found orphan overall leaderboard named "
|
||||
+ dbLeaderboard.get(FieldNames.LEADERBOARD_NAME.name())+". Ignoring.");
|
||||
result = null;
|
||||
} else {
|
||||
final ScoringScheme scoringScheme = loadScoringScheme(dbLeaderboard);
|
||||
RaceLogStore raceLogStore = MongoRaceLogStoreFactory.INSTANCE.getMongoRaceLogStore(
|
||||
new MongoObjectFactoryImpl(database), this);
|
||||
CourseArea courseArea = loadCourseAreaFromEvents(dbLeaderboard);
|
||||
result = new FlexibleLeaderboardImpl(raceLogStore, (String) dbLeaderboard.get(FieldNames.LEADERBOARD_NAME
|
||||
.name()), scoreCorrection, resultDiscardingRule, scoringScheme, courseArea);
|
||||
// For a FlexibleLeaderboard, fleets are owned by the leaderboard's RaceColumn objects. We need to manage
|
||||
// them here:
|
||||
Map<String, Fleet> fleetsByName = new HashMap<String, Fleet>();
|
||||
for (Object dbRaceColumnAsObject : dbRaceColumns) {
|
||||
BasicDBObject dbRaceColumn = (BasicDBObject) dbRaceColumnAsObject;
|
||||
@@ -367,8 +368,9 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
|
||||
raceIdentifiers.put(defaultFleet.getName(), defaultFleetRaceIdentifier);
|
||||
} else {
|
||||
// leaderboard has no default fleet; don't know what to do with default RaceIdentifier
|
||||
logger.warning("Discarding RaceIdentifier "+defaultFleetRaceIdentifier+" for default fleet for leaderboard "+result.getName()+
|
||||
" because no default fleet was found in leaderboard");
|
||||
logger.warning("Discarding RaceIdentifier " + defaultFleetRaceIdentifier
|
||||
+ " for default fleet for leaderboard " + result.getName()
|
||||
+ " because no default fleet was found in leaderboard");
|
||||
}
|
||||
raceIdentifiers.remove(null);
|
||||
}
|
||||
@@ -394,6 +396,7 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -539,7 +542,7 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
|
||||
leaderboardGroup = loadLeaderboardGroup(leaderboardGroupCollection.findOne(query), regattaRegistry, leaderboardRegistry);
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.SEVERE, "Error connecting to MongoDB, unable to load leaderboard group "+name+".");
|
||||
logger.throwing(DomainObjectFactoryImpl.class.getName(), "loadLeaderboardGroup", e);
|
||||
logger.log(Level.SEVERE, "loadLeaderboardGroup", e);
|
||||
}
|
||||
|
||||
return leaderboardGroup;
|
||||
@@ -555,7 +558,7 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.SEVERE, "Error connecting to MongoDB, unable to load leaderboard groups.");
|
||||
logger.throwing(DomainObjectFactoryImpl.class.getName(), "loadLeaderboardGroup", e);
|
||||
logger.log(Level.SEVERE, "loadLeaderboardGroup", e);
|
||||
}
|
||||
|
||||
return leaderboardGroups;
|
||||
@@ -575,11 +578,15 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
|
||||
for (Object object : dbLeaderboardIds) {
|
||||
ObjectId dbLeaderboardId = (ObjectId) object;
|
||||
DBObject dbLeaderboard = leaderboardCollection.findOne(dbLeaderboardId);
|
||||
if (dbLeaderboard != null) {
|
||||
final Leaderboard loadedLeaderboard = loadLeaderboard(dbLeaderboard, regattaRegistry,
|
||||
leaderboardRegistry, /* groupForMetaLeaderboard */null);
|
||||
if (loadedLeaderboard != null) {
|
||||
leaderboards.add(loadedLeaderboard);
|
||||
}
|
||||
} else {
|
||||
logger.warning("couldn't find leaderboard with ID "+dbLeaderboardId+" referenced by leaderboard group "+name);
|
||||
}
|
||||
}
|
||||
logger.info("loaded leaderboard group "+name);
|
||||
LeaderboardGroupImpl result = new LeaderboardGroupImpl(name, description, displayGroupsInReverseOrder, leaderboards);
|
||||
@@ -605,7 +612,9 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
|
||||
DBCollection leaderboardCollection = database.getCollection(CollectionNames.LEADERBOARDS.name());
|
||||
Set<Leaderboard> result = new HashSet<Leaderboard>();
|
||||
try {
|
||||
// Don't change the query object, unless you know what you're doing
|
||||
// Don't change the query object, unless you know what you're doing.
|
||||
// It queries all leaderboards not referenced to be part of a leaderboard group
|
||||
// and in particular not being an overall leaderboard of a leaderboard group.
|
||||
BasicDBObject query = new BasicDBObject("$where", "function() { return db." + CollectionNames.LEADERBOARD_GROUPS.name() + ".find({ "
|
||||
+ FieldNames.LEADERBOARD_GROUP_LEADERBOARDS.name() + ": this._id }).count() == 0 && "
|
||||
+ "db."+CollectionNames.LEADERBOARD_GROUPS.name()+".find({ "+FieldNames.LEADERBOARD_GROUP_OVERALL_LEADERBOARD.name() + ": this._id }).count() == 0 && "
|
||||
@@ -619,7 +628,7 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.SEVERE, "Error connecting to MongoDB, unable to load leaderboards.");
|
||||
logger.throwing(DomainObjectFactoryImpl.class.getName(), "getAllLeaderboards", e);
|
||||
logger.log(Level.SEVERE, "getAllLeaderboards", e);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -690,7 +699,7 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
|
||||
} catch (Exception e) {
|
||||
// something went wrong during DB access; report, then use empty new wind track
|
||||
logger.log(Level.SEVERE, "Error connecting to MongoDB, unable to load recorded wind data. Check MongoDB settings.");
|
||||
logger.throwing(DomainObjectFactoryImpl.class.getName(), "loadWindTrack", e);
|
||||
logger.log(Level.SEVERE, "loadWindTrack", e);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -741,7 +750,7 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.SEVERE, "Error connecting to MongoDB, unable to load events.");
|
||||
logger.throwing(DomainObjectFactoryImpl.class.getName(), "loadEvents", e);
|
||||
logger.log(Level.SEVERE, "loadEvents", e);
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -951,7 +960,7 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
|
||||
} catch (Throwable t) {
|
||||
// something went wrong during DB access; report, then use empty new race log
|
||||
logger.log(Level.SEVERE, "Error connecting to MongoDB, unable to load recorded race log data. Check MongoDB settings.");
|
||||
logger.throwing(DomainObjectFactoryImpl.class.getName(), "loadRaceLog", t);
|
||||
logger.log(Level.SEVERE, "loadRaceLog", t);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
+4
-3
@@ -2,6 +2,7 @@ package com.sap.sailing.domain.persistence.impl;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.bson.types.ObjectId;
|
||||
@@ -100,7 +101,7 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory {
|
||||
|
||||
public DBCollection getWindTrackCollection() {
|
||||
DBCollection result = database.getCollection(CollectionNames.WIND_TRACKS.name());
|
||||
result.ensureIndex(new BasicDBObject(FieldNames.EVENT_NAME.name(), null));
|
||||
result.ensureIndex(new BasicDBObject(FieldNames.REGATTA_NAME.name(), null));
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -148,7 +149,7 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory {
|
||||
leaderboardCollection.ensureIndex(FieldNames.LEADERBOARD_NAME.name());
|
||||
} catch (NullPointerException npe) {
|
||||
// sometimes, for reasons yet to be clarified, ensuring an index on the name field causes an NPE
|
||||
logger.throwing(MongoObjectFactoryImpl.class.getName(), "storeLeaderboard", npe);
|
||||
logger.log(Level.SEVERE, "storeLeaderboard", npe);
|
||||
}
|
||||
BasicDBObject query = new BasicDBObject(FieldNames.LEADERBOARD_NAME.name(), leaderboard.getName());
|
||||
BasicDBObject dbLeaderboard = new BasicDBObject();
|
||||
@@ -313,7 +314,7 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory {
|
||||
leaderboardGroupCollection.ensureIndex(FieldNames.LEADERBOARD_GROUP_NAME.name());
|
||||
} catch (NullPointerException npe) {
|
||||
// sometimes, for reasons yet to be clarified, ensuring an index on the name field causes an NPE
|
||||
logger.throwing(MongoObjectFactoryImpl.class.getName(), "storeLeaderboardGroup", npe);
|
||||
logger.log(Level.SEVERE, "storeLeaderboardGroup", npe);
|
||||
}
|
||||
BasicDBObject query = new BasicDBObject(FieldNames.LEADERBOARD_GROUP_NAME.name(), leaderboardGroup.getName());
|
||||
BasicDBObject dbLeaderboardGroup = new BasicDBObject();
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@ import com.sap.sailing.domain.common.Named;
|
||||
* Base interface for courses consisting of all static information, which might be shared
|
||||
* by the server and an Android application.
|
||||
*/
|
||||
public interface CourseData extends Named {
|
||||
public interface CourseBase extends Named {
|
||||
/**
|
||||
* Clients can safely iterate over the resulting list because it's a copy which therefore won't reflect
|
||||
* waypoint additions and removals.
|
||||
+1
-1
@@ -8,7 +8,7 @@ import com.sap.sailing.domain.common.WithID;
|
||||
* Base interface for an Event consisting of all static information, which might be shared
|
||||
* by the server and an Android application.
|
||||
*/
|
||||
public interface EventData extends Named, Renamable, WithID {
|
||||
public interface EventBase extends Named, Renamable, WithID {
|
||||
|
||||
/**
|
||||
* @return a non-<code>null</code> venue for this event
|
||||
+1
-1
@@ -5,7 +5,7 @@ import com.sap.sailing.domain.common.Named;
|
||||
/**
|
||||
* Base interface for series consisting of static information describing the Series.
|
||||
*/
|
||||
public interface SeriesData extends Named {
|
||||
public interface SeriesBase extends Named {
|
||||
|
||||
/**
|
||||
* Returns the fleets of this series, in ascending order, better fleets first.
|
||||
+4
-4
@@ -2,10 +2,10 @@ package com.sap.sailing.domain.base.impl;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import com.sap.sailing.domain.base.EventData;
|
||||
import com.sap.sailing.domain.base.EventBase;
|
||||
import com.sap.sailing.domain.base.Venue;
|
||||
|
||||
public class EventDataImpl implements EventData {
|
||||
public class EventBaseImpl implements EventBase {
|
||||
private static final long serialVersionUID = -5749964088848611074L;
|
||||
|
||||
private String name;
|
||||
@@ -14,14 +14,14 @@ public class EventDataImpl implements EventData {
|
||||
private boolean isPublic;
|
||||
private final Serializable id;
|
||||
|
||||
public EventDataImpl(String name, String venueName, String publicationUrl, boolean isPublic, Serializable id) {
|
||||
public EventBaseImpl(String name, String venueName, String publicationUrl, boolean isPublic, Serializable id) {
|
||||
this(name, new VenueImpl(venueName), publicationUrl, isPublic, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param venue must not be <code>null</code>
|
||||
*/
|
||||
public EventDataImpl(String name, Venue venue, String publicationUrl, boolean isPublic, Serializable id) {
|
||||
public EventBaseImpl(String name, Venue venue, String publicationUrl, boolean isPublic, Serializable id) {
|
||||
assert venue != null;
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
+2
-2
@@ -1,11 +1,11 @@
|
||||
package com.sap.sailing.domain.base.racegroup;
|
||||
|
||||
import com.sap.sailing.domain.base.SeriesData;
|
||||
import com.sap.sailing.domain.base.SeriesBase;
|
||||
|
||||
/**
|
||||
* The interface represents a series and holds its races. The interface abstracts from the server internal differences
|
||||
* between Leaderboards and Regattas for any app since Leaderboards do not have any series, but Regattas.
|
||||
*/
|
||||
public interface SeriesWithRows extends SeriesData {
|
||||
public interface SeriesWithRows extends SeriesBase {
|
||||
public Iterable<RaceRow> getRaceRows();
|
||||
}
|
||||
+108
-3
@@ -1,7 +1,9 @@
|
||||
package com.sap.sailing.util.impl;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.WeakHashMap;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.logging.Logger;
|
||||
@@ -14,14 +16,13 @@ public class LockUtil {
|
||||
private static final int NUMBER_OF_SECONDS_TO_WAIT_FOR_LOCK = 5;
|
||||
private static final Logger logger = Logger.getLogger(Util.class.getName());
|
||||
private static final Map<NamedReentrantReadWriteLock, TimePoint> lastTimeWriteLockWasObtained = new WeakHashMap<NamedReentrantReadWriteLock, TimePoint>();
|
||||
private static final Map<Thread, Map<Lock, Integer>> lockCounts = new ConcurrentHashMap<Thread, Map<Lock,Integer>>();
|
||||
|
||||
/**
|
||||
* Bug <a href="http://bugs.sun.com/view_bug.do?bug_id=6822370">http://bugs.sun.com/view_bug.do?bug_id=6822370</a> seems
|
||||
* dangerous, particularly if it happens in a <code>LiveLeaderboardUpdater</code> thread. Even though the bug is reported to
|
||||
* have been fixed in JDK 7(b79) we should be careful. This method tries to acquire a lock, allowing for five seconds to pass.
|
||||
* After five seconds and not having retrieved the lock, tries again until the lock has been acquired.
|
||||
* @param lockParent TODO
|
||||
* @throws InterruptedException
|
||||
*/
|
||||
private static void lock(Lock lock, String lockDescriptionForTimeoutLogMessage, NamedReentrantReadWriteLock lockParent) {
|
||||
boolean locked = false;
|
||||
@@ -35,7 +36,7 @@ public class LockUtil {
|
||||
message.append(lockDescriptionForTimeoutLogMessage);
|
||||
message.append(" in ");
|
||||
message.append(NUMBER_OF_SECONDS_TO_WAIT_FOR_LOCK);
|
||||
message.append("s at ");
|
||||
message.append("s in thread "+Thread.currentThread().getName()+" at ");
|
||||
message.append(getCurrentStackTrace());
|
||||
Thread writer = lockParent.getWriter();
|
||||
if (writer != null) {
|
||||
@@ -69,22 +70,78 @@ public class LockUtil {
|
||||
}
|
||||
|
||||
public static void lockForRead(NamedReentrantReadWriteLock lock) {
|
||||
if (isInCurrentThreadsLockSet(lock.readLock())) {
|
||||
incrementLockCountForCurrentThread(lock.readLock());
|
||||
} else {
|
||||
lock(lock.readLock(), lock.getReadLockName(), lock);
|
||||
addToCurrentThreadsLockSet(lock.readLock());
|
||||
}
|
||||
}
|
||||
|
||||
private static Map<Lock, Integer> getCurrentThreadsLockCounts() {
|
||||
final Thread currentThread = Thread.currentThread();
|
||||
return getLockCounts(currentThread);
|
||||
}
|
||||
|
||||
private static Map<Lock, Integer> getLockCounts(final Thread thread) {
|
||||
Map<Lock, Integer> result = lockCounts.get(thread);
|
||||
if (result == null) {
|
||||
result = new HashMap<Lock, Integer>();
|
||||
lockCounts.put(thread, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
private static void incrementLockCountForCurrentThread(Lock lock) {
|
||||
Map<Lock, Integer> map = getCurrentThreadsLockCounts();
|
||||
assert map.containsKey(lock);
|
||||
map.put(lock, map.get(lock) + 1);
|
||||
}
|
||||
|
||||
private static void decrementLockCountForCurrentThread(Lock lock) {
|
||||
Map<Lock, Integer> map = getCurrentThreadsLockCounts();
|
||||
assert map.containsKey(lock);
|
||||
map.put(lock, map.get(lock)-1);
|
||||
}
|
||||
|
||||
private static boolean isInCurrentThreadsLockSet(Lock lock) {
|
||||
return getCurrentThreadsLockCounts().containsKey(lock);
|
||||
}
|
||||
|
||||
private static void addToCurrentThreadsLockSet(Lock lock) {
|
||||
getCurrentThreadsLockCounts().put(lock, 1);
|
||||
}
|
||||
|
||||
private static void removeFromCurrentThreadsLockSet(Lock lock) {
|
||||
getCurrentThreadsLockCounts().remove(lock);
|
||||
}
|
||||
|
||||
public static void unlockAfterRead(NamedReentrantReadWriteLock lock) {
|
||||
assert isInCurrentThreadsLockSet(lock.readLock());
|
||||
if (getCurrentThreadsLockCounts().get(lock.readLock()) == 1) {
|
||||
lock.readLock().unlock();
|
||||
removeFromCurrentThreadsLockSet(lock.readLock());
|
||||
} else {
|
||||
decrementLockCountForCurrentThread(lock.readLock());
|
||||
}
|
||||
}
|
||||
|
||||
public static void lockForWrite(NamedReentrantReadWriteLock lock) {
|
||||
if (isInCurrentThreadsLockSet(lock.writeLock())) {
|
||||
incrementLockCountForCurrentThread(lock.writeLock());
|
||||
} else {
|
||||
lock(lock.writeLock(), lock.getWriteLockName(), lock);
|
||||
addToCurrentThreadsLockSet(lock.writeLock());
|
||||
synchronized (lastTimeWriteLockWasObtained) {
|
||||
lastTimeWriteLockWasObtained.put(lock, MillisecondsTimePoint.now());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void unlockAfterWrite(NamedReentrantReadWriteLock lock) {
|
||||
assert isInCurrentThreadsLockSet(lock.writeLock());
|
||||
if (getCurrentThreadsLockCounts().get(lock.writeLock()) == 1) {
|
||||
lock.writeLock().unlock();
|
||||
removeFromCurrentThreadsLockSet(lock.writeLock());
|
||||
final TimePoint timePointWriteLockWasObtained;
|
||||
synchronized (lastTimeWriteLockWasObtained) {
|
||||
timePointWriteLockWasObtained = lastTimeWriteLockWasObtained.get(lock);
|
||||
@@ -100,6 +157,10 @@ public class LockUtil {
|
||||
stackTrace);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
decrementLockCountForCurrentThread(lock.writeLock());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static String getStackTrace(Thread thread) {
|
||||
@@ -114,4 +175,48 @@ public class LockUtil {
|
||||
private static String getCurrentStackTrace() {
|
||||
return getStackTrace(Thread.currentThread());
|
||||
}
|
||||
|
||||
/**
|
||||
* ATTENTION: Calling this method makes a very strong assertion! It asserts that the calling thread
|
||||
* will call {@link #unpropagateLockSetFrom(Thread)} for the same <code>thread</code> passed to this
|
||||
* call before <code>thread</code> releases any of the locks it currently holds. The effect of making
|
||||
* this call is that the calling thread, when trying to acquire a lock already held by <code>thread</code>,
|
||||
* will not actually acquire that lock again. This, in particular, has the effect that a read lock
|
||||
* already held by <code>thread</code> will not have to be acquired again, which in turn avoids a
|
||||
* read-read deadlock on a fair lock in case another thread is attempting to acquire the corresponding
|
||||
* write lock before the current thread tries to re-acquire the read lock.<p>
|
||||
*
|
||||
* Always use this in a <code>try/finally</code> combination where in the <code>finally</code> you call
|
||||
* {@link #unpropagateLockSetFrom(Thread)}.
|
||||
*/
|
||||
public static void propagateLockSetFrom(Thread thread) {
|
||||
Map<Lock, Integer> otherMap = lockCounts.get(thread);
|
||||
if (otherMap != null) {
|
||||
Map<Lock, Integer> currentMap = getCurrentThreadsLockCounts();
|
||||
for (Map.Entry<Lock, Integer> otherEntry : otherMap.entrySet()) {
|
||||
if (currentMap.containsKey(otherEntry.getKey())) {
|
||||
currentMap.put(otherEntry.getKey(), currentMap.get(otherEntry.getKey()) + otherEntry.getValue());
|
||||
} else {
|
||||
currentMap.put(otherEntry.getKey(), otherEntry.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A thread that previously propagated the lock set from another thread by using {@link #propagateLockSetFrom(Thread)}
|
||||
* ends the propagation with this call. After this call, <code>thread</code> is free to release any locks it held
|
||||
* at the time {@link #propagateLockSetFrom(Thread)} was called by the current thread with <code>thread</code> as
|
||||
* the argument.
|
||||
*/
|
||||
public static void unpropagateLockSetFrom(Thread thread) {
|
||||
Map<Lock, Integer> otherMap = lockCounts.get(thread);
|
||||
if (otherMap != null) {
|
||||
Map<Lock, Integer> currentMap = getCurrentThreadsLockCounts();
|
||||
for (Map.Entry<Lock, Integer> otherEntry : otherMap.entrySet()) {
|
||||
assert currentMap.containsKey(otherEntry.getKey());
|
||||
currentMap.put(otherEntry.getKey(), currentMap.get(otherEntry.getKey()) - otherEntry.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+148
-67
@@ -7,7 +7,11 @@ import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.mongodb.BasicDBObject;
|
||||
@@ -63,7 +67,7 @@ public class StoreAndForward implements Runnable {
|
||||
|
||||
private final Thread storeAndForwardThread;
|
||||
|
||||
private Socket socket;
|
||||
private final Set<ReceivingThread> receivingThreads;
|
||||
|
||||
/**
|
||||
* Use of this server socket is optional and happens if and only if this object is operated in
|
||||
@@ -85,13 +89,14 @@ public class StoreAndForward implements Runnable {
|
||||
*/
|
||||
public StoreAndForward(String sailMasterHostname, int sailMasterPort, int portForClients, SwissTimingFactory swissTimingFactory,
|
||||
SwissTimingAdapterPersistence swissTimingAdapterPersistence, MongoDBService mongoDBService) throws InterruptedException, IOException {
|
||||
this.receivingThreads = new HashSet<>();
|
||||
this.db = mongoDBService.getDB();
|
||||
this.listenPort = -1;
|
||||
this.transceiver = swissTimingFactory.createSailMasterTransceiver();
|
||||
this.portForClients = portForClients;
|
||||
this.sailMasterHostname = sailMasterHostname;
|
||||
this.sailMasterPort = sailMasterPort;
|
||||
this.streamsToForwardTo = new ArrayList<OutputStream>();
|
||||
this.streamsToForwardTo = Collections.synchronizedList(new ArrayList<OutputStream>());
|
||||
this.socketsToForwardTo = new ArrayList<Socket>();
|
||||
this.swissTimingAdapterPersistence = swissTimingAdapterPersistence;
|
||||
this.swissTimingFactory = swissTimingFactory;
|
||||
@@ -125,11 +130,12 @@ public class StoreAndForward implements Runnable {
|
||||
*/
|
||||
public StoreAndForward(final int listenPort, final int portForClients, SwissTimingFactory swissTimingFactory,
|
||||
SwissTimingAdapterPersistence swissTimingAdapterPersistence, MongoDBService mongoDBService) throws InterruptedException, IOException {
|
||||
this.receivingThreads = new HashSet<>();
|
||||
this.db = mongoDBService.getDB();
|
||||
this.listenPort = listenPort;
|
||||
this.transceiver = swissTimingFactory.createSailMasterTransceiver();
|
||||
this.portForClients = portForClients;
|
||||
this.streamsToForwardTo = new ArrayList<OutputStream>();
|
||||
this.streamsToForwardTo = Collections.synchronizedList(new ArrayList<OutputStream>());
|
||||
this.socketsToForwardTo = new ArrayList<Socket>();
|
||||
this.swissTimingAdapterPersistence = swissTimingAdapterPersistence;
|
||||
this.swissTimingFactory = swissTimingFactory;
|
||||
@@ -201,13 +207,18 @@ public class StoreAndForward implements Runnable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Depending on the mode of operation, accepts an inbound connect from a SailMaster server / bridge or
|
||||
* actively initiates a TCP connection to the SailMaster address/port configured. When this method
|
||||
* returns, the {@link #socket} holds an open and connected socket.
|
||||
* Depending on the mode of operation, accepts an inbound connect from a SailMaster server / bridge or actively
|
||||
* initiates a TCP connection to the SailMaster address/port configured. When a connection has been established, a
|
||||
* new {@link ReceivingThread} is spawned for the new socket. If in listening mode, the
|
||||
* {@link ServerSocket#accept()} call is repeated until the instance is {@link #stop()}ed.
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
private void establishConnection() throws IOException {
|
||||
private ReceivingThread establishConnection() throws IOException {
|
||||
Socket socket;
|
||||
ReceivingThread receivingThread = null;
|
||||
if (isInSailMasterListeningMode()) {
|
||||
while (!stopped) {
|
||||
synchronized (this) {
|
||||
receivingFromSailMaster = true;
|
||||
logger.info("StoreAndForward waiting for inbound SailMaster connections on port " + listenPort);
|
||||
@@ -215,6 +226,10 @@ public class StoreAndForward implements Runnable {
|
||||
}
|
||||
socket = serverSocketListeningForSailMasterBridge.accept();
|
||||
logger.info("StoreAndForward received SailMaster connect on port " + listenPort);
|
||||
receivingThread = new ReceivingThread("SwissTiming SailMaster ReceivingThread for "+socket, socket);
|
||||
receivingThreads.add(receivingThread);
|
||||
receivingThread.start();
|
||||
}
|
||||
} else {
|
||||
synchronized (this) {
|
||||
receivingFromSailMaster = true;
|
||||
@@ -223,6 +238,119 @@ public class StoreAndForward implements Runnable {
|
||||
}
|
||||
socket = new Socket(sailMasterHostname, sailMasterPort);
|
||||
logger.info("StoreAndForward connections to SailMaster "+sailMasterHostname+":"+sailMasterPort+" established");
|
||||
receivingThread = new ReceivingThread("SwissTiming SailMaster ReceivingThread for "+socket, socket);
|
||||
receivingThreads.add(receivingThread);
|
||||
receivingThread.start();
|
||||
}
|
||||
return receivingThread;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the actual receiving. Is initialized with the socket on which to receive.
|
||||
*
|
||||
* @author Axel Uhl (D043530)
|
||||
*
|
||||
*/
|
||||
private class ReceivingThread extends Thread {
|
||||
private final Socket socket;
|
||||
private boolean stopped;
|
||||
|
||||
public ReceivingThread(String threadName, Socket socket) {
|
||||
super(threadName);
|
||||
this.socket = socket;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
logger.entering(getClass().getName(), "run");
|
||||
while (!stopped) {
|
||||
try {
|
||||
InputStream is = socket.getInputStream();
|
||||
Pair<String, Long> messageAndOptionalSequenceNumber = transceiver.receiveMessage(is);
|
||||
if (messageAndOptionalSequenceNumber == null) {
|
||||
// found EOF; stopping
|
||||
stopped = true;
|
||||
} else {
|
||||
// ignore any sequence number contained in the message; we'll create our own
|
||||
DBObject emptyQuery = new BasicDBObject();
|
||||
DBObject incrementLastMessageCountQuery = new BasicDBObject().append("$inc",
|
||||
new BasicDBObject().append(FieldNames.LAST_MESSAGE_COUNT.name(), 1));
|
||||
while (!stopped && messageAndOptionalSequenceNumber != null) {
|
||||
logger.fine("Thread " + this + " received message: "
|
||||
+ messageAndOptionalSequenceNumber.getA());
|
||||
DBObject newCountRecord = lastMessageCountCollection.findAndModify(emptyQuery,
|
||||
incrementLastMessageCountQuery);
|
||||
lastMessageCount = ((newCountRecord == null) ? 0l : ((Number) newCountRecord
|
||||
.get(FieldNames.LAST_MESSAGE_COUNT.name())).longValue());
|
||||
SailMasterMessage message = swissTimingFactory.createMessage(
|
||||
messageAndOptionalSequenceNumber.getA(), lastMessageCount);
|
||||
swissTimingAdapterPersistence.storeSailMasterMessage(message);
|
||||
synchronized (StoreAndForward.this) {
|
||||
for (OutputStream os : new ArrayList<OutputStream>(streamsToForwardTo)) {
|
||||
// write the sequence number of the message into the stream before actually writing
|
||||
// the
|
||||
// SwissTiming message
|
||||
try {
|
||||
transceiver.sendMessage(message, os);
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.SEVERE, "Error sending message to " + os, e);
|
||||
int i = streamsToForwardTo.indexOf(os);
|
||||
try {
|
||||
os.close();
|
||||
} catch (Exception exc) {
|
||||
logger.log(Level.SEVERE, "Exception closing socket output stream " + os
|
||||
+ " after being unable to forward message " + message, exc);
|
||||
}
|
||||
streamsToForwardTo.remove(os);
|
||||
Socket s = socketsToForwardTo.remove(i);
|
||||
logger.info("Unable to send to socket " + s
|
||||
+ ". Trying to close. Removing from sockets to forward to.");
|
||||
try {
|
||||
s.close();
|
||||
} catch (Exception exc) {
|
||||
logger.log(Level.WARNING, "Exception trying to close socket " + s
|
||||
+ " after being unable to forward message " + message, exc);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!stopped) {
|
||||
messageAndOptionalSequenceNumber = transceiver.receiveMessage(is);
|
||||
if (messageAndOptionalSequenceNumber == null) {
|
||||
// received EOF; stopping received
|
||||
stopped = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// note that we're not changing anything with the sockets to which we forward messages; that will only happen
|
||||
// if forwarding to any of those sockets fails
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
if (!stopped) {
|
||||
logger.log(Level.INFO, "Error during forwarding message. Continuing...", e);
|
||||
try {
|
||||
Thread.sleep(1000l); // wait a little bit before trying to re-establish a connection
|
||||
} catch (InterruptedException e1) {
|
||||
logger.log(Level.INFO, "Can't find any sleep...", e1);
|
||||
}
|
||||
} else {
|
||||
logger.info("StoreAndForward socket was closed.");
|
||||
}
|
||||
}
|
||||
}
|
||||
logger.info("Terminating StoreAndForward ReceivingThread for socket "+socket);
|
||||
receivingThreads.remove(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops execution after having received the next message
|
||||
*/
|
||||
public void stopReceiver() throws UnknownHostException, IOException, InterruptedException {
|
||||
logger.entering(getClass().getName(), "stop");
|
||||
stopped = true;
|
||||
logger.info("closing socket");
|
||||
socket.close(); // will let a read terminate abnormally
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,9 +364,16 @@ public class StoreAndForward implements Runnable {
|
||||
closer.close();
|
||||
logger.info("joining clientListener thread "+clientListener);
|
||||
clientListener.join();
|
||||
socket.close(); // will let a read terminate abnormally
|
||||
for (ReceivingThread receivingThread : receivingThreads) {
|
||||
receivingThread.stopReceiver();
|
||||
logger.info("joining storeAndForwardThread "+storeAndForwardThread);
|
||||
storeAndForwardThread.join();
|
||||
receivingThread.join();
|
||||
}
|
||||
logger.info("StoreAndForward is closing receiving server socket");
|
||||
if (serverSocketListeningForSailMasterBridge != null && !serverSocketListeningForSailMasterBridge.isClosed()) {
|
||||
serverSocketListeningForSailMasterBridge.close();
|
||||
}
|
||||
logger.info("Stopping StoreAndForward server.");
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws InterruptedException, IOException {
|
||||
@@ -266,66 +401,12 @@ public class StoreAndForward implements Runnable {
|
||||
try {
|
||||
while (!stopped) {
|
||||
try {
|
||||
establishConnection();
|
||||
InputStream is = socket.getInputStream();
|
||||
Pair<String, Long> messageAndOptionalSequenceNumber = transceiver.receiveMessage(is);
|
||||
// ignore any sequence number contained in the message; we'll create our own
|
||||
DBObject emptyQuery = new BasicDBObject();
|
||||
DBObject incrementLastMessageCountQuery = new BasicDBObject().
|
||||
append("$inc", new BasicDBObject().append(FieldNames.LAST_MESSAGE_COUNT.name(), 1));
|
||||
while (!stopped && messageAndOptionalSequenceNumber != null) {
|
||||
logger.fine("Received message: "+messageAndOptionalSequenceNumber.getA());
|
||||
DBObject newCountRecord = lastMessageCountCollection.findAndModify(emptyQuery, incrementLastMessageCountQuery);
|
||||
lastMessageCount = ((newCountRecord == null) ? 0l :
|
||||
((Number) newCountRecord.get(FieldNames.LAST_MESSAGE_COUNT.name())).longValue());
|
||||
SailMasterMessage message = swissTimingFactory.createMessage(messageAndOptionalSequenceNumber.getA(), lastMessageCount);
|
||||
swissTimingAdapterPersistence.storeSailMasterMessage(message);
|
||||
synchronized (this) {
|
||||
for (OutputStream os : new ArrayList<OutputStream>(streamsToForwardTo)) {
|
||||
// write the sequence number of the message into the stream before actually writing the
|
||||
// SwissTiming message
|
||||
try {
|
||||
// TODO if forwarding to os doesn't work, e.g., because the socket was closed or the client died, remove os from streamsToForwardTo and the socket from socketsToForwardTo
|
||||
transceiver.sendMessage(message, os);
|
||||
} catch (Exception e) {
|
||||
int i=streamsToForwardTo.indexOf(os);
|
||||
try {
|
||||
os.close();
|
||||
} catch (Exception exc) {
|
||||
logger.throwing(StoreAndForward.class.getName(), "run", exc);
|
||||
}
|
||||
streamsToForwardTo.remove(os);
|
||||
Socket s = socketsToForwardTo.remove(i);
|
||||
try {
|
||||
s.close();
|
||||
} catch (Exception exc) {
|
||||
logger.throwing(StoreAndForward.class.getName(), "run", exc);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!stopped) {
|
||||
messageAndOptionalSequenceNumber = transceiver.receiveMessage(is);
|
||||
}
|
||||
}
|
||||
for (OutputStream os : streamsToForwardTo) {
|
||||
os.close();
|
||||
}
|
||||
for (Socket socketToForwardTo : socketsToForwardTo) {
|
||||
socketToForwardTo.close();
|
||||
ReceivingThread receivingThread = establishConnection();
|
||||
if (!isInSailMasterListeningMode()) {
|
||||
receivingThread.join(); // we're in active connecting mode; wait for thread to die, then trie again
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
if (!stopped) {
|
||||
logger.throwing(StoreAndForward.class.getName(), "Error during forwarding message. Continuing...", e);
|
||||
try {
|
||||
Thread.sleep(1000l); // wait a little bit before trying to re-establish a connection
|
||||
} catch (InterruptedException e1) {
|
||||
logger.throwing(StoreAndForward.class.getName(), "Can't find any sleep...", e1);
|
||||
}
|
||||
} else {
|
||||
logger.info("StoreAndForward socket was closed.");
|
||||
}
|
||||
logger.log(Level.SEVERE, "Exception in StoreAndForward", e);
|
||||
}
|
||||
}
|
||||
logger.info("StoreAndForward is closing receiving server socket");
|
||||
|
||||
+1
-1
@@ -141,7 +141,7 @@ public class SwissTimingAdapterPersistenceImpl implements SwissTimingAdapterPers
|
||||
@Override
|
||||
public List<SailMasterMessage> loadRaceMessages(String raceID) {
|
||||
DBCollection racesMessagesCollection = database.getCollection(CollectionNames.RACES_MESSAGES.name());
|
||||
racesMessagesCollection.ensureIndex(new BasicDBObject(FieldNames.MESSAGE_SEQUENCE_NUMBER.name(), null)); // no sort without index
|
||||
racesMessagesCollection.ensureIndex(new BasicDBObject(FieldNames.MESSAGE_SEQUENCE_NUMBER.name(), 1)); // no sort without index
|
||||
BasicDBObject query = new BasicDBObject();
|
||||
query.append(FieldNames.RACE_ID.name(), raceID);
|
||||
DBCursor results = racesMessagesCollection.find(query).sort(
|
||||
|
||||
Executable
+6
@@ -0,0 +1,6 @@
|
||||
RAC|1|SAM005923;Men 470 Race 23
|
||||
STL|SAM005923|34|AUS-7;AUS;AUS-7 |AUT-11;AUT;AUT-11 |AUT-477;AUT;AUT-477 |BEL-16;BEL;BEL-16 |ESP-1;ESP;ESP-1 |ESP-11;ESP;ESP-11 |ESP-13;ESP;ESP-13 |ESP-16;ESP;ESP-16 |ESP-17;ESP;ESP-17 |ESP-18;ESP;ESP-18 |ESP-1868;ESP;ESP-1868 |ESP-22;ESP;ESP-22 |FIN-111;FIN;FIN-111 |FRA-15;FRA;FRA-15 |FRA-20;FRA;FRA-20 |FRA-76;FRA;FRA-76 |GBR-828;GBR;GBR-828 |GBR-843;GBR;GBR-843 |GER-13;GER;GER-13 |GER-15;GER;GER-15 |GER-48;GER;GER-48 |GER-5025;GER;GER-5025 |GER-84;GER;GER-84 |ITA-22;ITA;ITA-22 |ITA-25;ITA;ITA-25 |ITA-28;ITA;ITA-28 |ITA-300;ITA;ITA-300 |ITA-32;ITA;ITA-32 |ITA-99;ITA;ITA-99 |POR-2;POR;POR-2 |RSA-8;RSA;RSA-8 |RUS-100;RUS;RUS-100 |UKR-11;UKR;UKR-11 |UKR-23;UKR;UKR-23
|
||||
CCG|SAM005923|7|0;Start;1;SB;SP|1;Mark 1;2;M1;|2;Mark 2;1;M4p;M4s|3;Mark 3;2;M1;|4;Mark 4;2;M2;|5;Mark 5;2;M3p;|6;Finish;1;FB;FP
|
||||
TMD|SAM005923|GBR-828|1|0;;00:00:00
|
||||
RPD|SAM005923|0|2013-04-04T10:35:24+02:00|||||55|FB;4;2;39.514533;2.721949;0.8;;;256;;;;;|FP;4;2;39.512227;2.724561;0.4;;;146;;;;;|J1;3;-1;39.499894;2.750856;0.2;;;172;;;;;|J2;3;-1;39.499831;2.750965;0.0;;;32;;;;;|M1;5;2;39.526214;2.706992;0.4;;;138;;;;;|M2;5;2;39.518901;2.707612;11.9;;;316;;;;;|M3p;5;2;39.512236;2.724599;0.6;;;140;;;;;|M3s;5;378;39.513290;2.723822;1.2;;;240;;;;;|M4p;5;2;39.518983;2.720770;0.4;;;54;;;;;|M4s;5;2;39.518967;2.720791;0.4;;;60;;;;;|R1;4;2;39.518919;2.707606;10.9;;;322;;;;;|R2;4;2;39.526186;2.706922;0.6;;;124;;;;;|R3;4;0;39.499793;2.750926;0.0;;;334;;;;;|S1;5;2;39.526193;2.706945;0.4;;;140;;;;;|S2;5;2;39.518899;2.707619;11.1;;;320;;;;;|S3p;5;;;;;;;;;;;;|S3s;5;;;;;;;;;;;;|S4p;5;;;;;;;;;;;;|S4s;5;;;;;;;;;;;;|SB;2;0;39.519168;2.721933;0.4;;;280;;;;;|SP;4;0;39.517652;2.719715;0.8;;;116;;;;;|AUS-7;1;;;;;;;;;;;;|AUT-11;1;;;;;;;;;;;;|AUT-477;1;;;;;;;;;;;;|BEL-16;1;;;;;;;;;;;;|ESP-1;1;;;;;;;;;;;;|ESP-11;1;;;;;;;;;;;;|ESP-13;1;;;;;;;;;;;;|ESP-16;1;;;;;;;;;;;;|ESP-17;1;;;;;;;;;;;;|ESP-18;1;;;;;;;;;;;;|ESP-1868;1;;;;;;;;;;;;|ESP-22;1;;;;;;;;;;;;|FIN-111;1;;;;;;;;;;;;|FRA-15;1;;;;;;;;;;;;|FRA-20;1;;;;;;;;;;;;|FRA-76;1;;;;;;;;;;;;|GBR-828;1;;;;;;;;;;;;|GBR-843;1;;;;;;;;;;;;|GER-13;1;;;;;;;;;;;;|GER-15;1;;;;;;;;;;;;|GER-48;1;;;;;;;;;;;;|GER-5025;1;;;;;;;;;;;;|GER-84;1;;;;;;;;;;;;|ITA-22;1;;;;;;;;;;;;|ITA-25;1;;;;;;;;;;;;|ITA-28;1;;;;;;;;;;;;|ITA-300;1;;;;;;;;;;;;|ITA-32;1;;;;;;;;;;;;|ITA-99;1;;;;;;;;;;;;|POR-2;1;;;;;;;;;;;;|RSA-8;1;;;;;;;;;;;;|RUS-100;1;;;;;;;;;;;;|UKR-11;1;;;;;;;;;;;;|UKR-23;1;;;;;;;;;;;;
|
||||
RPD|SAM005923|3|2013-04-04T14:53:29+02:00|13:45:00|01:08:29|||55|FB;4;1;39.514595;2.726782;0.6;;;352;;;;;|FP;4;1;39.514796;2.727207;0.6;;;52;;;;;|J1;3;0;39.515297;2.721503;2.1;;;332;;;;;|J2;3;0;39.516209;2.721166;4.1;;;334;;;;;|M1;5;1;39.507557;2.700071;0.2;;;22;;;;;|M2;5;1;39.503624;2.704863;0.4;;;274;;;;;|M3p;5;1;39.511114;2.726937;0.6;;;268;;;;;|M3s;5;1;39.510720;2.727125;1.6;;;54;;;;;|M4p;5;1;39.514059;2.718241;1.4;;;110;;;;;|M4s;5;1;39.514376;2.718104;0.4;;;278;;;;;|R1;4;1;39.502029;2.704981;5.4;;;310;;;;;|R2;4;1;39.503993;2.703844;1.0;;;148;;;;;|R3;4;1;39.516238;2.721159;4.5;;;344;;;;;|S1;5;1;39.503955;2.703858;1.6;;;138;;;;;|S2;5;13;39.501492;2.706392;0.2;;;102;;;;;|S3p;5;;;;;;;;;;;;|S3s;5;;;;;;;;;;;;|S4p;5;;;;;;;;;;;;|S4s;5;;;;;;;;;;;;|SB;2;-1;39.515923;2.720501;0.2;;;160;;;;;|SP;4;1;39.512957;2.721845;0.4;;;22;;;;;|AUS-7;1;1;39.515843;2.721024;6.8;;;0;;6;0;;|AUT-11;1;0;39.516144;2.720703;0.8;;;208;;21;0;;|AUT-477;1;1;39.515635;2.720475;4.7;;;312;;29;0;;|BEL-16;1;0;39.515838;2.720738;2.3;;;224;;10;0;;|ESP-1;1;3;39.515593;2.720689;3.5;;;198;;18;0;;|ESP-11;1;0;39.516054;2.720826;5.1;;;308;;9;0;;|ESP-13;1;;;;;;;;;;;;|ESP-16;1;1;39.515497;2.721003;4.9;;;124;;27;0;;|ESP-17;1;0;39.516150;2.720747;2.5;;;204;;24;0;;|ESP-18;1;1;39.514838;2.721314;4.7;;;170;;31;0;;|ESP-1868;1;1;39.500431;2.749792;0.0;;;258;;23;0;;|ESP-22;1;1;39.516085;2.720959;4.7;;;328;;14;0;;|FIN-111;1;0;39.515651;2.721038;2.1;;;290;;13;0;;|FRA-15;1;1;39.515935;2.720659;1.6;;;178;;11;0;;|FRA-20;1;3;39.516128;2.720589;7.0;;;136;;15;0;;|FRA-76;1;1;39.516355;2.720682;6.4;;;162;;3;0;;|GBR-828;1;1;39.514721;2.721544;6.0;;;334;;26;0;;|GBR-843;1;1;39.514117;2.721600;7.2;;;330;;1;0;;|GER-13;1;-1;39.515559;2.721045;2.7;;;276;;20;0;;|GER-15;1;1;39.514951;2.721463;5.6;;;336;;2;0;;|GER-48;1;1;39.514667;2.721653;2.1;;;182;;25;0;;|GER-5025;1;1;39.515795;2.720919;2.9;;;270;;7;0;;|GER-84;1;1;39.516074;2.720768;1.7;;;280;;5;0;;|ITA-22;1;0;39.516090;2.720580;1.9;;;120;;17;0;;|ITA-25;1;1;39.515364;2.720907;1.6;;;188;;12;0;;|ITA-28;1;1;39.516405;2.720717;6.8;;;318;;19;0;;|ITA-300;1;0;39.515867;2.720828;4.1;;;182;;16;0;;|ITA-32;1;1;39.515687;2.720687;3.1;;;330;;22;0;;|ITA-99;1;-1;39.515737;2.720794;0.8;;;286;;4;0;;|POR-2;1;1;39.515852;2.720619;0.2;;;162;;8;0;;|RSA-8;1;1;39.515505;2.720828;6.6;;;20;;28;0;;|RUS-100;1;0;39.515896;2.720777;3.7;;;178;;30;0;;|UKR-11;1;1;39.516422;2.720378;3.1;;;174;;32;0;;|UKR-23;1;1;39.501535;2.750179;0.0;0.0;1.1;168;1;33;10221;3713;BFD
|
||||
java/com.sap.sailing.domain.swisstimingadapter.test/resources/TMDBeforeStartTimeExample_ManyTMDs.txt
Executable
+507
@@ -0,0 +1,507 @@
|
||||
RAC|1|SAM005923;Men 470 Race 23
|
||||
STL|SAM005923|34|AUS-7;AUS;AUS-7 |AUT-11;AUT;AUT-11 |AUT-477;AUT;AUT-477 |BEL-16;BEL;BEL-16 |ESP-1;ESP;ESP-1 |ESP-11;ESP;ESP-11 |ESP-13;ESP;ESP-13 |ESP-16;ESP;ESP-16 |ESP-17;ESP;ESP-17 |ESP-18;ESP;ESP-18 |ESP-1868;ESP;ESP-1868 |ESP-22;ESP;ESP-22 |FIN-111;FIN;FIN-111 |FRA-15;FRA;FRA-15 |FRA-20;FRA;FRA-20 |FRA-76;FRA;FRA-76 |GBR-828;GBR;GBR-828 |GBR-843;GBR;GBR-843 |GER-13;GER;GER-13 |GER-15;GER;GER-15 |GER-48;GER;GER-48 |GER-5025;GER;GER-5025 |GER-84;GER;GER-84 |ITA-22;ITA;ITA-22 |ITA-25;ITA;ITA-25 |ITA-28;ITA;ITA-28 |ITA-300;ITA;ITA-300 |ITA-32;ITA;ITA-32 |ITA-99;ITA;ITA-99 |POR-2;POR;POR-2 |RSA-8;RSA;RSA-8 |RUS-100;RUS;RUS-100 |UKR-11;UKR;UKR-11 |UKR-23;UKR;UKR-23
|
||||
CCG|SAM005923|7|0;Start;1;SB;SP|1;Mark 1;2;M1;|2;Mark 2;1;M4p;M4s|3;Mark 3;2;M1;|4;Mark 4;2;M2;|5;Mark 5;2;M3p;|6;Finish;1;FB;FP
|
||||
TMD|SAM005923|GBR-828|1|0;;00:00:00
|
||||
TMD|SAM005923|GBR-843|1|0;;00:00:00
|
||||
TMD|SAM005923|GER-13|1|0;;00:00:00
|
||||
TMD|SAM005923|GER-15|1|0;;00:00:00
|
||||
TMD|SAM005923|GER-48|1|0;;00:00:00
|
||||
TMD|SAM005923|GER-5025|1|0;;00:00:00
|
||||
TMD|SAM005923|GER-84|1|0;;00:00:00
|
||||
TMD|SAM005923|ITA-22|1|0;;00:00:00
|
||||
TMD|SAM005923|AUS-7|1|0;;00:00:00
|
||||
TMD|SAM005923|ESP-11|1|0;;00:00:00
|
||||
TMD|SAM005923|ESP-13|1|0;;00:00:00
|
||||
TMD|SAM005923|ESP-16|1|0;;00:00:00
|
||||
TMD|SAM005923|ESP-17|1|0;;00:00:00
|
||||
TMD|SAM005923|ESP-18|1|0;;00:00:00
|
||||
TMD|SAM005923|ESP-1868|1|0;;00:00:00
|
||||
TMD|SAM005923|ESP-22|1|0;;00:00:00
|
||||
TMD|SAM005923|FIN-111|1|0;;00:00:00
|
||||
TMD|SAM005923|FRA-15|1|0;;00:00:00
|
||||
TMD|SAM005923|FRA-20|1|0;;00:00:00
|
||||
TMD|SAM005923|FRA-76|1|0;;00:00:00
|
||||
TMD|SAM005923|ITA-25|1|0;;00:00:00
|
||||
TMD|SAM005923|ITA-28|1|0;;00:00:00
|
||||
TMD|SAM005923|ITA-300|1|0;;00:00:00
|
||||
TMD|SAM005923|ITA-32|1|0;;00:00:00
|
||||
TMD|SAM005923|ITA-99|1|0;;00:00:00
|
||||
TMD|SAM005923|POR-2|1|0;;00:00:00
|
||||
TMD|SAM005923|RSA-8|1|0;;00:00:00
|
||||
TMD|SAM005923|RUS-100|1|0;;00:00:00
|
||||
TMD|SAM005923|UKR-11|1|0;;00:00:00
|
||||
TMD|SAM005923|UKR-23|1|0;;00:00:00
|
||||
TMD|SAM005923|AUS-7|1|0;;00:00:00
|
||||
TMD|SAM005923|AUT-11|1|0;;00:00:00
|
||||
TMD|SAM005923|AUT-477|1|0;;00:00:00
|
||||
TMD|SAM005923|BEL-16|1|0;;00:00:00
|
||||
TMD|SAM005923|ESP-1|1|0;;00:00:00
|
||||
TMD|SAM005923|ESP-11|1|0;;00:00:00
|
||||
TMD|SAM005923|ESP-13|1|0;;00:00:00
|
||||
TMD|SAM005923|ESP-16|1|0;;00:00:00
|
||||
TMD|SAM005923|ESP-17|1|0;;00:00:00
|
||||
TMD|SAM005923|ESP-18|1|0;;00:00:00
|
||||
TMD|SAM005923|ESP-1868|1|0;;00:00:00
|
||||
TMD|SAM005923|ESP-22|1|0;;00:00:00
|
||||
TMD|SAM005923|FIN-111|1|0;;00:00:00
|
||||
TMD|SAM005923|FRA-15|1|0;;00:00:00
|
||||
TMD|SAM005923|FRA-20|1|0;;00:00:00
|
||||
TMD|SAM005923|FRA-76|1|0;;00:00:00
|
||||
TMD|SAM005923|GBR-828|1|0;;00:00:00
|
||||
TMD|SAM005923|GBR-843|1|0;;00:00:00
|
||||
TMD|SAM005923|GER-13|1|0;;00:00:00
|
||||
TMD|SAM005923|GER-15|1|0;;00:00:00
|
||||
TMD|SAM005923|GER-48|1|0;;00:00:00
|
||||
TMD|SAM005923|GER-5025|1|0;;00:00:00
|
||||
TMD|SAM005923|GER-84|1|0;;00:00:00
|
||||
TMD|SAM005923|ITA-22|1|0;;00:00:00
|
||||
TMD|SAM005923|ITA-25|1|0;;00:00:00
|
||||
TMD|SAM005923|ITA-28|1|0;;00:00:00
|
||||
TMD|SAM005923|ITA-300|1|0;;00:00:00
|
||||
TMD|SAM005923|ITA-32|1|0;;00:00:00
|
||||
TMD|SAM005923|ITA-99|1|0;;00:00:00
|
||||
TMD|SAM005923|POR-2|1|0;;00:00:00
|
||||
TMD|SAM005923|RSA-8|1|0;;00:00:00
|
||||
TMD|SAM005923|RUS-100|1|0;;00:00:00
|
||||
TMD|SAM005923|UKR-11|1|0;;00:00:00
|
||||
TMD|SAM005923|UKR-23|1|0;;00:00:00
|
||||
TMD|SAM005923|AUS-7|1|0;;00:00:00
|
||||
TMD|SAM005923|BEL-16|1|0;;00:00:00
|
||||
TMD|SAM005923|ESP-1|1|0;;00:00:00
|
||||
TMD|SAM005923|ESP-11|1|0;;00:00:00
|
||||
TMD|SAM005923|ESP-13|1|0;;00:00:00
|
||||
TMD|SAM005923|ESP-16|1|0;;00:00:00
|
||||
TMD|SAM005923|ESP-17|1|0;;00:00:00
|
||||
TMD|SAM005923|ESP-18|1|0;;00:00:00
|
||||
TMD|SAM005923|ESP-1868|1|0;;00:00:00
|
||||
TMD|SAM005923|ESP-22|1|0;;00:00:00
|
||||
TMD|SAM005923|FIN-111|1|0;;00:00:00
|
||||
TMD|SAM005923|FRA-15|1|0;;00:00:00
|
||||
TMD|SAM005923|FRA-20|1|0;;00:00:00
|
||||
TMD|SAM005923|FRA-76|1|0;;00:00:00
|
||||
TMD|SAM005923|GBR-828|1|0;;00:00:00
|
||||
TMD|SAM005923|GBR-843|1|0;;00:00:00
|
||||
TMD|SAM005923|GER-13|1|0;;00:00:00
|
||||
TMD|SAM005923|GER-15|1|0;;00:00:00
|
||||
TMD|SAM005923|GER-48|1|0;;00:00:00
|
||||
TMD|SAM005923|GER-5025|1|0;;00:00:00
|
||||
TMD|SAM005923|GER-84|1|0;;00:00:00
|
||||
TMD|SAM005923|ITA-22|1|0;;00:00:00
|
||||
TMD|SAM005923|ITA-25|1|0;;00:00:00
|
||||
TMD|SAM005923|ITA-28|1|0;;00:00:00
|
||||
TMD|SAM005923|ITA-300|1|0;;00:00:00
|
||||
TMD|SAM005923|ITA-32|1|0;;00:00:00
|
||||
TMD|SAM005923|ITA-99|1|0;;00:00:00
|
||||
TMD|SAM005923|POR-2|1|0;;00:00:00
|
||||
TMD|SAM005923|RSA-8|1|0;;00:00:00
|
||||
TMD|SAM005923|RUS-100|1|0;;00:00:00
|
||||
TMD|SAM005923|UKR-11|1|0;;00:00:00
|
||||
TMD|SAM005923|UKR-23|1|0;;00:00:00
|
||||
TMD|SAM005923|GBR-843|1|1;;00:13:14
|
||||
TMD|SAM005923|GBR-843|1|1;1;00:13:14
|
||||
TMD|SAM005923|FIN-111|1|1;;00:13:40
|
||||
TMD|SAM005923|FIN-111|1|1;2;00:13:40
|
||||
TMD|SAM005923|GER-5025|1|1;;00:13:42
|
||||
TMD|SAM005923|GER-5025|1|1;3;00:13:42
|
||||
TMD|SAM005923|GER-15|1|1;;00:13:48
|
||||
TMD|SAM005923|GER-15|1|1;4;00:13:48
|
||||
TMD|SAM005923|FRA-76|1|1;;00:13:52
|
||||
TMD|SAM005923|FRA-76|1|1;5;00:13:52
|
||||
TMD|SAM005923|ESP-11|1|1;;00:13:59
|
||||
TMD|SAM005923|ESP-11|1|1;6;00:13:59
|
||||
TMD|SAM005923|GBR-828|1|1;;00:13:59
|
||||
TMD|SAM005923|GBR-828|1|1;7;00:13:59
|
||||
TMD|SAM005923|GER-84|1|1;;00:14:08
|
||||
TMD|SAM005923|GER-84|1|1;8;00:14:08
|
||||
TMD|SAM005923|ESP-1|1|1;;00:14:09
|
||||
TMD|SAM005923|ESP-1|1|1;9;00:14:09
|
||||
TMD|SAM005923|ITA-22|1|1;;00:14:11
|
||||
TMD|SAM005923|ITA-22|1|1;10;00:14:11
|
||||
TMD|SAM005923|FRA-20|1|1;;00:14:11
|
||||
TMD|SAM005923|FRA-20|1|1;11;00:14:11
|
||||
TMD|SAM005923|ITA-99|1|1;;00:14:15
|
||||
TMD|SAM005923|ITA-99|1|1;12;00:14:15
|
||||
TMD|SAM005923|POR-2|1|1;;00:14:16
|
||||
TMD|SAM005923|POR-2|1|1;13;00:14:16
|
||||
TMD|SAM005923|ESP-16|1|1;;00:14:19
|
||||
TMD|SAM005923|ESP-16|1|1;14;00:14:19
|
||||
TMD|SAM005923|AUT-11|1|1;;00:14:24
|
||||
TMD|SAM005923|AUT-11|1|1;15;00:14:24
|
||||
TMD|SAM005923|FRA-15|1|1;;00:14:27
|
||||
TMD|SAM005923|FRA-15|1|1;16;00:14:27
|
||||
TMD|SAM005923|ITA-25|1|1;;00:14:31
|
||||
TMD|SAM005923|ITA-25|1|1;17;00:14:31
|
||||
TMD|SAM005923|ESP-17|1|1;;00:14:34
|
||||
TMD|SAM005923|ESP-17|1|1;18;00:14:34
|
||||
TMD|SAM005923|ESP-1868|1|1;;00:14:32
|
||||
TMD|SAM005923|ESP-1868|1|1;18;00:14:32
|
||||
TMD|SAM005923|ESP-17|1|1;19;00:14:34
|
||||
TMD|SAM005923|BEL-16|1|1;;00:14:35
|
||||
TMD|SAM005923|BEL-16|1|1;20;00:14:35
|
||||
TMD|SAM005923|ESP-22|1|1;;00:14:37
|
||||
TMD|SAM005923|ESP-22|1|1;21;00:14:37
|
||||
TMD|SAM005923|ITA-300|1|1;;00:14:37
|
||||
TMD|SAM005923|ITA-300|1|1;21;00:14:37
|
||||
TMD|SAM005923|ESP-22|1|1;22;00:14:37
|
||||
TMD|SAM005923|AUS-7|1|1;;00:14:39
|
||||
TMD|SAM005923|AUS-7|1|1;23;00:14:39
|
||||
TMD|SAM005923|ITA-28|1|1;;00:14:40
|
||||
TMD|SAM005923|ITA-28|1|1;24;00:14:40
|
||||
TMD|SAM005923|GER-48|1|1;;00:14:42
|
||||
TMD|SAM005923|GER-48|1|1;25;00:14:42
|
||||
TMD|SAM005923|ITA-32|1|1;;00:15:01
|
||||
TMD|SAM005923|ITA-32|1|1;26;00:15:01
|
||||
TMD|SAM005923|RSA-8|1|1;;00:15:00
|
||||
TMD|SAM005923|RSA-8|1|1;26;00:15:00
|
||||
TMD|SAM005923|ITA-32|1|1;27;00:15:01
|
||||
TMD|SAM005923|GER-13|1|1;;00:15:02
|
||||
TMD|SAM005923|GER-13|1|1;28;00:15:02
|
||||
TMD|SAM005923|UKR-11|1|1;;00:15:25
|
||||
TMD|SAM005923|UKR-11|1|1;29;00:15:25
|
||||
TMD|SAM005923|AUT-477|1|1;;00:15:37
|
||||
TMD|SAM005923|AUT-477|1|1;30;00:15:37
|
||||
TMD|SAM005923|RUS-100|1|1;;00:15:52
|
||||
TMD|SAM005923|RUS-100|1|1;31;00:15:52
|
||||
TMD|SAM005923|ESP-18|1|1;;00:16:07
|
||||
TMD|SAM005923|ESP-18|1|1;32;00:16:07
|
||||
TMD|SAM005923|GBR-843|1|2;;00:20:33
|
||||
TMD|SAM005923|GBR-843|1|2;1;00:20:33
|
||||
TMD|SAM005923|GER-15|1|2;;00:20:51
|
||||
TMD|SAM005923|GER-15|1|2;2;00:20:51
|
||||
TMD|SAM005923|FRA-76|1|2;;00:21:17
|
||||
TMD|SAM005923|FRA-76|1|2;3;00:21:17
|
||||
TMD|SAM005923|ITA-99|1|2;;00:21:27
|
||||
TMD|SAM005923|ITA-99|1|2;4;00:21:27
|
||||
TMD|SAM005923|GER-5025|1|2;;00:21:28
|
||||
TMD|SAM005923|GER-5025|1|2;5;00:21:28
|
||||
TMD|SAM005923|ESP-11|1|2;;00:21:29
|
||||
TMD|SAM005923|ESP-11|1|2;6;00:21:29
|
||||
TMD|SAM005923|ESP-1|1|2;;00:21:30
|
||||
TMD|SAM005923|ESP-1|1|2;7;00:21:30
|
||||
TMD|SAM005923|GER-84|1|2;;00:21:32
|
||||
TMD|SAM005923|GER-84|1|2;8;00:21:32
|
||||
TMD|SAM005923|FIN-111|1|2;;00:21:34
|
||||
TMD|SAM005923|FIN-111|1|2;9;00:21:34
|
||||
TMD|SAM005923|AUS-7|1|2;;00:21:47
|
||||
TMD|SAM005923|AUS-7|1|2;10;00:21:47
|
||||
TMD|SAM005923|ITA-22|1|2;;00:21:50
|
||||
TMD|SAM005923|ITA-22|1|2;11;00:21:50
|
||||
TMD|SAM005923|POR-2|1|2;;00:21:50
|
||||
TMD|SAM005923|POR-2|1|2;12;00:21:50
|
||||
TMD|SAM005923|BEL-16|1|2;;00:21:53
|
||||
TMD|SAM005923|BEL-16|1|2;13;00:21:53
|
||||
TMD|SAM005923|FRA-20|1|2;;00:21:46
|
||||
TMD|SAM005923|FRA-20|1|2;10;00:21:46
|
||||
TMD|SAM005923|AUS-7|1|2;11;00:21:47
|
||||
TMD|SAM005923|ITA-22|1|2;12;00:21:50
|
||||
TMD|SAM005923|POR-2|1|2;13;00:21:50
|
||||
TMD|SAM005923|BEL-16|1|2;14;00:21:53
|
||||
TMD|SAM005923|ESP-22|1|2;;00:21:57
|
||||
TMD|SAM005923|ESP-22|1|2;15;00:21:57
|
||||
TMD|SAM005923|FRA-15|1|2;;00:22:00
|
||||
TMD|SAM005923|FRA-15|1|2;16;00:22:00
|
||||
TMD|SAM005923|ITA-300|1|2;;00:22:06
|
||||
TMD|SAM005923|ITA-300|1|2;17;00:22:06
|
||||
TMD|SAM005923|ESP-17|1|2;;00:22:09
|
||||
TMD|SAM005923|ESP-17|1|2;18;00:22:09
|
||||
TMD|SAM005923|GER-48|1|2;;00:22:09
|
||||
TMD|SAM005923|GER-48|1|2;19;00:22:09
|
||||
TMD|SAM005923|ITA-25|1|2;;00:22:11
|
||||
TMD|SAM005923|ITA-25|1|2;20;00:22:11
|
||||
TMD|SAM005923|ITA-28|1|2;;00:22:13
|
||||
TMD|SAM005923|ITA-28|1|2;21;00:22:13
|
||||
TMD|SAM005923|ESP-16|1|2;;00:22:13
|
||||
TMD|SAM005923|ESP-16|1|2;21;00:22:13
|
||||
TMD|SAM005923|ITA-28|1|2;22;00:22:13
|
||||
TMD|SAM005923|AUT-11|1|2;;00:22:16
|
||||
TMD|SAM005923|AUT-11|1|2;23;00:22:16
|
||||
TMD|SAM005923|ESP-1868|1|2;;00:22:15
|
||||
TMD|SAM005923|ESP-1868|1|2;23;00:22:15
|
||||
TMD|SAM005923|AUT-11|1|2;24;00:22:16
|
||||
TMD|SAM005923|GBR-828|1|2;;00:22:16
|
||||
TMD|SAM005923|GBR-828|1|2;24;00:22:16
|
||||
TMD|SAM005923|AUT-11|1|2;25;00:22:16
|
||||
TMD|SAM005923|GER-13|1|2;;00:22:19
|
||||
TMD|SAM005923|GER-13|1|2;26;00:22:19
|
||||
TMD|SAM005923|ITA-32|1|2;;00:22:36
|
||||
TMD|SAM005923|ITA-32|1|2;27;00:22:36
|
||||
TMD|SAM005923|RSA-8|1|2;;00:23:04
|
||||
TMD|SAM005923|RSA-8|1|2;28;00:23:04
|
||||
TMD|SAM005923|AUT-477|1|2;;00:23:05
|
||||
TMD|SAM005923|AUT-477|1|2;29;00:23:05
|
||||
TMD|SAM005923|UKR-11|1|2;;00:23:09
|
||||
TMD|SAM005923|UKR-11|1|2;30;00:23:09
|
||||
TMD|SAM005923|RUS-100|1|2;;00:23:39
|
||||
TMD|SAM005923|RUS-100|1|2;31;00:23:39
|
||||
TMD|SAM005923|ESP-18|1|2;;00:23:43
|
||||
TMD|SAM005923|ESP-18|1|2;32;00:23:43
|
||||
TMD|SAM005923|GBR-843|1|3;;00:32:27
|
||||
TMD|SAM005923|GBR-843|1|3;1;00:32:27
|
||||
TMD|SAM005923|GER-15|1|3;;00:32:58
|
||||
TMD|SAM005923|GER-15|1|3;2;00:32:58
|
||||
TMD|SAM005923|FRA-76|1|3;;00:33:18
|
||||
TMD|SAM005923|FRA-76|1|3;3;00:33:18
|
||||
TMD|SAM005923|GER-84|1|3;;00:33:48
|
||||
TMD|SAM005923|GER-84|1|3;4;00:33:48
|
||||
TMD|SAM005923|GER-5025|1|3;;00:33:51
|
||||
TMD|SAM005923|GER-5025|1|3;5;00:33:51
|
||||
TMD|SAM005923|ESP-11|1|3;;00:34:00
|
||||
TMD|SAM005923|ESP-11|1|3;6;00:34:00
|
||||
TMD|SAM005923|ITA-99|1|3;;00:34:00
|
||||
TMD|SAM005923|ITA-99|1|3;7;00:34:00
|
||||
TMD|SAM005923|GBR-843|1|4;;00:34:12
|
||||
TMD|SAM005923|GBR-843|1|4;1;00:34:12
|
||||
TMD|SAM005923|AUS-7|1|3;;00:34:12
|
||||
TMD|SAM005923|AUS-7|1|3;8;00:34:12
|
||||
TMD|SAM005923|FIN-111|1|3;;00:34:18
|
||||
TMD|SAM005923|FIN-111|1|3;9;00:34:18
|
||||
TMD|SAM005923|FRA-20|1|3;;00:34:20
|
||||
TMD|SAM005923|FRA-20|1|3;10;00:34:20
|
||||
TMD|SAM005923|POR-2|1|3;;00:34:23
|
||||
TMD|SAM005923|POR-2|1|3;11;00:34:23
|
||||
TMD|SAM005923|FRA-15|1|3;;00:34:23
|
||||
TMD|SAM005923|FRA-15|1|3;11;00:34:23
|
||||
TMD|SAM005923|POR-2|1|3;12;00:34:23
|
||||
TMD|SAM005923|ESP-1|1|3;;00:34:29
|
||||
TMD|SAM005923|ESP-1|1|3;13;00:34:29
|
||||
TMD|SAM005923|ITA-22|1|3;;00:34:32
|
||||
TMD|SAM005923|ITA-22|1|3;14;00:34:32
|
||||
TMD|SAM005923|ESP-22|1|3;;00:34:30
|
||||
TMD|SAM005923|ESP-22|1|3;14;00:34:30
|
||||
TMD|SAM005923|ITA-22|1|3;15;00:34:32
|
||||
TMD|SAM005923|GER-15|1|4;;00:34:42
|
||||
TMD|SAM005923|GER-15|1|4;2;00:34:42
|
||||
TMD|SAM005923|ITA-300|1|3;;00:34:46
|
||||
TMD|SAM005923|ITA-300|1|3;16;00:34:46
|
||||
TMD|SAM005923|BEL-16|1|3;;00:34:47
|
||||
TMD|SAM005923|BEL-16|1|3;17;00:34:47
|
||||
TMD|SAM005923|ITA-28|1|3;;00:34:50
|
||||
TMD|SAM005923|ITA-28|1|3;18;00:34:50
|
||||
TMD|SAM005923|FRA-76|1|4;;00:35:04
|
||||
TMD|SAM005923|FRA-76|1|4;3;00:35:04
|
||||
TMD|SAM005923|ITA-25|1|3;;00:35:04
|
||||
TMD|SAM005923|ITA-25|1|3;19;00:35:04
|
||||
TMD|SAM005923|AUT-11|1|3;;00:35:14
|
||||
TMD|SAM005923|AUT-11|1|3;20;00:35:14
|
||||
TMD|SAM005923|GER-48|1|3;;00:35:13
|
||||
TMD|SAM005923|GER-48|1|3;20;00:35:13
|
||||
TMD|SAM005923|AUT-11|1|3;21;00:35:14
|
||||
TMD|SAM005923|GER-13|1|3;;00:35:27
|
||||
TMD|SAM005923|GER-13|1|3;22;00:35:27
|
||||
TMD|SAM005923|GER-84|1|4;;00:35:31
|
||||
TMD|SAM005923|GER-84|1|4;4;00:35:31
|
||||
TMD|SAM005923|ITA-32|1|3;;00:35:32
|
||||
TMD|SAM005923|ITA-32|1|3;23;00:35:32
|
||||
TMD|SAM005923|ESP-17|1|3;;00:35:37
|
||||
TMD|SAM005923|ESP-17|1|3;24;00:35:37
|
||||
TMD|SAM005923|GER-5025|1|4;;00:35:37
|
||||
TMD|SAM005923|GER-5025|1|4;5;00:35:37
|
||||
TMD|SAM005923|ESP-1868|1|3;;00:35:42
|
||||
TMD|SAM005923|ESP-1868|1|3;25;00:35:42
|
||||
TMD|SAM005923|ITA-99|1|4;;00:35:51
|
||||
TMD|SAM005923|ITA-99|1|4;6;00:35:51
|
||||
TMD|SAM005923|ESP-11|1|4;;00:35:58
|
||||
TMD|SAM005923|ESP-11|1|4;7;00:35:58
|
||||
TMD|SAM005923|ESP-16|1|3;;00:35:59
|
||||
TMD|SAM005923|ESP-16|1|3;26;00:35:59
|
||||
TMD|SAM005923|GBR-828|1|3;;00:36:02
|
||||
TMD|SAM005923|GBR-828|1|3;27;00:36:02
|
||||
TMD|SAM005923|AUS-7|1|4;;00:36:05
|
||||
TMD|SAM005923|AUS-7|1|4;8;00:36:05
|
||||
TMD|SAM005923|FIN-111|1|4;;00:36:19
|
||||
TMD|SAM005923|FIN-111|1|4;9;00:36:19
|
||||
TMD|SAM005923|RSA-8|1|3;;00:36:20
|
||||
TMD|SAM005923|RSA-8|1|3;28;00:36:20
|
||||
TMD|SAM005923|FRA-20|1|4;;00:36:23
|
||||
TMD|SAM005923|FRA-20|1|4;10;00:36:23
|
||||
TMD|SAM005923|RUS-100|1|3;;00:36:23
|
||||
TMD|SAM005923|RUS-100|1|3;29;00:36:23
|
||||
TMD|SAM005923|FRA-15|1|4;;00:36:25
|
||||
TMD|SAM005923|FRA-15|1|4;11;00:36:25
|
||||
TMD|SAM005923|ESP-22|1|4;;00:36:29
|
||||
TMD|SAM005923|ESP-22|1|4;12;00:36:29
|
||||
TMD|SAM005923|AUT-477|1|3;;00:36:29
|
||||
TMD|SAM005923|AUT-477|1|3;30;00:36:29
|
||||
TMD|SAM005923|POR-2|1|4;;00:36:30
|
||||
TMD|SAM005923|POR-2|1|4;13;00:36:30
|
||||
TMD|SAM005923|ESP-1|1|4;;00:36:32
|
||||
TMD|SAM005923|ESP-1|1|4;14;00:36:32
|
||||
TMD|SAM005923|ITA-22|1|4;;00:36:35
|
||||
TMD|SAM005923|ITA-22|1|4;15;00:36:35
|
||||
TMD|SAM005923|BEL-16|1|4;;00:36:42
|
||||
TMD|SAM005923|BEL-16|1|4;16;00:36:42
|
||||
TMD|SAM005923|UKR-11|1|3;;00:36:40
|
||||
TMD|SAM005923|UKR-11|1|3;31;00:36:40
|
||||
TMD|SAM005923|ITA-300|1|4;;00:36:55
|
||||
TMD|SAM005923|ITA-300|1|4;17;00:36:55
|
||||
TMD|SAM005923|ITA-28|1|4;;00:36:55
|
||||
TMD|SAM005923|ITA-28|1|4;17;00:36:55
|
||||
TMD|SAM005923|ITA-300|1|4;18;00:36:55
|
||||
TMD|SAM005923|ITA-25|1|4;;00:36:55
|
||||
TMD|SAM005923|ITA-25|1|4;18;00:36:55
|
||||
TMD|SAM005923|ITA-300|1|4;19;00:36:55
|
||||
TMD|SAM005923|ESP-18|1|3;;00:37:15
|
||||
TMD|SAM005923|ESP-18|1|3;32;00:37:15
|
||||
TMD|SAM005923|AUT-11|1|4;;00:37:20
|
||||
TMD|SAM005923|AUT-11|1|4;20;00:37:20
|
||||
TMD|SAM005923|GER-48|1|4;;00:37:26
|
||||
TMD|SAM005923|GER-48|1|4;21;00:37:26
|
||||
TMD|SAM005923|GER-13|1|4;;00:37:26
|
||||
TMD|SAM005923|GER-13|1|4;22;00:37:26
|
||||
TMD|SAM005923|ITA-32|1|4;;00:37:37
|
||||
TMD|SAM005923|ITA-32|1|4;23;00:37:37
|
||||
TMD|SAM005923|ESP-17|1|4;;00:37:46
|
||||
TMD|SAM005923|ESP-17|1|4;24;00:37:46
|
||||
TMD|SAM005923|ESP-1868|1|4;;00:37:54
|
||||
TMD|SAM005923|ESP-1868|1|4;25;00:37:54
|
||||
TMD|SAM005923|ESP-16|1|4;;00:38:10
|
||||
TMD|SAM005923|ESP-16|1|4;26;00:38:10
|
||||
TMD|SAM005923|GBR-828|1|4;;00:38:17
|
||||
TMD|SAM005923|GBR-828|1|4;27;00:38:17
|
||||
TMD|SAM005923|RSA-8|1|4;;00:38:22
|
||||
TMD|SAM005923|RSA-8|1|4;28;00:38:22
|
||||
TMD|SAM005923|RUS-100|1|4;;00:38:36
|
||||
TMD|SAM005923|RUS-100|1|4;29;00:38:36
|
||||
TMD|SAM005923|AUT-477|1|4;;00:38:41
|
||||
TMD|SAM005923|AUT-477|1|4;30;00:38:41
|
||||
TMD|SAM005923|UKR-11|1|4;;00:39:22
|
||||
TMD|SAM005923|UKR-11|1|4;31;00:39:22
|
||||
TMD|SAM005923|ESP-18|1|4;;00:39:46
|
||||
TMD|SAM005923|ESP-18|1|4;32;00:39:46
|
||||
TMD|SAM005923|GBR-843|1|5;;00:43:36
|
||||
TMD|SAM005923|GBR-843|1|5;1;00:43:36
|
||||
TMD|SAM005923|GER-15|1|5;;00:44:02
|
||||
TMD|SAM005923|GER-15|1|5;2;00:44:02
|
||||
TMD|SAM005923|FRA-76|1|5;;00:44:19
|
||||
TMD|SAM005923|FRA-76|1|5;3;00:44:19
|
||||
TMD|SAM005923|GBR-843|1|6;;00:44:49
|
||||
TMD|SAM005923|GBR-843|1|6;1;00:44:49
|
||||
TMD|SAM005923|GER-84|1|5;;00:44:55
|
||||
TMD|SAM005923|GER-84|1|5;4;00:44:55
|
||||
TMD|SAM005923|ITA-99|1|5;;00:44:54
|
||||
TMD|SAM005923|ITA-99|1|5;4;00:44:54
|
||||
TMD|SAM005923|GER-84|1|5;5;00:44:55
|
||||
TMD|SAM005923|AUS-7|1|5;;00:44:59
|
||||
TMD|SAM005923|AUS-7|1|5;6;00:44:59
|
||||
TMD|SAM005923|GER-5025|1|5;;00:45:03
|
||||
TMD|SAM005923|GER-5025|1|5;7;00:45:03
|
||||
TMD|SAM005923|GER-15|1|6;;00:45:07
|
||||
TMD|SAM005923|GER-15|1|6;2;00:45:07
|
||||
TMD|SAM005923|POR-2|1|5;;00:45:17
|
||||
TMD|SAM005923|POR-2|1|5;8;00:45:17
|
||||
TMD|SAM005923|ESP-11|1|5;;00:45:24
|
||||
TMD|SAM005923|ESP-11|1|5;9;00:45:24
|
||||
TMD|SAM005923|FRA-76|1|6;;00:45:26
|
||||
TMD|SAM005923|FRA-76|1|6;3;00:45:26
|
||||
TMD|SAM005923|FRA-15|1|5;;00:45:33
|
||||
TMD|SAM005923|FRA-15|1|5;10;00:45:33
|
||||
TMD|SAM005923|BEL-16|1|5;;00:45:41
|
||||
TMD|SAM005923|BEL-16|1|5;11;00:45:41
|
||||
TMD|SAM005923|ITA-25|1|5;;00:45:48
|
||||
TMD|SAM005923|ITA-25|1|5;12;00:45:48
|
||||
TMD|SAM005923|FIN-111|1|5;;00:45:51
|
||||
TMD|SAM005923|FIN-111|1|5;13;00:45:51
|
||||
TMD|SAM005923|ESP-22|1|5;;00:45:55
|
||||
TMD|SAM005923|ESP-22|1|5;14;00:45:55
|
||||
TMD|SAM005923|FRA-20|1|5;;00:45:57
|
||||
TMD|SAM005923|FRA-20|1|5;15;00:45:57
|
||||
TMD|SAM005923|ITA-22|1|5;;00:46:00
|
||||
TMD|SAM005923|ITA-22|1|5;16;00:46:00
|
||||
TMD|SAM005923|ITA-99|1|6;;00:46:03
|
||||
TMD|SAM005923|ITA-99|1|6;4;00:46:03
|
||||
TMD|SAM005923|ITA-300|1|5;;00:46:05
|
||||
TMD|SAM005923|ITA-300|1|5;17;00:46:05
|
||||
TMD|SAM005923|ESP-1|1|5;;00:46:08
|
||||
TMD|SAM005923|ESP-1|1|5;18;00:46:08
|
||||
TMD|SAM005923|GER-84|1|6;;00:46:08
|
||||
TMD|SAM005923|GER-84|1|6;5;00:46:08
|
||||
TMD|SAM005923|AUS-7|1|6;;00:46:11
|
||||
TMD|SAM005923|AUS-7|1|6;6;00:46:11
|
||||
TMD|SAM005923|GER-5025|1|6;;00:46:14
|
||||
TMD|SAM005923|GER-5025|1|6;7;00:46:14
|
||||
TMD|SAM005923|POR-2|1|6;;00:46:26
|
||||
TMD|SAM005923|POR-2|1|6;8;00:46:26
|
||||
TMD|SAM005923|ITA-28|1|5;;00:46:26
|
||||
TMD|SAM005923|ITA-28|1|5;19;00:46:26
|
||||
TMD|SAM005923|GER-13|1|5;;00:46:29
|
||||
TMD|SAM005923|GER-13|1|5;20;00:46:29
|
||||
TMD|SAM005923|ESP-11|1|6;;00:46:37
|
||||
TMD|SAM005923|ESP-11|1|6;9;00:46:37
|
||||
TMD|SAM005923|AUT-11|1|5;;00:46:50
|
||||
TMD|SAM005923|AUT-11|1|5;21;00:46:50
|
||||
TMD|SAM005923|ITA-32|1|5;;00:46:51
|
||||
TMD|SAM005923|ITA-32|1|5;22;00:46:51
|
||||
TMD|SAM005923|BEL-16|1|6;;00:46:52
|
||||
TMD|SAM005923|BEL-16|1|6;10;00:46:52
|
||||
TMD|SAM005923|FRA-15|1|6;;00:46:58
|
||||
TMD|SAM005923|FRA-15|1|6;11;00:46:58
|
||||
TMD|SAM005923|ITA-25|1|6;;00:46:59
|
||||
TMD|SAM005923|ITA-25|1|6;12;00:46:59
|
||||
TMD|SAM005923|ESP-1868|1|5;;00:46:59
|
||||
TMD|SAM005923|ESP-1868|1|5;23;00:46:59
|
||||
TMD|SAM005923|ESP-17|1|5;;00:47:04
|
||||
TMD|SAM005923|ESP-17|1|5;24;00:47:04
|
||||
TMD|SAM005923|FIN-111|1|6;;00:47:03
|
||||
TMD|SAM005923|FIN-111|1|6;13;00:47:03
|
||||
TMD|SAM005923|ESP-22|1|6;;00:47:07
|
||||
TMD|SAM005923|ESP-22|1|6;14;00:47:07
|
||||
TMD|SAM005923|FRA-20|1|6;;00:47:14
|
||||
TMD|SAM005923|FRA-20|1|6;15;00:47:14
|
||||
TMD|SAM005923|GER-48|1|5;;00:47:17
|
||||
TMD|SAM005923|GER-48|1|5;25;00:47:17
|
||||
TMD|SAM005923|ITA-300|1|6;;00:47:17
|
||||
TMD|SAM005923|ITA-300|1|6;16;00:47:17
|
||||
TMD|SAM005923|ITA-22|1|6;;00:47:20
|
||||
TMD|SAM005923|ITA-22|1|6;17;00:47:20
|
||||
TMD|SAM005923|ESP-1|1|6;;00:47:23
|
||||
TMD|SAM005923|ESP-1|1|6;18;00:47:23
|
||||
TMD|SAM005923|GBR-828|1|5;;00:47:26
|
||||
TMD|SAM005923|GBR-828|1|5;26;00:47:26
|
||||
TMD|SAM005923|ESP-16|1|5;;00:47:34
|
||||
TMD|SAM005923|ESP-16|1|5;27;00:47:34
|
||||
TMD|SAM005923|ITA-28|1|6;;00:47:39
|
||||
TMD|SAM005923|ITA-28|1|6;19;00:47:39
|
||||
TMD|SAM005923|RSA-8|1|5;;00:47:39
|
||||
TMD|SAM005923|RSA-8|1|5;28;00:47:39
|
||||
TMD|SAM005923|GER-13|1|6;;00:47:49
|
||||
TMD|SAM005923|GER-13|1|6;20;00:47:49
|
||||
TMD|SAM005923|AUT-11|1|6;;00:48:01
|
||||
TMD|SAM005923|AUT-11|1|6;21;00:48:01
|
||||
TMD|SAM005923|AUT-477|1|5;;00:48:03
|
||||
TMD|SAM005923|AUT-477|1|5;29;00:48:03
|
||||
TMD|SAM005923|ITA-32|1|6;;00:48:06
|
||||
TMD|SAM005923|ITA-32|1|6;22;00:48:06
|
||||
TMD|SAM005923|ESP-1868|1|6;;00:48:13
|
||||
TMD|SAM005923|ESP-1868|1|6;23;00:48:13
|
||||
TMD|SAM005923|RUS-100|1|5;;00:48:17
|
||||
TMD|SAM005923|RUS-100|1|5;30;00:48:17
|
||||
TMD|SAM005923|ESP-17|1|6;;00:48:19
|
||||
TMD|SAM005923|ESP-17|1|6;24;00:48:19
|
||||
TMD|SAM005923|GER-48|1|6;;00:48:28
|
||||
TMD|SAM005923|GER-48|1|6;25;00:48:28
|
||||
TMD|SAM005923|ESP-18|1|5;;00:48:42
|
||||
TMD|SAM005923|ESP-18|1|5;31;00:48:42
|
||||
TMD|SAM005923|GBR-828|1|6;;00:48:44
|
||||
TMD|SAM005923|GBR-828|1|6;26;00:48:44
|
||||
TMD|SAM005923|UKR-11|1|5;;00:48:44
|
||||
TMD|SAM005923|UKR-11|1|5;32;00:48:44
|
||||
TMD|SAM005923|ESP-16|1|6;;00:48:49
|
||||
TMD|SAM005923|ESP-16|1|6;27;00:48:49
|
||||
TMD|SAM005923|RSA-8|1|6;;00:48:50
|
||||
TMD|SAM005923|RSA-8|1|6;28;00:48:50
|
||||
TMD|SAM005923|AUT-477|1|6;;00:49:23
|
||||
TMD|SAM005923|AUT-477|1|6;29;00:49:23
|
||||
TMD|SAM005923|RUS-100|1|6;;00:49:34
|
||||
TMD|SAM005923|RUS-100|1|6;30;00:49:34
|
||||
TMD|SAM005923|ESP-18|1|6;;00:50:01
|
||||
TMD|SAM005923|ESP-18|1|6;31;00:50:01
|
||||
TMD|SAM005923|UKR-11|1|6;;00:50:06
|
||||
TMD|SAM005923|UKR-11|1|6;32;00:50:06
|
||||
TMD|SAM005923|AUT-11|1|0;;00:00:00
|
||||
TMD|SAM005923|AUT-477|1|0;;00:00:00
|
||||
TMD|SAM005923|BEL-16|1|0;;00:00:00
|
||||
TMD|SAM005923|ESP-1|1|0;;00:00:00
|
||||
TMD|SAM005923|AUT-11|1|0;;00:00:00
|
||||
TMD|SAM005923|AUT-477|1|0;;00:00:00
|
||||
RPD|SAM005923|0|2013-04-04T10:35:24+02:00|||||55|FB;4;2;39.514533;2.721949;0.8;;;256;;;;;|FP;4;2;39.512227;2.724561;0.4;;;146;;;;;|J1;3;-1;39.499894;2.750856;0.2;;;172;;;;;|J2;3;-1;39.499831;2.750965;0.0;;;32;;;;;|M1;5;2;39.526214;2.706992;0.4;;;138;;;;;|M2;5;2;39.518901;2.707612;11.9;;;316;;;;;|M3p;5;2;39.512236;2.724599;0.6;;;140;;;;;|M3s;5;378;39.513290;2.723822;1.2;;;240;;;;;|M4p;5;2;39.518983;2.720770;0.4;;;54;;;;;|M4s;5;2;39.518967;2.720791;0.4;;;60;;;;;|R1;4;2;39.518919;2.707606;10.9;;;322;;;;;|R2;4;2;39.526186;2.706922;0.6;;;124;;;;;|R3;4;0;39.499793;2.750926;0.0;;;334;;;;;|S1;5;2;39.526193;2.706945;0.4;;;140;;;;;|S2;5;2;39.518899;2.707619;11.1;;;320;;;;;|S3p;5;;;;;;;;;;;;|S3s;5;;;;;;;;;;;;|S4p;5;;;;;;;;;;;;|S4s;5;;;;;;;;;;;;|SB;2;0;39.519168;2.721933;0.4;;;280;;;;;|SP;4;0;39.517652;2.719715;0.8;;;116;;;;;|AUS-7;1;;;;;;;;;;;;|AUT-11;1;;;;;;;;;;;;|AUT-477;1;;;;;;;;;;;;|BEL-16;1;;;;;;;;;;;;|ESP-1;1;;;;;;;;;;;;|ESP-11;1;;;;;;;;;;;;|ESP-13;1;;;;;;;;;;;;|ESP-16;1;;;;;;;;;;;;|ESP-17;1;;;;;;;;;;;;|ESP-18;1;;;;;;;;;;;;|ESP-1868;1;;;;;;;;;;;;|ESP-22;1;;;;;;;;;;;;|FIN-111;1;;;;;;;;;;;;|FRA-15;1;;;;;;;;;;;;|FRA-20;1;;;;;;;;;;;;|FRA-76;1;;;;;;;;;;;;|GBR-828;1;;;;;;;;;;;;|GBR-843;1;;;;;;;;;;;;|GER-13;1;;;;;;;;;;;;|GER-15;1;;;;;;;;;;;;|GER-48;1;;;;;;;;;;;;|GER-5025;1;;;;;;;;;;;;|GER-84;1;;;;;;;;;;;;|ITA-22;1;;;;;;;;;;;;|ITA-25;1;;;;;;;;;;;;|ITA-28;1;;;;;;;;;;;;|ITA-300;1;;;;;;;;;;;;|ITA-32;1;;;;;;;;;;;;|ITA-99;1;;;;;;;;;;;;|POR-2;1;;;;;;;;;;;;|RSA-8;1;;;;;;;;;;;;|RUS-100;1;;;;;;;;;;;;|UKR-11;1;;;;;;;;;;;;|UKR-23;1;;;;;;;;;;;;
|
||||
RPD|SAM005923|3|2013-04-04T14:53:29+02:00|13:45:00|01:08:29|||55|FB;4;1;39.514595;2.726782;0.6;;;352;;;;;|FP;4;1;39.514796;2.727207;0.6;;;52;;;;;|J1;3;0;39.515297;2.721503;2.1;;;332;;;;;|J2;3;0;39.516209;2.721166;4.1;;;334;;;;;|M1;5;1;39.507557;2.700071;0.2;;;22;;;;;|M2;5;1;39.503624;2.704863;0.4;;;274;;;;;|M3p;5;1;39.511114;2.726937;0.6;;;268;;;;;|M3s;5;1;39.510720;2.727125;1.6;;;54;;;;;|M4p;5;1;39.514059;2.718241;1.4;;;110;;;;;|M4s;5;1;39.514376;2.718104;0.4;;;278;;;;;|R1;4;1;39.502029;2.704981;5.4;;;310;;;;;|R2;4;1;39.503993;2.703844;1.0;;;148;;;;;|R3;4;1;39.516238;2.721159;4.5;;;344;;;;;|S1;5;1;39.503955;2.703858;1.6;;;138;;;;;|S2;5;13;39.501492;2.706392;0.2;;;102;;;;;|S3p;5;;;;;;;;;;;;|S3s;5;;;;;;;;;;;;|S4p;5;;;;;;;;;;;;|S4s;5;;;;;;;;;;;;|SB;2;-1;39.515923;2.720501;0.2;;;160;;;;;|SP;4;1;39.512957;2.721845;0.4;;;22;;;;;|AUS-7;1;1;39.515843;2.721024;6.8;;;0;;6;0;;|AUT-11;1;0;39.516144;2.720703;0.8;;;208;;21;0;;|AUT-477;1;1;39.515635;2.720475;4.7;;;312;;29;0;;|BEL-16;1;0;39.515838;2.720738;2.3;;;224;;10;0;;|ESP-1;1;3;39.515593;2.720689;3.5;;;198;;18;0;;|ESP-11;1;0;39.516054;2.720826;5.1;;;308;;9;0;;|ESP-13;1;;;;;;;;;;;;|ESP-16;1;1;39.515497;2.721003;4.9;;;124;;27;0;;|ESP-17;1;0;39.516150;2.720747;2.5;;;204;;24;0;;|ESP-18;1;1;39.514838;2.721314;4.7;;;170;;31;0;;|ESP-1868;1;1;39.500431;2.749792;0.0;;;258;;23;0;;|ESP-22;1;1;39.516085;2.720959;4.7;;;328;;14;0;;|FIN-111;1;0;39.515651;2.721038;2.1;;;290;;13;0;;|FRA-15;1;1;39.515935;2.720659;1.6;;;178;;11;0;;|FRA-20;1;3;39.516128;2.720589;7.0;;;136;;15;0;;|FRA-76;1;1;39.516355;2.720682;6.4;;;162;;3;0;;|GBR-828;1;1;39.514721;2.721544;6.0;;;334;;26;0;;|GBR-843;1;1;39.514117;2.721600;7.2;;;330;;1;0;;|GER-13;1;-1;39.515559;2.721045;2.7;;;276;;20;0;;|GER-15;1;1;39.514951;2.721463;5.6;;;336;;2;0;;|GER-48;1;1;39.514667;2.721653;2.1;;;182;;25;0;;|GER-5025;1;1;39.515795;2.720919;2.9;;;270;;7;0;;|GER-84;1;1;39.516074;2.720768;1.7;;;280;;5;0;;|ITA-22;1;0;39.516090;2.720580;1.9;;;120;;17;0;;|ITA-25;1;1;39.515364;2.720907;1.6;;;188;;12;0;;|ITA-28;1;1;39.516405;2.720717;6.8;;;318;;19;0;;|ITA-300;1;0;39.515867;2.720828;4.1;;;182;;16;0;;|ITA-32;1;1;39.515687;2.720687;3.1;;;330;;22;0;;|ITA-99;1;-1;39.515737;2.720794;0.8;;;286;;4;0;;|POR-2;1;1;39.515852;2.720619;0.2;;;162;;8;0;;|RSA-8;1;1;39.515505;2.720828;6.6;;;20;;28;0;;|RUS-100;1;0;39.515896;2.720777;3.7;;;178;;30;0;;|UKR-11;1;1;39.516422;2.720378;3.1;;;174;;32;0;;|UKR-23;1;1;39.501535;2.750179;0.0;0.0;1.1;168;1;33;10221;3713;BFD
|
||||
+59
@@ -9,9 +9,12 @@ import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import java.net.UnknownHostException;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.NavigableSet;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@@ -43,6 +46,7 @@ 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.GPSFixTrack;
|
||||
import com.sap.sailing.domain.tracking.MarkPassing;
|
||||
import com.sap.sailing.domain.tracking.RacesHandle;
|
||||
import com.sap.sailing.domain.tracking.TrackedRace;
|
||||
import com.sap.sailing.domain.tracking.impl.EmptyWindStore;
|
||||
@@ -301,6 +305,61 @@ public class EndToEndListeningStoreAndFowardTest {
|
||||
assertEquals(7, Util.size(waypoints));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTMDMessageBeforeReceivingStartTime() throws Exception {
|
||||
final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
|
||||
String[] racesToTrack = new String[] { "SAM005923" };
|
||||
setUpUsingScript(racesToTrack, "/TMDBeforeStartTimeExample.txt");
|
||||
Set<TrackedRace> allTrackedRaces = new HashSet<TrackedRace>();
|
||||
Iterable<Regatta> allEvents = racingEventService.getAllRegattas();
|
||||
for (Regatta event : allEvents) {
|
||||
DynamicTrackedRegatta trackedRegatta = racingEventService.getTrackedRegatta(event);
|
||||
Iterable<TrackedRace> trackedRaces = trackedRegatta.getTrackedRaces();
|
||||
for (TrackedRace trackedRace : trackedRaces) {
|
||||
allTrackedRaces.add(trackedRace);
|
||||
}
|
||||
}
|
||||
assertEquals(1, allTrackedRaces.size());
|
||||
TrackedRace trackedRace = allTrackedRaces.iterator().next();
|
||||
assertEquals("SAM005923", trackedRace.getRace().getName());
|
||||
assertEquals(dateFormat.parse("2013-04-04T13:45:00+0200"), trackedRace.getStartOfRace().asDate());
|
||||
for (Competitor competitor : trackedRace.getRace().getCompetitors()) {
|
||||
if (competitor.getBoat().getSailID().equals("GBR-828")) {
|
||||
NavigableSet<MarkPassing> markPassings = trackedRace.getMarkPassings(competitor);
|
||||
assertEquals(1, markPassings.size());
|
||||
assertNotNull(markPassings.iterator().next().getTimePoint());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTMDMessageBeforeReceivingStartTimeWithManyTMDs() throws Exception {
|
||||
final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
|
||||
String[] racesToTrack = new String[] { "SAM005923" };
|
||||
setUpUsingScript(racesToTrack, "/TMDBeforeStartTimeExample_ManyTMDs.txt");
|
||||
Set<TrackedRace> allTrackedRaces = new HashSet<TrackedRace>();
|
||||
Iterable<Regatta> allEvents = racingEventService.getAllRegattas();
|
||||
for (Regatta event : allEvents) {
|
||||
DynamicTrackedRegatta trackedRegatta = racingEventService.getTrackedRegatta(event);
|
||||
Iterable<TrackedRace> trackedRaces = trackedRegatta.getTrackedRaces();
|
||||
for (TrackedRace trackedRace : trackedRaces) {
|
||||
allTrackedRaces.add(trackedRace);
|
||||
}
|
||||
}
|
||||
assertEquals(1, allTrackedRaces.size());
|
||||
TrackedRace trackedRace = allTrackedRaces.iterator().next();
|
||||
assertEquals("SAM005923", trackedRace.getRace().getName());
|
||||
assertEquals(dateFormat.parse("2013-04-04T13:45:00+0200"), trackedRace.getStartOfRace().asDate());
|
||||
for (Competitor competitor : trackedRace.getRace().getCompetitors()) {
|
||||
NavigableSet<MarkPassing> markPassings = trackedRace.getMarkPassings(competitor);
|
||||
if (competitor.getBoat().getSailID().equals("GBR-828")) {
|
||||
assertEquals(7, markPassings.size());
|
||||
}
|
||||
assertNotNull(markPassings.iterator().next().getTimePoint());
|
||||
assertEquals(trackedRace.getStartOfRace(), markPassings.iterator().next().getTimePoint());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRongRaceLogRACZero() throws Exception{
|
||||
String[] racesToTrack = new String[] { "W4702" };
|
||||
|
||||
+2
-2
@@ -121,8 +121,8 @@ public class DomainFactoryImpl implements DomainFactory {
|
||||
return baseDomainFactory.getExistingCompetitorById(getCompetitorID(boatID, boatClass));
|
||||
}
|
||||
|
||||
private Pair<String, BoatClass> getCompetitorID(String boatID, BoatClass boatClass) {
|
||||
return new Util.Pair<String, BoatClass>(boatID, boatClass);
|
||||
private String getCompetitorID(String boatID, BoatClass boatClass) {
|
||||
return boatID + "/" + boatClass.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+26
-7
@@ -22,6 +22,7 @@ import java.util.Set;
|
||||
import java.util.TimeZone;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.sap.sailing.domain.base.SpeedWithBearing;
|
||||
@@ -55,7 +56,7 @@ import com.sap.sailing.domain.swisstimingadapter.TrackerType;
|
||||
|
||||
/**
|
||||
* Implements the connector to the SwissTiming Sail Master system. It uses a host name and port number to establish the
|
||||
* connecting via TCP. The connector offers a number of explicit service request methods. Additionally, the connector
|
||||
* connection via TCP. The connector offers a number of explicit service request methods. Additionally, the connector
|
||||
* can receive "spontaneous" events sent by the sail master system. Clients can register for those spontaneous events
|
||||
* (see {@link #addSailMasterListener}).
|
||||
* <p>
|
||||
@@ -182,6 +183,7 @@ public class SailMasterConnectorImpl extends SailMasterTransceiverImpl implement
|
||||
if (message.getSequenceNumber() != null) {
|
||||
maxSequenceNumber = message.getSequenceNumber();
|
||||
}
|
||||
i++;
|
||||
notifyListenersStoredDataProgress(raceID, ((double) i)/(double) messages.size());
|
||||
}
|
||||
// now process the buffered messages one by one:
|
||||
@@ -222,9 +224,16 @@ public class SailMasterConnectorImpl extends SailMasterTransceiverImpl implement
|
||||
public void run() {
|
||||
try {
|
||||
while (!stopped) {
|
||||
ensureSocketIsOpen();
|
||||
try {
|
||||
ensureSocketIsOpen();
|
||||
Pair<String, Long> receivedMessageAndOptionalSequenceNumber = receiveMessage(socket.getInputStream());
|
||||
if (receivedMessageAndOptionalSequenceNumber == null) {
|
||||
// reached EOF; this means the socket is or can be closed
|
||||
if (socket != null && !socket.isClosed()) {
|
||||
socket.close();
|
||||
}
|
||||
socket = null;
|
||||
} else {
|
||||
SailMasterMessage message = new SailMasterMessageImpl(
|
||||
receivedMessageAndOptionalSequenceNumber.getA(),
|
||||
receivedMessageAndOptionalSequenceNumber.getB());
|
||||
@@ -243,9 +252,11 @@ public class SailMasterConnectorImpl extends SailMasterTransceiverImpl implement
|
||||
rendevouz(message);
|
||||
} else if (message.isEvent()) {
|
||||
// only notify if it hasn't been loaded from a store yet
|
||||
if (!message.getType().isRaceSpecific() || message.getSequenceNumber() == null ||
|
||||
(sequenceNumberOfLastMessageForRaceID.containsKey(message.getRaceID()) &&
|
||||
message.getSequenceNumber() > sequenceNumberOfLastMessageForRaceID.get(message.getRaceID()))) {
|
||||
if (!message.getType().isRaceSpecific()
|
||||
|| message.getSequenceNumber() == null
|
||||
|| (sequenceNumberOfLastMessageForRaceID.containsKey(message.getRaceID()) && message
|
||||
.getSequenceNumber() > sequenceNumberOfLastMessageForRaceID
|
||||
.get(message.getRaceID()))) {
|
||||
// a spontaneous event
|
||||
logger.fine("notifying message " + message);
|
||||
notifyListeners(message);
|
||||
@@ -259,16 +270,19 @@ public class SailMasterConnectorImpl extends SailMasterTransceiverImpl implement
|
||||
logger.info("SailMasterConnector received " + MessageType._STOPSERVER.name());
|
||||
stop();
|
||||
}
|
||||
}
|
||||
} catch (SocketException se) {
|
||||
// This occurs if the socket was closed which may mean the connector was stopped. Check in while
|
||||
logger.info("Caught exception "+se+" during socket operation; setting socket to null");
|
||||
socket = null;
|
||||
Thread.sleep(1000); // try again in 1s
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.throwing(SailMasterConnectorImpl.class.getName(), "run", e);
|
||||
logger.log(Level.SEVERE, "Exception in sail master connector "+SailMasterConnectorImpl.class.getName()+".run", e);
|
||||
}
|
||||
logger.info("Stopping Sail Master connector thread");
|
||||
stopped = true;
|
||||
}
|
||||
|
||||
private synchronized void buffer(SailMasterMessage message) {
|
||||
@@ -304,7 +318,8 @@ public class SailMasterConnectorImpl extends SailMasterTransceiverImpl implement
|
||||
blockingQueue.offer(message);
|
||||
}
|
||||
|
||||
protected void notifyListeners(SailMasterMessage message) throws ParseException {
|
||||
protected void notifyListeners(SailMasterMessage message) {
|
||||
try {
|
||||
switch (message.getType()) {
|
||||
case RPD:
|
||||
notifyListenersRPD(message);
|
||||
@@ -327,6 +342,10 @@ public class SailMasterConnectorImpl extends SailMasterTransceiverImpl implement
|
||||
default:
|
||||
// ignore all other messages because there are no notification patterns for those
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// broken messages are ignored
|
||||
logger.warning("Exception caught during parsing of message '" + message.getMessage() + "' : " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyListenersStoredDataProgress(String raceID, double progress) {
|
||||
|
||||
+1
-1
@@ -42,7 +42,7 @@ public class SailMasterLiveSimulatorConnectorImpl extends SailMasterConnectorImp
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void notifyListeners(SailMasterMessage message) throws ParseException {
|
||||
protected void notifyListeners(SailMasterMessage message) {
|
||||
// we cache all messages here and deliver them with a different speed
|
||||
bufferedMessageList.add(message);
|
||||
}
|
||||
|
||||
+7
@@ -5,6 +5,7 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.sap.sailing.domain.common.impl.Util.Pair;
|
||||
import com.sap.sailing.domain.swisstimingadapter.SailMasterMessage;
|
||||
@@ -17,6 +18,7 @@ import com.sap.sailing.domain.swisstimingadapter.SailMasterTransceiver;
|
||||
*
|
||||
*/
|
||||
public class SailMasterTransceiverImpl implements SailMasterTransceiver {
|
||||
private static final Logger logger = Logger.getLogger(SailMasterTransceiverImpl.class.getName());
|
||||
private static final byte STX = 0x02;
|
||||
private static final byte ETX = 0x03;
|
||||
|
||||
@@ -35,6 +37,7 @@ public class SailMasterTransceiverImpl implements SailMasterTransceiver {
|
||||
public synchronized void sendMessage(SailMasterMessage message, OutputStream os) throws IOException {
|
||||
os.write(("" + message.getSequenceNumber()).getBytes());
|
||||
sendMessage(message.getMessage(), os);
|
||||
os.flush();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -59,9 +62,13 @@ public class SailMasterTransceiverImpl implements SailMasterTransceiver {
|
||||
bufferForOptionalSequenceNumber.write(read);
|
||||
read = inputStream.read();
|
||||
}
|
||||
// now either we've read an STX byte or reached EOF
|
||||
String message = null;
|
||||
if (read == STX) {
|
||||
message = readMessage(inputStream);
|
||||
} else {
|
||||
// EOF; indicate by returning null
|
||||
logger.info("Received EOF in SailMasterTransceiver. Returning null as message.");
|
||||
}
|
||||
if (bufferForOptionalSequenceNumber.size() > 0) {
|
||||
sequenceNumber = getSequenceNumber(bufferForOptionalSequenceNumber);
|
||||
|
||||
+73
-8
@@ -10,9 +10,9 @@ import java.util.List;
|
||||
import java.util.NavigableSet;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
|
||||
import com.sap.sailing.domain.base.BoatClass;
|
||||
import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.base.Mark;
|
||||
@@ -47,6 +47,7 @@ import com.sap.sailing.domain.tracking.MarkPassing;
|
||||
import com.sap.sailing.domain.tracking.RaceTracker;
|
||||
import com.sap.sailing.domain.tracking.RacesHandle;
|
||||
import com.sap.sailing.domain.tracking.TrackedRace;
|
||||
import com.sap.sailing.domain.tracking.TrackedRaceStatus;
|
||||
import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
|
||||
import com.sap.sailing.domain.tracking.WindStore;
|
||||
import com.sap.sailing.domain.tracking.WindTrack;
|
||||
@@ -65,7 +66,12 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl implemen
|
||||
private final Regatta regatta;
|
||||
private final WindStore windStore;
|
||||
|
||||
/**
|
||||
* Starts out as <code>null</code> and is set when the race definition has been created. When this happens, this object is
|
||||
* {@link Object#notifyAll() notified}.
|
||||
*/
|
||||
private RaceDefinition race;
|
||||
|
||||
private Course course;
|
||||
private StartList startList;
|
||||
private DynamicTrackedRace trackedRace;
|
||||
@@ -74,6 +80,13 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl implemen
|
||||
private boolean loggedIgnore;
|
||||
private final long delayToLiveInMillis;
|
||||
|
||||
/**
|
||||
* For TMD messages received when there was no start time set, this message queue stores those TMD messages. When a start
|
||||
* time is received, it will be sent to the queue which in turn will re-apply all not yet applied TMD messages again to this
|
||||
* tracker by calling {@link #receivedTimingData(String, String, List)}.
|
||||
*/
|
||||
private final TMDMessageQueue tmdMessageQueue;
|
||||
|
||||
protected SwissTimingRaceTrackerImpl(String raceID, String hostname, int port, RaceLogStore raceLogStore, WindStore windStore,
|
||||
DomainFactory domainFactory, SwissTimingFactory factory, RaceSpecificMessageLoader messageLoader,
|
||||
TrackedRegattaRegistry trackedRegattaRegistry, boolean canSendRequests, long delayToLiveInMillis) throws InterruptedException,
|
||||
@@ -87,6 +100,7 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl implemen
|
||||
TrackedRegattaRegistry trackedRegattaRegistry, boolean canSendRequests, long delayToLiveInMillis) throws InterruptedException,
|
||||
UnknownHostException, IOException, ParseException {
|
||||
super();
|
||||
this.tmdMessageQueue = new TMDMessageQueue(this);
|
||||
this.regatta = regatta;
|
||||
this.connector = factory.getOrCreateSailMasterConnector(hostname, port, messageLoader, canSendRequests);
|
||||
this.domainFactory = domainFactory;
|
||||
@@ -111,6 +125,10 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl implemen
|
||||
|
||||
@Override
|
||||
public void stop() throws MalformedURLException, IOException, InterruptedException {
|
||||
if (isTrackedRaceStillReachable()) {
|
||||
TrackedRaceStatus newStatus = new TrackedRaceStatusImpl(TrackedRaceStatusEnum.FINISHED, 1.0);
|
||||
trackedRace.setStatus(newStatus);
|
||||
}
|
||||
connector.removeSailMasterListener(raceID, this);
|
||||
}
|
||||
|
||||
@@ -130,12 +148,37 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl implemen
|
||||
|
||||
@Override
|
||||
public Set<RaceDefinition> getRaces() {
|
||||
synchronized (this) {
|
||||
while (race == null) {
|
||||
try {
|
||||
this.wait();
|
||||
} catch (InterruptedException e) {
|
||||
logger.log(Level.SEVERE, "Interrupted wait", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return Collections.singleton(race);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<RaceDefinition> getRaces(long timeoutInMilliseconds) {
|
||||
return Collections.singleton(race);
|
||||
long start = System.currentTimeMillis();
|
||||
synchronized (this) {
|
||||
RaceDefinition result = race;
|
||||
boolean interrupted = false;
|
||||
while ((System.currentTimeMillis()-start < timeoutInMilliseconds) && !interrupted && result == null) {
|
||||
try {
|
||||
long timeToWait = timeoutInMilliseconds - (System.currentTimeMillis() - start);
|
||||
if (timeToWait > 0) {
|
||||
this.wait(timeToWait);
|
||||
}
|
||||
result = race;
|
||||
} catch (InterruptedException e) {
|
||||
interrupted = true;
|
||||
}
|
||||
}
|
||||
return result == null ? null : Collections.singleton(result);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -174,6 +217,7 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl implemen
|
||||
if (this.raceID.equals(raceID)) {
|
||||
if (startTime != null) {
|
||||
trackedRace.setStartTimeReceived(startTime);
|
||||
tmdMessageQueue.validStartTimeReceived();
|
||||
}
|
||||
for (Fix fix : fixes) {
|
||||
GPSFixMoving gpsFix = domainFactory.createGPSFix(timePoint, fix);
|
||||
@@ -236,11 +280,23 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl implemen
|
||||
for (Triple<Integer, Integer, Long> markIndexRankAndTimeSinceStartInMilliseconds : markIndicesRanksAndTimesSinceStartInMilliseconds) {
|
||||
Waypoint waypoint = Util.get(trackedRace.getRace().getCourse().getWaypoints(),
|
||||
markIndexRankAndTimeSinceStartInMilliseconds.getA());
|
||||
MillisecondsTimePoint timePoint = trackedRace.getStartOfRace() == null ? null : new MillisecondsTimePoint(
|
||||
trackedRace.getStartOfRace().asMillis() + markIndexRankAndTimeSinceStartInMilliseconds.getC());
|
||||
// update mark passing only if we have a start time; guessed start times don't make sense and
|
||||
// for the start line would lead subsequent calls to getStartOfRace() return that guessed start time
|
||||
// which then cannot be identified as "guessed" anymore...
|
||||
if (trackedRace.getStartOfRace() != null) {
|
||||
final TimePoint startTime = trackedRace.getStartOfRace();
|
||||
MillisecondsTimePoint timePoint = new MillisecondsTimePoint(
|
||||
startTime.asMillis() + markIndexRankAndTimeSinceStartInMilliseconds.getC());
|
||||
MarkPassing markPassing = domainFactory.createMarkPassing(timePoint, waypoint,
|
||||
domainFactory.getCompetitorByBoatIDAndBoatClass(boatID, boatClass));
|
||||
markPassingsByMarkIndex.put(markIndexRankAndTimeSinceStartInMilliseconds.getA(), markPassing);
|
||||
} else {
|
||||
//
|
||||
logger.warning("Received mark passing with time relative to start of race "+trackedRace.getRace().getName()+
|
||||
" before having received a race start time."
|
||||
+ " Queueing message for re-application when a start time has been received.");
|
||||
tmdMessageQueue.enqueue(raceID, boatID, markIndicesRanksAndTimesSinceStartInMilliseconds);
|
||||
}
|
||||
}
|
||||
trackedRace.updateMarkPassings(competitor, markPassingsByMarkIndex.values());
|
||||
} else {
|
||||
@@ -282,11 +338,11 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl implemen
|
||||
StartList oldStartList = this.startList;
|
||||
this.startList = startList;
|
||||
if (oldStartList == null && course != null) {
|
||||
createRaceDefinition(raceID);
|
||||
createRaceDefinition(raceID, course);
|
||||
}
|
||||
}
|
||||
|
||||
private void createRaceDefinition(String raceID) {
|
||||
private void createRaceDefinition(String raceID, Course course) {
|
||||
assert this.raceID.equals(raceID);
|
||||
assert startList != null;
|
||||
assert course != null;
|
||||
@@ -320,15 +376,16 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl implemen
|
||||
@Override
|
||||
public void receivedCourseConfiguration(String raceID, Course course) {
|
||||
Course oldCourse = this.course;
|
||||
this.course = course;
|
||||
if (trackedRace == null) {
|
||||
if (oldCourse == null && startList != null) {
|
||||
createRaceDefinition(raceID);
|
||||
createRaceDefinition(raceID, course);
|
||||
this.course = course;
|
||||
}
|
||||
} else {
|
||||
if (isTrackedRaceStillReachable()) {
|
||||
try {
|
||||
domainFactory.updateCourseWaypoints(trackedRace.getRace().getCourse(), course.getMarks());
|
||||
this.course = course;
|
||||
} catch (PatchFailedException e) {
|
||||
logger.info("Internal error trying to update course: " + e.getMessage());
|
||||
logger.throwing(SwissTimingRaceTrackerImpl.class.getName(), "receivedCourseConfiguration", e);
|
||||
@@ -348,4 +405,12 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl implemen
|
||||
public void receivedAvailableRaces(Iterable<Race> races) {
|
||||
// don't care
|
||||
}
|
||||
|
||||
protected DynamicTrackedRace getTrackedRace() {
|
||||
return trackedRace;
|
||||
}
|
||||
|
||||
public DomainFactory getDomainFactory() {
|
||||
return domainFactory;
|
||||
}
|
||||
}
|
||||
+152
@@ -0,0 +1,152 @@
|
||||
package com.sap.sailing.domain.swisstimingadapter.impl;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.NavigableSet;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.base.Course;
|
||||
import com.sap.sailing.domain.common.impl.Util.Triple;
|
||||
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
|
||||
import com.sap.sailing.domain.tracking.MarkPassing;
|
||||
import com.sap.sailing.domain.tracking.TrackedRace;
|
||||
|
||||
/**
|
||||
* When TMD messages about mark passings arrive with their race start time-relative time stamp, and no race start time
|
||||
* has been received yet, the TMD message contents need to be queued and re-applied once a start time for the race was
|
||||
* received. The queuing is managed by this class.
|
||||
*
|
||||
* @author Axel Uhl (D043530)
|
||||
*
|
||||
*/
|
||||
public class TMDMessageQueue {
|
||||
private static final Logger logger = Logger.getLogger(TMDMessageQueue.class.getName());
|
||||
|
||||
private static class TMDMessageContents {
|
||||
private final String raceID;
|
||||
private final String boatID;
|
||||
private final List<Triple<Integer, Integer, Long>> markIndicesRanksAndTimesSinceStartInMilliseconds;
|
||||
|
||||
public TMDMessageContents(String raceID, String boatID,
|
||||
List<Triple<Integer, Integer, Long>> markIndicesRanksAndTimesSinceStartInMilliseconds) {
|
||||
super();
|
||||
this.raceID = raceID;
|
||||
this.boatID = boatID;
|
||||
this.markIndicesRanksAndTimesSinceStartInMilliseconds = markIndicesRanksAndTimesSinceStartInMilliseconds;
|
||||
}
|
||||
|
||||
public String getRaceID() {
|
||||
return raceID;
|
||||
}
|
||||
|
||||
public String getBoatID() {
|
||||
return boatID;
|
||||
}
|
||||
|
||||
public List<Triple<Integer, Integer, Long>> getMarkIndicesRanksAndTimesSinceStartInMilliseconds() {
|
||||
return markIndicesRanksAndTimesSinceStartInMilliseconds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder result = new StringBuilder("TMD|"+getRaceID()+"|"+getBoatID()+"|");
|
||||
result.append(getMarkIndicesRanksAndTimesSinceStartInMilliseconds().size());
|
||||
result.append('|');
|
||||
for (Triple<Integer, Integer, Long> markIndexRankAndTimeSinceStartInMilliseconds : getMarkIndicesRanksAndTimesSinceStartInMilliseconds()) {
|
||||
result.append(markIndexRankAndTimeSinceStartInMilliseconds.getA());
|
||||
result.append(';');
|
||||
if (markIndexRankAndTimeSinceStartInMilliseconds.getB() != null) {
|
||||
result.append(markIndexRankAndTimeSinceStartInMilliseconds.getB());
|
||||
}
|
||||
result.append(";<");
|
||||
result.append(markIndexRankAndTimeSinceStartInMilliseconds.getC());
|
||||
result.append("ms>");
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
}
|
||||
|
||||
private final SwissTimingRaceTrackerImpl raceTracker;
|
||||
|
||||
private final Set<TMDMessageContents> queuedMessages;
|
||||
|
||||
public TMDMessageQueue(SwissTimingRaceTrackerImpl raceTracker) {
|
||||
super();
|
||||
this.raceTracker = raceTracker;
|
||||
queuedMessages = new HashSet<>();
|
||||
}
|
||||
|
||||
public synchronized void enqueue(String raceID, String boatID, List<Triple<Integer, Integer, Long>> markIndicesRanksAndTimesSinceStartInMilliseconds) {
|
||||
final TMDMessageContents message = new TMDMessageContents(raceID, boatID, markIndicesRanksAndTimesSinceStartInMilliseconds);
|
||||
queuedMessages.add(message);
|
||||
logger.info("Queued TMD message "+message+" for replay when start time has been received");
|
||||
}
|
||||
|
||||
/**
|
||||
* The tracker shall call this when a start time has been received. In case there are any messages in this queue, they will
|
||||
* be re-sent to the tracker where they now can be evaluated properly. Precondition to calling this method is that the start
|
||||
* time was set on the {@link TrackedRace} managed by the {@link SwissTimingRaceTrackerImpl}.
|
||||
*/
|
||||
public synchronized void validStartTimeReceived() {
|
||||
DynamicTrackedRace trackedRace = raceTracker.getTrackedRace();
|
||||
Iterator<TMDMessageContents> i = queuedMessages.iterator();
|
||||
Map<Competitor, TreeMap<Integer, MarkPassing>> cleansedMarkPassings = new HashMap<>();
|
||||
// first remove all mark passings that will then be updated to make sure no "guessed time" remains;
|
||||
// otherwise, this could lead to the "guessed time" to
|
||||
while (i.hasNext()) {
|
||||
TMDMessageContents messageContents = i.next();
|
||||
Competitor competitor = raceTracker.getDomainFactory().getCompetitorByBoatIDAndBoatClass(messageContents.getBoatID(),
|
||||
trackedRace.getRace().getBoatClass());
|
||||
NavigableSet<MarkPassing> oldMarkPassings = trackedRace.getMarkPassings(competitor);
|
||||
TreeMap<Integer, MarkPassing> cleansedMarkPassingsForCompetitor = cleansedMarkPassings.get(competitor);
|
||||
if (cleansedMarkPassingsForCompetitor == null) {
|
||||
cleansedMarkPassingsForCompetitor = new TreeMap<>();
|
||||
cleansedMarkPassings.put(competitor, cleansedMarkPassingsForCompetitor);
|
||||
Course course = trackedRace.getRace().getCourse();
|
||||
course.lockForRead();
|
||||
try {
|
||||
trackedRace.lockForRead(oldMarkPassings);
|
||||
try {
|
||||
for (MarkPassing oldMarkPassing : oldMarkPassings) {
|
||||
int waypointIndex = course.getIndexOfWaypoint(oldMarkPassing.getWaypoint());
|
||||
cleansedMarkPassingsForCompetitor.put(waypointIndex, oldMarkPassing);
|
||||
}
|
||||
} finally {
|
||||
trackedRace.unlockAfterRead(oldMarkPassings);
|
||||
}
|
||||
} finally {
|
||||
course.unlockAfterRead();
|
||||
}
|
||||
}
|
||||
// remove those mark passings for which the TMD message has mark passing times; their time points would just have been guessed.
|
||||
// This will avoid that the start time inference
|
||||
// rules consider them and let them take precedence over the start time received
|
||||
for (Triple<Integer, Integer, Long> markIndexRankAndTimeSinceStartInMilliseconds : messageContents.getMarkIndicesRanksAndTimesSinceStartInMilliseconds()) {
|
||||
if (cleansedMarkPassingsForCompetitor.containsKey(markIndexRankAndTimeSinceStartInMilliseconds.getA())) {
|
||||
logger.info("Removing mark passing for mark #"+markIndexRankAndTimeSinceStartInMilliseconds.getA()+
|
||||
" for competitor "+competitor.getName()+" because its time point "+
|
||||
cleansedMarkPassingsForCompetitor.get(markIndexRankAndTimeSinceStartInMilliseconds.getA()).getTimePoint()+
|
||||
" was guessed; will replace momentarily...");
|
||||
cleansedMarkPassingsForCompetitor.remove(markIndexRankAndTimeSinceStartInMilliseconds.getA());
|
||||
}
|
||||
}
|
||||
}
|
||||
for (Map.Entry<Competitor, TreeMap<Integer, MarkPassing>> e : cleansedMarkPassings.entrySet()) {
|
||||
trackedRace.updateMarkPassings(e.getKey(), e.getValue().values());
|
||||
}
|
||||
i = queuedMessages.iterator();
|
||||
while (i.hasNext()) {
|
||||
TMDMessageContents messageContents = i.next();
|
||||
logger.info("Re-Playing TMD message "+messageContents+" with new race start time "+trackedRace.getStartOfRace());
|
||||
raceTracker.receivedTimingData(messageContents.getRaceID(), messageContents.getBoatID(),
|
||||
messageContents.getMarkIndicesRanksAndTimesSinceStartInMilliseconds());
|
||||
i.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
+42
-27
@@ -151,8 +151,8 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest {
|
||||
|
||||
/**
|
||||
* Regarding bug 961, test scoring in a leaderboard that has a qualification series with two unordered groups where for one
|
||||
* column only one group has raced (expressed by a mocked TrackedRace attached to the column). Ensure that the column doesn't
|
||||
* count for neither the total points sum nor the competitor ordering.
|
||||
* column only one group has raced (expressed by a mocked TrackedRace attached to the column). Ensure that the column does
|
||||
* count for the total points sum but that the competitor ordering is controlled by the number of races.
|
||||
*/
|
||||
@Test
|
||||
public void testUnorderedGroupsWithOneGroupNotHavingRacedInAColumn() throws NoWindException {
|
||||
@@ -180,18 +180,21 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest {
|
||||
assertEquals((double) (i+1), leaderboard.getTotalPoints(competitors.get(i), later), 0.000000001);
|
||||
assertEquals((double) (i+1), leaderboard.getTotalPoints(competitors.get(i+5), later), 0.0000000001);
|
||||
}
|
||||
// now add one race for yellow fleet and test that it doesn't count because blue fleet is still missing its race for Q2
|
||||
// now add one race for yellow fleet and test that it counts for the score but not the ordering
|
||||
// because blue fleet is still missing its race for Q2
|
||||
TrackedRace q2Yellow = new MockedTrackedRaceWithStartTimeAndRanks(now, competitors.subList(3, 8));
|
||||
RaceColumn q2Column = qualificationSeries.getRaceColumnByName("Q2");
|
||||
q2Column.setTrackedRace(q2Column.getFleetByName("Yellow"), q2Yellow);
|
||||
List<Competitor> rankedCompetitorsWithOneRaceMissingInQ2 = leaderboard.getCompetitorsFromBestToWorst(later);
|
||||
for (int i=0; i<5; i++) {
|
||||
assertTrue(competitors.get(i) == rankedCompetitorsWithOneRaceMissingInQ2.get(2*i) ||
|
||||
competitors.get(i) == rankedCompetitorsWithOneRaceMissingInQ2.get(2*i+1));
|
||||
assertTrue(competitors.get(i+5) == rankedCompetitorsWithOneRaceMissingInQ2.get(2*i) ||
|
||||
competitors.get(i+5) == rankedCompetitorsWithOneRaceMissingInQ2.get(2*i+1));
|
||||
assertEquals((double) (i+1), leaderboard.getTotalPoints(competitors.get(i), later), 0.000000001);
|
||||
assertEquals((double) (i+1), leaderboard.getTotalPoints(competitors.get(i+5), later), 0.0000000001);
|
||||
// scores: C1=1, C2=2, C3=3, C4=5, C5=7, C6=4, C7=6, C8=8, C9=4, C10=5
|
||||
// ordered by scores: C1, C2, C3, C6/C9, C4/C10, C7, C5, C8
|
||||
// ordered primarily by number of races: C6, C4, C7, C5, C8, C1, C2, C3, C9, C10
|
||||
assertEquals(Arrays.asList(new Competitor[] { competitors.get(5), competitors.get(3), competitors.get(6),
|
||||
competitors.get(4), competitors.get(7), competitors.get(0), competitors.get(1), competitors.get(2),
|
||||
competitors.get(8), competitors.get(9) }), rankedCompetitorsWithOneRaceMissingInQ2);
|
||||
double[] points = new double[] { 1, 2, 3, 5, 7, 4, 6, 8, 4, 5 };
|
||||
for (int i=0; i<9; i++) {
|
||||
assertEquals(points[i], leaderboard.getTotalPoints(competitors.get(i), later), 0.000000001);
|
||||
}
|
||||
// now add a tracked race for the blue fleet for Q2 and assert that the Q2 scores count for the total points sum
|
||||
TrackedRace q2Blue = new MockedTrackedRaceWithStartTimeAndRanks(now,
|
||||
@@ -216,10 +219,13 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest {
|
||||
}
|
||||
|
||||
/**
|
||||
* Regarding bug 961, test scoring in a leaderboard that has a qualification series with two unordered groups where for one
|
||||
* column only one group has raced (expressed by a mocked TrackedRace attached to the column). Then add the second tracked
|
||||
* race for the second column but such that it hasn't started yet at the time point of the query. Then add a score correction,
|
||||
* simulating the use of a proxy race (that doesn't ever start) and a manual score entry.
|
||||
* Regarding bug 961 and 1023, test scoring in a leaderboard that has a qualification series with two unordered
|
||||
* groups where for one column only one group has raced (expressed by a mocked TrackedRace attached to the column).
|
||||
* Those competitors are expected to rank better than those who haven't raced because they have a greater number of
|
||||
* races. Then add the second tracked race for the second column but such that it hasn't started yet at the time
|
||||
* point of the query. Still, the competitors of the fleet that did race already shall be scored better. Then add a
|
||||
* score correction, simulating the use of a proxy race (that doesn't ever start) and a manual score entry. Now, the
|
||||
* second fleet shall be ranked according to the score comparison with the competitors from the first fleet.
|
||||
*/
|
||||
@Test
|
||||
public void testUnorderedGroupsWithOneGroupNotHavingStartedRacedInAcolumnAndThenCorrectingScore() throws NoWindException {
|
||||
@@ -251,15 +257,19 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest {
|
||||
competitors.get(8), competitors.get(9) }));
|
||||
q2Column.setTrackedRace(q2Column.getFleetByName("Blue"), q2Blue);
|
||||
List<Competitor> rankedCompetitorsWithOneRaceMissingInQ2 = leaderboard.getCompetitorsFromBestToWorst(later);
|
||||
for (int i=0; i<5; i++) {
|
||||
assertTrue(competitors.get(i) == rankedCompetitorsWithOneRaceMissingInQ2.get(2*i) ||
|
||||
competitors.get(i) == rankedCompetitorsWithOneRaceMissingInQ2.get(2*i+1));
|
||||
assertTrue(competitors.get(i+5) == rankedCompetitorsWithOneRaceMissingInQ2.get(2*i) ||
|
||||
competitors.get(i+5) == rankedCompetitorsWithOneRaceMissingInQ2.get(2*i+1));
|
||||
assertEquals((double) (i+1), leaderboard.getTotalPoints(competitors.get(i), later), 0.000000001);
|
||||
assertEquals((double) (i+1), leaderboard.getTotalPoints(competitors.get(i+5), later), 0.0000000001);
|
||||
// scores: C1=1, C2=2, C3=3, C4=5, C5=7, C6=4, C7=6, C8=8, C9=4, C10=5
|
||||
// ordered by scores: C1, C2, C3, C6/C9, C4/C10, C7, C5, C8
|
||||
// ordered primarily by number of races: C6, C4, C7, C5, C8, C1, C2, C3, C9, C10
|
||||
assertEquals(Arrays.asList(new Competitor[] { competitors.get(5), competitors.get(3), competitors.get(6),
|
||||
competitors.get(4), competitors.get(7), competitors.get(0), competitors.get(1), competitors.get(2),
|
||||
competitors.get(8), competitors.get(9) }), rankedCompetitorsWithOneRaceMissingInQ2);
|
||||
double[] points = new double[] { 1, 2, 3, 5, 7, 4, 6, 8, 4, 5 };
|
||||
for (int i=0; i<9; i++) {
|
||||
assertEquals(points[i], leaderboard.getTotalPoints(competitors.get(i), later), 0.000000001);
|
||||
}
|
||||
assertFalse(leaderboard.getScoringScheme().isValidInTotalScore(leaderboard, q2Column, later));
|
||||
// expect all results to be valid because a fleet will have its score counted even if not all fleets have raced;
|
||||
// see discussion for bug 961, but more importantly later on bug 1023.
|
||||
assertTrue(leaderboard.getScoringScheme().isValidInTotalScore(leaderboard, q2Column, later));
|
||||
// now add a score correction for Q2/Blue to make it count:
|
||||
leaderboard.getScoreCorrection().correctScore(competitors.get(9), q2Column, 42.);
|
||||
assertTrue(leaderboard.getScoringScheme().isValidInTotalScore(leaderboard, q2Column, later));
|
||||
@@ -271,8 +281,8 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest {
|
||||
|
||||
/**
|
||||
* Regarding bug 961, test scoring in a leaderboard that has a qualification series with two unordered groups where for one
|
||||
* column only one group has raced (expressed by a mocked TrackedRace attached to the column). Ensure that the column doesn't
|
||||
* count for computing the number of discards.
|
||||
* column only one group has raced (expressed by a mocked TrackedRace attached to the column). Those who already raced their
|
||||
* second race will have one discard, the others won't. Test this.
|
||||
*/
|
||||
@Test
|
||||
public void testDiscardsForUnorderedGroupsWithOneGroupNotHavingRacedInAColumn() throws NoWindException {
|
||||
@@ -297,9 +307,14 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest {
|
||||
TrackedRace q2Yellow = new MockedTrackedRaceWithStartTimeAndRanks(now, competitors.subList(3, 8));
|
||||
RaceColumn q2Column = qualificationSeries.getRaceColumnByName("Q2");
|
||||
q2Column.setTrackedRace(q2Column.getFleetByName("Yellow"), q2Yellow);
|
||||
for (Competitor competitor : competitors) {
|
||||
assertFalse("Competitor "+competitor+" has a discard in Q1 but shouldn't", leaderboard.isDiscarded(competitor, q1Column, later));
|
||||
assertFalse("Competitor "+competitor+" has a discard in Q2 but shouldn't", leaderboard.isDiscarded(competitor, q2Column, later));
|
||||
for (Competitor competitor : competitors.subList(3, 8)) {
|
||||
assertTrue("Competitor "+competitor+" has no discard but should have one",
|
||||
leaderboard.isDiscarded(competitor, q1Column, later) || leaderboard.isDiscarded(competitor, q2Column, later));
|
||||
}
|
||||
for (Competitor competitor : Arrays.asList(new Competitor[] { competitors.get(0), competitors.get(1), competitors.get(2),
|
||||
competitors.get(8), competitors.get(9) })) {
|
||||
assertFalse("Competitor "+competitor+" has a discard but should'nt have one",
|
||||
leaderboard.isDiscarded(competitor, q1Column, later) || leaderboard.isDiscarded(competitor, q2Column, later));
|
||||
}
|
||||
// now add a tracked race for the blue fleet for Q2 and assert that all competitors have one discard
|
||||
TrackedRace q2Blue = new MockedTrackedRaceWithStartTimeAndRanks(now,
|
||||
|
||||
Executable
+100
@@ -0,0 +1,100 @@
|
||||
package com.sap.sailing.domain.test;
|
||||
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.sap.sailing.util.impl.LockUtil;
|
||||
import com.sap.sailing.util.impl.NamedReentrantReadWriteLock;
|
||||
|
||||
/**
|
||||
* {@link ReentrantReadWriteLock} can produce a lock between readers and other readers if the lock
|
||||
* is <em>fair</em> and a writer applies between the first read lock acquisition and the second read
|
||||
* lock acquisition attempt. We try to enhance {@link LockUtil} such that it is possible to pass
|
||||
* a "lock set" from one thread to the next, such that when a thread tries to acquire a read lock
|
||||
* that is already in the thread's lock set, it doesn't really acquire the lock but only increments
|
||||
* a counter. This will then avoid the read-read deadlock.
|
||||
*
|
||||
* @author Axel Uhl (D043530)
|
||||
*
|
||||
*/
|
||||
public class ReadReadDeadlockTest {
|
||||
@Test
|
||||
public void testReadReadDeadlock() throws InterruptedException {
|
||||
final Object notifier = new Object();
|
||||
final NamedReentrantReadWriteLock lock = new NamedReentrantReadWriteLock("testReadReadDeadlock", /* fair */ true);
|
||||
final boolean[] t1ObtainedReadLock = new boolean[1];
|
||||
final Thread t1 = new Thread("t1") {
|
||||
public void run() {
|
||||
LockUtil.lockForRead(lock);
|
||||
try {
|
||||
synchronized (notifier) {
|
||||
t1ObtainedReadLock[0] = true;
|
||||
notifier.notifyAll();
|
||||
try {
|
||||
notifier.wait(); // wait for t2 to have announced acquiring the write lock
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
Thread.sleep(100); // wait until t2 really has requested the write lock
|
||||
final Thread myT1 = Thread.currentThread();
|
||||
// Now spawn a new thread and wait for its termination synchronously.
|
||||
// This guarantees that t1 will hold on to the read lock until t3's run method
|
||||
// returns. Therefore, t3 doesn't necessarily need to apply for the read lock again.
|
||||
final Thread t3 = new Thread("t3") {
|
||||
public void run() {
|
||||
LockUtil.propagateLockSetFrom(myT1);
|
||||
// without lock set propagation, this will cause a deadlock
|
||||
LockUtil.lockForRead(lock);
|
||||
try {
|
||||
System.out.println("Got read lock in t3");
|
||||
} finally {
|
||||
LockUtil.unlockAfterRead(lock);
|
||||
}
|
||||
LockUtil.unpropagateLockSetFrom(myT1);
|
||||
}
|
||||
};
|
||||
t3.start();
|
||||
try {
|
||||
t3.join(); // joins only if lock set propagation works
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
} catch (InterruptedException e1) {
|
||||
throw new RuntimeException(e1);
|
||||
} finally {
|
||||
LockUtil.unlockAfterRead(lock); // this will let t2 continue and terminate
|
||||
}
|
||||
}
|
||||
};
|
||||
final Thread t2 = new Thread("t2") {
|
||||
public void run() {
|
||||
// wait until t1 has obtained the read lock
|
||||
synchronized (notifier) {
|
||||
while (!t1ObtainedReadLock[0]) {
|
||||
try {
|
||||
notifier.wait();
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
synchronized (notifier) {
|
||||
notifier.notifyAll();
|
||||
}
|
||||
// safe to lock after notifying because waiters can only continue once
|
||||
// this method has left the synchronized(notifier) block
|
||||
LockUtil.lockForWrite(lock); // will wait for t1 which owns the read lock
|
||||
} finally {
|
||||
LockUtil.unlockAfterWrite(lock);
|
||||
}
|
||||
}
|
||||
};
|
||||
t1.start();
|
||||
t2.start();
|
||||
t1.join();
|
||||
t2.join();
|
||||
}
|
||||
}
|
||||
+80
-10
@@ -5,6 +5,12 @@ import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentSkipListSet;
|
||||
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.sap.sailing.util.impl.SmartFutureCache;
|
||||
@@ -36,10 +42,20 @@ public class SmartFutureCacheTest {
|
||||
final boolean[] updateWasCalled = new boolean[1];
|
||||
final boolean[] cacheWasCalled = new boolean[1];
|
||||
final boolean[] mayProceed = new boolean[1];
|
||||
|
||||
SmartFutureCache<String, String, SmartFutureCache.EmptyUpdateInterval> sfc = new SmartFutureCache<String, String, SmartFutureCache.EmptyUpdateInterval>(
|
||||
new SmartFutureCache.AbstractCacheUpdater<String, String, SmartFutureCache.EmptyUpdateInterval>() {
|
||||
@Override
|
||||
public String computeCacheUpdate(String key, EmptyUpdateInterval updateInterval) throws Exception {
|
||||
synchronized (mayProceed) {
|
||||
while (!mayProceed[0]) {
|
||||
try {
|
||||
mayProceed.wait();
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
} // wait until test driver has checked the un-updated cache
|
||||
}
|
||||
}
|
||||
updateWasCalled[0] = true;
|
||||
return "Humba";
|
||||
}
|
||||
@@ -47,22 +63,13 @@ public class SmartFutureCacheTest {
|
||||
@Override
|
||||
protected void cache(String key, String value) {
|
||||
super.cache(key, value);
|
||||
synchronized (mayProceed) {
|
||||
while (!mayProceed[0]) {
|
||||
try {
|
||||
cacheWasCalled.wait();
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
} // wait until test driver has checked the un-updated cache
|
||||
}
|
||||
}
|
||||
synchronized (cacheWasCalled) {
|
||||
cacheWasCalled[0] = true;
|
||||
cacheWasCalled.notifyAll();
|
||||
}
|
||||
}
|
||||
};
|
||||
sfc.triggerUpdate("Trala", /* updateInterval */ null);
|
||||
sfc.triggerUpdate("Trala", /* updateInterval */ null); // will be held up in computeCacheUpdate
|
||||
assertNull(sfc.get("Trala", /* waitForLatest */ false));
|
||||
synchronized (mayProceed) {
|
||||
mayProceed[0] = true;
|
||||
@@ -100,6 +107,11 @@ public class SmartFutureCacheTest {
|
||||
public int getB() {
|
||||
return b;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "["+getA()+", "+getB()+"]";
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -158,4 +170,62 @@ public class SmartFutureCacheTest {
|
||||
assertTrue(updateWasCalled[0]);
|
||||
assertEquals(new Integer(91), sfc.get("Trala", /* waitForLatest */ true));
|
||||
}
|
||||
|
||||
@Ignore // used only to solve bug 1314; it's hard to test re-cycling of tasks reliably without depending on timing issues
|
||||
@Test
|
||||
public void testOverloadingCacheWithUpdateRequests() throws InterruptedException {
|
||||
final Set<String> updateWasCalled = new ConcurrentSkipListSet<String>();
|
||||
final boolean[] cacheWasCalled = new boolean[1];
|
||||
final int[] computeCacheUpdateCount = new int[1];
|
||||
SmartFutureCache<String, Integer, FromAToBUpdateInterval> sfc = new SmartFutureCache<String, Integer, FromAToBUpdateInterval>(
|
||||
new SmartFutureCache.AbstractCacheUpdater<String, Integer, FromAToBUpdateInterval>() {
|
||||
@Override
|
||||
public Integer computeCacheUpdate(String key, FromAToBUpdateInterval updateInterval)
|
||||
throws Exception {
|
||||
updateWasCalled.add(key);
|
||||
computeCacheUpdateCount[0]++;
|
||||
Thread.sleep(10); // pretending a long calculation
|
||||
return updateInterval.getA() + updateInterval.getB();
|
||||
}
|
||||
}, "SmartFutureCacheTest.testJoiningOfUpdateIntervalsWhenBeingResumed") {
|
||||
@Override
|
||||
protected void cache(String key, Integer value) {
|
||||
super.cache(key, value);
|
||||
synchronized (cacheWasCalled) {
|
||||
cacheWasCalled[0] = true;
|
||||
cacheWasCalled.notifyAll();
|
||||
}
|
||||
}
|
||||
};
|
||||
String[] strings = new String[100];
|
||||
for (int i=0; i<strings.length; i++) {
|
||||
strings[i] = Integer.toString(i);
|
||||
}
|
||||
Random random = new Random();
|
||||
// Trigger very many updates shortly after each other, causing many re-calculations to be scheduled.
|
||||
final int numberOfUpdateTriggers = 1000000;
|
||||
Set<Integer> updatesTriggeredFor = new HashSet<Integer>();
|
||||
for (int i=0; i<numberOfUpdateTriggers; i++) {
|
||||
final int nextInt = random.nextInt(strings.length);
|
||||
sfc.triggerUpdate(strings[nextInt], new FromAToBUpdateInterval(i, nextInt));
|
||||
updatesTriggeredFor.add(nextInt);
|
||||
}
|
||||
System.out.println(updatesTriggeredFor.size());
|
||||
System.out.println(updatesTriggeredFor);
|
||||
System.out.println(updateWasCalled.size());
|
||||
System.out.println(updateWasCalled);
|
||||
System.out.println(computeCacheUpdateCount[0]);
|
||||
System.out.println("Tasks re-used: "+sfc.getSmartFutureCacheTaskReuseCounter());
|
||||
Thread.sleep(500);
|
||||
System.out.println(updateWasCalled.size());
|
||||
System.out.println(updateWasCalled);
|
||||
System.out.println(computeCacheUpdateCount[0]);
|
||||
System.out.println("Tasks re-used: "+sfc.getSmartFutureCacheTaskReuseCounter());
|
||||
Thread.sleep(500);
|
||||
System.out.println(updateWasCalled.size());
|
||||
System.out.println(updateWasCalled);
|
||||
System.out.println(computeCacheUpdateCount[0]);
|
||||
System.out.println("Tasks re-used: "+sfc.getSmartFutureCacheTaskReuseCounter());
|
||||
assertEquals(updatesTriggeredFor.size(), updateWasCalled.size());
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -36,7 +36,7 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
|
||||
} catch (Exception e) {
|
||||
// something went wrong during DB access; report, then use empty new wind track
|
||||
logger.log(Level.SEVERE, "Error connecting to MongoDB, unable to load recorded TracTrac configurations. Check MongoDB settings.");
|
||||
logger.throwing(DomainObjectFactoryImpl.class.getName(), "getTracTracConfigurations", e);
|
||||
logger.log(Level.SEVERE, "getTracTracConfigurations", e);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
+3
-2
@@ -14,6 +14,7 @@ import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
@@ -366,7 +367,7 @@ public class ClientParamsPHP {
|
||||
return getTimePoint("RaceTrackingStartTime");
|
||||
} catch (ParseException e) {
|
||||
logger.info("Exception trying to parse property RaceTrackingStartTime with value "+properties.get("RaceTrackingStartTime"));
|
||||
logger.throwing(ClientParamsPHP.class.getName(), "getRaceTrackingStartTime", e);
|
||||
logger.log(Level.SEVERE, "getRaceTrackingStartTime", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -376,7 +377,7 @@ public class ClientParamsPHP {
|
||||
return getTimePoint("RaceTrackingEndTime");
|
||||
} catch (ParseException e) {
|
||||
logger.info("Exception trying to parse property RaceTrackingEndTime with value "+properties.get("RaceTrackingEndTime"));
|
||||
logger.throwing(ClientParamsPHP.class.getName(), "RaceTrackingEndTime", e);
|
||||
logger.log(Level.SEVERE, "RaceTrackingEndTime", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -114,7 +114,7 @@ public class RaceCourseReceiver extends AbstractReceiverWithQueue<Route, RouteDa
|
||||
}
|
||||
} catch (PatchFailedException e) {
|
||||
logger.log(Level.SEVERE, "Internal error updating race course "+course+": "+e.getMessage());
|
||||
logger.throwing(RaceCourseReceiver.class.getName(), "handleEvent", e);
|
||||
logger.log(Level.SEVERE, "handleEvent", e);
|
||||
}
|
||||
} else {
|
||||
logger.log(Level.INFO, "Received course for non-existing race "+event.getC().getName()+". Creating RaceDefinition.");
|
||||
|
||||
+11
-7
@@ -21,6 +21,7 @@ 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;
|
||||
|
||||
|
||||
@@ -261,18 +262,21 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl implements
|
||||
private void pollAndParseClientParamsPHP(final URL paramURL, final Simulator simulator) {
|
||||
Set<RaceDefinition> raceDefinitions = getRaces();
|
||||
if (raceDefinitions != null && !raceDefinitions.isEmpty()) {
|
||||
logger.info("fetching paramURL "+paramURL+" to check for updates for race(s) "+getRaces());
|
||||
logger.fine("Fetching paramURL "+paramURL+" to check for updates for race(s) "+getRaces());
|
||||
final ClientParamsPHP clientParams;
|
||||
try {
|
||||
clientParams = new ClientParamsPHP(new InputStreamReader(paramURL.openStream()));
|
||||
List<Pair<com.sap.sailing.domain.base.ControlPoint, NauticalSide>> newCourseControlPoints = new ArrayList<>();
|
||||
List<com.sap.sailing.domain.base.ControlPoint> newCourseControlPoints = new ArrayList<>();
|
||||
List<Pair<com.sap.sailing.domain.base.ControlPoint, NauticalSide>> newCourseControlPointsWithPassingSide = new ArrayList<>();
|
||||
final List<? extends TracTracControlPoint> newTracTracControlPoints = clientParams.getRaceDefaultRoute().getControlPoints();
|
||||
Map<Integer, NauticalSide> passingSideData = parsePassingSideData(clientParams.getRaceDefaultRoute(), newTracTracControlPoints);
|
||||
int i = 1;
|
||||
for (TracTracControlPoint newTracTracControlPoint : newTracTracControlPoints) {
|
||||
NauticalSide nauticalSide = passingSideData.containsKey(i) ? passingSideData.get(i) : null;
|
||||
newCourseControlPoints.add(new Pair<com.sap.sailing.domain.base.ControlPoint,
|
||||
NauticalSide>(domainFactory.getOrCreateControlPoint(newTracTracControlPoint), nauticalSide));
|
||||
final com.sap.sailing.domain.base.ControlPoint newControlPoint = domainFactory.getOrCreateControlPoint(newTracTracControlPoint);
|
||||
newCourseControlPoints.add(newControlPoint);
|
||||
newCourseControlPointsWithPassingSide.add(new Pair<com.sap.sailing.domain.base.ControlPoint,
|
||||
NauticalSide>(newControlPoint, nauticalSide));
|
||||
i++;
|
||||
}
|
||||
List<com.sap.sailing.domain.base.ControlPoint> currentCourseControlPoints = new ArrayList<>();
|
||||
@@ -283,10 +287,10 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl implements
|
||||
if (!newCourseControlPoints.equals(currentCourseControlPoints)) {
|
||||
logger.info("Detected course change based on clientparams.php contents for races "+getRaces());
|
||||
try {
|
||||
course.update(newCourseControlPoints, domainFactory.getBaseDomainFactory());
|
||||
course.update(newCourseControlPointsWithPassingSide, domainFactory.getBaseDomainFactory());
|
||||
} catch (PatchFailedException pfe) {
|
||||
logger.severe("Failed to apply course update "+newTracTracControlPoints+" to course "+course);
|
||||
logger.throwing(TracTracRaceTrackerImpl.class.getName(), "scheduleClientParamsPHPPoller.run", pfe);
|
||||
logger.log(Level.SEVERE, "scheduleClientParamsPHPPoller.run", pfe);
|
||||
}
|
||||
}
|
||||
updateStartStopTimesAndLiveDelay(clientParams, simulator);
|
||||
@@ -311,7 +315,7 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl implements
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.info("Exception "+e.getMessage()+" while trying to read clientparams.php for races "+getRaces());
|
||||
logger.throwing(TracTracRaceTracker.class.getName(), "scheduleClientParamsPHPPoller.run", e);
|
||||
logger.log(Level.SEVERE, "scheduleClientParamsPHPPoller.run", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ import difflib.PatchFailedException;
|
||||
* @author Axel Uhl (d043530)
|
||||
*
|
||||
*/
|
||||
public interface Course extends CourseData {
|
||||
public interface Course extends CourseBase {
|
||||
void lockForRead();
|
||||
|
||||
void unlockAfterRead();
|
||||
|
||||
@@ -12,7 +12,7 @@ import com.sap.sailing.domain.common.WithID;
|
||||
* @author Axel Uhl (D043530)
|
||||
*
|
||||
*/
|
||||
public interface Event extends Named, Renamable, WithID, EventData {
|
||||
public interface Event extends Named, Renamable, WithID, EventBase {
|
||||
Iterable<Regatta> getRegattas();
|
||||
|
||||
void addRegatta(Regatta regatta);
|
||||
|
||||
@@ -18,7 +18,7 @@ import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
|
||||
* @author Axel Uhl (D043530)
|
||||
*
|
||||
*/
|
||||
public interface Series extends SeriesData {
|
||||
public interface Series extends SeriesBase {
|
||||
|
||||
/**
|
||||
* A series consists of one or more "race columns." Some people would just say "race," but we use the term "race" for
|
||||
|
||||
@@ -181,7 +181,7 @@ public class CourseImpl extends NamedImpl implements Course {
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.SEVERE, "Exception while notifying listener about waypoint " + waypointToRemove
|
||||
+ " that got removed from course " + this + ": " + e.getMessage());
|
||||
logger.throwing(CourseImpl.class.getName(), "notifyListenersWaypointRemoved", e);
|
||||
logger.log(Level.SEVERE, "notifyListenersWaypointRemoved", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -193,7 +193,7 @@ public class CourseImpl extends NamedImpl implements Course {
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.SEVERE, "Exception while notifying listener about waypoint " + waypointToAdd
|
||||
+ " that got added to course " + this + ": " + e.getMessage());
|
||||
logger.throwing(CourseImpl.class.getName(), "notifyListenersWaypointAdded", e);
|
||||
logger.log(Level.SEVERE, "notifyListenersWaypointAdded", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+6
-4
@@ -32,9 +32,10 @@ import com.sap.sailing.domain.common.WithID;
|
||||
import com.sap.sailing.domain.leaderboard.ScoringScheme;
|
||||
import com.sap.sailing.domain.leaderboard.impl.HighPoint;
|
||||
import com.sap.sailing.domain.leaderboard.impl.HighPointExtremeSailingSeriesOverall;
|
||||
import com.sap.sailing.domain.leaderboard.impl.HighPointFirstGets10;
|
||||
import com.sap.sailing.domain.leaderboard.impl.HighPointFirstGets10LastBreaksTie;
|
||||
import com.sap.sailing.domain.leaderboard.impl.HighPointLastBreaksTie;
|
||||
import com.sap.sailing.domain.leaderboard.impl.LowPoint;
|
||||
import com.sap.sailing.domain.leaderboard.impl.LowPointWinnerGetsZero;
|
||||
import com.sap.sailing.domain.tracking.MarkPassing;
|
||||
import com.sap.sailing.domain.tracking.impl.MarkPassingImpl;
|
||||
|
||||
@@ -287,9 +288,10 @@ public class DomainFactoryImpl implements DomainFactory {
|
||||
case HIGH_POINT_LAST_BREAKS_TIE:
|
||||
return new HighPointLastBreaksTie();
|
||||
case HIGH_POINT_FIRST_GETS_TEN:
|
||||
return new HighPointFirstGets10();
|
||||
default:
|
||||
return new HighPointFirstGets10LastBreaksTie();
|
||||
case LOW_POINT_WINNER_GETS_ZERO:
|
||||
return new LowPointWinnerGetsZero();
|
||||
}
|
||||
throw new RuntimeException("Unknown scoring scheme type "+scoringSchemeType.name());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import com.sap.sailing.domain.base.Event;
|
||||
import com.sap.sailing.domain.base.Regatta;
|
||||
import com.sap.sailing.domain.base.Venue;
|
||||
|
||||
public class EventImpl extends EventDataImpl implements Event {
|
||||
public class EventImpl extends EventBaseImpl implements Event {
|
||||
private static final long serialVersionUID = 855135446595485715L;
|
||||
|
||||
private final Set<Regatta> regattas;
|
||||
|
||||
@@ -20,6 +20,7 @@ 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.Series;
|
||||
import com.sap.sailing.domain.common.LeaderboardNameConstants;
|
||||
import com.sap.sailing.domain.common.RegattaIdentifier;
|
||||
import com.sap.sailing.domain.common.RegattaName;
|
||||
import com.sap.sailing.domain.common.impl.NamedImpl;
|
||||
@@ -84,8 +85,9 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene
|
||||
* carried out.
|
||||
*/
|
||||
public RegattaImpl(RaceLogStore raceLogStore, String baseName, BoatClass boatClass, TrackedRegattaRegistry trackedRegattaRegistry, ScoringScheme scoringScheme, Serializable id, CourseArea courseArea) {
|
||||
this(raceLogStore, baseName, boatClass, Collections.singletonList(new SeriesImpl("Default", /* isMedal */false, Collections
|
||||
.singletonList(new FleetImpl("Default")), /* race column names */new ArrayList<String>(),
|
||||
this(raceLogStore, baseName, boatClass, Collections.singletonList(new SeriesImpl(LeaderboardNameConstants.DEFAULT_SERIES_NAME,
|
||||
/* isMedal */false, Collections
|
||||
.singletonList(new FleetImpl(LeaderboardNameConstants.DEFAULT_FLEET_NAME)), /* race column names */new ArrayList<String>(),
|
||||
trackedRegattaRegistry)), /* persistent */false, scoringScheme, id, courseArea);
|
||||
}
|
||||
|
||||
@@ -95,7 +97,7 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene
|
||||
* this new regatta.
|
||||
*/
|
||||
public RegattaImpl(RaceLogStore raceLogStore, String baseName, BoatClass boatClass, Iterable<? extends Series> series, boolean persistent, ScoringScheme scoringScheme, Serializable id, CourseArea courseArea) {
|
||||
super(getFullName(baseName, boatClass==null?null:boatClass.getName()));
|
||||
super(getDefaultName(baseName, boatClass==null?null:boatClass.getName()));
|
||||
this.id = id;
|
||||
this.raceLogStore = raceLogStore;
|
||||
races = new HashSet<RaceDefinition>();
|
||||
@@ -126,7 +128,7 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene
|
||||
new RaceLogOnRegattaIdentifier(this, raceColumn.getName())));
|
||||
}
|
||||
|
||||
public static String getFullName(String baseName, String boatClassName) {
|
||||
public static String getDefaultName(String baseName, String boatClassName) {
|
||||
return baseName+(boatClassName==null?"":" ("+boatClassName+")");
|
||||
}
|
||||
|
||||
|
||||
@@ -90,7 +90,6 @@ public interface ScoringScheme extends Serializable {
|
||||
* for the leaderboard's total scores. This may, e.g., be the case if a column is split into more than one fleet and
|
||||
* those fleets are unordered. In that case, scores need to be available for all fleets before the column counts
|
||||
* for the total scores.
|
||||
* @param at TODO
|
||||
*/
|
||||
boolean isValidInTotalScore(Leaderboard leaderboard, RaceColumn raceColumn, TimePoint at);
|
||||
}
|
||||
+20
-12
@@ -70,6 +70,13 @@ public abstract class AbstractScoringSchemeImpl implements ScoringScheme {
|
||||
return new ScoreComparator(nullScoresAreBetter);
|
||||
}
|
||||
|
||||
/**
|
||||
* This default implementation handles the somewhat tricky case where a score is considered valid for the total score only
|
||||
* if for multiple unordered fleets in the race all fleets have raced or are currently racing. There have been controversial
|
||||
* discussions whether this is desired. Usually, subclasses will override such that a result is generally valid for the
|
||||
* total scores, and the {@link #compareByNumberOfRacesScored(int, int)} method has to just rank those better who ran
|
||||
* more races.
|
||||
*/
|
||||
@Override
|
||||
public boolean isValidInTotalScore(Leaderboard leaderboard, RaceColumn raceColumn, TimePoint at) {
|
||||
boolean result;
|
||||
@@ -124,22 +131,23 @@ public abstract class AbstractScoringSchemeImpl implements ScoringScheme {
|
||||
public int compareByBetterScore(List<Pair<RaceColumn, Double>> o1Scores, List<Pair<RaceColumn, Double>> o2Scores, boolean nullScoresAreBetter) {
|
||||
assert o1Scores.size() == o2Scores.size();
|
||||
final Comparator<Double> pureScoreComparator = getScoreComparator(nullScoresAreBetter);
|
||||
Comparator<Pair<RaceColumn, Double>> scoreComparator = new Comparator<Pair<RaceColumn, Double>>() {
|
||||
@Override
|
||||
public int compare(Pair<RaceColumn, Double> o1, Pair<RaceColumn, Double> o2) {
|
||||
return pureScoreComparator.compare(o1.getB(), o2.getB());
|
||||
// needs to compare net points; therefore, divide the total points by the column factor for comparison:
|
||||
List<Double> o1NetScores = new ArrayList<>();
|
||||
for (Pair<RaceColumn, Double> o1ColumnAndScore : o1Scores) {
|
||||
o1NetScores.add(o1ColumnAndScore.getB()/o1ColumnAndScore.getA().getFactor());
|
||||
}
|
||||
};
|
||||
List<Pair<RaceColumn, Double>> o1ScoresCopy = new ArrayList<Pair<RaceColumn, Double>>(o1Scores);
|
||||
List<Pair<RaceColumn, Double>> o2ScoresCopy = new ArrayList<Pair<RaceColumn, Double>>(o2Scores);
|
||||
Collections.sort(o1ScoresCopy, scoreComparator);
|
||||
Collections.sort(o2ScoresCopy, scoreComparator);
|
||||
List<Double> o2NetScores = new ArrayList<>();
|
||||
for (Pair<RaceColumn, Double> o2ColumnAndScore : o2Scores) {
|
||||
o2NetScores.add(o2ColumnAndScore.getB()/o2ColumnAndScore.getA().getFactor());
|
||||
}
|
||||
Collections.sort(o1NetScores, pureScoreComparator);
|
||||
Collections.sort(o2NetScores, pureScoreComparator);
|
||||
// now both lists are sorted from best to worst score
|
||||
Iterator<Pair<RaceColumn, Double>> o1Iter = o1ScoresCopy.iterator();
|
||||
Iterator<Pair<RaceColumn, Double>> o2Iter = o2ScoresCopy.iterator();
|
||||
Iterator<Double> o1Iter = o1NetScores.iterator();
|
||||
Iterator<Double> o2Iter = o2NetScores.iterator();
|
||||
int result = 0;
|
||||
while (result == 0 && o1Iter.hasNext() && o2Iter.hasNext()) {
|
||||
result = scoreComparator.compare(o1Iter.next(), o2Iter.next());
|
||||
result = pureScoreComparator.compare(o1Iter.next(), o2Iter.next());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
+1
-1
@@ -280,7 +280,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
|
||||
|
||||
/**
|
||||
* All competitors with non-<code>null</code> net points are added to the result which is then sorted by net points in ascending
|
||||
* order. The fleet is the primary ordering criterion, followed by the net points.
|
||||
* order. The fleet, if ordered, is the primary ordering criterion, followed by the net points.
|
||||
*/
|
||||
@Override
|
||||
public List<Competitor> getCompetitorsFromBestToWorst(final RaceColumn raceColumn, TimePoint timePoint) throws NoWindException {
|
||||
|
||||
+2
-1
@@ -14,6 +14,7 @@ import com.sap.sailing.domain.base.Fleet;
|
||||
import com.sap.sailing.domain.base.RaceColumn;
|
||||
import com.sap.sailing.domain.base.RaceColumnListener;
|
||||
import com.sap.sailing.domain.base.impl.FleetImpl;
|
||||
import com.sap.sailing.domain.common.LeaderboardNameConstants;
|
||||
import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard;
|
||||
import com.sap.sailing.domain.leaderboard.FlexibleRaceColumn;
|
||||
import com.sap.sailing.domain.leaderboard.ScoringScheme;
|
||||
@@ -38,7 +39,7 @@ import com.sap.sailing.domain.tracking.TrackedRace;
|
||||
public class FlexibleLeaderboardImpl extends AbstractLeaderboardImpl implements FlexibleLeaderboard {
|
||||
private static Logger logger = Logger.getLogger(FlexibleLeaderboardImpl.class.getName());
|
||||
|
||||
protected static final Fleet defaultFleet = new FleetImpl("Default");
|
||||
protected static final Fleet defaultFleet = new FleetImpl(LeaderboardNameConstants.DEFAULT_FLEET_NAME);
|
||||
private static final long serialVersionUID = -5708971849158747846L;
|
||||
private final List<FlexibleRaceColumn> races;
|
||||
private final ScoringScheme scoringScheme;
|
||||
|
||||
@@ -4,6 +4,8 @@ import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.base.RaceColumn;
|
||||
import com.sap.sailing.domain.common.MaxPointsReason;
|
||||
import com.sap.sailing.domain.common.ScoringSchemeType;
|
||||
import com.sap.sailing.domain.common.TimePoint;
|
||||
import com.sap.sailing.domain.leaderboard.Leaderboard;
|
||||
|
||||
|
||||
/**
|
||||
@@ -44,4 +46,10 @@ public class HighPoint extends AbstractScoringSchemeImpl {
|
||||
public ScoringSchemeType getType() {
|
||||
return ScoringSchemeType.HIGH_POINT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValidInTotalScore(Leaderboard leaderboard, RaceColumn raceColumn, TimePoint at) {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
-16
@@ -1,16 +0,0 @@
|
||||
package com.sap.sailing.domain.leaderboard.impl;
|
||||
|
||||
import com.sap.sailing.domain.common.ScoringSchemeType;
|
||||
|
||||
public class HighPointFirstGets10 extends HighPointFirstGetsFixedScore {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public HighPointFirstGets10() {
|
||||
super(10.0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScoringSchemeType getType() {
|
||||
return ScoringSchemeType.HIGH_POINT_FIRST_GETS_TEN;
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package com.sap.sailing.domain.leaderboard.impl;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.sap.sailing.domain.base.RaceColumn;
|
||||
import com.sap.sailing.domain.common.ScoringSchemeType;
|
||||
import com.sap.sailing.domain.common.impl.Util.Pair;
|
||||
|
||||
public class HighPointFirstGets10LastBreaksTie extends HighPointFirstGetsFixedScore {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public HighPointFirstGets10LastBreaksTie() {
|
||||
super(10.0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScoringSchemeType getType() {
|
||||
return ScoringSchemeType.HIGH_POINT_FIRST_GETS_TEN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareByBetterScore(List<Pair<RaceColumn, Double>> o1Scores, List<Pair<RaceColumn, Double>> o2Scores, boolean nullScoresAreBetter) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
-6
@@ -2,7 +2,6 @@ package com.sap.sailing.domain.leaderboard.impl;
|
||||
|
||||
import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.base.RaceColumn;
|
||||
import com.sap.sailing.domain.common.MaxPointsReason;
|
||||
|
||||
|
||||
/**
|
||||
@@ -36,9 +35,4 @@ public abstract class HighPointFirstGetsFixedScore extends HighPoint {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double getPenaltyScore(RaceColumn raceColumn, Competitor competitor, MaxPointsReason maxPointsReason, Integer numberOfCompetitorsInRace, int numberOfCompetitorsInLeaderboard) {
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -261,6 +261,6 @@ public class LeaderboardTotalRankComparator implements Comparator<Competitor> {
|
||||
* Compares a competitor better (lesser) if it has the greater number of races scored
|
||||
*/
|
||||
protected int compareByNumberOfRacesScored(int o1NumberOfRacesScored, int o2NumberOfRacesScored) {
|
||||
return o2NumberOfRacesScored - o1NumberOfRacesScored;
|
||||
return scoringScheme.compareByNumberOfRacesScored(o1NumberOfRacesScored, o2NumberOfRacesScored);
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,8 @@ import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.base.RaceColumn;
|
||||
import com.sap.sailing.domain.common.MaxPointsReason;
|
||||
import com.sap.sailing.domain.common.ScoringSchemeType;
|
||||
import com.sap.sailing.domain.common.TimePoint;
|
||||
import com.sap.sailing.domain.leaderboard.Leaderboard;
|
||||
|
||||
|
||||
/**
|
||||
@@ -42,4 +44,9 @@ public class LowPoint extends AbstractScoringSchemeImpl {
|
||||
public ScoringSchemeType getType() {
|
||||
return ScoringSchemeType.LOW_POINT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValidInTotalScore(Leaderboard leaderboard, RaceColumn raceColumn, TimePoint at) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Executable
+41
@@ -0,0 +1,41 @@
|
||||
package com.sap.sailing.domain.leaderboard.impl;
|
||||
|
||||
import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.base.RaceColumn;
|
||||
import com.sap.sailing.domain.common.ScoringSchemeType;
|
||||
|
||||
/**
|
||||
* As compared to the regular low-point scheme, the 2013 ISAF World Cup will score the winner of a race with
|
||||
* 0 points, all others with their ranks. Ties are broken like this:
|
||||
* <ul>
|
||||
* <li>25.9 RRS B8 is deleted. RRS A8 is changed as follows:
|
||||
* <li> 25.9.1 For boats competing in a medal stage, ties in the regatta score are broken by the medal stage score.
|
||||
* <li> 25.9.2 Ties in a medal stage with a single race are broken by applying RRS A8 to the opening series scores.
|
||||
* <li> 25.9.3 All other series ties shall be broken in accordance with A8.2.
|
||||
* </ul>
|
||||
*
|
||||
* Discards are to be constrained to each series, with no discards in the medal series. However, since there is
|
||||
* a new rule for carrying the qualification results to the final series, this won't be a regular regatta
|
||||
* leaderboard anyhow. Instead, the final series starts out with an artificial race that has its scores based
|
||||
* on the competitor rankings after the qualification series, where each fleet's scores again start with 0, then 2, ...
|
||||
* So the best boat in the silver fleet will get 0 points in the carry race, the next boat 2, and so on.
|
||||
* The carry race is discardable.
|
||||
*
|
||||
* @author Axel Uhl (D043530)
|
||||
*
|
||||
*/
|
||||
public class LowPointWinnerGetsZero extends LowPoint {
|
||||
private static final long serialVersionUID = 3405249102284162690L;
|
||||
|
||||
@Override
|
||||
public Double getScoreForRank(RaceColumn raceColumn, Competitor competitor, int rank, Integer numberOfCompetitorsInRace) {
|
||||
return rank == 0 ? null : rank == 1 ? 0.0 : (double) rank;
|
||||
}
|
||||
|
||||
// TODO handle tie-breaking rules properly
|
||||
|
||||
@Override
|
||||
public ScoringSchemeType getType() {
|
||||
return ScoringSchemeType.LOW_POINT_WINNER_GETS_ZERO;
|
||||
}
|
||||
}
|
||||
+4
-1
@@ -11,12 +11,15 @@ public abstract class AbstractRaceTrackerImpl implements RaceTracker {
|
||||
@Override
|
||||
public Set<RegattaAndRaceIdentifier> getRaceIdentifiers() {
|
||||
Set<RegattaAndRaceIdentifier> result = new HashSet<RegattaAndRaceIdentifier>();
|
||||
for (RaceDefinition race : getRaces()) {
|
||||
final Set<RaceDefinition> races = getRaces();
|
||||
if (races != null) {
|
||||
for (RaceDefinition race : races) {
|
||||
TrackedRace trackedRace = getTrackedRegatta().getTrackedRace(race);
|
||||
if (trackedRace != null) {
|
||||
result.add(trackedRace.getRaceIdentifier());
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -47,11 +47,11 @@ public interface RaceTracker {
|
||||
com.sap.sailing.domain.base.Regatta getRegatta();
|
||||
|
||||
/**
|
||||
* Returns the races being tracked by this tracker. Non-blocking call that returns <code>null</code> if
|
||||
* the {@link RaceDefinition} for a TracTrac Event hasn't been created yet, e.g., because the course definition
|
||||
* hasn't been received yet or the listener for receiving course information hasn't been registered (yet).
|
||||
* Also returns races that have been removed from containing structures which may lead this tracker to no
|
||||
* longer update their {@link TrackedRace} with new data.
|
||||
* Returns the races being tracked by this tracker. Non-blocking call that returns <code>null</code> or an empty set
|
||||
* if the {@link RaceDefinition} for a TracTrac Event hasn't been created yet, e.g., because the course definition
|
||||
* hasn't been received yet or the listener for receiving course information hasn't been registered (yet). Also
|
||||
* returns races that have been removed from containing structures which may lead this tracker to no longer update
|
||||
* their {@link TrackedRace} with new data.
|
||||
*/
|
||||
Set<RaceDefinition> getRaces();
|
||||
|
||||
|
||||
+7
-2
@@ -1,14 +1,17 @@
|
||||
package com.sap.sailing.domain.tracking.impl;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.sap.sailing.domain.base.Mark;
|
||||
import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.base.Leg;
|
||||
import com.sap.sailing.domain.base.Mark;
|
||||
import com.sap.sailing.domain.base.Timed;
|
||||
import com.sap.sailing.domain.base.Waypoint;
|
||||
import com.sap.sailing.domain.base.impl.MillisecondsTimePoint;
|
||||
@@ -334,7 +337,9 @@ public class CrossTrackErrorCache extends AbstractRaceChangeListener {
|
||||
@Override
|
||||
public void markPositionChanged(GPSFix fix, Mark mark) {
|
||||
TimePoint from = owner.getOrCreateTrack(mark).getEstimatedPositionTimePeriodAffectedBy(fix).getA();
|
||||
for (Competitor competitor : cachePerCompetitor.keySet()) {
|
||||
final List<Competitor> shuffledCompetitors = new ArrayList<>(cachePerCompetitor.keySet());
|
||||
Collections.shuffle(shuffledCompetitors);
|
||||
for (Competitor competitor : shuffledCompetitors) {
|
||||
invalidate(competitor, from);
|
||||
}
|
||||
}
|
||||
|
||||
+24
-11
@@ -21,6 +21,7 @@ import com.sap.sailing.domain.base.RaceDefinition;
|
||||
import com.sap.sailing.domain.base.Waypoint;
|
||||
import com.sap.sailing.domain.base.impl.MillisecondsTimePoint;
|
||||
import com.sap.sailing.domain.common.TimePoint;
|
||||
import com.sap.sailing.domain.common.TimingConstants;
|
||||
import com.sap.sailing.domain.common.WindSource;
|
||||
import com.sap.sailing.domain.common.WindSourceType;
|
||||
import com.sap.sailing.domain.common.impl.Util;
|
||||
@@ -238,7 +239,7 @@ DynamicTrackedRace, GPSTrackListener<Competitor, GPSFixMoving> {
|
||||
listener.windSourcesToExcludeChanged(windSourcesToExclude);
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.SEVERE, "RaceChangeListener " + listener + " threw exception " + e.getMessage());
|
||||
logger.throwing(DynamicTrackedRaceImpl.class.getName(), "notifyListenersWindSourcesToExcludeChanged(Iterable<WindSource>)", e);
|
||||
logger.log(Level.SEVERE, "notifyListenersWindSourcesToExcludeChanged(Iterable<WindSource>)", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -254,7 +255,7 @@ DynamicTrackedRace, GPSTrackListener<Competitor, GPSFixMoving> {
|
||||
listener.raceTimesChanged(startOfTracking, endOfTracking, startTimeReceived);
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.SEVERE, "RaceChangeListener " + listener + " threw exception " + e.getMessage());
|
||||
logger.throwing(DynamicTrackedRaceImpl.class.getName(), "notifyListenersRaceTimesChanged(TimePoint, TimePoint, TimePoint)", e);
|
||||
logger.log(Level.SEVERE, "notifyListenersRaceTimesChanged(TimePoint, TimePoint, TimePoint)", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -269,7 +270,7 @@ DynamicTrackedRace, GPSTrackListener<Competitor, GPSFixMoving> {
|
||||
listener.markPositionChanged(fix, mark);
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.SEVERE, "RaceChangeListener " + listener + " threw exception " + e.getMessage());
|
||||
logger.throwing(DynamicTrackedRaceImpl.class.getName(), "notifyListeners(GPSFix, Competitor)", e);
|
||||
logger.log(Level.SEVERE, "notifyListeners(GPSFix, Competitor)", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -284,7 +285,7 @@ DynamicTrackedRace, GPSTrackListener<Competitor, GPSFixMoving> {
|
||||
listener.competitorPositionChanged(fix, competitor);
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.SEVERE, "RaceChangeListener " + listener + " threw exception " + e.getMessage());
|
||||
logger.throwing(DynamicTrackedRaceImpl.class.getName(), "notifyListeners(GPSFix, Competitor)", e);
|
||||
logger.log(Level.SEVERE, "notifyListeners(GPSFix, Competitor)", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -299,7 +300,7 @@ DynamicTrackedRace, GPSTrackListener<Competitor, GPSFixMoving> {
|
||||
listener.statusChanged(status);
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.SEVERE, "RaceChangeListener " + listener + " threw exception " + e.getMessage());
|
||||
logger.throwing(DynamicTrackedRaceImpl.class.getName(), "notifyListeners(TrackedRaceStatus)", e);
|
||||
logger.log(Level.SEVERE, "notifyListeners(TrackedRaceStatus)", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -314,7 +315,7 @@ DynamicTrackedRace, GPSTrackListener<Competitor, GPSFixMoving> {
|
||||
listener.windDataReceived(wind, windSource);
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.SEVERE, "RaceChangeListener " + listener + " threw exception " + e.getMessage());
|
||||
logger.throwing(DynamicTrackedRaceImpl.class.getName(), "notifyListeners(Wind)", e);
|
||||
logger.log(Level.SEVERE, "notifyListeners(Wind)", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -330,7 +331,7 @@ DynamicTrackedRace, GPSTrackListener<Competitor, GPSFixMoving> {
|
||||
newMillisecondsOverWhichToAverageSpeed);
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.SEVERE, "RaceChangeListener " + listener + " threw exception " + e.getMessage());
|
||||
logger.throwing(DynamicTrackedRaceImpl.class.getName(),
|
||||
logger.log(Level.SEVERE,
|
||||
"notifyListenersSpeedAveragingChanged(long, long)", e);
|
||||
}
|
||||
}
|
||||
@@ -347,7 +348,7 @@ DynamicTrackedRace, GPSTrackListener<Competitor, GPSFixMoving> {
|
||||
newMillisecondsOverWhichToAverageWind);
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.SEVERE, "RaceChangeListener " + listener + " threw exception " + e.getMessage());
|
||||
logger.throwing(DynamicTrackedRaceImpl.class.getName(),
|
||||
logger.log(Level.SEVERE,
|
||||
"notifyListenersWindAveragingChanged(long, long)", e);
|
||||
}
|
||||
}
|
||||
@@ -363,7 +364,7 @@ DynamicTrackedRace, GPSTrackListener<Competitor, GPSFixMoving> {
|
||||
listener.delayToLiveChanged(delayToLiveInMillis);
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.SEVERE, "RaceChangeListener " + listener + " threw exception " + e.getMessage());
|
||||
logger.throwing(DynamicTrackedRaceImpl.class.getName(),
|
||||
logger.log(Level.SEVERE,
|
||||
"notifyListenersDelayToLiveChanged(long)", e);
|
||||
}
|
||||
}
|
||||
@@ -379,7 +380,7 @@ DynamicTrackedRace, GPSTrackListener<Competitor, GPSFixMoving> {
|
||||
listener.windDataRemoved(wind, windSource);
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.SEVERE, "RaceChangeListener " + listener + " threw exception " + e.getMessage());
|
||||
logger.throwing(DynamicTrackedRaceImpl.class.getName(), "notifyListenersWindRemoved(Wind)", e);
|
||||
logger.log(Level.SEVERE, "notifyListenersWindRemoved(Wind)", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -394,7 +395,7 @@ DynamicTrackedRace, GPSTrackListener<Competitor, GPSFixMoving> {
|
||||
listener.markPassingReceived(competitor, oldMarkPassings, markPassings);
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.SEVERE, "RaceChangeListener " + listener + " threw exception " + e.getMessage());
|
||||
logger.throwing(DynamicTrackedRaceImpl.class.getName(), "notifyListeners(MarkPassing)", e);
|
||||
logger.log(Level.SEVERE, "notifyListeners(MarkPassing)", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -567,11 +568,23 @@ DynamicTrackedRace, GPSTrackListener<Competitor, GPSFixMoving> {
|
||||
|
||||
@Override
|
||||
public void recordWind(Wind wind, WindSource windSource) {
|
||||
// TODO check what a good filter is; remember that start/end of tracking may change over time; what if we have discarded valuable wind fixes?
|
||||
TimePoint startOfRace = getStartOfRace();
|
||||
TimePoint startOfTracking = getStartOfTracking();
|
||||
TimePoint endOfRace = getEndOfRace();
|
||||
TimePoint endOfTracking = getEndOfTracking();
|
||||
// record wind fix only if it's still within reasonable time after the race has ended or the race hasn't ended yet
|
||||
if ((startOfTracking == null || !startOfTracking.after(wind.getTimePoint()) ||
|
||||
(startOfRace != null && !startOfRace.after(wind.getTimePoint())))
|
||||
&&
|
||||
(endOfTracking == null || endOfTracking.plus(TimingConstants.IS_LIVE_GRACE_PERIOD_IN_MILLIS).after(wind.getTimePoint()) ||
|
||||
(endOfRace != null && endOfRace.plus(TimingConstants.IS_LIVE_GRACE_PERIOD_IN_MILLIS).after(wind.getTimePoint())))) {
|
||||
getOrCreateWindTrack(windSource).add(wind);
|
||||
updated(/* time point */null); // wind events shouldn't advance race time
|
||||
triggerManeuverCacheRecalculationForAllCompetitors();
|
||||
notifyListeners(wind, windSource);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeWind(Wind wind, WindSource windSource) {
|
||||
|
||||
+19
-2
@@ -1604,7 +1604,15 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
getRace().getCourse().lockForRead(); // ensure the course doesn't change, particularly lose the leg we're interested in, while we're running
|
||||
try {
|
||||
for (Competitor competitor : getRace().getCompetitors()) {
|
||||
TrackedLegOfCompetitor leg = getTrackedLeg(competitor, timePoint);
|
||||
TrackedLegOfCompetitor leg;
|
||||
try {
|
||||
leg = getTrackedLeg(competitor, timePoint);
|
||||
} catch (IllegalArgumentException iae) {
|
||||
logger.warning("Caught "+iae+" during wind estimation; ignoring seemingly broken leg");
|
||||
logger.log(Level.SEVERE, "clusterBearingsByLegType", iae);
|
||||
// supposedly, we got a "Waypoint X isn't start of any leg in Y" exception; leg not found
|
||||
leg = null;
|
||||
}
|
||||
// if bearings was set to null this indicates there was an exception; no need for further calculations, return null
|
||||
if (bearings != null && leg != null) {
|
||||
TrackedLeg trackedLeg = getTrackedLeg(leg.getLeg());
|
||||
@@ -1756,7 +1764,12 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
}
|
||||
|
||||
protected void triggerManeuverCacheRecalculationForAllCompetitors() {
|
||||
for (Competitor competitor : getRace().getCompetitors()) {
|
||||
final List<Competitor> shuffledCompetitors = new ArrayList<>();
|
||||
for (Competitor competitor : (getRace().getCompetitors())) {
|
||||
shuffledCompetitors.add(competitor);
|
||||
}
|
||||
Collections.shuffle(shuffledCompetitors);
|
||||
for (Competitor competitor : shuffledCompetitors) {
|
||||
triggerManeuverCacheRecalculation(competitor);
|
||||
}
|
||||
}
|
||||
@@ -1768,6 +1781,8 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
}
|
||||
|
||||
private Triple<TimePoint, TimePoint, List<Maneuver>> computeManeuvers(Competitor competitor) throws NoWindException {
|
||||
logger.fine("computeManeuvers("+competitor.getName()+") called in tracked race "+this);
|
||||
long startedAt = System.currentTimeMillis();
|
||||
// compute the maneuvers for competitor
|
||||
Triple<TimePoint, TimePoint, List<Maneuver>> result = null;
|
||||
NavigableSet<MarkPassing> markPassings = getMarkPassings(competitor);
|
||||
@@ -1806,6 +1821,8 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
result = new Triple<TimePoint, TimePoint, List<Maneuver>>(extendedFrom, extendedTo, extendedResultForCache);
|
||||
} // else competitor has no fixes to consider; remove any maneuver cache entry
|
||||
} // else competitor hasn't started yet; remove any maneuver cache entry
|
||||
logger.fine("computeManeuvers("+competitor.getName()+") called in tracked race "+this+
|
||||
" took "+(System.currentTimeMillis()-startedAt)+"ms");
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -115,7 +115,7 @@ public class WindTrackImpl extends TrackImpl<Wind> implements WindTrack {
|
||||
listener.windDataReceived(wind);
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.SEVERE, "WindListener " + listener + " threw exception " + e.getMessage());
|
||||
logger.throwing(WindTrackImpl.class.getName(), "notifyListenersAboutReceive(Wind)", e);
|
||||
logger.log(Level.SEVERE, "notifyListenersAboutReceive(Wind)", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -128,7 +128,7 @@ public class WindTrackImpl extends TrackImpl<Wind> implements WindTrack {
|
||||
listener.windAveragingChanged(oldMillisecondsOverWhichToAverage, newMillisecondsOverWhichToAverage);
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.SEVERE, "WindListener " + listener + " threw exception " + e.getMessage());
|
||||
logger.throwing(WindTrackImpl.class.getName(), "notifyListenersAboutAveragingChange(long, long)", e);
|
||||
logger.log(Level.SEVERE, "notifyListenersAboutAveragingChange(long, long)", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -141,7 +141,7 @@ public class WindTrackImpl extends TrackImpl<Wind> implements WindTrack {
|
||||
listener.windDataRemoved(wind);
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.SEVERE, "WindListener " + listener + " threw exception " + e.getMessage());
|
||||
logger.throwing(WindTrackImpl.class.getName(), "notifyListenersAboutRemoval(Wind)", e);
|
||||
logger.log(Level.SEVERE, "notifyListenersAboutRemoval(Wind)", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@ import java.util.concurrent.FutureTask;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.sap.sailing.util.impl.SmartFutureCache.UpdateInterval;
|
||||
@@ -74,11 +76,20 @@ public class SmartFutureCache<K, V, U extends UpdateInterval<U>> {
|
||||
* {@link #ongoingManeuverCacheRecalculations}. This will let {@link Future#cancel(boolean)} return <code>false</code>
|
||||
* should it be called on that Future.
|
||||
*/
|
||||
private final Map<K, FutureTaskWithCancelBlocking<V, U>> ongoingRecalculations;
|
||||
private final Map<K, FutureTaskWithCancelBlocking> ongoingRecalculations;
|
||||
|
||||
private final Map<K, V> cache;
|
||||
|
||||
private final Executor recalculator;
|
||||
/**
|
||||
* Note that this needs to have more than one thread because there may be calculations used for cache updates that
|
||||
* need to wait for other cache updates to finish. If those were all to be handled by a single thread, deadlocks
|
||||
* would occur. Remember that there may still be single-core machines, so the factor with which
|
||||
* <code>availableProcessors</code> is multiplied needs to be greater than one at least.
|
||||
*/
|
||||
private final static Executor recalculator = new ThreadPoolExecutor(/* corePoolSize */ 0,
|
||||
/* maximumPoolSize */ 3*Runtime.getRuntime().availableProcessors(),
|
||||
/* keepAliveTime */ 60, TimeUnit.SECONDS,
|
||||
/* workQueue */ new LinkedBlockingQueue<Runnable>());
|
||||
|
||||
private final CacheUpdater<K, V, U> cacheUpdateComputer;
|
||||
|
||||
@@ -93,6 +104,8 @@ public class SmartFutureCache<K, V, U extends UpdateInterval<U>> {
|
||||
|
||||
private final Map<K, U> triggeredWhileSuspended;
|
||||
|
||||
private int smartFutureCacheTaskReuseCounter;
|
||||
|
||||
/**
|
||||
* An immutable "interval" description for a cache update
|
||||
*
|
||||
@@ -162,6 +175,20 @@ public class SmartFutureCache<K, V, U extends UpdateInterval<U>> {
|
||||
}
|
||||
}
|
||||
|
||||
private static class SettableCallable<V> implements Callable<V> {
|
||||
private Callable<V> callable;
|
||||
|
||||
public void setCallable(Callable<V> callable) {
|
||||
this.callable = callable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public V call() throws Exception {
|
||||
return callable.call();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Once a client has fetched such a Future from {@link TrackedRaceImpl##ongoingManeuverCacheRecalculations} while
|
||||
* holding the object monitor of {@link TrackedRaceImpl##ongoingManeuverCacheRecalculations}, the client knows that
|
||||
@@ -173,14 +200,88 @@ public class SmartFutureCache<K, V, U extends UpdateInterval<U>> {
|
||||
* @author Axel Uhl (D043530)
|
||||
*
|
||||
*/
|
||||
private static class FutureTaskWithCancelBlocking<V, U extends UpdateInterval<U>> extends FutureTask<V> {
|
||||
private class FutureTaskWithCancelBlocking extends FutureTask<V> implements Callable<V> {
|
||||
private boolean dontCancel;
|
||||
|
||||
private final U updateInterval;
|
||||
private boolean runningAndReadUpdateInterval;
|
||||
|
||||
public FutureTaskWithCancelBlocking(Callable<V> callable, U updateInterval) {
|
||||
private final K key;
|
||||
|
||||
private U updateInterval;
|
||||
|
||||
private final boolean callerWaitsSynchronouslyForResult;
|
||||
|
||||
private final Thread callerThread;
|
||||
|
||||
public FutureTaskWithCancelBlocking(final K key, final U updateInterval,
|
||||
final boolean callerWaitsSynchronouslyForResult, final Thread callerThread) {
|
||||
this(new SettableCallable<V>(), key, updateInterval, callerWaitsSynchronouslyForResult, callerThread);
|
||||
}
|
||||
|
||||
public FutureTaskWithCancelBlocking(SettableCallable<V> callable, final K key, final U updateInterval,
|
||||
final boolean callerWaitsSynchronouslyForResult, final Thread callerThread) {
|
||||
super(callable);
|
||||
callable.setCallable(this);
|
||||
this.key = key;
|
||||
this.updateInterval = updateInterval;
|
||||
this.callerWaitsSynchronouslyForResult = callerWaitsSynchronouslyForResult;
|
||||
this.callerThread = callerThread;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to update this task's update interval so as to re-used an already scheduled task for a given key.
|
||||
* The update can only be performed if the task hasn't read the update interval after it has been started.
|
||||
*
|
||||
* @return <code>true</code> if the update was applied successfully before the task read the update interval; this means
|
||||
* that if the task is not cancelled, it will use the new update interval. If <code>false</code> is returned, the task has already
|
||||
* read the update interval, and a change to the update interval for this task is no longer possible. The caller will probably
|
||||
* have to schedule a new task with the new update interval.
|
||||
*/
|
||||
public synchronized boolean tryToUpdateUpdateInterval(U newUpdateInterval) {
|
||||
final boolean result;
|
||||
if (runningAndReadUpdateInterval) {
|
||||
result = false;
|
||||
} else {
|
||||
result = true;
|
||||
updateInterval = newUpdateInterval;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public V call() {
|
||||
try {
|
||||
final U updateInterval;
|
||||
synchronized (this) {
|
||||
updateInterval = getUpdateInterval();
|
||||
runningAndReadUpdateInterval = true;
|
||||
}
|
||||
if (callerWaitsSynchronouslyForResult) {
|
||||
LockUtil.propagateLockSetFrom(callerThread);
|
||||
}
|
||||
try {
|
||||
V preResult = cacheUpdateComputer.computeCacheUpdate(key, updateInterval);
|
||||
final NamedReentrantReadWriteLock lock = getOrCreateLockForKey(key);
|
||||
LockUtil.lockForWrite(lock);
|
||||
try {
|
||||
V result = cacheUpdateComputer.provideNewCacheValue(key, cache.get(key), preResult,
|
||||
updateInterval);
|
||||
cache(key, result);
|
||||
return result;
|
||||
} finally {
|
||||
LockUtil.unlockAfterWrite(lock);
|
||||
ongoingRecalculations.remove(key);
|
||||
}
|
||||
} finally {
|
||||
if (callerWaitsSynchronouslyForResult) {
|
||||
LockUtil.unpropagateLockSetFrom(callerThread);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// cache won't be updated
|
||||
logger.log(Level.SEVERE, "triggerUpdate", e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void dontCancel() {
|
||||
@@ -196,17 +297,18 @@ public class SmartFutureCache<K, V, U extends UpdateInterval<U>> {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isCallerWaitsSynchronouslyForResult() {
|
||||
return callerWaitsSynchronouslyForResult;
|
||||
}
|
||||
|
||||
public U getUpdateInterval() {
|
||||
return updateInterval;
|
||||
}
|
||||
}
|
||||
|
||||
public SmartFutureCache(CacheUpdater<K, V, U> cacheUpdateComputer, String nameForLocks) {
|
||||
this.ongoingRecalculations = new ConcurrentHashMap<K, FutureTaskWithCancelBlocking<V, U>>();
|
||||
this.ongoingRecalculations = new ConcurrentHashMap<K, FutureTaskWithCancelBlocking>();
|
||||
this.cache = new ConcurrentHashMap<K, V>();
|
||||
this.recalculator = new ThreadPoolExecutor(/* corePoolSize */ 0,
|
||||
/* maximumPoolSize */ 1, /* keepAliveTime */ 60, TimeUnit.SECONDS,
|
||||
/* workQueue */ new LinkedBlockingQueue<Runnable>());
|
||||
this.cacheUpdateComputer = cacheUpdateComputer;
|
||||
this.locksForKeys = new ConcurrentHashMap<K, NamedReentrantReadWriteLock>();
|
||||
this.nameForLocks = nameForLocks;
|
||||
@@ -241,19 +343,28 @@ public class SmartFutureCache<K, V, U extends UpdateInterval<U>> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers a cache update for <code>key</code> for the <code>updateInterval</code> specified. If a re-calculation
|
||||
* for this key is already scheduled, this method will try to update its update interval by
|
||||
* {@link UpdateInterval#join(UpdateInterval) joining} the new with the existing one. If that doesn't work because
|
||||
* the task has already been started and has already read its update interval, this method will schedule a new task
|
||||
* with the extended update interval.
|
||||
* <p>
|
||||
*
|
||||
* If the running task has a different setting for the caller's waiting for the task, the task will be canceled
|
||||
* (which may or may not work), and a new task with the joined update interval is scheduled.
|
||||
*/
|
||||
public void triggerUpdate(final K key, U updateInterval) {
|
||||
// establish and maintain the following invariant: after lock on ongoingManeuverCacheRecalculations is released,
|
||||
// establish and maintain the following invariant: after lock on ongoingRecalculations is released,
|
||||
// no Future contained in it is in cancelled state
|
||||
synchronized (ongoingRecalculations) {
|
||||
final U oldUpdateInterval;
|
||||
final U joinedUpdateInterval;
|
||||
FutureTaskWithCancelBlocking oldFuture = null;
|
||||
if (suspended) {
|
||||
oldUpdateInterval = triggeredWhileSuspended.get(key);
|
||||
} else {
|
||||
FutureTaskWithCancelBlocking<V, U> oldFuture = ongoingRecalculations.get(key);
|
||||
if (oldFuture != null) {
|
||||
oldFuture.cancel(/* mayInterruptIfRunning */false);
|
||||
}
|
||||
oldFuture = ongoingRecalculations.get(key);
|
||||
oldUpdateInterval = oldFuture == null ? null : oldFuture.getUpdateInterval();
|
||||
}
|
||||
if (oldUpdateInterval == null) {
|
||||
@@ -268,42 +379,41 @@ public class SmartFutureCache<K, V, U extends UpdateInterval<U>> {
|
||||
if (suspended) {
|
||||
triggeredWhileSuspended.put(key, joinedUpdateInterval);
|
||||
} else {
|
||||
createAndExecuteRecalculation(key, joinedUpdateInterval);
|
||||
boolean reuseExistingFuture = false;
|
||||
if (oldFuture != null) {
|
||||
if (!oldFuture.isCallerWaitsSynchronouslyForResult()) {
|
||||
reuseExistingFuture = oldFuture.tryToUpdateUpdateInterval(joinedUpdateInterval);
|
||||
}
|
||||
if (!reuseExistingFuture) {
|
||||
oldFuture.cancel(/* mayInterruptIfRunning */ false);
|
||||
} else {
|
||||
smartFutureCacheTaskReuseCounter++;
|
||||
}
|
||||
}
|
||||
if (!reuseExistingFuture) {
|
||||
// the following call adds the resulting new Future to ongoingRecalculations
|
||||
createAndExecuteRecalculation(key, joinedUpdateInterval, /* callerWaitsSynchronouslyForResult */ false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link FutureTask} for the (re-)calculation of the cache entry for <code>key</code> across update interval
|
||||
* <code>joinedUpdateInterval</code>, enters it into {@link #ongoingRecalculations} and schedules its execution with
|
||||
* {@link #recalculator}. The method synchronizes on {@link #ongoingRecalculations}.
|
||||
* Creates a {@link FutureTask} for the (re-)calculation of the cache entry for <code>key</code> across update
|
||||
* interval <code>joinedUpdateInterval</code>, enters it into {@link #ongoingRecalculations} and schedules its
|
||||
* execution with {@link #recalculator}. The method synchronizes on {@link #ongoingRecalculations}.
|
||||
*
|
||||
* @param callerWaitsSynchronouslyForResult
|
||||
* if <code>true</code>, this allows the future to assume the caller's locks. See also
|
||||
* {@link LockUtil#propagateLockSetFrom(Thread)}. This can be helpful to avoid read-read deadlocks
|
||||
* in conjunction with fair {@link ReentrantReadWriteLock}s.
|
||||
*/
|
||||
private void createAndExecuteRecalculation(final K key, final U joinedUpdateInterval) {
|
||||
private void createAndExecuteRecalculation(final K key, final U joinedUpdateInterval,
|
||||
final boolean callerWaitsSynchronouslyForResult) {
|
||||
final Thread callerThread = Thread.currentThread();
|
||||
synchronized (ongoingRecalculations) {
|
||||
final FutureTaskWithCancelBlocking<V, U> future;
|
||||
future = new FutureTaskWithCancelBlocking<V, U>(new Callable<V>() {
|
||||
@Override
|
||||
public V call() {
|
||||
try {
|
||||
V preResult = cacheUpdateComputer.computeCacheUpdate(key, joinedUpdateInterval);
|
||||
final NamedReentrantReadWriteLock lock = getOrCreateLockForKey(key);
|
||||
LockUtil.lockForWrite(lock);
|
||||
try {
|
||||
V result = cacheUpdateComputer.provideNewCacheValue(key, cache.get(key), preResult,
|
||||
joinedUpdateInterval);
|
||||
cache(key, result);
|
||||
return result;
|
||||
} finally {
|
||||
LockUtil.unlockAfterWrite(lock);
|
||||
ongoingRecalculations.remove(key);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// cache won't be updated
|
||||
logger.throwing(SmartFutureCache.class.getName(), "triggerUpdate", e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}, joinedUpdateInterval);
|
||||
final FutureTaskWithCancelBlocking future = new FutureTaskWithCancelBlocking(key,
|
||||
joinedUpdateInterval, callerWaitsSynchronouslyForResult, callerThread);
|
||||
ongoingRecalculations.put(key, future);
|
||||
recalculator.execute(future);
|
||||
}
|
||||
@@ -318,16 +428,22 @@ public class SmartFutureCache<K, V, U extends UpdateInterval<U>> {
|
||||
public V get(K key, boolean waitForLatest) {
|
||||
V value = null;
|
||||
if (waitForLatest) {
|
||||
FutureTaskWithCancelBlocking<V, U> future;
|
||||
FutureTaskWithCancelBlocking future;
|
||||
synchronized (ongoingRecalculations) {
|
||||
if (suspended && waitForLatest) {
|
||||
final boolean wasTriggeredWhileSuspended = triggeredWhileSuspended.containsKey(key); // update interval may have deliberately been null
|
||||
if (wasTriggeredWhileSuspended) {
|
||||
// If suspended and the caller wants to get the latest results, and during suspend recalculations were
|
||||
// triggered, and execute them now.
|
||||
// If suspended and the caller wants to get the latest results, and while suspended recalculations were
|
||||
// triggered, execute them now.
|
||||
// This will enter the new future into ongoingRecalculations, and therefore this method
|
||||
// will wait for its completion below.
|
||||
createAndExecuteRecalculation(key, triggeredWhileSuspended.remove(key));
|
||||
// It is OK to assert that this method will synchronously wait for the results before
|
||||
// releasing any locks held by the current thread because
|
||||
// createAndExecuteRecalculation adds key to ongoingRecalculations, and
|
||||
// further down this method, the future will be fetched from ongoingRecalculations
|
||||
// with that same key, and a get will be performed on that future.
|
||||
createAndExecuteRecalculation(key, triggeredWhileSuspended.remove(key),
|
||||
/* callerWaitsSynchronouslyForResult */ true);
|
||||
}
|
||||
}
|
||||
// as long as we hold the lock on ongoingManeuverCacheRecalculations, the Futures contained in it are not cancelled
|
||||
@@ -342,7 +458,7 @@ public class SmartFutureCache<K, V, U extends UpdateInterval<U>> {
|
||||
try {
|
||||
value = future.get();
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
logger.throwing(SmartFutureCache.class.getName(), "get", e);
|
||||
logger.log(Level.SEVERE, "get", e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
} // else no calculation currently going on; value has been fetched from latest cache entry
|
||||
@@ -364,8 +480,18 @@ public class SmartFutureCache<K, V, U extends UpdateInterval<U>> {
|
||||
protected void cache(final K key, V value) {
|
||||
if (value == null) {
|
||||
cache.remove(key);
|
||||
locksForKeys.remove(key);
|
||||
} else {
|
||||
cache.put(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For debugging and testing; tells how many times a task got re-cycled with a new update interval instead of canceling it
|
||||
* and scheduling a new one. See also bug 1314.
|
||||
*/
|
||||
public int getSmartFutureCacheTaskReuseCounter() {
|
||||
return smartFutureCacheTaskReuseCounter;
|
||||
}
|
||||
|
||||
}
|
||||
+2
-1
@@ -79,7 +79,8 @@ public class UDPExpeditionReceiverTest {
|
||||
"#0,9,320.0*05",
|
||||
"#0,9,320.0*05",
|
||||
"#0,1,7.700,2,-36.0,3,25.10,4,-49.5,5,19.41,6,271.5,9,321.0,12,1.07,50,327.3,146,40348.390058*10",
|
||||
"#0,9,321.0*04"
|
||||
"#0,9,321.0*04",
|
||||
"#5,2,-163.2,3,0.00,13,305.8,39,2.0,48,39.500717,49,2.747750*X19"
|
||||
};
|
||||
|
||||
someValidWithFourInvalidLines = new String[] {
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<launchConfiguration type="org.eclipse.jdt.launching.localJavaApplication">
|
||||
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
|
||||
<listEntry value="/com.sap.sailing.expeditionconnector/src/com/sap/sailing/expeditionconnector/FromHttpToUdpMirror.java"/>
|
||||
</listAttribute>
|
||||
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
|
||||
<listEntry value="1"/>
|
||||
</listAttribute>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.sap.sailing.expeditionconnector.FromHttpToUdpMirror"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-v http://test.sapsailing.com/sailingserver/expedition 2012"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="com.sap.sailing.expeditionconnector"/>
|
||||
</launchConfiguration>
|
||||
+1
-8
@@ -13,7 +13,7 @@ import java.net.InetAddress;
|
||||
* @author Axel Uhl (d043530)
|
||||
*
|
||||
*/
|
||||
public class UDPMirror implements Runnable {
|
||||
public class UDPMirror {
|
||||
|
||||
/**
|
||||
* @param args 0: the port to listen to; 2*i-1, 2*i for i>0: host/port to which to forward
|
||||
@@ -55,13 +55,6 @@ public class UDPMirror implements Runnable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Listens for an inbound TCP connection
|
||||
*/
|
||||
public void run() {
|
||||
|
||||
}
|
||||
|
||||
private static void usage() {
|
||||
System.out.println("Usage: java "+UDPMirror.class.getName()+" [-v] <listeningport> hostname1 port1 [hostname2 port2]*");
|
||||
System.out.println(" -v\tPrint packets received to stdout");
|
||||
|
||||
+10
-3
@@ -3,6 +3,7 @@ package com.sap.sailing.expeditionconnector.impl;
|
||||
import java.net.DatagramPacket;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@@ -13,6 +14,7 @@ import com.sap.sailing.expeditionconnector.UDPExpeditionReceiver;
|
||||
import com.sap.sailing.udpconnector.UDPMessageParser;
|
||||
|
||||
public class ExpeditionMessageParser implements UDPMessageParser<ExpeditionMessage> {
|
||||
private static final Logger logger = Logger.getLogger(ExpeditionMessageParser.class.getName());
|
||||
|
||||
private UDPExpeditionReceiver receiver;
|
||||
|
||||
@@ -25,7 +27,7 @@ public class ExpeditionMessageParser implements UDPMessageParser<ExpeditionMessa
|
||||
String packetAsString = new String(p.getData(), p.getOffset(), p.getLength()).trim();
|
||||
if (packetAsString.length() > 0) {
|
||||
Pattern completeLinePattern = Pattern
|
||||
.compile("#([0-9]*)(( *,([0-9][0-9]*) *, *(-?[0-9]*(\\.[0-9]*)?))*)\\*([0-9a-fA-F][0-9a-fA-F]*)");
|
||||
.compile("#([0-9]*)(( *,([0-9][0-9]*) *, *(-?[0-9]*(\\.[0-9]*)?))*)\\*X?([0-9a-fA-F][0-9a-fA-F]*)");
|
||||
Matcher m = completeLinePattern.matcher(packetAsString);
|
||||
boolean valid = m.matches();
|
||||
if (valid) {
|
||||
@@ -62,7 +64,7 @@ public class ExpeditionMessageParser implements UDPMessageParser<ExpeditionMessa
|
||||
}
|
||||
return result;
|
||||
} else {
|
||||
System.err.println("Unparsable expedition message: " + packetAsString);
|
||||
logger.warning("Unparsable expedition message: " + packetAsString);
|
||||
return null; // couldn't even parse
|
||||
}
|
||||
} else {
|
||||
@@ -72,7 +74,12 @@ public class ExpeditionMessageParser implements UDPMessageParser<ExpeditionMessa
|
||||
|
||||
private boolean checksumOk(int checksum, String packetAsString) {
|
||||
int b = 0;
|
||||
for (byte stringByte : packetAsString.substring(0, packetAsString.lastIndexOf('*')).getBytes()) {
|
||||
int posOfLastCharToAddToChecksum = packetAsString.lastIndexOf('*');
|
||||
if (packetAsString.length() > posOfLastCharToAddToChecksum && packetAsString.charAt(posOfLastCharToAddToChecksum+1) == 'X') {
|
||||
posOfLastCharToAddToChecksum++;
|
||||
}
|
||||
String checksumString = packetAsString.substring(0, posOfLastCharToAddToChecksum);
|
||||
for (byte stringByte : checksumString.getBytes()) {
|
||||
b ^= stringByte;
|
||||
}
|
||||
return b == checksum;
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
<plugin id="com.sap.sailing.www" autoStart="true" startLevel="4" />
|
||||
<plugin id="com.sap.sailing.www.events" autoStart="true" startLevel="4" />
|
||||
<plugin id="com.sap.sailing.xcelsiusadapter" autoStart="true" startLevel="4" />
|
||||
<plugin id="com.sap.sailing.xrr.resultimport" autoStart="true" startLevel="3" />
|
||||
<plugin id="org.eclipse.equinox.common" autoStart="true" startLevel="2" />
|
||||
<plugin id="org.eclipse.equinox.simpleconfigurator" autoStart="true" startLevel="2" />
|
||||
<plugin id="org.eclipse.jetty.continuation" autoStart="true" startLevel="4" />
|
||||
|
||||
@@ -230,4 +230,11 @@ Computes leaderboard information for sailing races and offers RESTful APIs to al
|
||||
version="0.0.0"
|
||||
unpack="false"/>
|
||||
|
||||
<plugin
|
||||
id="com.sap.sailing.xrr.resultimport"
|
||||
download-size="0"
|
||||
install-size="0"
|
||||
version="0.0.0"
|
||||
unpack="false"/>
|
||||
|
||||
</feature>
|
||||
+2
-1
@@ -24,7 +24,8 @@ public class TestColumnSwapping {
|
||||
private LeaderboardDTO lb = null;
|
||||
private static final String LEADERBOARDNAME = "test";
|
||||
private static final String DEFAULT_FLEET_NAME = "Default";
|
||||
private static final FleetDTO DEFAULT_FLEET = new FleetDTO(DEFAULT_FLEET_NAME, /* ordering */ 0, /* color */ null);
|
||||
private static final String DEFAULT_SERIES_NAME = "Default";
|
||||
private static final FleetDTO DEFAULT_FLEET = new FleetDTO(DEFAULT_FLEET_NAME, DEFAULT_SERIES_NAME, /* ordering */ 0, /* color */ null);
|
||||
private SailingServiceImpl service;
|
||||
private LeaderboardDTO leaderboardOriginalDTO;
|
||||
private LeaderboardDTO leaderboardDTO;
|
||||
|
||||
@@ -131,3 +131,7 @@ body, table td, select, button {
|
||||
.gwt-DialogBox {
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
.bold {
|
||||
font-family: "OpenSansCondensedBold", sans-serif;
|
||||
}
|
||||
@@ -176,8 +176,7 @@ input.magnifier {
|
||||
margin: 0 0 25px 0;
|
||||
}
|
||||
|
||||
.leaderboardGroupPanel-LeaderboardsTableLabel {
|
||||
margin: 0 0 10px;
|
||||
.leaderboardGroupPanel-LeaderboardsLabel {
|
||||
font-size: 17px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
-39
@@ -1,39 +0,0 @@
|
||||
package com.sap.sailing.gwt.ui.actions;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.gwt.user.client.rpc.AsyncCallback;
|
||||
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
|
||||
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
|
||||
import com.sap.sailing.gwt.ui.shared.CompetitorDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.GPSFixDTO;
|
||||
|
||||
|
||||
public class GetBoatPositionsAction extends DefaultAsyncAction<Map<CompetitorDTO, List<GPSFixDTO>>> {
|
||||
private final SailingServiceAsync sailingService;
|
||||
private final RegattaAndRaceIdentifier raceIdentifier;
|
||||
private final Map<CompetitorDTO, Date> from;
|
||||
private final Map<CompetitorDTO, Date> to;
|
||||
private final boolean extrapolate;
|
||||
|
||||
|
||||
public GetBoatPositionsAction(SailingServiceAsync sailingService, RegattaAndRaceIdentifier raceIdentifier,
|
||||
Map<CompetitorDTO, Date> from, Map<CompetitorDTO, Date> to, boolean extrapolate,
|
||||
AsyncCallback<Map<CompetitorDTO, List<GPSFixDTO>>> callback) {
|
||||
super(callback);
|
||||
this.sailingService = sailingService;
|
||||
this.raceIdentifier = raceIdentifier;
|
||||
this.from = from;
|
||||
this.to = to;
|
||||
this.extrapolate = extrapolate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(AsyncActionsExecutor asyncActionsExecutor) {
|
||||
sailingService.getBoatPositions(raceIdentifier, from, to, extrapolate,
|
||||
(AsyncCallback<Map<CompetitorDTO, List<GPSFixDTO>>>) getWrapperCallback(asyncActionsExecutor));
|
||||
}
|
||||
|
||||
}
|
||||
+19
-14
@@ -1,5 +1,7 @@
|
||||
package com.sap.sailing.gwt.ui.adminconsole;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import com.google.gwt.user.client.ui.DoubleBox;
|
||||
import com.google.gwt.user.client.ui.Grid;
|
||||
import com.google.gwt.user.client.ui.Label;
|
||||
@@ -13,7 +15,7 @@ import com.sap.sailing.gwt.ui.shared.PositionDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.RaceDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.WindDTO;
|
||||
|
||||
public class WindSettingDialog extends DataEntryDialog<WindDTO> {
|
||||
public class AddWindFixDialog extends DataEntryDialog<WindDTO> {
|
||||
private final StringMessages stringMessages;
|
||||
|
||||
private final DoubleBox speedInKnotsBox;
|
||||
@@ -23,7 +25,7 @@ public class WindSettingDialog extends DataEntryDialog<WindDTO> {
|
||||
private final DateBox timeBox;
|
||||
|
||||
protected static class WindDataValidator implements Validator<WindDTO> {
|
||||
protected final StringMessages stringMessages;
|
||||
private final StringMessages stringMessages;
|
||||
|
||||
public WindDataValidator(StringMessages stringMessages){
|
||||
super();
|
||||
@@ -42,6 +44,8 @@ public class WindSettingDialog extends DataEntryDialog<WindDTO> {
|
||||
errorMessage = stringMessages.pleaseEnterAValue();
|
||||
} else if(windDTO.trueWindFromDeg != null && (windDTO.trueWindFromDeg < 0.0 || windDTO.trueWindFromDeg > 360.0)){
|
||||
errorMessage = stringMessages.valueMustBeBetweenMinMax(stringMessages.fromDeg(), "0", "360");
|
||||
} else if(windDTO.measureTimepoint == null) {
|
||||
errorMessage = stringMessages.pleaseEnterAValue();
|
||||
} else if(windDTO.position != null) {
|
||||
if(windDTO.position.latDeg < -90.0 || windDTO.position.latDeg > 90.0){
|
||||
errorMessage = stringMessages.valueMustBeBetweenMinMax(stringMessages.latitude(), "-90", "90");
|
||||
@@ -54,12 +58,12 @@ public class WindSettingDialog extends DataEntryDialog<WindDTO> {
|
||||
}
|
||||
}
|
||||
|
||||
public WindSettingDialog(RaceDTO race, CoursePositionsDTO courseDTO, StringMessages stringMessages, DialogCallback<WindDTO> callback) {
|
||||
public AddWindFixDialog(RaceDTO race, CoursePositionsDTO courseDTO, StringMessages stringMessages, DialogCallback<WindDTO> callback) {
|
||||
super(stringMessages.actionAddWindData(), null, stringMessages.ok(), stringMessages.cancel(), new WindDataValidator(stringMessages), callback);
|
||||
this.stringMessages = stringMessages;
|
||||
speedInKnotsBox = createDoubleBox(5);
|
||||
fromInDegBox = createDoubleBox(5);
|
||||
if(courseDTO != null && courseDTO.waypointPositions.get(0) != null) {
|
||||
if(courseDTO != null && courseDTO.waypointPositions != null && courseDTO.waypointPositions.get(0) != null) {
|
||||
PositionDTO positionDTO = courseDTO.waypointPositions.get(0);
|
||||
latDegBox = createDoubleBox(positionDTO.latDeg, 10);
|
||||
lngDegBox = createDoubleBox(positionDTO.lngDeg, 10);
|
||||
@@ -67,10 +71,11 @@ public class WindSettingDialog extends DataEntryDialog<WindDTO> {
|
||||
latDegBox = createDoubleBox(10);
|
||||
lngDegBox = createDoubleBox(10);
|
||||
}
|
||||
if(race.startOfRace != null) {
|
||||
timeBox = createDateBox(race.startOfRace.getTime(), 20);
|
||||
if(race.trackedRace != null && race.trackedRace.startOfTracking != null) {
|
||||
timeBox = createDateBox(race.trackedRace.startOfTracking.getTime(), 20);
|
||||
} else {
|
||||
timeBox = createDateBox(20);
|
||||
Date now = new Date();
|
||||
timeBox = createDateBox(now.getTime(), 20);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,12 +89,12 @@ public class WindSettingDialog extends DataEntryDialog<WindDTO> {
|
||||
grid.setWidget(0, 1, speedInKnotsBox);
|
||||
grid.setWidget(1, 0, new Label(stringMessages.fromDeg() + ":"));
|
||||
grid.setWidget(1, 1, fromInDegBox);
|
||||
grid.setWidget(2, 0, new Label(stringMessages.latitude() + " (" + stringMessages.optional() + "):"));
|
||||
grid.setWidget(2, 1, latDegBox);
|
||||
grid.setWidget(3, 0, new Label(stringMessages.longitude() + " (" + stringMessages.optional() + "):"));
|
||||
grid.setWidget(3, 1, lngDegBox);
|
||||
grid.setWidget(4, 0, new Label(stringMessages.time() + " (" + stringMessages.optional() + "):"));
|
||||
grid.setWidget(4, 1, timeBox);
|
||||
grid.setWidget(2, 0, new Label(stringMessages.time() + ":"));
|
||||
grid.setWidget(2, 1, timeBox);
|
||||
grid.setWidget(3, 0, new Label(stringMessages.latitude() + " (" + stringMessages.optional() + "):"));
|
||||
grid.setWidget(3, 1, latDegBox);
|
||||
grid.setWidget(4, 0, new Label(stringMessages.longitude() + " (" + stringMessages.optional() + "):"));
|
||||
grid.setWidget(4, 1, lngDegBox);
|
||||
|
||||
return mainPanel;
|
||||
}
|
||||
@@ -100,7 +105,7 @@ public class WindSettingDialog extends DataEntryDialog<WindDTO> {
|
||||
|
||||
result.trueWindSpeedInKnots = speedInKnotsBox.getValue();
|
||||
result.trueWindFromDeg = fromInDegBox.getValue();
|
||||
result.measureTimepoint = timeBox.getValue() != null ? timeBox.getValue().getTime() : System.currentTimeMillis();
|
||||
result.measureTimepoint = timeBox.getValue() != null ? timeBox.getValue().getTime() : null;
|
||||
if (latDegBox.getValue() != null && lngDegBox.getValue() != null) {
|
||||
result.position = new PositionDTO(latDegBox.getValue(), lngDegBox.getValue());
|
||||
}
|
||||
+1
-1
@@ -96,7 +96,7 @@ public class AdminConsoleEntryPoint extends AbstractEntryPoint implements Regatt
|
||||
|
||||
final LeaderboardConfigPanel leaderboardConfigPanel = new LeaderboardConfigPanel(sailingService, this, this, stringMessages,
|
||||
/* showRaceDetails */ true);
|
||||
//leaderboardConfigPanel.ensureDebugId("LeaderboardConfiguration");
|
||||
leaderboardConfigPanel.ensureDebugId("LeaderboardConfiguration");
|
||||
leaderboardConfigPanel.setSize("90%", "90%");
|
||||
tabPanel.add(leaderboardConfigPanel, stringMessages.leaderboardConfiguration(), /* asHTML */ false);
|
||||
regattaDisplayers.add(leaderboardConfigPanel);
|
||||
|
||||
+1
@@ -13,6 +13,7 @@ public class FlexibleLeaderboardCreateDialog extends FlexibleLeaderboardDialog {
|
||||
Collection<EventDTO> existingEvents, ErrorReporter errorReporter, DialogCallback<LeaderboardDescriptor> callback) {
|
||||
super(stringMessages.createFlexibleLeaderboard(), new LeaderboardDescriptor(), stringMessages, existingEvents, errorReporter, new FlexibleLeaderboardDialog.LeaderboardParameterValidator(stringMessages, existingLeaderboards), callback);
|
||||
nameTextBox = createTextBox(null);
|
||||
nameTextBox.ensureDebugId("LeaderboardNameField");
|
||||
displayNameTextBox = createTextBox(null);
|
||||
nameTextBox.setVisibleLength(50);
|
||||
displayNameTextBox.setVisibleLength(50);
|
||||
|
||||
+10
-6
@@ -52,6 +52,7 @@ import com.sap.sailing.gwt.ui.adminconsole.DisablableCheckboxCell.IsEnabled;
|
||||
import com.sap.sailing.gwt.ui.adminconsole.RaceColumnInLeaderboardDialog.RaceColumnDescriptor;
|
||||
import com.sap.sailing.gwt.ui.client.DataEntryDialog.DialogCallback;
|
||||
import com.sap.sailing.gwt.ui.client.ErrorReporter;
|
||||
import com.sap.sailing.gwt.ui.client.MarkedAsyncCallback;
|
||||
import com.sap.sailing.gwt.ui.client.ParallelExecutionCallback;
|
||||
import com.sap.sailing.gwt.ui.client.ParallelExecutionHolder;
|
||||
import com.sap.sailing.gwt.ui.client.RaceSelectionChangeListener;
|
||||
@@ -311,6 +312,7 @@ public class LeaderboardConfigPanel extends FormPanel implements SelectedLeaderb
|
||||
leaderboardButtonPanel.setSpacing(5);
|
||||
leaderboardsPanel.add(leaderboardButtonPanel);
|
||||
Button createFlexibleLeaderboardBtn = new Button(stringMessages.createFlexibleLeaderboard() + "...");
|
||||
createFlexibleLeaderboardBtn.ensureDebugId("CreateFlexibleLeaderboardButton");
|
||||
leaderboardButtonPanel.add(createFlexibleLeaderboardBtn);
|
||||
createFlexibleLeaderboardBtn.addClickHandler(new ClickHandler() {
|
||||
@Override
|
||||
@@ -320,6 +322,7 @@ public class LeaderboardConfigPanel extends FormPanel implements SelectedLeaderb
|
||||
});
|
||||
|
||||
Button createRegattaLeaderboardBtn = new Button(stringMessages.createRegattaLeaderboard() + "...");
|
||||
createRegattaLeaderboardBtn.ensureDebugId("CreateRegattaLeaderboardButton");
|
||||
leaderboardButtonPanel.add(createRegattaLeaderboardBtn);
|
||||
createRegattaLeaderboardBtn.addClickHandler(new ClickHandler() {
|
||||
@Override
|
||||
@@ -929,15 +932,15 @@ public class LeaderboardConfigPanel extends FormPanel implements SelectedLeaderb
|
||||
}
|
||||
|
||||
private void createFlexibleLeaderboard() {
|
||||
sailingService.getEvents(new AsyncCallback<List<EventDTO>>() {
|
||||
sailingService.getEvents(new MarkedAsyncCallback<List<EventDTO>>() {
|
||||
|
||||
@Override
|
||||
public void onSuccess(List<EventDTO> result) {
|
||||
public void handleSuccess(List<EventDTO> result) {
|
||||
createFlexibleLeaderboard(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable caught) {
|
||||
public void handleFailure(Throwable caught) {
|
||||
createFlexibleLeaderboard(new ArrayList<EventDTO>());
|
||||
}
|
||||
});
|
||||
@@ -955,20 +958,21 @@ public class LeaderboardConfigPanel extends FormPanel implements SelectedLeaderb
|
||||
public void ok(final LeaderboardDescriptor newLeaderboard) {
|
||||
sailingService.createFlexibleLeaderboard(newLeaderboard.getName(), newLeaderboard.getDisplayName(), newLeaderboard.getDiscardThresholds(),
|
||||
newLeaderboard.getScoringScheme(), newLeaderboard.getCourseAreaIdAsString(),
|
||||
new AsyncCallback<StrippedLeaderboardDTO>() {
|
||||
new MarkedAsyncCallback<StrippedLeaderboardDTO>() {
|
||||
@Override
|
||||
public void onFailure(Throwable t) {
|
||||
public void handleFailure(Throwable t) {
|
||||
errorReporter.reportError("Error trying to create the new flexible leaderboard " + newLeaderboard.getName()
|
||||
+ ": " + t.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(StrippedLeaderboardDTO result) {
|
||||
public void handleSuccess(StrippedLeaderboardDTO result) {
|
||||
addLeaderboard(result);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
dialog.ensureDebugId("CreateFlexibleLeaderboardDialog");
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -138,7 +138,7 @@ public class RaceColumnInLeaderboardDialog extends DataEntryDialog<RaceColumnInL
|
||||
super.show();
|
||||
if (isRegattaLeaderboard) {
|
||||
explicitFactorBox.setFocus(true);
|
||||
explicitFactorBox.setSelectionRange(0, explicitFactorBox.getText().length());
|
||||
explicitFactorBox.selectAll();
|
||||
} else {
|
||||
raceNameBox.setFocus(true);
|
||||
}
|
||||
|
||||
+38
-3
@@ -34,11 +34,14 @@ import com.google.gwt.user.client.ui.TextBox;
|
||||
import com.google.gwt.user.client.ui.VerticalPanel;
|
||||
import com.google.gwt.view.client.ListDataProvider;
|
||||
import com.google.gwt.view.client.MultiSelectionModel;
|
||||
import com.sap.sailing.domain.common.RegattaIdentifier;
|
||||
import com.sap.sailing.domain.common.RegattaName;
|
||||
import com.sap.sailing.gwt.ui.client.ErrorReporter;
|
||||
import com.sap.sailing.gwt.ui.client.RaceSelectionModel;
|
||||
import com.sap.sailing.gwt.ui.client.RegattaRefresher;
|
||||
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
|
||||
import com.sap.sailing.gwt.ui.client.StringMessages;
|
||||
import com.sap.sailing.gwt.ui.shared.RegattaDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.SwissTimingConfigurationDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.SwissTimingRaceRecordDTO;
|
||||
|
||||
@@ -138,7 +141,6 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
TextColumn<SwissTimingRaceRecordDTO> raceNameColumn = new TextColumn<SwissTimingRaceRecordDTO>() {
|
||||
@Override
|
||||
public String getValue(SwissTimingRaceRecordDTO object) {
|
||||
@@ -146,6 +148,24 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane
|
||||
}
|
||||
};
|
||||
|
||||
TextColumn<SwissTimingRaceRecordDTO> raceDescriptionColumn = new TextColumn<SwissTimingRaceRecordDTO>() {
|
||||
@Override
|
||||
public String getValue(SwissTimingRaceRecordDTO object) {
|
||||
return object.description;
|
||||
}
|
||||
};
|
||||
|
||||
TextColumn<SwissTimingRaceRecordDTO> boatClassColumn = new TextColumn<SwissTimingRaceRecordDTO>() {
|
||||
@Override
|
||||
public String getValue(SwissTimingRaceRecordDTO object) {
|
||||
String result = object.boatClass != null ? object.boatClass : "";
|
||||
if(object.discipline != null) {
|
||||
result += " (" + object.discipline + ")";
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
TextColumn<SwissTimingRaceRecordDTO> raceStartTrackingColumn = new TextColumn<SwissTimingRaceRecordDTO>() {
|
||||
@Override
|
||||
public String getValue(SwissTimingRaceRecordDTO object) {
|
||||
@@ -153,7 +173,6 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
HorizontalPanel racesSplitPanel = new HorizontalPanel();
|
||||
mainPanel.add(racesSplitPanel);
|
||||
|
||||
@@ -179,6 +198,15 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane
|
||||
filterPanel.setSpacing(5);
|
||||
racesPanel.add(filterPanel);
|
||||
|
||||
// Regatta
|
||||
HorizontalPanel regattaPanel = new HorizontalPanel();
|
||||
regattaPanel.setSpacing(5);
|
||||
Label regattaForTrackingLabel = new Label(stringMessages.regattaUsedForTheTrackedRace());
|
||||
regattaForTrackingLabel.setWordWrap(false);
|
||||
regattaPanel.add(regattaForTrackingLabel);
|
||||
regattaPanel.add(getAvailableRegattasListBox());
|
||||
racesPanel.add(regattaPanel);
|
||||
|
||||
Label lblFilterEvents = new Label(stringConstants.filterRacesByName() + ":");
|
||||
filterPanel.add(lblFilterEvents);
|
||||
filterPanel.setCellVerticalAlignment(lblFilterEvents, HasVerticalAlignment.ALIGN_MIDDLE);
|
||||
@@ -205,6 +233,8 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane
|
||||
AdminConsoleTableResources tableRes = GWT.create(AdminConsoleTableResources.class);
|
||||
raceTable = new CellTable<SwissTimingRaceRecordDTO>(/* pageSize */ 10000, tableRes);
|
||||
raceTable.addColumn(raceNameColumn, stringConstants.name());
|
||||
raceTable.addColumn(raceDescriptionColumn, stringConstants.description());
|
||||
raceTable.addColumn(boatClassColumn, stringConstants.boatClass());
|
||||
raceTable.addColumn(raceStartTrackingColumn, stringConstants.raceStartTimeColumn());
|
||||
raceTable.setWidth("300px");
|
||||
raceTable.setSelectionModel(new MultiSelectionModel<SwissTimingRaceRecordDTO>() {});
|
||||
@@ -345,8 +375,13 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane
|
||||
selectedRaces.add(race);
|
||||
}
|
||||
}
|
||||
RegattaDTO selectedRegatta = getSelectedRegatta();
|
||||
RegattaIdentifier regattaIdentifier = null;
|
||||
if (selectedRegatta != null) {
|
||||
regattaIdentifier = new RegattaName(selectedRegatta.name);
|
||||
}
|
||||
sailingService.trackWithSwissTiming(
|
||||
/* regattaToAddTo */null, // TODO allow user to select a pre-defined regatta
|
||||
/* regattaToAddTo */ regattaIdentifier,
|
||||
selectedRaces, hostname, port, /* canSendRequests */false, trackWind, correctWindByDeclination,
|
||||
new AsyncCallback<Void>() {
|
||||
@Override
|
||||
|
||||
+2
-13
@@ -186,9 +186,8 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel {
|
||||
layoutTable.setWidget(2, 0, hostnameLabel);
|
||||
layoutTable.setWidget(2, 1, this.hostnameTextBox);
|
||||
|
||||
// Regatta name
|
||||
// Event name
|
||||
Label eventNameLabel = new Label(this.stringMessages.eventName() + ":");
|
||||
|
||||
this.eventNameTextBox = new TextBox();
|
||||
this.eventNameTextBox.ensureDebugId("RegattaName");
|
||||
this.eventNameTextBox.setText("event_2011...");
|
||||
@@ -198,7 +197,6 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel {
|
||||
synchURIs();
|
||||
}
|
||||
});
|
||||
|
||||
layoutTable.setWidget(3, 0, eventNameLabel);
|
||||
layoutTable.setWidget(3, 1, this.eventNameTextBox);
|
||||
|
||||
@@ -338,9 +336,8 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel {
|
||||
//columnFormatter.setWidth(1, "80%");
|
||||
|
||||
// Regatta
|
||||
Label regattaForTrackingLabel = new Label("Regatta used for the tracked race:");
|
||||
Label regattaForTrackingLabel = new Label(stringMessages.regattaUsedForTheTrackedRace());
|
||||
regattaForTrackingLabel.setWordWrap(false);
|
||||
|
||||
layoutTable.setWidget(0, 0, regattaForTrackingLabel);
|
||||
layoutTable.setWidget(0, 1, getAvailableRegattasListBox());
|
||||
|
||||
@@ -619,11 +616,9 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel {
|
||||
String storedURI = this.storedURITextBox.getValue();
|
||||
RegattaDTO selectedRegatta = getSelectedRegatta();
|
||||
RegattaIdentifier regattaIdentifier = null;
|
||||
|
||||
if (selectedRegatta != null) {
|
||||
regattaIdentifier = new RegattaName(selectedRegatta.name);
|
||||
}
|
||||
|
||||
// Check if the assigned regatta makes sense
|
||||
List<TracTracRaceRecordDTO> allRaces = this.raceList.getList();
|
||||
List<TracTracRaceRecordDTO> racesWithNotMatchingBoatClasses = new ArrayList<TracTracRaceRecordDTO>();
|
||||
@@ -635,28 +630,22 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel {
|
||||
racesWithNotMatchingBoatClasses.add(race);
|
||||
}
|
||||
}
|
||||
|
||||
if (racesWithNotMatchingBoatClasses.size() > 0) {
|
||||
StringBuilder builder = new StringBuilder(100 + racesWithNotMatchingBoatClasses.size() * 30);
|
||||
|
||||
builder.append("WARNING\n");
|
||||
|
||||
if (selectedRegatta != null) {
|
||||
builder.append(this.stringMessages.boatClassDoesNotMatchSelectedRegatta(selectedRegatta.boatClass.name,
|
||||
selectedRegatta.name));
|
||||
} else {
|
||||
builder.append(this.stringMessages.regattaExistForSelectedBoatClass());
|
||||
}
|
||||
|
||||
builder.append("\n\n");
|
||||
builder.append(this.stringMessages.races());
|
||||
builder.append("\n");
|
||||
|
||||
for (TracTracRaceRecordDTO record: racesWithNotMatchingBoatClasses) {
|
||||
builder.append(record.name);
|
||||
builder.append("\n");
|
||||
}
|
||||
|
||||
if (!Window.confirm(builder.toString())) {
|
||||
return;
|
||||
}
|
||||
|
||||
+2
-2
@@ -109,7 +109,7 @@ public class WindPanel extends FormPanel implements RegattaDisplayer, WindShower
|
||||
RegattaAndRaceIdentifier selectedRace = getSelectedRace();
|
||||
if(selectedRace != null) {
|
||||
final RaceDTO race = trackedRacesListComposite.getRaceByIdentifier(selectedRace);
|
||||
sailingService.getCoursePositions(selectedRace, race.startOfRace, new AsyncCallback<CoursePositionsDTO>() {
|
||||
sailingService.getCoursePositions(selectedRace, race.trackedRace.startOfTracking, new AsyncCallback<CoursePositionsDTO>() {
|
||||
@Override
|
||||
public void onFailure(Throwable caught) {
|
||||
showWindSettingDialog(race, null);
|
||||
@@ -215,7 +215,7 @@ public class WindPanel extends FormPanel implements RegattaDisplayer, WindShower
|
||||
}
|
||||
|
||||
private void showWindSettingDialog(RaceDTO race, CoursePositionsDTO course) {
|
||||
WindSettingDialog windSettingDialog = new WindSettingDialog(race, course, stringMessages,
|
||||
AddWindFixDialog windSettingDialog = new AddWindFixDialog(race, course, stringMessages,
|
||||
new DialogCallback<WindDTO>() {
|
||||
@Override
|
||||
public void cancel() {
|
||||
|
||||
+16
-9
@@ -88,9 +88,11 @@ public abstract class DataEntryDialog<T> {
|
||||
this.validator = validator;
|
||||
okButton = new Button(okButtonName);
|
||||
okButton.getElement().getStyle().setMargin(3, Unit.PX);
|
||||
okButton.ensureDebugId("OkButton");
|
||||
FlowPanel dialogFPanel = new FlowPanel();
|
||||
dialogFPanel.setWidth("100%");
|
||||
statusLabel = new Label();
|
||||
statusLabel.ensureDebugId("StatusLabel");
|
||||
dialogFPanel.add(statusLabel);
|
||||
if (message != null) {
|
||||
Label messageLabel = new Label(message);
|
||||
@@ -107,6 +109,7 @@ public abstract class DataEntryDialog<T> {
|
||||
buttonPanel.add(okButton);
|
||||
cancelButton = new Button(cancelButtonName);
|
||||
cancelButton.getElement().getStyle().setMargin(3, Unit.PX);
|
||||
cancelButton.ensureDebugId("CancelButton");
|
||||
buttonPanel.add(cancelButton);
|
||||
cancelButton.addClickHandler(new ClickHandler() {
|
||||
@Override
|
||||
@@ -238,19 +241,20 @@ public abstract class DataEntryDialog<T> {
|
||||
}
|
||||
|
||||
private DateBox createDateBoxInternal(Date initialDate, int visibleLength) {
|
||||
DateBox textBox = new DateBox();
|
||||
textBox.getTextBox().setVisibleLength(visibleLength);
|
||||
textBox.setValue(initialDate);
|
||||
AbstractEntryPoint.addFocusUponKeyUpToggler(textBox.getTextBox());
|
||||
textBox.getTextBox().addChangeHandler(new ChangeHandler() {
|
||||
DateBox dateBox = new DateBox();
|
||||
dateBox.getTextBox().setVisibleLength(visibleLength);
|
||||
dateBox.setFireNullValues(true);
|
||||
dateBox.setValue(initialDate);
|
||||
AbstractEntryPoint.addFocusUponKeyUpToggler(dateBox.getTextBox());
|
||||
dateBox.addValueChangeHandler(new ValueChangeHandler<Date>() {
|
||||
@Override
|
||||
public void onChange(ChangeEvent event) {
|
||||
public void onValueChange(ValueChangeEvent<Date> event) {
|
||||
validate();
|
||||
}
|
||||
});
|
||||
AbstractEntryPoint.linkEnterToButton(getOkButton(), textBox.getTextBox());
|
||||
AbstractEntryPoint.linkEscapeToButton(getCancelButton(), textBox.getTextBox());
|
||||
return textBox;
|
||||
AbstractEntryPoint.linkEnterToButton(getOkButton(), dateBox.getTextBox());
|
||||
AbstractEntryPoint.linkEscapeToButton(getCancelButton(), dateBox.getTextBox());
|
||||
return dateBox;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -405,4 +409,7 @@ public abstract class DataEntryDialog<T> {
|
||||
dateEntryDialog.center();
|
||||
}
|
||||
|
||||
public void ensureDebugId(String debugId) {
|
||||
dateEntryDialog.ensureDebugId(debugId);
|
||||
}
|
||||
}
|
||||
+4
-6
@@ -50,11 +50,8 @@ public class RaceTimePanel extends TimePanel<RaceTimePanelSettings> implements R
|
||||
// in case the race is not tracked anymore we reset the time slider
|
||||
resetTimeSlider();
|
||||
} else {
|
||||
// check if the live delay is already been set
|
||||
if (!isUserExplicitlyChangedLivePlayDelay() && timer.getLivePlayDelayInMillis() != raceTimesInfo.delayToLiveInMs) {
|
||||
// timer will only accept this update if the delay hasn't been updated explicitly
|
||||
timer.setLivePlayDelayInMillis(raceTimesInfo.delayToLiveInMs);
|
||||
}
|
||||
|
||||
if ((raceTimesInfo.startOfTracking != null || raceTimesInfo.startOfRace != null) &&
|
||||
(raceTimesInfo.newestTrackingEvent != null || raceTimesInfo.endOfRace != null)) {
|
||||
// we set here the min and max of the time slider, the start and end of the race as well as the known
|
||||
@@ -109,13 +106,14 @@ public class RaceTimePanel extends TimePanel<RaceTimePanelSettings> implements R
|
||||
protected boolean isLiveModeToBeMadePossible() {
|
||||
long livePlayDelayInMillis = timer.getLivePlayDelayInMillis();
|
||||
long eventTimeoutTolerance = 60 * 1000; // 60s
|
||||
long timeBeforeRaceStartTolerance = 3 * 60 * 1000; // 3min
|
||||
long liveTimePointInMillis = System.currentTimeMillis() - livePlayDelayInMillis;
|
||||
RaceTimesInfoDTO lastRaceTimesInfo = raceTimesInfoProvider != null ? raceTimesInfoProvider.getRaceTimesInfo(selectedRace) : null;
|
||||
return lastRaceTimesInfo != null &&
|
||||
lastRaceTimesInfo.newestTrackingEvent != null &&
|
||||
liveTimePointInMillis < lastRaceTimesInfo.newestTrackingEvent.getTime() + eventTimeoutTolerance &&
|
||||
lastRaceTimesInfo.startOfTracking != null &&
|
||||
liveTimePointInMillis > lastRaceTimesInfo.startOfTracking.getTime();
|
||||
((lastRaceTimesInfo.startOfTracking != null && liveTimePointInMillis > lastRaceTimesInfo.startOfTracking.getTime())||
|
||||
(lastRaceTimesInfo.startOfRace != null && liveTimePointInMillis > lastRaceTimesInfo.startOfRace.getTime() - timeBeforeRaceStartTolerance));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+4
@@ -367,6 +367,7 @@ public interface StringMessages extends Messages {
|
||||
String insertWaypointAfterSelected();
|
||||
String scoringSchemeLowPointSystem();
|
||||
String scoringSchemeHighPointSystem();
|
||||
String scoringSchemeLowPointWinnerGetsZero();
|
||||
String scoringSystem();
|
||||
String createFlexibleLeaderboard();
|
||||
String createRegattaLeaderboard();
|
||||
@@ -520,4 +521,7 @@ public interface StringMessages extends Messages {
|
||||
String correctScoreFor(String competitorName, String raceColumnName);
|
||||
String penaltyOrRedress();
|
||||
String netScore();
|
||||
String timePointMustBeAfterStartOfTracking();
|
||||
String timePointMustBeBeforeEndOfTracking();
|
||||
String regattaUsedForTheTrackedRace();
|
||||
}
|
||||
+4
@@ -395,6 +395,7 @@ scoringSchemeLowPointSystem=Low Point System
|
||||
scoringSchemeHighPointSystem=High Point System
|
||||
scoringSchemeHighPointEssOverall=High Point System ESS Overall
|
||||
scoringSchemeHighPointLastBreaksTie=High Point System, Last Race Breaks Tie
|
||||
scoringSchemeLowPointWinnerGetsZero=Low Point System, Winner Gets Zero
|
||||
scoringSystem=Scoring system
|
||||
createFlexibleLeaderboard=Create flexible leaderboard
|
||||
createRegattaLeaderboard=Create regatta leaderboard
|
||||
@@ -522,3 +523,6 @@ correctScore=Correct Score
|
||||
correctScoreFor=Correct score for competitor {0} in race {1}
|
||||
penaltyOrRedress=Penalty/Redress
|
||||
netScore=Net points
|
||||
timePointMustBeAfterStartOfTracking=Time point must be after start of tracking.
|
||||
timePointMustBeBeforeEndOfTracking=Time point must be before end of tracking.
|
||||
regattaUsedForTheTrackedRace=Regatta used for the tracked race
|
||||
+4
@@ -396,6 +396,7 @@ scoringSchemeLowPointSystem=Low Point System
|
||||
scoringSchemeHighPointSystem=High Point System
|
||||
scoringSchemeHighPointEssOverall=High Point System ESS Gesamtwertung
|
||||
scoringSchemeHighPointLastBreaksTie=High Point System, letztes Rennen entscheidet bei Gleichstand
|
||||
scoringSchemeLowPointWinnerGetsZero=Low Point System, Gewinner erhält 0 Punkte
|
||||
scoringSystem=Bepunktungssystem
|
||||
createFlexibleLeaderboard=Flexible Rangliste anlegen
|
||||
createRegattaLeaderboard=Rangliste für Regatta anlegen
|
||||
@@ -522,3 +523,6 @@ correctScore=Ergebnis bearbeiten
|
||||
correctScoreFor=Ergebnis für Teilnehmer {0} in Rennen {1} bearbeiten
|
||||
penaltyOrRedress=Strafe/Gutschrift
|
||||
netScore=Nettoergebnis
|
||||
timePointMustBeAfterStartOfTracking=Der Zeitpunkt muß nach dem Trackingbeginn sein.
|
||||
timePointMustBeBeforeEndOfTracking=Der Zeitpunkt muß vor dem Trackingende sein.
|
||||
regattaUsedForTheTrackedRace=Regatta, zu der das Rennen rehört
|
||||
+2
-14
@@ -55,13 +55,6 @@ public class TimePanel<T extends TimePanelSettings> extends FormPanel implements
|
||||
private final Button slowDownButton;
|
||||
private final Button speedUpButton;
|
||||
|
||||
/**
|
||||
* The live delay may be adjusted automatically if the server decides so. However, if the user explicitly sets a live delay,
|
||||
* server-side updates to the delay should be suppressed. This flag records whether the user has performed a manual delay
|
||||
* override.
|
||||
*/
|
||||
private boolean userExplicitlyChangedLivePlayDelay;
|
||||
|
||||
/**
|
||||
* the minimum time the slider extends it's time when the end of the slider is reached
|
||||
*/
|
||||
@@ -89,7 +82,6 @@ public class TimePanel<T extends TimePanelSettings> extends FormPanel implements
|
||||
timer.addTimeListener(this);
|
||||
timer.addPlayStateListener(this);
|
||||
timeRangeProvider.addTimeRangeChangeListener(this);
|
||||
userExplicitlyChangedLivePlayDelay = false;
|
||||
FlowPanel timePanelInnerWrapper = new FlowPanel();
|
||||
timePanelInnerWrapper.setStyleName("timePanelInnerWrapper");
|
||||
timePanelInnerWrapper.setSize("100%", "100%");
|
||||
@@ -460,16 +452,12 @@ public class TimePanel<T extends TimePanelSettings> extends FormPanel implements
|
||||
return new TimePanelSettingsDialogComponent<T>(getSettings(), stringMessages);
|
||||
}
|
||||
|
||||
protected boolean isUserExplicitlyChangedLivePlayDelay() {
|
||||
return userExplicitlyChangedLivePlayDelay;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateSettings(T newSettings) {
|
||||
boolean delayChanged = newSettings.getDelayToLivePlayInSeconds() != getSettings().getDelayToLivePlayInSeconds();
|
||||
if (delayChanged) {
|
||||
userExplicitlyChangedLivePlayDelay = true;
|
||||
timer.setLivePlayDelayInMillis(1000l * newSettings.getDelayToLivePlayInSeconds());
|
||||
// explicit change always goes through and disables further automatic delay updates in timer
|
||||
timer.setLivePlayDelayInMillisExplicitly(1000l * newSettings.getDelayToLivePlayInSeconds());
|
||||
if (timer.getPlayMode() == PlayModes.Live) {
|
||||
timeDelayLabel.setText(String.valueOf(newSettings.getDelayToLivePlayInSeconds()) + " s");
|
||||
}
|
||||
|
||||
@@ -79,6 +79,13 @@ public class Timer {
|
||||
*/
|
||||
private boolean autoAdvance;
|
||||
|
||||
/**
|
||||
* If {@link #setLivePlayDelayInMillisExplicitly(long)} was called, this flag is set to <code>true</code> which will
|
||||
* cause calls to {@link #setLivePlayDelayInMillis(long)} to be ignored. Then, the only way to update the live delay is
|
||||
* to call {@link #setLivePlayDelayInMillisExplicitly(long)}.
|
||||
*/
|
||||
private boolean delaySetExplicity;
|
||||
|
||||
/**
|
||||
* The timer can run in two different modes: Live and Replay
|
||||
* 'Live' means the timer is used for a real time event
|
||||
@@ -261,6 +268,21 @@ public class Timer {
|
||||
}
|
||||
|
||||
public void setLivePlayDelayInMillis(long delayInMilliseconds) {
|
||||
if (!delaySetExplicity) {
|
||||
basicSetLivePlayDelayInMillis(delayInMilliseconds);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Always updates the delay if it is different from the current delay setting. The fact that an explicit delay update was
|
||||
* performed is recorded which will block further automatic delay updates from this point onwards.
|
||||
*/
|
||||
public void setLivePlayDelayInMillisExplicitly(long delayInMilliseconds) {
|
||||
basicSetLivePlayDelayInMillis(delayInMilliseconds);
|
||||
delaySetExplicity = true;
|
||||
}
|
||||
|
||||
private void basicSetLivePlayDelayInMillis(long delayInMilliseconds) {
|
||||
if (this.livePlayDelayInMillis != delayInMilliseconds) {
|
||||
this.livePlayDelayInMillis = delayInMilliseconds;
|
||||
if (getPlayMode() == PlayModes.Live) {
|
||||
|
||||
+5
-2
@@ -24,10 +24,13 @@ public abstract class AbstractRaceColumnSelection implements RaceColumnSelection
|
||||
|
||||
@Override
|
||||
public Iterable<RaceColumnDTO> getSelectedRaceColumnsOrderedAsInLeaderboard(LeaderboardDTO leaderboard) {
|
||||
Iterable<RaceColumnDTO> selectedColumns = getSelectedRaceColumns();
|
||||
List<String> namesOfSelectedRaceColumns = new ArrayList<String>();
|
||||
for (RaceColumnDTO selectedColumn : getSelectedRaceColumns()) {
|
||||
namesOfSelectedRaceColumns.add(selectedColumn.name);
|
||||
}
|
||||
List<RaceColumnDTO> result = new ArrayList<RaceColumnDTO>();
|
||||
for (RaceColumnDTO column : leaderboard.getRaceList()) {
|
||||
if (Util.contains(selectedColumns, column)) {
|
||||
if (Util.contains(namesOfSelectedRaceColumns, column.name)) {
|
||||
result.add(column);
|
||||
}
|
||||
}
|
||||
|
||||
+18
-13
@@ -4,7 +4,6 @@ import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
@@ -15,15 +14,19 @@ import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
|
||||
import com.sap.sailing.gwt.ui.client.RaceTimesInfoProvider;
|
||||
import com.sap.sailing.gwt.ui.client.RaceTimesInfoProviderListener;
|
||||
import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettings.RaceColumnSelectionStrategies;
|
||||
import com.sap.sailing.gwt.ui.shared.CompetitorDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.FleetDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.LeaderboardDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.LeaderboardEntryDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.LeaderboardRowDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.RaceColumnDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.RaceTimesInfoDTO;
|
||||
|
||||
/**
|
||||
* Doesn't respect requests to explicitly turn on or off certain columns. Instead, up to the last <code>n</code> (with
|
||||
* <code>n</code> being an integer number to configure through the constructor) race columns will be selected. "Last" is
|
||||
* decided based on the race start times which are obtained through a {@link RaceTimesInfoProvider}.
|
||||
* decided based on the column ordering in the leaderboard, considering races with a valid start time which are obtained
|
||||
* through a {@link RaceTimesInfoProvider} or a score correction.
|
||||
*
|
||||
* @author Axel Uhl (D043530)
|
||||
*
|
||||
@@ -87,33 +90,35 @@ public class LastNRacesColumnSelection extends AbstractRaceColumnSelection imple
|
||||
this.raceTimesInfo = raceTimesInfo;
|
||||
}
|
||||
|
||||
private Iterable<RaceColumnDTO> getRaceColumnsFromNewestToOldest(LeaderboardDTO leaderboard,
|
||||
private Iterable<RaceColumnDTO> getRaceColumnsFromNewestToOldest(final LeaderboardDTO leaderboard,
|
||||
Map<RegattaAndRaceIdentifier, RaceTimesInfoDTO> raceTimesInfo) {
|
||||
final Map<RaceColumnDTO, Date> latestStarts = new HashMap<RaceColumnDTO, Date>();
|
||||
List<RaceColumnDTO> columns = new ArrayList<RaceColumnDTO>(leaderboard.getRaceList());
|
||||
for (Iterator<RaceColumnDTO> i=columns.iterator(); i.hasNext(); ) {
|
||||
RaceColumnDTO column = i.next();
|
||||
Date latestStart = getLatestStart(column);
|
||||
if (latestStart == null) {
|
||||
if (getLatestStart(column) == null && !hasScoreCorrections(leaderboard, column)) {
|
||||
i.remove();
|
||||
} else {
|
||||
latestStarts.put(column, latestStart);
|
||||
}
|
||||
}
|
||||
Comparator<RaceColumnDTO> comparator = new Comparator<RaceColumnDTO>() {
|
||||
@Override
|
||||
public int compare(RaceColumnDTO o1, RaceColumnDTO o2) {
|
||||
int result;
|
||||
Date latestStartO1 = latestStarts.get(o1);
|
||||
Date latestStartO2 = latestStarts.get(o2);
|
||||
result = latestStartO2.compareTo(latestStartO1);
|
||||
return result;
|
||||
return leaderboard.getRaceList().indexOf(o2) - leaderboard.getRaceList().indexOf(o1);
|
||||
}
|
||||
};
|
||||
Collections.sort(columns, comparator);
|
||||
return columns;
|
||||
}
|
||||
|
||||
private boolean hasScoreCorrections(LeaderboardDTO leaderboard, RaceColumnDTO column) {
|
||||
for (Map.Entry<CompetitorDTO, LeaderboardRowDTO> e : leaderboard.rows.entrySet()) {
|
||||
LeaderboardEntryDTO entry = e.getValue().fieldsByRaceColumnName.get(column.name);
|
||||
if (entry != null && entry.hasScoreCorrection()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private Date getLatestStart(RaceColumnDTO column) {
|
||||
Date latestStart = null;
|
||||
for (FleetDTO fleet : column.getFleets()) {
|
||||
|
||||
+9
-1
@@ -47,6 +47,14 @@ public class LeaderboardEntryPoint extends AbstractEntryPoint {
|
||||
private static final String PARAM_REFRESH_INTERVAL_MILLIS = "refreshIntervalMillis";
|
||||
private static final String PARAM_SHOW_OVERALL_LEADERBOARDS_ON_SAME_PAGE = "showOverallLeaderboardsOnSamePage";
|
||||
private static final String PARAM_DELAY_TO_LIVE_MILLIS = "delayToLiveMillis";
|
||||
|
||||
/**
|
||||
* Parameter to support scaling the complete page by a given factor. This works by either using the
|
||||
* CSS3 zoom property or by applying scale operation to the body element. This comes in handy
|
||||
* when having to deal with screens that have high resolutions and that can't be controlled manually.
|
||||
* It is also a very simple method of adapting the viewport to a tv resolution. This parameter works
|
||||
* with value from 0.0 to 10.0 where 1.0 denotes the unchanged level (100%).
|
||||
*/
|
||||
private static final String PARAM_ZOOM_TO = "zoomTo";
|
||||
|
||||
/**
|
||||
@@ -80,7 +88,7 @@ public class LeaderboardEntryPoint extends AbstractEntryPoint {
|
||||
|
||||
final String zoomTo = Window.Location.getParameter(PARAM_ZOOM_TO);
|
||||
if (zoomTo != null) {
|
||||
RootPanel.getBodyElement().setAttribute("style", "zoom:"+zoomTo);
|
||||
RootPanel.getBodyElement().setAttribute("style", "zoom: "+zoomTo+";-moz-transform: scale("+zoomTo+");-moz-transform-origin: 0 0;-o-transform: scale("+zoomTo+");-o-transform-origin: 0 0;-webkit-transform: scale("+zoomTo+");-webkit-transform-origin: 0 0;");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+5
-5
@@ -242,7 +242,7 @@ public class LeaderboardPanel extends FormPanel implements TimeListener, PlaySta
|
||||
private final Label liveRaceLabel;
|
||||
|
||||
private final DateTimeFormat dateFormatter = DateTimeFormat.getFormat(DateTimeFormat.PredefinedFormat.DATE_LONG);
|
||||
private final DateTimeFormat timeFormatter = DateTimeFormat.getFormat("HH:mm:ss");
|
||||
private final DateTimeFormat timeFormatter = DateTimeFormat.getFormat("HH:mm:ss zzz");
|
||||
|
||||
private boolean isEmbedded = false;
|
||||
|
||||
@@ -430,7 +430,7 @@ public class LeaderboardPanel extends FormPanel implements TimeListener, PlaySta
|
||||
timer.setRefreshInterval(newSettings.getDelayBetweenAutoAdvancesInMilliseconds());
|
||||
}
|
||||
if (newSettings.getDelayInMilliseconds() != null) {
|
||||
setDelayInMilliseconds(newSettings.getDelayInMilliseconds());
|
||||
timer.setLivePlayDelayInMillisExplicitly(newSettings.getDelayInMilliseconds());
|
||||
}
|
||||
for (ExpandableSortableColumn<?> expandableSortableColumn : columnsToExpandAgain) {
|
||||
expandableSortableColumn.toggleExpansion();
|
||||
@@ -1840,11 +1840,11 @@ public class LeaderboardPanel extends FormPanel implements TimeListener, PlaySta
|
||||
|
||||
/**
|
||||
* Based on the {@link LeaderboardDTO#getDelayToLiveInMillisForLatestRace()} for the race that has the latest
|
||||
* {@link RaceColumnDTO#getStartDate(FleetDTO) start time}, if no {@link #settingsUpdatedExplicitly explicit setting
|
||||
* changes} were performed, automatically adjusts the delay accordingly.
|
||||
* {@link RaceColumnDTO#getStartDate(FleetDTO) start time}, automatically adjusts the delay accordingly unless it
|
||||
* was manually explicitly updated.
|
||||
*/
|
||||
private void adjustDelayToLive() {
|
||||
if (!settingsUpdatedExplicitly && leaderboard.getDelayToLiveInMillisForLatestRace() != null) {
|
||||
if (leaderboard.getDelayToLiveInMillisForLatestRace() != null) {
|
||||
setDelayInMilliseconds(leaderboard.getDelayToLiveInMillisForLatestRace());
|
||||
}
|
||||
}
|
||||
|
||||
+8
-6
@@ -4,18 +4,20 @@ import com.sap.sailing.domain.common.ScoringSchemeType;
|
||||
import com.sap.sailing.gwt.ui.client.StringMessages;
|
||||
|
||||
public class ScoringSchemeTypeFormatter {
|
||||
public static String format(ScoringSchemeType scoringSchemeType, StringMessages stringConstants) {
|
||||
public static String format(ScoringSchemeType scoringSchemeType, StringMessages stringMessages) {
|
||||
switch (scoringSchemeType) {
|
||||
case LOW_POINT:
|
||||
return stringConstants.scoringSchemeLowPointSystem();
|
||||
return stringMessages.scoringSchemeLowPointSystem();
|
||||
case HIGH_POINT:
|
||||
return stringConstants.scoringSchemeHighPointSystem();
|
||||
return stringMessages.scoringSchemeHighPointSystem();
|
||||
case HIGH_POINT_ESS_OVERALL:
|
||||
return stringConstants.scoringSchemeHighPointEssOverall();
|
||||
return stringMessages.scoringSchemeHighPointEssOverall();
|
||||
case HIGH_POINT_LAST_BREAKS_TIE:
|
||||
return stringConstants.scoringSchemeHighPointLastBreaksTie();
|
||||
return stringMessages.scoringSchemeHighPointLastBreaksTie();
|
||||
case HIGH_POINT_FIRST_GETS_TEN:
|
||||
return stringConstants.scoringSchemeHighPointFirstGetsTen();
|
||||
return stringMessages.scoringSchemeHighPointFirstGetsTen();
|
||||
case LOW_POINT_WINNER_GETS_ZERO:
|
||||
return stringMessages.scoringSchemeLowPointWinnerGetsZero();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
+7
@@ -60,4 +60,11 @@ public class EditScoreDialog extends DataEntryDialog<Pair<MaxPointsReason, Doubl
|
||||
grid.setWidget(1, 1, netPointsBox);
|
||||
return grid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void show() {
|
||||
super.show();
|
||||
netPointsBox.setFocus(true);
|
||||
netPointsBox.selectAll();
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -390,7 +390,7 @@ public class EditableLeaderboardPanel extends LeaderboardPanel {
|
||||
String prefix;
|
||||
String suffix;
|
||||
if (netPointsCorrected) {
|
||||
prefix = "<b>";
|
||||
prefix = "<b class='bold'>";
|
||||
suffix = "</b>";
|
||||
} else {
|
||||
prefix = "";
|
||||
|
||||
+22
-12
@@ -251,6 +251,9 @@ public class MatchAndApplyScoreCorrectionsDialog extends DataEntryDialog<BulkSco
|
||||
if (officialSailID != null && raceNameOrNumber != null) {
|
||||
ScoreCorrectionEntryDTO officialCorrectionEntry = regattaScoreCorrection
|
||||
.getScoreCorrectionsByRaceNameOrNumber().get(raceNameOrNumber).get(officialSailID);
|
||||
// the competitor may not have an official result for this race, for example, if in case of split fleets
|
||||
// the competitor raced in a different fleet
|
||||
if (officialCorrectionEntry != null) {
|
||||
result.addMaxPointsReasonUpdate(competitor, raceColumn,
|
||||
officialCorrectionEntry.getMaxPointsReason());
|
||||
if (officialCorrectionEntry.getScore() != null) {
|
||||
@@ -262,6 +265,7 @@ public class MatchAndApplyScoreCorrectionsDialog extends DataEntryDialog<BulkSco
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -308,36 +312,42 @@ public class MatchAndApplyScoreCorrectionsDialog extends DataEntryDialog<BulkSco
|
||||
ScoreCorrectionEntryDTO officialCorrectionEntry =
|
||||
regattaScoreCorrection.getScoreCorrectionsByRaceNameOrNumber()
|
||||
.get(raceNameOrNumber).get(officialSailID);
|
||||
final Double officialTotalPoints = officialCorrectionEntry.isDiscarded() ? new Double(0) : officialCorrectionEntry.getScore();
|
||||
final Double officialNetPoints = officialCorrectionEntry.getScore() == null ? null :
|
||||
final Double officialTotalPoints = officialCorrectionEntry == null ? null :
|
||||
officialCorrectionEntry.isDiscarded() ? new Double(0) : officialCorrectionEntry.getScore();
|
||||
final Double officialNetPoints = officialCorrectionEntry == null ? null :
|
||||
officialCorrectionEntry.getScore() == null ? null :
|
||||
officialCorrectionEntry.getScore() / raceColumn.getEffectiveFactor();
|
||||
final MaxPointsReason officialMaxPointsReason = officialCorrectionEntry == null ? null :
|
||||
officialCorrectionEntry.getMaxPointsReason();
|
||||
SafeHtmlBuilder sb = new SafeHtmlBuilder();
|
||||
boolean entriesDiffer =
|
||||
((officialNetPoints == null && entry.netPoints != null) || (officialNetPoints != null && entry.netPoints != null && !new Double(entry.netPoints).equals(officialNetPoints))) ||
|
||||
((officialTotalPoints == null && entry.totalPoints != null) || (officialTotalPoints != null && entry.totalPoints != null && !new Double(entry.totalPoints).equals(officialTotalPoints))) ||
|
||||
((officialCorrectionEntry.getMaxPointsReason() == null && entry.reasonForMaxPoints != MaxPointsReason.NONE) ||
|
||||
officialCorrectionEntry.getMaxPointsReason() != null && officialCorrectionEntry.getMaxPointsReason() != entry.reasonForMaxPoints);
|
||||
// entries not considered "different' in case we haven't got an entry for the competitor/race at all;
|
||||
// those non-existing "entries" will be ignored by getResults anyhow
|
||||
boolean entriesDiffer = officialCorrectionEntry != null &&
|
||||
(((officialNetPoints == null && entry.netPoints != null) || (officialNetPoints != null && (entry.netPoints == null || !new Double(entry.netPoints).equals(officialNetPoints)))) ||
|
||||
((officialTotalPoints == null && entry.totalPoints != null) || (officialTotalPoints != null && (entry.totalPoints == null || !new Double(entry.totalPoints).equals(officialTotalPoints)))) ||
|
||||
((officialMaxPointsReason == null && entry.reasonForMaxPoints != MaxPointsReason.NONE) ||
|
||||
officialMaxPointsReason != null && officialMaxPointsReason != entry.reasonForMaxPoints));
|
||||
if (entriesDiffer) {
|
||||
sb.appendHtmlConstant("<span style=\"color: #0000FF;\"><b>");
|
||||
}
|
||||
if (officialNetPoints == null) {
|
||||
sb.append(0);
|
||||
sb.appendEscaped("null");
|
||||
} else {
|
||||
sb.append(officialNetPoints);
|
||||
}
|
||||
sb.appendEscaped("/");
|
||||
if (officialTotalPoints == null) {
|
||||
sb.append(0);
|
||||
sb.appendEscaped("null");
|
||||
} else {
|
||||
sb.append(officialTotalPoints);
|
||||
}
|
||||
sb.appendEscaped("/");
|
||||
if (officialCorrectionEntry.getMaxPointsReason() != null) {
|
||||
sb.appendEscaped(officialCorrectionEntry.getMaxPointsReason().name());
|
||||
if (officialMaxPointsReason != null) {
|
||||
sb.appendEscaped(officialMaxPointsReason.name());
|
||||
} else {
|
||||
sb.appendEscaped(MaxPointsReason.NONE.name());
|
||||
}
|
||||
if (officialCorrectionEntry.isDiscarded()) {
|
||||
if (officialCorrectionEntry != null && officialCorrectionEntry.isDiscarded()) {
|
||||
sb.appendEscaped("/discarded");
|
||||
}
|
||||
if (entriesDiffer) {
|
||||
|
||||
+15
-9
@@ -88,11 +88,11 @@ public class MediaSelector implements /*RaceTimesInfoProviderListener,*/ PlaySta
|
||||
Collection<MediaTrack> reachableVideoTracks = new ArrayList<MediaTrack>();
|
||||
Collection<MediaTrack> reachableAudioTracks = new ArrayList<MediaTrack>();
|
||||
for (MediaTrack mediaTrack : mediaTracks) {
|
||||
if (MediaTrack.Status.REACHABLE.equals(mediaTrack.status)) {
|
||||
if (isPotentiallyPlayable(mediaTrack)) {
|
||||
switch (mediaTrack.mimeType.mediaType) {
|
||||
case video:
|
||||
reachableVideoTracks.add(mediaTrack);
|
||||
case audio: //intentionall fall through
|
||||
case audio: //intentional fall through
|
||||
reachableAudioTracks.add(mediaTrack);
|
||||
}
|
||||
}
|
||||
@@ -129,13 +129,15 @@ public class MediaSelector implements /*RaceTimesInfoProviderListener,*/ PlaySta
|
||||
|
||||
}
|
||||
|
||||
private boolean isPotentiallyPlayable(MediaTrack mediaTrack) {
|
||||
return MediaTrack.Status.REACHABLE.equals(mediaTrack.status) || MediaTrack.Status.UNDEFINED.equals(mediaTrack.status);
|
||||
}
|
||||
|
||||
private void setStatus(final MediaTrack mediaTrack) {
|
||||
if (!mediaTrack.isYoutube()) {
|
||||
Audio audio = Audio.createIfSupported();
|
||||
if (audio != null) {
|
||||
AudioElement mediaReachableTester = audio.getAudioElement();
|
||||
String canPlay = mediaReachableTester.canPlayType(mediaTrack.typeToString());
|
||||
if (MediaElement.CAN_PLAY_PROBABLY.equals(canPlay) || MediaElement.CAN_PLAY_MAYBE.equals(canPlay)) {
|
||||
addLoadMetadataHandler(mediaReachableTester, mediaTrack);
|
||||
mediaReachableTester.setPreload(MediaElement.PRELOAD_METADATA);
|
||||
mediaReachableTester.setSrc(mediaTrack.url);
|
||||
@@ -143,9 +145,6 @@ public class MediaSelector implements /*RaceTimesInfoProviderListener,*/ PlaySta
|
||||
} else {
|
||||
mediaTrack.status = Status.CANNOT_PLAY;
|
||||
}
|
||||
} else {
|
||||
mediaTrack.status = Status.CANNOT_PLAY;
|
||||
}
|
||||
} else {
|
||||
mediaTrack.status = Status.REACHABLE;
|
||||
}
|
||||
@@ -156,12 +155,19 @@ public class MediaSelector implements /*RaceTimesInfoProviderListener,*/ PlaySta
|
||||
mediaElement.addEventListener('loadedmetadata', function() {
|
||||
that.@com.sap.sailing.gwt.ui.raceboard.MediaSelector::loadedmetadata(Lcom/sap/sailing/gwt/ui/shared/media/MediaTrack;)(mediaTrack);
|
||||
});
|
||||
mediaElement.addEventListener('error', function() {
|
||||
that.@com.sap.sailing.gwt.ui.raceboard.MediaSelector::mediaError(Lcom/sap/sailing/gwt/ui/shared/media/MediaTrack;)(mediaTrack);
|
||||
});
|
||||
}-*/;
|
||||
|
||||
public void loadedmetadata(MediaTrack mediaTrack) {
|
||||
mediaTrack.status = Status.REACHABLE;
|
||||
}
|
||||
|
||||
public void mediaError(MediaTrack mediaTrack) {
|
||||
mediaTrack.status = Status.NOT_REACHABLE;
|
||||
}
|
||||
|
||||
private void playDefault() {
|
||||
MediaTrack defaultVideo = getDefaultVideo();
|
||||
if (defaultVideo != null) {
|
||||
@@ -176,7 +182,7 @@ public class MediaSelector implements /*RaceTimesInfoProviderListener,*/ PlaySta
|
||||
private MediaTrack getDefaultAudio() {
|
||||
//TODO: implement a better heuristic than just taking the first to come
|
||||
for (MediaTrack mediaTrack : mediaTracks) {
|
||||
if (MediaType.audio.equals(mediaTrack.mimeType.mediaType)) {
|
||||
if (MediaType.audio.equals(mediaTrack.mimeType.mediaType) && isPotentiallyPlayable(mediaTrack)) {
|
||||
return mediaTrack;
|
||||
}
|
||||
}
|
||||
@@ -185,7 +191,7 @@ public class MediaSelector implements /*RaceTimesInfoProviderListener,*/ PlaySta
|
||||
|
||||
private MediaTrack getDefaultVideo() {
|
||||
for (MediaTrack mediaTrack : mediaTracks) {
|
||||
if (MediaType.video.equals(mediaTrack.mimeType.mediaType)) {
|
||||
if (MediaType.video.equals(mediaTrack.mimeType.mediaType) && isPotentiallyPlayable(mediaTrack)) {
|
||||
return mediaTrack;
|
||||
}
|
||||
}
|
||||
|
||||
+12
-2
@@ -44,6 +44,7 @@ import com.sap.sailing.domain.tracking.TrackedRace;
|
||||
import com.sap.sailing.domain.tracking.TrackedRaceStatus;
|
||||
import com.sap.sailing.domain.tracking.Wind;
|
||||
import com.sap.sailing.gwt.ui.shared.LeaderboardDTO;
|
||||
import com.sap.sailing.util.impl.LockUtil;
|
||||
|
||||
/**
|
||||
* Caches the expensive to compute {@link LeaderboardDTO} results of a
|
||||
@@ -402,12 +403,21 @@ public class LeaderboardDTOCache {
|
||||
future = map.get(key);
|
||||
}
|
||||
if (future == null) {
|
||||
final Thread callerThread = Thread.currentThread();
|
||||
future = new FutureTask<LeaderboardDTO>(new Callable<LeaderboardDTO>() {
|
||||
@Override
|
||||
public LeaderboardDTO call() throws Exception {
|
||||
LeaderboardDTO result = sailingService.computeLeaderboardByName(leaderboard, adjustedTimePoint,
|
||||
namesOfRaceColumnsForWhichToLoadLegDetails, waitForLatestAnalyses);
|
||||
// The outer getLeaderboardByName(...) method will always wait for this future's completion.
|
||||
// Therefore, it's safe to propagate the calling thread's locks to this one:
|
||||
LockUtil.propagateLockSetFrom(callerThread);
|
||||
try {
|
||||
LeaderboardDTO result = sailingService.computeLeaderboardByName(leaderboard,
|
||||
adjustedTimePoint, namesOfRaceColumnsForWhichToLoadLegDetails,
|
||||
waitForLatestAnalyses);
|
||||
return result;
|
||||
} finally {
|
||||
LockUtil.unpropagateLockSetFrom(callerThread);
|
||||
}
|
||||
}
|
||||
});
|
||||
computeLeadearboardByNameExecutor.execute(future);
|
||||
|
||||
+33
-15
@@ -72,6 +72,7 @@ import com.sap.sailing.domain.common.Color;
|
||||
import com.sap.sailing.domain.common.CountryCode;
|
||||
import com.sap.sailing.domain.common.DetailType;
|
||||
import com.sap.sailing.domain.common.Distance;
|
||||
import com.sap.sailing.domain.common.LeaderboardNameConstants;
|
||||
import com.sap.sailing.domain.common.LegType;
|
||||
import com.sap.sailing.domain.common.ManeuverType;
|
||||
import com.sap.sailing.domain.common.MaxPointsReason;
|
||||
@@ -347,7 +348,11 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
/* workQueue */ new LinkedBlockingQueue<Runnable>());
|
||||
raceDetailsExecutor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
|
||||
leaderboardByNameLiveUpdaters = new HashMap<String, LiveLeaderboardUpdater>();
|
||||
leaderboardDTOCacheWaitingForLatestAnalysis = new LeaderboardDTOCache(this, /* waitForLatestAnalyses */ true);
|
||||
// The leaderboard cache is invalidated upon all competitor and mark position changes; some analyses
|
||||
// are pretty expensive, such as the maneuver re-calculation. Waiting for the latest analysis after only a
|
||||
// single fix was updated is too expensive if users use the replay feature while a race is still running.
|
||||
// Therefore, using waitForLatestAnalyses==false seems appropriate here.
|
||||
leaderboardDTOCacheWaitingForLatestAnalysis = new LeaderboardDTOCache(this, /* waitForLatestAnalyses */ false);
|
||||
}
|
||||
|
||||
private void writeObject(ObjectOutputStream oos) throws IOException {
|
||||
@@ -509,7 +514,8 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
RegattaAndRaceIdentifier raceIdentifier = null;
|
||||
RaceDTO race = null;
|
||||
TrackedRace trackedRace = raceColumn.getTrackedRace(fleet);
|
||||
final FleetDTO fleetDTO = convertToFleetDTO(fleet);
|
||||
|
||||
final FleetDTO fleetDTO = convertToFleetDTO(raceColumn, fleet);
|
||||
if (trackedRace != null) {
|
||||
raceIdentifier = new RegattaNameAndRaceName(trackedRace.getTrackedRegatta().getRegatta()
|
||||
.getName(), trackedRace.getRace().getName());
|
||||
@@ -577,7 +583,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
public LeaderboardEntryDTO call() {
|
||||
try {
|
||||
Entry entry = leaderboard.getEntry(competitor, raceColumn, timePoint);
|
||||
return getLeaderboardEntryDTO(entry, raceColumn.getTrackedRace(competitor), competitor, timePoint,
|
||||
return getLeaderboardEntryDTO(entry, raceColumn, competitor, timePoint,
|
||||
namesOfRaceColumnsForWhichToLoadLegDetails != null
|
||||
&& namesOfRaceColumnsForWhichToLoadLegDetails.contains(raceColumn.getName()),
|
||||
waitForLatestAnalyses, legRanksCache);
|
||||
@@ -608,7 +614,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
}
|
||||
}
|
||||
}
|
||||
logger.fine("computeLeaderboardByName("+leaderboard.getName()+", "+timePoint+", "+namesOfRaceColumnsForWhichToLoadLegDetails+") took "+
|
||||
logger.info("computeLeaderboardByName("+leaderboard.getName()+", "+timePoint+", "+namesOfRaceColumnsForWhichToLoadLegDetails+") took "+
|
||||
(System.currentTimeMillis()-startOfRequestHandling)+"ms");
|
||||
return result;
|
||||
}
|
||||
@@ -646,10 +652,11 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
* may not reflect all data already received; otherwise, the method will always block for the latest
|
||||
* cache updates to have happened before returning.
|
||||
*/
|
||||
private LeaderboardEntryDTO getLeaderboardEntryDTO(Entry entry, TrackedRace trackedRace, Competitor competitor,
|
||||
private LeaderboardEntryDTO getLeaderboardEntryDTO(Entry entry, RaceColumn raceColumn, Competitor competitor,
|
||||
TimePoint timePoint, boolean addLegDetails, boolean waitForLatestAnalyses,
|
||||
Map<Leg, LinkedHashMap<Competitor, Integer>> legRanksCache) throws NoWindException {
|
||||
LeaderboardEntryDTO entryDTO = new LeaderboardEntryDTO();
|
||||
TrackedRace trackedRace = raceColumn.getTrackedRace(competitor);
|
||||
entryDTO.race = trackedRace == null ? null : trackedRace.getRaceIdentifier();
|
||||
entryDTO.netPoints = entry.getNetPoints();
|
||||
entryDTO.netPointsCorrected = entry.isNetPointsCorrected();
|
||||
@@ -671,7 +678,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
}
|
||||
}
|
||||
final Fleet fleet = entry.getFleet();
|
||||
entryDTO.fleet = fleet == null ? null : convertToFleetDTO(fleet);
|
||||
entryDTO.fleet = fleet == null ? null : convertToFleetDTO(raceColumn, fleet);
|
||||
return entryDTO;
|
||||
}
|
||||
|
||||
@@ -1068,7 +1075,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
private SeriesDTO convertToSeriesDTO(Series series) {
|
||||
List<FleetDTO> fleets = new ArrayList<FleetDTO>();
|
||||
for (Fleet fleet : series.getFleets()) {
|
||||
fleets.add(convertToFleetDTO(fleet));
|
||||
fleets.add(convertToFleetDTO(series, fleet));
|
||||
}
|
||||
List<RaceColumnDTO> raceColumns = new ArrayList<RaceColumnDTO>();
|
||||
for (RaceColumnInSeries raceColumn : series.getRaceColumns()) {
|
||||
@@ -1082,8 +1089,16 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
return result;
|
||||
}
|
||||
|
||||
private FleetDTO convertToFleetDTO(Fleet fleet) {
|
||||
return new FleetDTO(fleet.getName(), fleet.getOrdering(), fleet.getColor());
|
||||
private FleetDTO convertToFleetDTO(Series series, Fleet fleet) {
|
||||
return new FleetDTO(fleet.getName(), series.getName(), fleet.getOrdering(), fleet.getColor());
|
||||
}
|
||||
|
||||
private FleetDTO convertToFleetDTO(RaceColumn raceColumn, Fleet fleet) {
|
||||
String seriesNameOfFleet = null;
|
||||
if (raceColumn instanceof RaceColumnInSeries) {
|
||||
seriesNameOfFleet = ((RaceColumnInSeries) raceColumn).getSeries().getName();
|
||||
}
|
||||
return new FleetDTO(fleet.getName(), seriesNameOfFleet, fleet.getOrdering(), fleet.getColor());
|
||||
}
|
||||
|
||||
private List<RaceWithCompetitorsDTO> convertToRaceDTOs(Regatta regatta) {
|
||||
@@ -2132,7 +2147,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
latestTimePointAfterQueryTimePointWhenATrackedRaceWasLive = liveTimePointForTrackedRace;
|
||||
}
|
||||
}
|
||||
final FleetDTO fleetDTO = convertToFleetDTO(fleet);
|
||||
final FleetDTO fleetDTO = convertToFleetDTO(raceColumn, fleet);
|
||||
RaceColumnDTO raceColumnDTO = leaderboardDTO.addRace(raceColumn.getName(), raceColumn.getExplicitFactor(), raceColumn.getFactor(),
|
||||
fleetDTO, raceColumn.isMedalRace(), raceIdentifier, raceDTO);
|
||||
if (latestTimePointAfterQueryTimePointWhenATrackedRaceWasLive != null) {
|
||||
@@ -2407,7 +2422,11 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
throws UnknownHostException, IOException, InterruptedException, ParseException {
|
||||
List<SwissTimingRaceRecordDTO> result = new ArrayList<SwissTimingRaceRecordDTO>();
|
||||
for (com.sap.sailing.domain.swisstimingadapter.RaceRecord rr : getService().getSwissTimingRaceRecords(hostname, port, canSendRequests)) {
|
||||
result.add(new SwissTimingRaceRecordDTO(rr.getRaceID(), rr.getDescription(), rr.getStartTime()));
|
||||
SwissTimingRaceRecordDTO swissTimingRaceRecordDTO = new SwissTimingRaceRecordDTO(rr.getRaceID(), rr.getDescription(), rr.getStartTime());
|
||||
BoatClass boatClass = com.sap.sailing.domain.swisstimingadapter.DomainFactory.INSTANCE.getOrCreateBoatClassFromRaceID(rr.getRaceID());
|
||||
swissTimingRaceRecordDTO.boatClass = boatClass != null ? boatClass.getName() : null;
|
||||
swissTimingRaceRecordDTO.discipline = rr.getRaceID().length() >= 3 ? rr.getRaceID().substring(2, 3) : null;
|
||||
result.add(swissTimingRaceRecordDTO);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -2421,7 +2440,6 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
public void trackWithSwissTiming(RegattaIdentifier regattaToAddTo, Iterable<SwissTimingRaceRecordDTO> rrs, String hostname, int port,
|
||||
boolean canSendRequests, boolean trackWind, final boolean correctWindByDeclination) throws Exception {
|
||||
for (SwissTimingRaceRecordDTO rr : rrs) {
|
||||
|
||||
final RacesHandle raceHandle = getService().addSwissTimingRace(regattaToAddTo, rr.ID, hostname, port,
|
||||
canSendRequests,
|
||||
MongoWindStoreFactory.INSTANCE.getMongoWindStore(mongoObjectFactory, domainObjectFactory),
|
||||
@@ -2476,11 +2494,11 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
regatta = getService().createRegatta(
|
||||
replayRaceDTO.rsc,
|
||||
boatClass.trim(),
|
||||
RegattaImpl.getFullName(replayRaceDTO.rsc, replayRaceDTO.boat_class),
|
||||
RegattaImpl.getDefaultName(replayRaceDTO.rsc, replayRaceDTO.boat_class),
|
||||
Collections.singletonList(new SeriesImpl(
|
||||
"Default",
|
||||
LeaderboardNameConstants.DEFAULT_SERIES_NAME,
|
||||
/* isMedal */false,
|
||||
Collections.singletonList(new FleetImpl("Default")),
|
||||
Collections.singletonList(new FleetImpl(LeaderboardNameConstants.DEFAULT_FLEET_NAME)),
|
||||
/* race column names */ new ArrayList<String>(), getService())),
|
||||
false,
|
||||
baseDomainFactory.createScoringScheme(ScoringSchemeType.LOW_POINT), null);
|
||||
|
||||
+20
-5
@@ -7,10 +7,14 @@ public class FleetDTO extends NamedDTO implements IsSerializable {
|
||||
private Color color;
|
||||
private int orderNo;
|
||||
|
||||
/** the name of the series the fleet belongs to */
|
||||
private String seriesName;
|
||||
|
||||
public FleetDTO() {}
|
||||
|
||||
public FleetDTO(String fleetName, int orderNo, Color color) {
|
||||
public FleetDTO(String fleetName, String seriesName, int orderNo, Color color) {
|
||||
super(fleetName);
|
||||
this.seriesName = seriesName;
|
||||
this.orderNo = orderNo;
|
||||
this.color = color;
|
||||
}
|
||||
@@ -31,12 +35,17 @@ public class FleetDTO extends NamedDTO implements IsSerializable {
|
||||
this.orderNo = orderNo;
|
||||
}
|
||||
|
||||
public String getSeriesName() {
|
||||
return seriesName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = super.hashCode();
|
||||
result = prime * result + ((color == null) ? 0 : color.hashCode());
|
||||
result = prime * result + ((name == null) ? 0 : name.hashCode());
|
||||
result = prime * result + orderNo;
|
||||
result = prime * result + ((seriesName == null) ? 0 : seriesName.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -49,13 +58,19 @@ public class FleetDTO extends NamedDTO implements IsSerializable {
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
FleetDTO other = (FleetDTO) obj;
|
||||
if (color == null) {
|
||||
if (other.color != null)
|
||||
if (name == null) {
|
||||
if (other.name != null)
|
||||
return false;
|
||||
} else if (!color.equals(other.color))
|
||||
} else if (!name.equals(other.name))
|
||||
return false;
|
||||
if (orderNo != other.orderNo)
|
||||
return false;
|
||||
if (seriesName == null) {
|
||||
if (other.seriesName != null)
|
||||
return false;
|
||||
} else if (!seriesName.equals(other.seriesName))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
Loaded 100 of 360 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user