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
+39846
-3757
No files matched your search
@@ -274,6 +274,13 @@ if [[ "$@" == "install" ]] || [[ "$@" == "all" ]]; then
|
|||||||
mkdir $ACDIR/configuration
|
mkdir $ACDIR/configuration
|
||||||
fi
|
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
|
cd $ACDIR
|
||||||
|
|
||||||
rm -rf $ACDIR/plugins/*.*
|
rm -rf $ACDIR/plugins/*.*
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
port = 27017
|
port = 27017
|
||||||
master = true
|
master = true
|
||||||
dbpath = /opt/mongodb/data//mongodb-data
|
dbpath = /opt/mongodb/data/mongodb-data
|
||||||
logpath = /opt/mongodb/data/logs/mongodb-data/mongodb.log
|
|
||||||
logappend = true
|
logappend = true
|
||||||
directoryperdb = true
|
directoryperdb = true
|
||||||
rest = 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
|
### 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]]
|
* [[General Project Information|wiki/general-information]]
|
||||||
* [[OnBoarding Information|wiki/onboarding]]
|
* [[OnBoarding Information|wiki/onboarding]]
|
||||||
* [[Architecture and Infrastructure|wiki/architecture-and-infrastructure]]
|
* [[Architecture and Infrastructure|wiki/architecture-and-infrastructure]]
|
||||||
|
* [[Production Environment|wiki/production-environment]]
|
||||||
|
* [[Development Environment|wiki/development-environment]]
|
||||||
* [[Server Replication|wiki/server-replication]]
|
* [[Server Replication|wiki/server-replication]]
|
||||||
* [[Configure Races on Server|wiki/configure-races-on-server]]
|
* [[Configure Races on Server|wiki/configure-races-on-server]]
|
||||||
|
* [[Cook Book|wiki/cook-book]]
|
||||||
* [[Planning|wiki/planning]]
|
* [[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)
|
### 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 DEFAULT_LEADERBOARD_NAME = "Default Leaderboard";
|
||||||
|
|
||||||
static final String OVERALL = "Overall";
|
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 {
|
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
|
* 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
|
* score corrections taken at different times. Later score corrections are expected to be cumulative, meaning they
|
||||||
* also contain all previous corrections.
|
* also contain all previous corrections.
|
||||||
|
|||||||
+2
-1
@@ -1,5 +1,6 @@
|
|||||||
package com.sap.sailing.domain.common;
|
package com.sap.sailing.domain.common;
|
||||||
|
|
||||||
public enum ScoringSchemeType {
|
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() {
|
public String getITUCallPrefix() {
|
||||||
return ituCallPrefix;
|
return ituCallPrefix;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return getThreeLetterIOCCode();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+69
-60
@@ -161,7 +161,7 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
|
|||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// something went wrong during DB access; report, then use empty new wind track
|
// 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.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;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -180,7 +180,7 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
|
|||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// something went wrong during DB access; report, then use empty new wind track
|
// 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.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;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -341,58 +341,61 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
|
|||||||
|
|
||||||
private FlexibleLeaderboard loadFlexibleLeaderboard(DBObject dbLeaderboard,
|
private FlexibleLeaderboard loadFlexibleLeaderboard(DBObject dbLeaderboard,
|
||||||
SettableScoreCorrection scoreCorrection, ThresholdBasedResultDiscardingRule resultDiscardingRule) {
|
SettableScoreCorrection scoreCorrection, ThresholdBasedResultDiscardingRule resultDiscardingRule) {
|
||||||
final ScoringScheme scoringScheme = loadScoringScheme(dbLeaderboard);
|
final FlexibleLeaderboardImpl result;
|
||||||
|
|
||||||
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);
|
|
||||||
BasicDBList dbRaceColumns = (BasicDBList) dbLeaderboard.get(FieldNames.LEADERBOARD_COLUMNS.name());
|
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) {
|
||||||
Map<String, Fleet> fleetsByName = new HashMap<String, Fleet>();
|
// this was probably an orphaned overall leaderboard
|
||||||
for (Object dbRaceColumnAsObject : dbRaceColumns) {
|
logger.warning("Probably found orphan overall leaderboard named "
|
||||||
BasicDBObject dbRaceColumn = (BasicDBObject) dbRaceColumnAsObject;
|
+ dbLeaderboard.get(FieldNames.LEADERBOARD_NAME.name())+". Ignoring.");
|
||||||
Map<String, RaceIdentifier> raceIdentifiers = loadRaceIdentifiers(dbRaceColumn);
|
result = null;
|
||||||
RaceIdentifier defaultFleetRaceIdentifier = raceIdentifiers.get(null);
|
} else {
|
||||||
if (defaultFleetRaceIdentifier != null) {
|
final ScoringScheme scoringScheme = loadScoringScheme(dbLeaderboard);
|
||||||
Fleet defaultFleet = result.getFleet(null);
|
RaceLogStore raceLogStore = MongoRaceLogStoreFactory.INSTANCE.getMongoRaceLogStore(
|
||||||
if (defaultFleet != null) {
|
new MongoObjectFactoryImpl(database), this);
|
||||||
raceIdentifiers.put(defaultFleet.getName(), defaultFleetRaceIdentifier);
|
CourseArea courseArea = loadCourseAreaFromEvents(dbLeaderboard);
|
||||||
} else {
|
result = new FlexibleLeaderboardImpl(raceLogStore, (String) dbLeaderboard.get(FieldNames.LEADERBOARD_NAME
|
||||||
// leaderboard has no default fleet; don't know what to do with default RaceIdentifier
|
.name()), scoreCorrection, resultDiscardingRule, scoringScheme, courseArea);
|
||||||
logger.warning("Discarding RaceIdentifier "+defaultFleetRaceIdentifier+" for default fleet for leaderboard "+result.getName()+
|
// For a FlexibleLeaderboard, fleets are owned by the leaderboard's RaceColumn objects. We need to manage
|
||||||
" because no default fleet was found in leaderboard");
|
// them here:
|
||||||
|
Map<String, Fleet> fleetsByName = new HashMap<String, Fleet>();
|
||||||
|
for (Object dbRaceColumnAsObject : dbRaceColumns) {
|
||||||
|
BasicDBObject dbRaceColumn = (BasicDBObject) dbRaceColumnAsObject;
|
||||||
|
Map<String, RaceIdentifier> raceIdentifiers = loadRaceIdentifiers(dbRaceColumn);
|
||||||
|
RaceIdentifier defaultFleetRaceIdentifier = raceIdentifiers.get(null);
|
||||||
|
if (defaultFleetRaceIdentifier != null) {
|
||||||
|
Fleet defaultFleet = result.getFleet(null);
|
||||||
|
if (defaultFleet != null) {
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
raceIdentifiers.remove(null);
|
||||||
}
|
}
|
||||||
raceIdentifiers.remove(null);
|
List<Fleet> fleets = new ArrayList<Fleet>();
|
||||||
}
|
for (String fleetName : raceIdentifiers.keySet()) {
|
||||||
List<Fleet> fleets = new ArrayList<Fleet>();
|
Fleet fleet = fleetsByName.get(fleetName);
|
||||||
for (String fleetName : raceIdentifiers.keySet()) {
|
if (fleet == null) {
|
||||||
Fleet fleet = fleetsByName.get(fleetName);
|
fleet = new FleetImpl(fleetName);
|
||||||
if (fleet == null) {
|
fleetsByName.put(fleetName, fleet);
|
||||||
fleet = new FleetImpl(fleetName);
|
}
|
||||||
fleetsByName.put(fleetName, fleet);
|
fleets.add(fleet);
|
||||||
}
|
}
|
||||||
fleets.add(fleet);
|
if (fleets.isEmpty()) {
|
||||||
}
|
fleets.add(result.getFleet(null));
|
||||||
if (fleets.isEmpty()) {
|
}
|
||||||
fleets.add(result.getFleet(null));
|
String columnName = (String) dbRaceColumn.get(FieldNames.LEADERBOARD_COLUMN_NAME.name());
|
||||||
}
|
|
||||||
String columnName = (String) dbRaceColumn.get(FieldNames.LEADERBOARD_COLUMN_NAME.name());
|
|
||||||
|
|
||||||
RaceColumn raceColumn = result.addRaceColumn(columnName,
|
RaceColumn raceColumn = result.addRaceColumn(columnName,
|
||||||
(Boolean) dbRaceColumn.get(FieldNames.LEADERBOARD_IS_MEDAL_RACE_COLUMN.name()),
|
(Boolean) dbRaceColumn.get(FieldNames.LEADERBOARD_IS_MEDAL_RACE_COLUMN.name()),
|
||||||
fleets.toArray(new Fleet[0]));
|
fleets.toArray(new Fleet[0]));
|
||||||
for (Map.Entry<String, RaceIdentifier> e : raceIdentifiers.entrySet()) {
|
for (Map.Entry<String, RaceIdentifier> e : raceIdentifiers.entrySet()) {
|
||||||
raceColumn.setRaceIdentifier(fleetsByName.get(e.getKey()), e.getValue());
|
raceColumn.setRaceIdentifier(fleetsByName.get(e.getKey()), e.getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -539,7 +542,7 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
|
|||||||
leaderboardGroup = loadLeaderboardGroup(leaderboardGroupCollection.findOne(query), regattaRegistry, leaderboardRegistry);
|
leaderboardGroup = loadLeaderboardGroup(leaderboardGroupCollection.findOne(query), regattaRegistry, leaderboardRegistry);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.log(Level.SEVERE, "Error connecting to MongoDB, unable to load leaderboard group "+name+".");
|
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;
|
return leaderboardGroup;
|
||||||
@@ -555,7 +558,7 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
|
|||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.log(Level.SEVERE, "Error connecting to MongoDB, unable to load leaderboard groups.");
|
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;
|
return leaderboardGroups;
|
||||||
@@ -575,10 +578,14 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
|
|||||||
for (Object object : dbLeaderboardIds) {
|
for (Object object : dbLeaderboardIds) {
|
||||||
ObjectId dbLeaderboardId = (ObjectId) object;
|
ObjectId dbLeaderboardId = (ObjectId) object;
|
||||||
DBObject dbLeaderboard = leaderboardCollection.findOne(dbLeaderboardId);
|
DBObject dbLeaderboard = leaderboardCollection.findOne(dbLeaderboardId);
|
||||||
final Leaderboard loadedLeaderboard = loadLeaderboard(dbLeaderboard, regattaRegistry,
|
if (dbLeaderboard != null) {
|
||||||
leaderboardRegistry, /* groupForMetaLeaderboard */ null);
|
final Leaderboard loadedLeaderboard = loadLeaderboard(dbLeaderboard, regattaRegistry,
|
||||||
if (loadedLeaderboard != null) {
|
leaderboardRegistry, /* groupForMetaLeaderboard */null);
|
||||||
leaderboards.add(loadedLeaderboard);
|
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);
|
logger.info("loaded leaderboard group "+name);
|
||||||
@@ -605,7 +612,9 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
|
|||||||
DBCollection leaderboardCollection = database.getCollection(CollectionNames.LEADERBOARDS.name());
|
DBCollection leaderboardCollection = database.getCollection(CollectionNames.LEADERBOARDS.name());
|
||||||
Set<Leaderboard> result = new HashSet<Leaderboard>();
|
Set<Leaderboard> result = new HashSet<Leaderboard>();
|
||||||
try {
|
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({ "
|
BasicDBObject query = new BasicDBObject("$where", "function() { return db." + CollectionNames.LEADERBOARD_GROUPS.name() + ".find({ "
|
||||||
+ FieldNames.LEADERBOARD_GROUP_LEADERBOARDS.name() + ": this._id }).count() == 0 && "
|
+ 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 && "
|
+ "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) {
|
} catch (Exception e) {
|
||||||
logger.log(Level.SEVERE, "Error connecting to MongoDB, unable to load leaderboards.");
|
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;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -690,7 +699,7 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
|
|||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// something went wrong during DB access; report, then use empty new wind track
|
// 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.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;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -741,7 +750,7 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
|
|||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.log(Level.SEVERE, "Error connecting to MongoDB, unable to load events.");
|
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;
|
return result;
|
||||||
@@ -951,7 +960,7 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
|
|||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
// something went wrong during DB access; report, then use empty new race log
|
// 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.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;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-3
@@ -2,6 +2,7 @@ package com.sap.sailing.domain.persistence.impl;
|
|||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import org.bson.types.ObjectId;
|
import org.bson.types.ObjectId;
|
||||||
@@ -100,7 +101,7 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory {
|
|||||||
|
|
||||||
public DBCollection getWindTrackCollection() {
|
public DBCollection getWindTrackCollection() {
|
||||||
DBCollection result = database.getCollection(CollectionNames.WIND_TRACKS.name());
|
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;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,7 +149,7 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory {
|
|||||||
leaderboardCollection.ensureIndex(FieldNames.LEADERBOARD_NAME.name());
|
leaderboardCollection.ensureIndex(FieldNames.LEADERBOARD_NAME.name());
|
||||||
} catch (NullPointerException npe) {
|
} catch (NullPointerException npe) {
|
||||||
// sometimes, for reasons yet to be clarified, ensuring an index on the name field causes an 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 query = new BasicDBObject(FieldNames.LEADERBOARD_NAME.name(), leaderboard.getName());
|
||||||
BasicDBObject dbLeaderboard = new BasicDBObject();
|
BasicDBObject dbLeaderboard = new BasicDBObject();
|
||||||
@@ -313,7 +314,7 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory {
|
|||||||
leaderboardGroupCollection.ensureIndex(FieldNames.LEADERBOARD_GROUP_NAME.name());
|
leaderboardGroupCollection.ensureIndex(FieldNames.LEADERBOARD_GROUP_NAME.name());
|
||||||
} catch (NullPointerException npe) {
|
} catch (NullPointerException npe) {
|
||||||
// sometimes, for reasons yet to be clarified, ensuring an index on the name field causes an 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 query = new BasicDBObject(FieldNames.LEADERBOARD_GROUP_NAME.name(), leaderboardGroup.getName());
|
||||||
BasicDBObject dbLeaderboardGroup = new BasicDBObject();
|
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
|
* Base interface for courses consisting of all static information, which might be shared
|
||||||
* by the server and an Android application.
|
* 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
|
* Clients can safely iterate over the resulting list because it's a copy which therefore won't reflect
|
||||||
* waypoint additions and removals.
|
* 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
|
* Base interface for an Event consisting of all static information, which might be shared
|
||||||
* by the server and an Android application.
|
* 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
|
* @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.
|
* 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.
|
* 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 java.io.Serializable;
|
||||||
|
|
||||||
import com.sap.sailing.domain.base.EventData;
|
import com.sap.sailing.domain.base.EventBase;
|
||||||
import com.sap.sailing.domain.base.Venue;
|
import com.sap.sailing.domain.base.Venue;
|
||||||
|
|
||||||
public class EventDataImpl implements EventData {
|
public class EventBaseImpl implements EventBase {
|
||||||
private static final long serialVersionUID = -5749964088848611074L;
|
private static final long serialVersionUID = -5749964088848611074L;
|
||||||
|
|
||||||
private String name;
|
private String name;
|
||||||
@@ -14,14 +14,14 @@ public class EventDataImpl implements EventData {
|
|||||||
private boolean isPublic;
|
private boolean isPublic;
|
||||||
private final Serializable id;
|
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);
|
this(name, new VenueImpl(venueName), publicationUrl, isPublic, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param venue must not be <code>null</code>
|
* @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;
|
assert venue != null;
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
+2
-2
@@ -1,11 +1,11 @@
|
|||||||
package com.sap.sailing.domain.base.racegroup;
|
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
|
* 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.
|
* 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();
|
public Iterable<RaceRow> getRaceRows();
|
||||||
}
|
}
|
||||||
+127
-22
@@ -1,7 +1,9 @@
|
|||||||
package com.sap.sailing.util.impl;
|
package com.sap.sailing.util.impl;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.WeakHashMap;
|
import java.util.WeakHashMap;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.locks.Lock;
|
import java.util.concurrent.locks.Lock;
|
||||||
import java.util.logging.Logger;
|
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 int NUMBER_OF_SECONDS_TO_WAIT_FOR_LOCK = 5;
|
||||||
private static final Logger logger = Logger.getLogger(Util.class.getName());
|
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<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
|
* 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
|
* 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.
|
* 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.
|
* 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) {
|
private static void lock(Lock lock, String lockDescriptionForTimeoutLogMessage, NamedReentrantReadWriteLock lockParent) {
|
||||||
boolean locked = false;
|
boolean locked = false;
|
||||||
@@ -35,7 +36,7 @@ public class LockUtil {
|
|||||||
message.append(lockDescriptionForTimeoutLogMessage);
|
message.append(lockDescriptionForTimeoutLogMessage);
|
||||||
message.append(" in ");
|
message.append(" in ");
|
||||||
message.append(NUMBER_OF_SECONDS_TO_WAIT_FOR_LOCK);
|
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());
|
message.append(getCurrentStackTrace());
|
||||||
Thread writer = lockParent.getWriter();
|
Thread writer = lockParent.getWriter();
|
||||||
if (writer != null) {
|
if (writer != null) {
|
||||||
@@ -69,37 +70,97 @@ public class LockUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void lockForRead(NamedReentrantReadWriteLock lock) {
|
public static void lockForRead(NamedReentrantReadWriteLock lock) {
|
||||||
lock(lock.readLock(), lock.getReadLockName(), 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) {
|
public static void unlockAfterRead(NamedReentrantReadWriteLock lock) {
|
||||||
lock.readLock().unlock();
|
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) {
|
public static void lockForWrite(NamedReentrantReadWriteLock lock) {
|
||||||
lock(lock.writeLock(), lock.getWriteLockName(), lock);
|
if (isInCurrentThreadsLockSet(lock.writeLock())) {
|
||||||
synchronized (lastTimeWriteLockWasObtained) {
|
incrementLockCountForCurrentThread(lock.writeLock());
|
||||||
lastTimeWriteLockWasObtained.put(lock, MillisecondsTimePoint.now());
|
} else {
|
||||||
|
lock(lock.writeLock(), lock.getWriteLockName(), lock);
|
||||||
|
addToCurrentThreadsLockSet(lock.writeLock());
|
||||||
|
synchronized (lastTimeWriteLockWasObtained) {
|
||||||
|
lastTimeWriteLockWasObtained.put(lock, MillisecondsTimePoint.now());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void unlockAfterWrite(NamedReentrantReadWriteLock lock) {
|
public static void unlockAfterWrite(NamedReentrantReadWriteLock lock) {
|
||||||
lock.writeLock().unlock();
|
assert isInCurrentThreadsLockSet(lock.writeLock());
|
||||||
final TimePoint timePointWriteLockWasObtained;
|
if (getCurrentThreadsLockCounts().get(lock.writeLock()) == 1) {
|
||||||
synchronized (lastTimeWriteLockWasObtained) {
|
lock.writeLock().unlock();
|
||||||
timePointWriteLockWasObtained = lastTimeWriteLockWasObtained.get(lock);
|
removeFromCurrentThreadsLockSet(lock.writeLock());
|
||||||
}
|
final TimePoint timePointWriteLockWasObtained;
|
||||||
if (timePointWriteLockWasObtained == null) {
|
synchronized (lastTimeWriteLockWasObtained) {
|
||||||
logger.info("Internal error: write lock "+lock.getName()+" to be unlocked but no time recorded for when it was last obtained.\n"+
|
timePointWriteLockWasObtained = lastTimeWriteLockWasObtained.get(lock);
|
||||||
"This is where the lock interaction happened:\n"+getCurrentStackTrace());
|
|
||||||
} else {
|
|
||||||
TimePoint now = MillisecondsTimePoint.now();
|
|
||||||
if (now.asMillis()-timePointWriteLockWasObtained.asMillis() > 10000l) {
|
|
||||||
String stackTrace = getCurrentStackTrace();
|
|
||||||
logger.info("write lock "+lock.getName()+" was held for more than 10s. It got unlocked here: "+
|
|
||||||
stackTrace);
|
|
||||||
}
|
}
|
||||||
|
if (timePointWriteLockWasObtained == null) {
|
||||||
|
logger.info("Internal error: write lock "+lock.getName()+" to be unlocked but no time recorded for when it was last obtained.\n"+
|
||||||
|
"This is where the lock interaction happened:\n"+getCurrentStackTrace());
|
||||||
|
} else {
|
||||||
|
TimePoint now = MillisecondsTimePoint.now();
|
||||||
|
if (now.asMillis()-timePointWriteLockWasObtained.asMillis() > 10000l) {
|
||||||
|
String stackTrace = getCurrentStackTrace();
|
||||||
|
logger.info("write lock "+lock.getName()+" was held for more than 10s. It got unlocked here: "+
|
||||||
|
stackTrace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
decrementLockCountForCurrentThread(lock.writeLock());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String getStackTrace(Thread thread) {
|
private static String getStackTrace(Thread thread) {
|
||||||
@@ -114,4 +175,48 @@ public class LockUtil {
|
|||||||
private static String getCurrentStackTrace() {
|
private static String getCurrentStackTrace() {
|
||||||
return getStackTrace(Thread.currentThread());
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+156
-75
@@ -7,7 +7,11 @@ import java.net.ServerSocket;
|
|||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import com.mongodb.BasicDBObject;
|
import com.mongodb.BasicDBObject;
|
||||||
@@ -63,7 +67,7 @@ public class StoreAndForward implements Runnable {
|
|||||||
|
|
||||||
private final Thread storeAndForwardThread;
|
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
|
* 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,
|
public StoreAndForward(String sailMasterHostname, int sailMasterPort, int portForClients, SwissTimingFactory swissTimingFactory,
|
||||||
SwissTimingAdapterPersistence swissTimingAdapterPersistence, MongoDBService mongoDBService) throws InterruptedException, IOException {
|
SwissTimingAdapterPersistence swissTimingAdapterPersistence, MongoDBService mongoDBService) throws InterruptedException, IOException {
|
||||||
|
this.receivingThreads = new HashSet<>();
|
||||||
this.db = mongoDBService.getDB();
|
this.db = mongoDBService.getDB();
|
||||||
this.listenPort = -1;
|
this.listenPort = -1;
|
||||||
this.transceiver = swissTimingFactory.createSailMasterTransceiver();
|
this.transceiver = swissTimingFactory.createSailMasterTransceiver();
|
||||||
this.portForClients = portForClients;
|
this.portForClients = portForClients;
|
||||||
this.sailMasterHostname = sailMasterHostname;
|
this.sailMasterHostname = sailMasterHostname;
|
||||||
this.sailMasterPort = sailMasterPort;
|
this.sailMasterPort = sailMasterPort;
|
||||||
this.streamsToForwardTo = new ArrayList<OutputStream>();
|
this.streamsToForwardTo = Collections.synchronizedList(new ArrayList<OutputStream>());
|
||||||
this.socketsToForwardTo = new ArrayList<Socket>();
|
this.socketsToForwardTo = new ArrayList<Socket>();
|
||||||
this.swissTimingAdapterPersistence = swissTimingAdapterPersistence;
|
this.swissTimingAdapterPersistence = swissTimingAdapterPersistence;
|
||||||
this.swissTimingFactory = swissTimingFactory;
|
this.swissTimingFactory = swissTimingFactory;
|
||||||
@@ -125,11 +130,12 @@ public class StoreAndForward implements Runnable {
|
|||||||
*/
|
*/
|
||||||
public StoreAndForward(final int listenPort, final int portForClients, SwissTimingFactory swissTimingFactory,
|
public StoreAndForward(final int listenPort, final int portForClients, SwissTimingFactory swissTimingFactory,
|
||||||
SwissTimingAdapterPersistence swissTimingAdapterPersistence, MongoDBService mongoDBService) throws InterruptedException, IOException {
|
SwissTimingAdapterPersistence swissTimingAdapterPersistence, MongoDBService mongoDBService) throws InterruptedException, IOException {
|
||||||
|
this.receivingThreads = new HashSet<>();
|
||||||
this.db = mongoDBService.getDB();
|
this.db = mongoDBService.getDB();
|
||||||
this.listenPort = listenPort;
|
this.listenPort = listenPort;
|
||||||
this.transceiver = swissTimingFactory.createSailMasterTransceiver();
|
this.transceiver = swissTimingFactory.createSailMasterTransceiver();
|
||||||
this.portForClients = portForClients;
|
this.portForClients = portForClients;
|
||||||
this.streamsToForwardTo = new ArrayList<OutputStream>();
|
this.streamsToForwardTo = Collections.synchronizedList(new ArrayList<OutputStream>());
|
||||||
this.socketsToForwardTo = new ArrayList<Socket>();
|
this.socketsToForwardTo = new ArrayList<Socket>();
|
||||||
this.swissTimingAdapterPersistence = swissTimingAdapterPersistence;
|
this.swissTimingAdapterPersistence = swissTimingAdapterPersistence;
|
||||||
this.swissTimingFactory = swissTimingFactory;
|
this.swissTimingFactory = swissTimingFactory;
|
||||||
@@ -201,20 +207,29 @@ public class StoreAndForward implements Runnable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Depending on the mode of operation, accepts an inbound connect from a SailMaster server / bridge or
|
* Depending on the mode of operation, accepts an inbound connect from a SailMaster server / bridge or actively
|
||||||
* actively initiates a TCP connection to the SailMaster address/port configured. When this method
|
* initiates a TCP connection to the SailMaster address/port configured. When a connection has been established, a
|
||||||
* returns, the {@link #socket} holds an open and connected socket.
|
* new {@link ReceivingThread} is spawned for the new socket. If in listening mode, the
|
||||||
* @throws IOException
|
* {@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()) {
|
if (isInSailMasterListeningMode()) {
|
||||||
synchronized (this) {
|
while (!stopped) {
|
||||||
receivingFromSailMaster = true;
|
synchronized (this) {
|
||||||
logger.info("StoreAndForward waiting for inbound SailMaster connections on port "+listenPort);
|
receivingFromSailMaster = true;
|
||||||
notifyAll();
|
logger.info("StoreAndForward waiting for inbound SailMaster connections on port " + listenPort);
|
||||||
|
notifyAll();
|
||||||
|
}
|
||||||
|
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();
|
||||||
}
|
}
|
||||||
socket = serverSocketListeningForSailMasterBridge.accept();
|
|
||||||
logger.info("StoreAndForward received SailMaster connect on port "+listenPort);
|
|
||||||
} else {
|
} else {
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
receivingFromSailMaster = true;
|
receivingFromSailMaster = true;
|
||||||
@@ -223,6 +238,119 @@ public class StoreAndForward implements Runnable {
|
|||||||
}
|
}
|
||||||
socket = new Socket(sailMasterHostname, sailMasterPort);
|
socket = new Socket(sailMasterHostname, sailMasterPort);
|
||||||
logger.info("StoreAndForward connections to SailMaster "+sailMasterHostname+":"+sailMasterPort+" established");
|
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();
|
closer.close();
|
||||||
logger.info("joining clientListener thread "+clientListener);
|
logger.info("joining clientListener thread "+clientListener);
|
||||||
clientListener.join();
|
clientListener.join();
|
||||||
socket.close(); // will let a read terminate abnormally
|
for (ReceivingThread receivingThread : receivingThreads) {
|
||||||
logger.info("joining storeAndForwardThread "+storeAndForwardThread);
|
receivingThread.stopReceiver();
|
||||||
storeAndForwardThread.join();
|
logger.info("joining storeAndForwardThread "+storeAndForwardThread);
|
||||||
|
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 {
|
public static void main(String[] args) throws InterruptedException, IOException {
|
||||||
@@ -266,66 +401,12 @@ public class StoreAndForward implements Runnable {
|
|||||||
try {
|
try {
|
||||||
while (!stopped) {
|
while (!stopped) {
|
||||||
try {
|
try {
|
||||||
establishConnection();
|
ReceivingThread receivingThread = establishConnection();
|
||||||
InputStream is = socket.getInputStream();
|
if (!isInSailMasterListeningMode()) {
|
||||||
Pair<String, Long> messageAndOptionalSequenceNumber = transceiver.receiveMessage(is);
|
receivingThread.join(); // we're in active connecting mode; wait for thread to die, then trie again
|
||||||
// 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();
|
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
logger.log(Level.SEVERE, "Exception in StoreAndForward", e);
|
||||||
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.info("StoreAndForward is closing receiving server socket");
|
logger.info("StoreAndForward is closing receiving server socket");
|
||||||
|
|||||||
+1
-1
@@ -141,7 +141,7 @@ public class SwissTimingAdapterPersistenceImpl implements SwissTimingAdapterPers
|
|||||||
@Override
|
@Override
|
||||||
public List<SailMasterMessage> loadRaceMessages(String raceID) {
|
public List<SailMasterMessage> loadRaceMessages(String raceID) {
|
||||||
DBCollection racesMessagesCollection = database.getCollection(CollectionNames.RACES_MESSAGES.name());
|
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();
|
BasicDBObject query = new BasicDBObject();
|
||||||
query.append(FieldNames.RACE_ID.name(), raceID);
|
query.append(FieldNames.RACE_ID.name(), raceID);
|
||||||
DBCursor results = racesMessagesCollection.find(query).sort(
|
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.io.OutputStream;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
|
import java.text.DateFormat;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.NavigableSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.logging.Logger;
|
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.GPSFix;
|
||||||
import com.sap.sailing.domain.tracking.GPSFixMoving;
|
import com.sap.sailing.domain.tracking.GPSFixMoving;
|
||||||
import com.sap.sailing.domain.tracking.GPSFixTrack;
|
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.RacesHandle;
|
||||||
import com.sap.sailing.domain.tracking.TrackedRace;
|
import com.sap.sailing.domain.tracking.TrackedRace;
|
||||||
import com.sap.sailing.domain.tracking.impl.EmptyWindStore;
|
import com.sap.sailing.domain.tracking.impl.EmptyWindStore;
|
||||||
@@ -301,6 +305,61 @@ public class EndToEndListeningStoreAndFowardTest {
|
|||||||
assertEquals(7, Util.size(waypoints));
|
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
|
@Test
|
||||||
public void testRongRaceLogRACZero() throws Exception{
|
public void testRongRaceLogRACZero() throws Exception{
|
||||||
String[] racesToTrack = new String[] { "W4702" };
|
String[] racesToTrack = new String[] { "W4702" };
|
||||||
|
|||||||
+2
-2
@@ -121,8 +121,8 @@ public class DomainFactoryImpl implements DomainFactory {
|
|||||||
return baseDomainFactory.getExistingCompetitorById(getCompetitorID(boatID, boatClass));
|
return baseDomainFactory.getExistingCompetitorById(getCompetitorID(boatID, boatClass));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Pair<String, BoatClass> getCompetitorID(String boatID, BoatClass boatClass) {
|
private String getCompetitorID(String boatID, BoatClass boatClass) {
|
||||||
return new Util.Pair<String, BoatClass>(boatID, boatClass);
|
return boatID + "/" + boatClass.getName();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
+73
-54
@@ -22,6 +22,7 @@ import java.util.Set;
|
|||||||
import java.util.TimeZone;
|
import java.util.TimeZone;
|
||||||
import java.util.concurrent.BlockingQueue;
|
import java.util.concurrent.BlockingQueue;
|
||||||
import java.util.concurrent.LinkedBlockingQueue;
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import com.sap.sailing.domain.base.SpeedWithBearing;
|
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
|
* 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
|
* can receive "spontaneous" events sent by the sail master system. Clients can register for those spontaneous events
|
||||||
* (see {@link #addSailMasterListener}).
|
* (see {@link #addSailMasterListener}).
|
||||||
* <p>
|
* <p>
|
||||||
@@ -182,6 +183,7 @@ public class SailMasterConnectorImpl extends SailMasterTransceiverImpl implement
|
|||||||
if (message.getSequenceNumber() != null) {
|
if (message.getSequenceNumber() != null) {
|
||||||
maxSequenceNumber = message.getSequenceNumber();
|
maxSequenceNumber = message.getSequenceNumber();
|
||||||
}
|
}
|
||||||
|
i++;
|
||||||
notifyListenersStoredDataProgress(raceID, ((double) i)/(double) messages.size());
|
notifyListenersStoredDataProgress(raceID, ((double) i)/(double) messages.size());
|
||||||
}
|
}
|
||||||
// now process the buffered messages one by one:
|
// now process the buffered messages one by one:
|
||||||
@@ -222,53 +224,65 @@ public class SailMasterConnectorImpl extends SailMasterTransceiverImpl implement
|
|||||||
public void run() {
|
public void run() {
|
||||||
try {
|
try {
|
||||||
while (!stopped) {
|
while (!stopped) {
|
||||||
ensureSocketIsOpen();
|
|
||||||
try {
|
try {
|
||||||
|
ensureSocketIsOpen();
|
||||||
Pair<String, Long> receivedMessageAndOptionalSequenceNumber = receiveMessage(socket.getInputStream());
|
Pair<String, Long> receivedMessageAndOptionalSequenceNumber = receiveMessage(socket.getInputStream());
|
||||||
SailMasterMessage message = new SailMasterMessageImpl(
|
if (receivedMessageAndOptionalSequenceNumber == null) {
|
||||||
receivedMessageAndOptionalSequenceNumber.getA(),
|
// reached EOF; this means the socket is or can be closed
|
||||||
receivedMessageAndOptionalSequenceNumber.getB());
|
if (socket != null && !socket.isClosed()) {
|
||||||
// drop race-specific messages for non-tracked races
|
socket.close();
|
||||||
if (!message.getType().isRaceSpecific() || idsOfTrackedRaces.contains(message.getRaceID())) {
|
|
||||||
boolean messageProcessed = false;
|
|
||||||
synchronized (this) {
|
|
||||||
if (message.getType().isRaceSpecific() && isCurrentlyBuffering(message.getRaceID())) {
|
|
||||||
buffer(message);
|
|
||||||
messageProcessed = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (!messageProcessed) {
|
socket = null;
|
||||||
if (message.isResponse()) {
|
} else {
|
||||||
// this is a response for an explicit request
|
SailMasterMessage message = new SailMasterMessageImpl(
|
||||||
rendevouz(message);
|
receivedMessageAndOptionalSequenceNumber.getA(),
|
||||||
} else if (message.isEvent()) {
|
receivedMessageAndOptionalSequenceNumber.getB());
|
||||||
// only notify if it hasn't been loaded from a store yet
|
// drop race-specific messages for non-tracked races
|
||||||
if (!message.getType().isRaceSpecific() || message.getSequenceNumber() == null ||
|
if (!message.getType().isRaceSpecific() || idsOfTrackedRaces.contains(message.getRaceID())) {
|
||||||
(sequenceNumberOfLastMessageForRaceID.containsKey(message.getRaceID()) &&
|
boolean messageProcessed = false;
|
||||||
message.getSequenceNumber() > sequenceNumberOfLastMessageForRaceID.get(message.getRaceID()))) {
|
synchronized (this) {
|
||||||
// a spontaneous event
|
if (message.getType().isRaceSpecific() && isCurrentlyBuffering(message.getRaceID())) {
|
||||||
logger.fine("notifying message " + message);
|
buffer(message);
|
||||||
notifyListeners(message);
|
messageProcessed = true;
|
||||||
} else {
|
}
|
||||||
logger.info("discarding already notified message " + message);
|
}
|
||||||
|
if (!messageProcessed) {
|
||||||
|
if (message.isResponse()) {
|
||||||
|
// this is a response for an explicit request
|
||||||
|
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()))) {
|
||||||
|
// a spontaneous event
|
||||||
|
logger.fine("notifying message " + message);
|
||||||
|
notifyListeners(message);
|
||||||
|
} else {
|
||||||
|
logger.info("discarding already notified message " + message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
if (message.getType() == MessageType._STOPSERVER) {
|
||||||
if (message.getType() == MessageType._STOPSERVER) {
|
logger.info("SailMasterConnector received " + MessageType._STOPSERVER.name());
|
||||||
logger.info("SailMasterConnector received "+MessageType._STOPSERVER.name());
|
stop();
|
||||||
stop();
|
}
|
||||||
}
|
}
|
||||||
} catch (SocketException se) {
|
} catch (SocketException se) {
|
||||||
// This occurs if the socket was closed which may mean the connector was stopped. Check in while
|
// 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");
|
logger.info("Caught exception "+se+" during socket operation; setting socket to null");
|
||||||
socket = null;
|
socket = null;
|
||||||
|
Thread.sleep(1000); // try again in 1s
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} 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");
|
logger.info("Stopping Sail Master connector thread");
|
||||||
|
stopped = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void buffer(SailMasterMessage message) {
|
private synchronized void buffer(SailMasterMessage message) {
|
||||||
@@ -304,28 +318,33 @@ public class SailMasterConnectorImpl extends SailMasterTransceiverImpl implement
|
|||||||
blockingQueue.offer(message);
|
blockingQueue.offer(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void notifyListeners(SailMasterMessage message) throws ParseException {
|
protected void notifyListeners(SailMasterMessage message) {
|
||||||
switch (message.getType()) {
|
try {
|
||||||
case RPD:
|
switch (message.getType()) {
|
||||||
notifyListenersRPD(message);
|
case RPD:
|
||||||
break;
|
notifyListenersRPD(message);
|
||||||
case RAC:
|
break;
|
||||||
notifyListenersRAC(message);
|
case RAC:
|
||||||
break;
|
notifyListenersRAC(message);
|
||||||
case CCG:
|
break;
|
||||||
notifyListenersCCG(message);
|
case CCG:
|
||||||
break;
|
notifyListenersCCG(message);
|
||||||
case STL:
|
break;
|
||||||
notifyListenersSTL(message);
|
case STL:
|
||||||
break;
|
notifyListenersSTL(message);
|
||||||
case CAM:
|
break;
|
||||||
notifyListenersCAM(message);
|
case CAM:
|
||||||
break;
|
notifyListenersCAM(message);
|
||||||
case TMD:
|
break;
|
||||||
notifyListenersTMD(message);
|
case TMD:
|
||||||
break;
|
notifyListenersTMD(message);
|
||||||
default:
|
break;
|
||||||
// ignore all other messages because there are no notification patterns for those
|
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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -42,7 +42,7 @@ public class SailMasterLiveSimulatorConnectorImpl extends SailMasterConnectorImp
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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
|
// we cache all messages here and deliver them with a different speed
|
||||||
bufferedMessageList.add(message);
|
bufferedMessageList.add(message);
|
||||||
}
|
}
|
||||||
|
|||||||
+7
@@ -5,6 +5,7 @@ import java.io.IOException;
|
|||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import com.sap.sailing.domain.common.impl.Util.Pair;
|
import com.sap.sailing.domain.common.impl.Util.Pair;
|
||||||
import com.sap.sailing.domain.swisstimingadapter.SailMasterMessage;
|
import com.sap.sailing.domain.swisstimingadapter.SailMasterMessage;
|
||||||
@@ -17,6 +18,7 @@ import com.sap.sailing.domain.swisstimingadapter.SailMasterTransceiver;
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public class SailMasterTransceiverImpl implements 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 STX = 0x02;
|
||||||
private static final byte ETX = 0x03;
|
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 {
|
public synchronized void sendMessage(SailMasterMessage message, OutputStream os) throws IOException {
|
||||||
os.write(("" + message.getSequenceNumber()).getBytes());
|
os.write(("" + message.getSequenceNumber()).getBytes());
|
||||||
sendMessage(message.getMessage(), os);
|
sendMessage(message.getMessage(), os);
|
||||||
|
os.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -59,9 +62,13 @@ public class SailMasterTransceiverImpl implements SailMasterTransceiver {
|
|||||||
bufferForOptionalSequenceNumber.write(read);
|
bufferForOptionalSequenceNumber.write(read);
|
||||||
read = inputStream.read();
|
read = inputStream.read();
|
||||||
}
|
}
|
||||||
|
// now either we've read an STX byte or reached EOF
|
||||||
String message = null;
|
String message = null;
|
||||||
if (read == STX) {
|
if (read == STX) {
|
||||||
message = readMessage(inputStream);
|
message = readMessage(inputStream);
|
||||||
|
} else {
|
||||||
|
// EOF; indicate by returning null
|
||||||
|
logger.info("Received EOF in SailMasterTransceiver. Returning null as message.");
|
||||||
}
|
}
|
||||||
if (bufferForOptionalSequenceNumber.size() > 0) {
|
if (bufferForOptionalSequenceNumber.size() > 0) {
|
||||||
sequenceNumber = getSequenceNumber(bufferForOptionalSequenceNumber);
|
sequenceNumber = getSequenceNumber(bufferForOptionalSequenceNumber);
|
||||||
|
|||||||
+76
-11
@@ -10,9 +10,9 @@ import java.util.List;
|
|||||||
import java.util.NavigableSet;
|
import java.util.NavigableSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
|
||||||
import com.sap.sailing.domain.base.BoatClass;
|
import com.sap.sailing.domain.base.BoatClass;
|
||||||
import com.sap.sailing.domain.base.Competitor;
|
import com.sap.sailing.domain.base.Competitor;
|
||||||
import com.sap.sailing.domain.base.Mark;
|
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.RaceTracker;
|
||||||
import com.sap.sailing.domain.tracking.RacesHandle;
|
import com.sap.sailing.domain.tracking.RacesHandle;
|
||||||
import com.sap.sailing.domain.tracking.TrackedRace;
|
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.TrackedRegattaRegistry;
|
||||||
import com.sap.sailing.domain.tracking.WindStore;
|
import com.sap.sailing.domain.tracking.WindStore;
|
||||||
import com.sap.sailing.domain.tracking.WindTrack;
|
import com.sap.sailing.domain.tracking.WindTrack;
|
||||||
@@ -65,7 +66,12 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl implemen
|
|||||||
private final Regatta regatta;
|
private final Regatta regatta;
|
||||||
private final WindStore windStore;
|
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 RaceDefinition race;
|
||||||
|
|
||||||
private Course course;
|
private Course course;
|
||||||
private StartList startList;
|
private StartList startList;
|
||||||
private DynamicTrackedRace trackedRace;
|
private DynamicTrackedRace trackedRace;
|
||||||
@@ -74,6 +80,13 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl implemen
|
|||||||
private boolean loggedIgnore;
|
private boolean loggedIgnore;
|
||||||
private final long delayToLiveInMillis;
|
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,
|
protected SwissTimingRaceTrackerImpl(String raceID, String hostname, int port, RaceLogStore raceLogStore, WindStore windStore,
|
||||||
DomainFactory domainFactory, SwissTimingFactory factory, RaceSpecificMessageLoader messageLoader,
|
DomainFactory domainFactory, SwissTimingFactory factory, RaceSpecificMessageLoader messageLoader,
|
||||||
TrackedRegattaRegistry trackedRegattaRegistry, boolean canSendRequests, long delayToLiveInMillis) throws InterruptedException,
|
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,
|
TrackedRegattaRegistry trackedRegattaRegistry, boolean canSendRequests, long delayToLiveInMillis) throws InterruptedException,
|
||||||
UnknownHostException, IOException, ParseException {
|
UnknownHostException, IOException, ParseException {
|
||||||
super();
|
super();
|
||||||
|
this.tmdMessageQueue = new TMDMessageQueue(this);
|
||||||
this.regatta = regatta;
|
this.regatta = regatta;
|
||||||
this.connector = factory.getOrCreateSailMasterConnector(hostname, port, messageLoader, canSendRequests);
|
this.connector = factory.getOrCreateSailMasterConnector(hostname, port, messageLoader, canSendRequests);
|
||||||
this.domainFactory = domainFactory;
|
this.domainFactory = domainFactory;
|
||||||
@@ -111,6 +125,10 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl implemen
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void stop() throws MalformedURLException, IOException, InterruptedException {
|
public void stop() throws MalformedURLException, IOException, InterruptedException {
|
||||||
|
if (isTrackedRaceStillReachable()) {
|
||||||
|
TrackedRaceStatus newStatus = new TrackedRaceStatusImpl(TrackedRaceStatusEnum.FINISHED, 1.0);
|
||||||
|
trackedRace.setStatus(newStatus);
|
||||||
|
}
|
||||||
connector.removeSailMasterListener(raceID, this);
|
connector.removeSailMasterListener(raceID, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,12 +148,37 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl implemen
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Set<RaceDefinition> getRaces() {
|
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);
|
return Collections.singleton(race);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Set<RaceDefinition> getRaces(long timeoutInMilliseconds) {
|
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
|
@Override
|
||||||
@@ -174,6 +217,7 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl implemen
|
|||||||
if (this.raceID.equals(raceID)) {
|
if (this.raceID.equals(raceID)) {
|
||||||
if (startTime != null) {
|
if (startTime != null) {
|
||||||
trackedRace.setStartTimeReceived(startTime);
|
trackedRace.setStartTimeReceived(startTime);
|
||||||
|
tmdMessageQueue.validStartTimeReceived();
|
||||||
}
|
}
|
||||||
for (Fix fix : fixes) {
|
for (Fix fix : fixes) {
|
||||||
GPSFixMoving gpsFix = domainFactory.createGPSFix(timePoint, fix);
|
GPSFixMoving gpsFix = domainFactory.createGPSFix(timePoint, fix);
|
||||||
@@ -236,11 +280,23 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl implemen
|
|||||||
for (Triple<Integer, Integer, Long> markIndexRankAndTimeSinceStartInMilliseconds : markIndicesRanksAndTimesSinceStartInMilliseconds) {
|
for (Triple<Integer, Integer, Long> markIndexRankAndTimeSinceStartInMilliseconds : markIndicesRanksAndTimesSinceStartInMilliseconds) {
|
||||||
Waypoint waypoint = Util.get(trackedRace.getRace().getCourse().getWaypoints(),
|
Waypoint waypoint = Util.get(trackedRace.getRace().getCourse().getWaypoints(),
|
||||||
markIndexRankAndTimeSinceStartInMilliseconds.getA());
|
markIndexRankAndTimeSinceStartInMilliseconds.getA());
|
||||||
MillisecondsTimePoint timePoint = trackedRace.getStartOfRace() == null ? null : new MillisecondsTimePoint(
|
// update mark passing only if we have a start time; guessed start times don't make sense and
|
||||||
trackedRace.getStartOfRace().asMillis() + markIndexRankAndTimeSinceStartInMilliseconds.getC());
|
// for the start line would lead subsequent calls to getStartOfRace() return that guessed start time
|
||||||
MarkPassing markPassing = domainFactory.createMarkPassing(timePoint, waypoint,
|
// which then cannot be identified as "guessed" anymore...
|
||||||
domainFactory.getCompetitorByBoatIDAndBoatClass(boatID, boatClass));
|
if (trackedRace.getStartOfRace() != null) {
|
||||||
markPassingsByMarkIndex.put(markIndexRankAndTimeSinceStartInMilliseconds.getA(), markPassing);
|
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());
|
trackedRace.updateMarkPassings(competitor, markPassingsByMarkIndex.values());
|
||||||
} else {
|
} else {
|
||||||
@@ -282,11 +338,11 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl implemen
|
|||||||
StartList oldStartList = this.startList;
|
StartList oldStartList = this.startList;
|
||||||
this.startList = startList;
|
this.startList = startList;
|
||||||
if (oldStartList == null && course != null) {
|
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 this.raceID.equals(raceID);
|
||||||
assert startList != null;
|
assert startList != null;
|
||||||
assert course != null;
|
assert course != null;
|
||||||
@@ -320,15 +376,16 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl implemen
|
|||||||
@Override
|
@Override
|
||||||
public void receivedCourseConfiguration(String raceID, Course course) {
|
public void receivedCourseConfiguration(String raceID, Course course) {
|
||||||
Course oldCourse = this.course;
|
Course oldCourse = this.course;
|
||||||
this.course = course;
|
|
||||||
if (trackedRace == null) {
|
if (trackedRace == null) {
|
||||||
if (oldCourse == null && startList != null) {
|
if (oldCourse == null && startList != null) {
|
||||||
createRaceDefinition(raceID);
|
createRaceDefinition(raceID, course);
|
||||||
|
this.course = course;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (isTrackedRaceStillReachable()) {
|
if (isTrackedRaceStillReachable()) {
|
||||||
try {
|
try {
|
||||||
domainFactory.updateCourseWaypoints(trackedRace.getRace().getCourse(), course.getMarks());
|
domainFactory.updateCourseWaypoints(trackedRace.getRace().getCourse(), course.getMarks());
|
||||||
|
this.course = course;
|
||||||
} catch (PatchFailedException e) {
|
} catch (PatchFailedException e) {
|
||||||
logger.info("Internal error trying to update course: " + e.getMessage());
|
logger.info("Internal error trying to update course: " + e.getMessage());
|
||||||
logger.throwing(SwissTimingRaceTrackerImpl.class.getName(), "receivedCourseConfiguration", e);
|
logger.throwing(SwissTimingRaceTrackerImpl.class.getName(), "receivedCourseConfiguration", e);
|
||||||
@@ -348,4 +405,12 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl implemen
|
|||||||
public void receivedAvailableRaces(Iterable<Race> races) {
|
public void receivedAvailableRaces(Iterable<Race> races) {
|
||||||
// don't care
|
// 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
|
* 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
|
* column only one group has raced (expressed by a mocked TrackedRace attached to the column). Ensure that the column does
|
||||||
* count for neither the total points sum nor the competitor ordering.
|
* count for the total points sum but that the competitor ordering is controlled by the number of races.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testUnorderedGroupsWithOneGroupNotHavingRacedInAColumn() throws NoWindException {
|
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), later), 0.000000001);
|
||||||
assertEquals((double) (i+1), leaderboard.getTotalPoints(competitors.get(i+5), later), 0.0000000001);
|
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));
|
TrackedRace q2Yellow = new MockedTrackedRaceWithStartTimeAndRanks(now, competitors.subList(3, 8));
|
||||||
RaceColumn q2Column = qualificationSeries.getRaceColumnByName("Q2");
|
RaceColumn q2Column = qualificationSeries.getRaceColumnByName("Q2");
|
||||||
q2Column.setTrackedRace(q2Column.getFleetByName("Yellow"), q2Yellow);
|
q2Column.setTrackedRace(q2Column.getFleetByName("Yellow"), q2Yellow);
|
||||||
List<Competitor> rankedCompetitorsWithOneRaceMissingInQ2 = leaderboard.getCompetitorsFromBestToWorst(later);
|
List<Competitor> rankedCompetitorsWithOneRaceMissingInQ2 = leaderboard.getCompetitorsFromBestToWorst(later);
|
||||||
for (int i=0; i<5; i++) {
|
// scores: C1=1, C2=2, C3=3, C4=5, C5=7, C6=4, C7=6, C8=8, C9=4, C10=5
|
||||||
assertTrue(competitors.get(i) == rankedCompetitorsWithOneRaceMissingInQ2.get(2*i) ||
|
// ordered by scores: C1, C2, C3, C6/C9, C4/C10, C7, C5, C8
|
||||||
competitors.get(i) == rankedCompetitorsWithOneRaceMissingInQ2.get(2*i+1));
|
// ordered primarily by number of races: C6, C4, C7, C5, C8, C1, C2, C3, C9, C10
|
||||||
assertTrue(competitors.get(i+5) == rankedCompetitorsWithOneRaceMissingInQ2.get(2*i) ||
|
assertEquals(Arrays.asList(new Competitor[] { competitors.get(5), competitors.get(3), competitors.get(6),
|
||||||
competitors.get(i+5) == rankedCompetitorsWithOneRaceMissingInQ2.get(2*i+1));
|
competitors.get(4), competitors.get(7), competitors.get(0), competitors.get(1), competitors.get(2),
|
||||||
assertEquals((double) (i+1), leaderboard.getTotalPoints(competitors.get(i), later), 0.000000001);
|
competitors.get(8), competitors.get(9) }), rankedCompetitorsWithOneRaceMissingInQ2);
|
||||||
assertEquals((double) (i+1), leaderboard.getTotalPoints(competitors.get(i+5), later), 0.0000000001);
|
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
|
// 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,
|
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
|
* Regarding bug 961 and 1023, test scoring in a leaderboard that has a qualification series with two unordered
|
||||||
* column only one group has raced (expressed by a mocked TrackedRace attached to the column). Then add the second tracked
|
* groups where for one column only one group has raced (expressed by a mocked TrackedRace attached to the column).
|
||||||
* 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,
|
* Those competitors are expected to rank better than those who haven't raced because they have a greater number of
|
||||||
* simulating the use of a proxy race (that doesn't ever start) and a manual score entry.
|
* 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
|
@Test
|
||||||
public void testUnorderedGroupsWithOneGroupNotHavingStartedRacedInAcolumnAndThenCorrectingScore() throws NoWindException {
|
public void testUnorderedGroupsWithOneGroupNotHavingStartedRacedInAcolumnAndThenCorrectingScore() throws NoWindException {
|
||||||
@@ -251,15 +257,19 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest {
|
|||||||
competitors.get(8), competitors.get(9) }));
|
competitors.get(8), competitors.get(9) }));
|
||||||
q2Column.setTrackedRace(q2Column.getFleetByName("Blue"), q2Blue);
|
q2Column.setTrackedRace(q2Column.getFleetByName("Blue"), q2Blue);
|
||||||
List<Competitor> rankedCompetitorsWithOneRaceMissingInQ2 = leaderboard.getCompetitorsFromBestToWorst(later);
|
List<Competitor> rankedCompetitorsWithOneRaceMissingInQ2 = leaderboard.getCompetitorsFromBestToWorst(later);
|
||||||
for (int i=0; i<5; i++) {
|
// scores: C1=1, C2=2, C3=3, C4=5, C5=7, C6=4, C7=6, C8=8, C9=4, C10=5
|
||||||
assertTrue(competitors.get(i) == rankedCompetitorsWithOneRaceMissingInQ2.get(2*i) ||
|
// ordered by scores: C1, C2, C3, C6/C9, C4/C10, C7, C5, C8
|
||||||
competitors.get(i) == rankedCompetitorsWithOneRaceMissingInQ2.get(2*i+1));
|
// ordered primarily by number of races: C6, C4, C7, C5, C8, C1, C2, C3, C9, C10
|
||||||
assertTrue(competitors.get(i+5) == rankedCompetitorsWithOneRaceMissingInQ2.get(2*i) ||
|
assertEquals(Arrays.asList(new Competitor[] { competitors.get(5), competitors.get(3), competitors.get(6),
|
||||||
competitors.get(i+5) == rankedCompetitorsWithOneRaceMissingInQ2.get(2*i+1));
|
competitors.get(4), competitors.get(7), competitors.get(0), competitors.get(1), competitors.get(2),
|
||||||
assertEquals((double) (i+1), leaderboard.getTotalPoints(competitors.get(i), later), 0.000000001);
|
competitors.get(8), competitors.get(9) }), rankedCompetitorsWithOneRaceMissingInQ2);
|
||||||
assertEquals((double) (i+1), leaderboard.getTotalPoints(competitors.get(i+5), later), 0.0000000001);
|
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:
|
// now add a score correction for Q2/Blue to make it count:
|
||||||
leaderboard.getScoreCorrection().correctScore(competitors.get(9), q2Column, 42.);
|
leaderboard.getScoreCorrection().correctScore(competitors.get(9), q2Column, 42.);
|
||||||
assertTrue(leaderboard.getScoringScheme().isValidInTotalScore(leaderboard, q2Column, later));
|
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
|
* 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
|
* column only one group has raced (expressed by a mocked TrackedRace attached to the column). Those who already raced their
|
||||||
* count for computing the number of discards.
|
* second race will have one discard, the others won't. Test this.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testDiscardsForUnorderedGroupsWithOneGroupNotHavingRacedInAColumn() throws NoWindException {
|
public void testDiscardsForUnorderedGroupsWithOneGroupNotHavingRacedInAColumn() throws NoWindException {
|
||||||
@@ -297,9 +307,14 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest {
|
|||||||
TrackedRace q2Yellow = new MockedTrackedRaceWithStartTimeAndRanks(now, competitors.subList(3, 8));
|
TrackedRace q2Yellow = new MockedTrackedRaceWithStartTimeAndRanks(now, competitors.subList(3, 8));
|
||||||
RaceColumn q2Column = qualificationSeries.getRaceColumnByName("Q2");
|
RaceColumn q2Column = qualificationSeries.getRaceColumnByName("Q2");
|
||||||
q2Column.setTrackedRace(q2Column.getFleetByName("Yellow"), q2Yellow);
|
q2Column.setTrackedRace(q2Column.getFleetByName("Yellow"), q2Yellow);
|
||||||
for (Competitor competitor : competitors) {
|
for (Competitor competitor : competitors.subList(3, 8)) {
|
||||||
assertFalse("Competitor "+competitor+" has a discard in Q1 but shouldn't", leaderboard.isDiscarded(competitor, q1Column, later));
|
assertTrue("Competitor "+competitor+" has no discard but should have one",
|
||||||
assertFalse("Competitor "+competitor+" has a discard in Q2 but shouldn't", leaderboard.isDiscarded(competitor, q2Column, later));
|
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
|
// now add a tracked race for the blue fleet for Q2 and assert that all competitors have one discard
|
||||||
TrackedRace q2Blue = new MockedTrackedRaceWithStartTimeAndRanks(now,
|
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.assertNull;
|
||||||
import static org.junit.Assert.assertTrue;
|
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 org.junit.Test;
|
||||||
|
|
||||||
import com.sap.sailing.util.impl.SmartFutureCache;
|
import com.sap.sailing.util.impl.SmartFutureCache;
|
||||||
@@ -36,10 +42,20 @@ public class SmartFutureCacheTest {
|
|||||||
final boolean[] updateWasCalled = new boolean[1];
|
final boolean[] updateWasCalled = new boolean[1];
|
||||||
final boolean[] cacheWasCalled = new boolean[1];
|
final boolean[] cacheWasCalled = new boolean[1];
|
||||||
final boolean[] mayProceed = new boolean[1];
|
final boolean[] mayProceed = new boolean[1];
|
||||||
|
|
||||||
SmartFutureCache<String, String, SmartFutureCache.EmptyUpdateInterval> sfc = new SmartFutureCache<String, String, SmartFutureCache.EmptyUpdateInterval>(
|
SmartFutureCache<String, String, SmartFutureCache.EmptyUpdateInterval> sfc = new SmartFutureCache<String, String, SmartFutureCache.EmptyUpdateInterval>(
|
||||||
new SmartFutureCache.AbstractCacheUpdater<String, String, SmartFutureCache.EmptyUpdateInterval>() {
|
new SmartFutureCache.AbstractCacheUpdater<String, String, SmartFutureCache.EmptyUpdateInterval>() {
|
||||||
@Override
|
@Override
|
||||||
public String computeCacheUpdate(String key, EmptyUpdateInterval updateInterval) throws Exception {
|
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;
|
updateWasCalled[0] = true;
|
||||||
return "Humba";
|
return "Humba";
|
||||||
}
|
}
|
||||||
@@ -47,22 +63,13 @@ public class SmartFutureCacheTest {
|
|||||||
@Override
|
@Override
|
||||||
protected void cache(String key, String value) {
|
protected void cache(String key, String value) {
|
||||||
super.cache(key, 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) {
|
synchronized (cacheWasCalled) {
|
||||||
cacheWasCalled[0] = true;
|
cacheWasCalled[0] = true;
|
||||||
cacheWasCalled.notifyAll();
|
cacheWasCalled.notifyAll();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
sfc.triggerUpdate("Trala", /* updateInterval */ null);
|
sfc.triggerUpdate("Trala", /* updateInterval */ null); // will be held up in computeCacheUpdate
|
||||||
assertNull(sfc.get("Trala", /* waitForLatest */ false));
|
assertNull(sfc.get("Trala", /* waitForLatest */ false));
|
||||||
synchronized (mayProceed) {
|
synchronized (mayProceed) {
|
||||||
mayProceed[0] = true;
|
mayProceed[0] = true;
|
||||||
@@ -100,6 +107,11 @@ public class SmartFutureCacheTest {
|
|||||||
public int getB() {
|
public int getB() {
|
||||||
return b;
|
return b;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "["+getA()+", "+getB()+"]";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -158,4 +170,62 @@ public class SmartFutureCacheTest {
|
|||||||
assertTrue(updateWasCalled[0]);
|
assertTrue(updateWasCalled[0]);
|
||||||
assertEquals(new Integer(91), sfc.get("Trala", /* waitForLatest */ true));
|
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) {
|
} catch (Exception e) {
|
||||||
// something went wrong during DB access; report, then use empty new wind track
|
// 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.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;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-2
@@ -14,6 +14,7 @@ import java.util.Iterator;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
@@ -366,7 +367,7 @@ public class ClientParamsPHP {
|
|||||||
return getTimePoint("RaceTrackingStartTime");
|
return getTimePoint("RaceTrackingStartTime");
|
||||||
} catch (ParseException e) {
|
} catch (ParseException e) {
|
||||||
logger.info("Exception trying to parse property RaceTrackingStartTime with value "+properties.get("RaceTrackingStartTime"));
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -376,7 +377,7 @@ public class ClientParamsPHP {
|
|||||||
return getTimePoint("RaceTrackingEndTime");
|
return getTimePoint("RaceTrackingEndTime");
|
||||||
} catch (ParseException e) {
|
} catch (ParseException e) {
|
||||||
logger.info("Exception trying to parse property RaceTrackingEndTime with value "+properties.get("RaceTrackingEndTime"));
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -114,7 +114,7 @@ public class RaceCourseReceiver extends AbstractReceiverWithQueue<Route, RouteDa
|
|||||||
}
|
}
|
||||||
} catch (PatchFailedException e) {
|
} catch (PatchFailedException e) {
|
||||||
logger.log(Level.SEVERE, "Internal error updating race course "+course+": "+e.getMessage());
|
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 {
|
} else {
|
||||||
logger.log(Level.INFO, "Received course for non-existing race "+event.getC().getName()+". Creating RaceDefinition.");
|
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.ScheduledExecutorService;
|
||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
|
||||||
@@ -261,18 +262,21 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl implements
|
|||||||
private void pollAndParseClientParamsPHP(final URL paramURL, final Simulator simulator) {
|
private void pollAndParseClientParamsPHP(final URL paramURL, final Simulator simulator) {
|
||||||
Set<RaceDefinition> raceDefinitions = getRaces();
|
Set<RaceDefinition> raceDefinitions = getRaces();
|
||||||
if (raceDefinitions != null && !raceDefinitions.isEmpty()) {
|
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;
|
final ClientParamsPHP clientParams;
|
||||||
try {
|
try {
|
||||||
clientParams = new ClientParamsPHP(new InputStreamReader(paramURL.openStream()));
|
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();
|
final List<? extends TracTracControlPoint> newTracTracControlPoints = clientParams.getRaceDefaultRoute().getControlPoints();
|
||||||
Map<Integer, NauticalSide> passingSideData = parsePassingSideData(clientParams.getRaceDefaultRoute(), newTracTracControlPoints);
|
Map<Integer, NauticalSide> passingSideData = parsePassingSideData(clientParams.getRaceDefaultRoute(), newTracTracControlPoints);
|
||||||
int i = 1;
|
int i = 1;
|
||||||
for (TracTracControlPoint newTracTracControlPoint : newTracTracControlPoints) {
|
for (TracTracControlPoint newTracTracControlPoint : newTracTracControlPoints) {
|
||||||
NauticalSide nauticalSide = passingSideData.containsKey(i) ? passingSideData.get(i) : null;
|
NauticalSide nauticalSide = passingSideData.containsKey(i) ? passingSideData.get(i) : null;
|
||||||
newCourseControlPoints.add(new Pair<com.sap.sailing.domain.base.ControlPoint,
|
final com.sap.sailing.domain.base.ControlPoint newControlPoint = domainFactory.getOrCreateControlPoint(newTracTracControlPoint);
|
||||||
NauticalSide>(domainFactory.getOrCreateControlPoint(newTracTracControlPoint), nauticalSide));
|
newCourseControlPoints.add(newControlPoint);
|
||||||
|
newCourseControlPointsWithPassingSide.add(new Pair<com.sap.sailing.domain.base.ControlPoint,
|
||||||
|
NauticalSide>(newControlPoint, nauticalSide));
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
List<com.sap.sailing.domain.base.ControlPoint> currentCourseControlPoints = new ArrayList<>();
|
List<com.sap.sailing.domain.base.ControlPoint> currentCourseControlPoints = new ArrayList<>();
|
||||||
@@ -283,10 +287,10 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl implements
|
|||||||
if (!newCourseControlPoints.equals(currentCourseControlPoints)) {
|
if (!newCourseControlPoints.equals(currentCourseControlPoints)) {
|
||||||
logger.info("Detected course change based on clientparams.php contents for races "+getRaces());
|
logger.info("Detected course change based on clientparams.php contents for races "+getRaces());
|
||||||
try {
|
try {
|
||||||
course.update(newCourseControlPoints, domainFactory.getBaseDomainFactory());
|
course.update(newCourseControlPointsWithPassingSide, domainFactory.getBaseDomainFactory());
|
||||||
} catch (PatchFailedException pfe) {
|
} catch (PatchFailedException pfe) {
|
||||||
logger.severe("Failed to apply course update "+newTracTracControlPoints+" to course "+course);
|
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);
|
updateStartStopTimesAndLiveDelay(clientParams, simulator);
|
||||||
@@ -311,7 +315,7 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl implements
|
|||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logger.info("Exception "+e.getMessage()+" while trying to read clientparams.php for races "+getRaces());
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,46 +1,46 @@
|
|||||||
package com.sap.sailing.domain.base;
|
package com.sap.sailing.domain.base;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||||
|
|
||||||
|
|
||||||
import com.sap.sailing.domain.common.NauticalSide;
|
import com.sap.sailing.domain.common.NauticalSide;
|
||||||
import com.sap.sailing.domain.common.impl.Util.Pair;
|
import com.sap.sailing.domain.common.impl.Util.Pair;
|
||||||
|
|
||||||
import difflib.PatchFailedException;
|
import difflib.PatchFailedException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A course consists of a sequence of {@link Waypoint}s. The {@link Leg}s extend between the adjacent waypoints.
|
* A course consists of a sequence of {@link Waypoint}s. The {@link Leg}s extend between the adjacent waypoints.
|
||||||
* Therefore, there is one waypoint more than there are legs.
|
* Therefore, there is one waypoint more than there are legs.
|
||||||
* <p>
|
* <p>
|
||||||
*
|
*
|
||||||
* A course maintains a re-entrant read/write lock (see {@link ReentrantReadWriteLock}. Individual reading methods
|
* A course maintains a re-entrant read/write lock (see {@link ReentrantReadWriteLock}. Individual reading methods
|
||||||
* internally use the read lock to protect against concurrent modifications through {@link #removeWaypoint(int)} or
|
* internally use the read lock to protect against concurrent modifications through {@link #removeWaypoint(int)} or
|
||||||
* {@link #addWaypoint(int, Waypoint)} which each obtain the write lock. However, if callers expect the waypoint or leg
|
* {@link #addWaypoint(int, Waypoint)} which each obtain the write lock. However, if callers expect the waypoint or leg
|
||||||
* obtained from a reading call to remain valid, holding the read lock is required. Otherwise, particularly a
|
* obtained from a reading call to remain valid, holding the read lock is required. Otherwise, particularly a
|
||||||
* {@link #removeWaypoint(int)} call may turn a leg and of course the waypoint invalid and therefore violate the
|
* {@link #removeWaypoint(int)} call may turn a leg and of course the waypoint invalid and therefore violate the
|
||||||
* reader's assumptions. The write lock is still held while propagating the updates to all {@link CourseListener}s
|
* reader's assumptions. The write lock is still held while propagating the updates to all {@link CourseListener}s
|
||||||
* registered. It is of course necessary to always use {@link #lockForRead()} and {@link #unlockAfterRead()}
|
* registered. It is of course necessary to always use {@link #lockForRead()} and {@link #unlockAfterRead()}
|
||||||
* symmetrically, where the {@link #unlockAfterRead()} call should happen in a <code>finally</code> clause to guarantee
|
* symmetrically, where the {@link #unlockAfterRead()} call should happen in a <code>finally</code> clause to guarantee
|
||||||
* execution.
|
* execution.
|
||||||
*
|
*
|
||||||
* @author Axel Uhl (d043530)
|
* @author Axel Uhl (d043530)
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public interface Course extends CourseData {
|
public interface Course extends CourseBase {
|
||||||
void lockForRead();
|
void lockForRead();
|
||||||
|
|
||||||
void unlockAfterRead();
|
void unlockAfterRead();
|
||||||
|
|
||||||
void addCourseListener(CourseListener listener);
|
void addCourseListener(CourseListener listener);
|
||||||
|
|
||||||
void removeCourseListener(CourseListener listener);
|
void removeCourseListener(CourseListener listener);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Carefully merges the new list of control points into this course by constructing a minimal difference between the
|
* Carefully merges the new list of control points into this course by constructing a minimal difference between the
|
||||||
* control point list and the control points referenced by this course's waypoints. Change events are propagated
|
* control point list and the control points referenced by this course's waypoints. Change events are propagated
|
||||||
* to the registered {@link CourseListener}s as if {@link #addWaypoint(int, Waypoint)} and {@link #removeWaypoint(int)}
|
* to the registered {@link CourseListener}s as if {@link #addWaypoint(int, Waypoint)} and {@link #removeWaypoint(int)}
|
||||||
* had been used.
|
* had been used.
|
||||||
*/
|
*/
|
||||||
void update(List<Pair<ControlPoint, NauticalSide>> newControlPoints, DomainFactory baseDomainFactory) throws PatchFailedException;
|
void update(List<Pair<ControlPoint, NauticalSide>> newControlPoints, DomainFactory baseDomainFactory) throws PatchFailedException;
|
||||||
}
|
}
|
||||||
@@ -1,21 +1,21 @@
|
|||||||
package com.sap.sailing.domain.base;
|
package com.sap.sailing.domain.base;
|
||||||
|
|
||||||
import com.sap.sailing.domain.common.Named;
|
import com.sap.sailing.domain.common.Named;
|
||||||
import com.sap.sailing.domain.common.Renamable;
|
import com.sap.sailing.domain.common.Renamable;
|
||||||
import com.sap.sailing.domain.common.WithID;
|
import com.sap.sailing.domain.common.WithID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An event is a group of {@link Regatta regattas} carried out at a common venue within a common time frame. For
|
* An event is a group of {@link Regatta regattas} carried out at a common venue within a common time frame. For
|
||||||
* example, Kiel Week 2011 is an event, and the International German Championship 2011 held, e.g., in Travem�nde, is an event,
|
* example, Kiel Week 2011 is an event, and the International German Championship 2011 held, e.g., in Travem�nde, is an event,
|
||||||
* too.
|
* too.
|
||||||
*
|
*
|
||||||
* @author Axel Uhl (D043530)
|
* @author Axel Uhl (D043530)
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public interface Event extends Named, Renamable, WithID, EventData {
|
public interface Event extends Named, Renamable, WithID, EventBase {
|
||||||
Iterable<Regatta> getRegattas();
|
Iterable<Regatta> getRegattas();
|
||||||
|
|
||||||
void addRegatta(Regatta regatta);
|
void addRegatta(Regatta regatta);
|
||||||
|
|
||||||
void removeRegatta(Regatta regatta);
|
void removeRegatta(Regatta regatta);
|
||||||
}
|
}
|
||||||
@@ -18,7 +18,7 @@ import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
|
|||||||
* @author Axel Uhl (D043530)
|
* @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
|
* 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) {
|
} catch (Exception e) {
|
||||||
logger.log(Level.SEVERE, "Exception while notifying listener about waypoint " + waypointToRemove
|
logger.log(Level.SEVERE, "Exception while notifying listener about waypoint " + waypointToRemove
|
||||||
+ " that got removed from course " + this + ": " + e.getMessage());
|
+ " 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) {
|
} catch (Exception e) {
|
||||||
logger.log(Level.SEVERE, "Exception while notifying listener about waypoint " + waypointToAdd
|
logger.log(Level.SEVERE, "Exception while notifying listener about waypoint " + waypointToAdd
|
||||||
+ " that got added to course " + this + ": " + e.getMessage());
|
+ " 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.ScoringScheme;
|
||||||
import com.sap.sailing.domain.leaderboard.impl.HighPoint;
|
import com.sap.sailing.domain.leaderboard.impl.HighPoint;
|
||||||
import com.sap.sailing.domain.leaderboard.impl.HighPointExtremeSailingSeriesOverall;
|
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.HighPointLastBreaksTie;
|
||||||
import com.sap.sailing.domain.leaderboard.impl.LowPoint;
|
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.MarkPassing;
|
||||||
import com.sap.sailing.domain.tracking.impl.MarkPassingImpl;
|
import com.sap.sailing.domain.tracking.impl.MarkPassingImpl;
|
||||||
|
|
||||||
@@ -287,9 +288,10 @@ public class DomainFactoryImpl implements DomainFactory {
|
|||||||
case HIGH_POINT_LAST_BREAKS_TIE:
|
case HIGH_POINT_LAST_BREAKS_TIE:
|
||||||
return new HighPointLastBreaksTie();
|
return new HighPointLastBreaksTie();
|
||||||
case HIGH_POINT_FIRST_GETS_TEN:
|
case HIGH_POINT_FIRST_GETS_TEN:
|
||||||
return new HighPointFirstGets10();
|
return new HighPointFirstGets10LastBreaksTie();
|
||||||
default:
|
case LOW_POINT_WINNER_GETS_ZERO:
|
||||||
throw new RuntimeException("Unknown scoring scheme type "+scoringSchemeType.name());
|
return new LowPointWinnerGetsZero();
|
||||||
}
|
}
|
||||||
|
throw new RuntimeException("Unknown scoring scheme type "+scoringSchemeType.name());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,43 +1,43 @@
|
|||||||
package com.sap.sailing.domain.base.impl;
|
package com.sap.sailing.domain.base.impl;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import com.sap.sailing.domain.base.Event;
|
import com.sap.sailing.domain.base.Event;
|
||||||
import com.sap.sailing.domain.base.Regatta;
|
import com.sap.sailing.domain.base.Regatta;
|
||||||
import com.sap.sailing.domain.base.Venue;
|
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 static final long serialVersionUID = 855135446595485715L;
|
||||||
|
|
||||||
private final Set<Regatta> regattas;
|
private final Set<Regatta> regattas;
|
||||||
|
|
||||||
public EventImpl(String name, String venueName, String publicationUrl, boolean isPublic, Serializable id) {
|
public EventImpl(String name, String venueName, String publicationUrl, boolean isPublic, Serializable id) {
|
||||||
this(name, new VenueImpl(venueName), publicationUrl, isPublic, id);
|
this(name, new VenueImpl(venueName), publicationUrl, isPublic, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param venue must not be <code>null</code>
|
* @param venue must not be <code>null</code>
|
||||||
*/
|
*/
|
||||||
public EventImpl(String name, Venue venue, String publicationUrl, boolean isPublic, Serializable id) {
|
public EventImpl(String name, Venue venue, String publicationUrl, boolean isPublic, Serializable id) {
|
||||||
super(name, venue, publicationUrl, isPublic, id);
|
super(name, venue, publicationUrl, isPublic, id);
|
||||||
this.regattas = new HashSet<Regatta>();
|
this.regattas = new HashSet<Regatta>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Iterable<Regatta> getRegattas() {
|
public Iterable<Regatta> getRegattas() {
|
||||||
return Collections.unmodifiableSet(regattas);
|
return Collections.unmodifiableSet(regattas);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addRegatta(Regatta regatta) {
|
public void addRegatta(Regatta regatta) {
|
||||||
regattas.add(regatta);
|
regattas.add(regatta);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void removeRegatta(Regatta regatta) {
|
public void removeRegatta(Regatta regatta) {
|
||||||
regattas.remove(regatta);
|
regattas.remove(regatta);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -20,6 +20,7 @@ import com.sap.sailing.domain.base.RaceDefinition;
|
|||||||
import com.sap.sailing.domain.base.Regatta;
|
import com.sap.sailing.domain.base.Regatta;
|
||||||
import com.sap.sailing.domain.base.RegattaListener;
|
import com.sap.sailing.domain.base.RegattaListener;
|
||||||
import com.sap.sailing.domain.base.Series;
|
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.RegattaIdentifier;
|
||||||
import com.sap.sailing.domain.common.RegattaName;
|
import com.sap.sailing.domain.common.RegattaName;
|
||||||
import com.sap.sailing.domain.common.impl.NamedImpl;
|
import com.sap.sailing.domain.common.impl.NamedImpl;
|
||||||
@@ -84,8 +85,9 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene
|
|||||||
* carried out.
|
* carried out.
|
||||||
*/
|
*/
|
||||||
public RegattaImpl(RaceLogStore raceLogStore, String baseName, BoatClass boatClass, TrackedRegattaRegistry trackedRegattaRegistry, ScoringScheme scoringScheme, Serializable id, CourseArea courseArea) {
|
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
|
this(raceLogStore, baseName, boatClass, Collections.singletonList(new SeriesImpl(LeaderboardNameConstants.DEFAULT_SERIES_NAME,
|
||||||
.singletonList(new FleetImpl("Default")), /* race column names */new ArrayList<String>(),
|
/* isMedal */false, Collections
|
||||||
|
.singletonList(new FleetImpl(LeaderboardNameConstants.DEFAULT_FLEET_NAME)), /* race column names */new ArrayList<String>(),
|
||||||
trackedRegattaRegistry)), /* persistent */false, scoringScheme, id, courseArea);
|
trackedRegattaRegistry)), /* persistent */false, scoringScheme, id, courseArea);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,7 +97,7 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene
|
|||||||
* this new regatta.
|
* this new regatta.
|
||||||
*/
|
*/
|
||||||
public RegattaImpl(RaceLogStore raceLogStore, String baseName, BoatClass boatClass, Iterable<? extends Series> series, boolean persistent, ScoringScheme scoringScheme, Serializable id, CourseArea courseArea) {
|
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.id = id;
|
||||||
this.raceLogStore = raceLogStore;
|
this.raceLogStore = raceLogStore;
|
||||||
races = new HashSet<RaceDefinition>();
|
races = new HashSet<RaceDefinition>();
|
||||||
@@ -126,7 +128,7 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene
|
|||||||
new RaceLogOnRegattaIdentifier(this, raceColumn.getName())));
|
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+")");
|
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
|
* 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
|
* those fleets are unordered. In that case, scores need to be available for all fleets before the column counts
|
||||||
* for the total scores.
|
* for the total scores.
|
||||||
* @param at TODO
|
|
||||||
*/
|
*/
|
||||||
boolean isValidInTotalScore(Leaderboard leaderboard, RaceColumn raceColumn, TimePoint at);
|
boolean isValidInTotalScore(Leaderboard leaderboard, RaceColumn raceColumn, TimePoint at);
|
||||||
}
|
}
|
||||||
+21
-13
@@ -70,6 +70,13 @@ public abstract class AbstractScoringSchemeImpl implements ScoringScheme {
|
|||||||
return new ScoreComparator(nullScoresAreBetter);
|
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
|
@Override
|
||||||
public boolean isValidInTotalScore(Leaderboard leaderboard, RaceColumn raceColumn, TimePoint at) {
|
public boolean isValidInTotalScore(Leaderboard leaderboard, RaceColumn raceColumn, TimePoint at) {
|
||||||
boolean result;
|
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) {
|
public int compareByBetterScore(List<Pair<RaceColumn, Double>> o1Scores, List<Pair<RaceColumn, Double>> o2Scores, boolean nullScoresAreBetter) {
|
||||||
assert o1Scores.size() == o2Scores.size();
|
assert o1Scores.size() == o2Scores.size();
|
||||||
final Comparator<Double> pureScoreComparator = getScoreComparator(nullScoresAreBetter);
|
final Comparator<Double> pureScoreComparator = getScoreComparator(nullScoresAreBetter);
|
||||||
Comparator<Pair<RaceColumn, Double>> scoreComparator = new Comparator<Pair<RaceColumn, Double>>() {
|
// needs to compare net points; therefore, divide the total points by the column factor for comparison:
|
||||||
@Override
|
List<Double> o1NetScores = new ArrayList<>();
|
||||||
public int compare(Pair<RaceColumn, Double> o1, Pair<RaceColumn, Double> o2) {
|
for (Pair<RaceColumn, Double> o1ColumnAndScore : o1Scores) {
|
||||||
return pureScoreComparator.compare(o1.getB(), o2.getB());
|
o1NetScores.add(o1ColumnAndScore.getB()/o1ColumnAndScore.getA().getFactor());
|
||||||
}
|
}
|
||||||
};
|
List<Double> o2NetScores = new ArrayList<>();
|
||||||
List<Pair<RaceColumn, Double>> o1ScoresCopy = new ArrayList<Pair<RaceColumn, Double>>(o1Scores);
|
for (Pair<RaceColumn, Double> o2ColumnAndScore : o2Scores) {
|
||||||
List<Pair<RaceColumn, Double>> o2ScoresCopy = new ArrayList<Pair<RaceColumn, Double>>(o2Scores);
|
o2NetScores.add(o2ColumnAndScore.getB()/o2ColumnAndScore.getA().getFactor());
|
||||||
Collections.sort(o1ScoresCopy, scoreComparator);
|
}
|
||||||
Collections.sort(o2ScoresCopy, scoreComparator);
|
Collections.sort(o1NetScores, pureScoreComparator);
|
||||||
|
Collections.sort(o2NetScores, pureScoreComparator);
|
||||||
// now both lists are sorted from best to worst score
|
// now both lists are sorted from best to worst score
|
||||||
Iterator<Pair<RaceColumn, Double>> o1Iter = o1ScoresCopy.iterator();
|
Iterator<Double> o1Iter = o1NetScores.iterator();
|
||||||
Iterator<Pair<RaceColumn, Double>> o2Iter = o2ScoresCopy.iterator();
|
Iterator<Double> o2Iter = o2NetScores.iterator();
|
||||||
int result = 0;
|
int result = 0;
|
||||||
while (result == 0 && o1Iter.hasNext() && o2Iter.hasNext()) {
|
while (result == 0 && o1Iter.hasNext() && o2Iter.hasNext()) {
|
||||||
result = scoreComparator.compare(o1Iter.next(), o2Iter.next());
|
result = pureScoreComparator.compare(o1Iter.next(), o2Iter.next());
|
||||||
}
|
}
|
||||||
return result;
|
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
|
* 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
|
@Override
|
||||||
public List<Competitor> getCompetitorsFromBestToWorst(final RaceColumn raceColumn, TimePoint timePoint) throws NoWindException {
|
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.RaceColumn;
|
||||||
import com.sap.sailing.domain.base.RaceColumnListener;
|
import com.sap.sailing.domain.base.RaceColumnListener;
|
||||||
import com.sap.sailing.domain.base.impl.FleetImpl;
|
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.FlexibleLeaderboard;
|
||||||
import com.sap.sailing.domain.leaderboard.FlexibleRaceColumn;
|
import com.sap.sailing.domain.leaderboard.FlexibleRaceColumn;
|
||||||
import com.sap.sailing.domain.leaderboard.ScoringScheme;
|
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 {
|
public class FlexibleLeaderboardImpl extends AbstractLeaderboardImpl implements FlexibleLeaderboard {
|
||||||
private static Logger logger = Logger.getLogger(FlexibleLeaderboardImpl.class.getName());
|
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 static final long serialVersionUID = -5708971849158747846L;
|
||||||
private final List<FlexibleRaceColumn> races;
|
private final List<FlexibleRaceColumn> races;
|
||||||
private final ScoringScheme scoringScheme;
|
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.base.RaceColumn;
|
||||||
import com.sap.sailing.domain.common.MaxPointsReason;
|
import com.sap.sailing.domain.common.MaxPointsReason;
|
||||||
import com.sap.sailing.domain.common.ScoringSchemeType;
|
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() {
|
public ScoringSchemeType getType() {
|
||||||
return ScoringSchemeType.HIGH_POINT;
|
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.Competitor;
|
||||||
import com.sap.sailing.domain.base.RaceColumn;
|
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;
|
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
|
* Compares a competitor better (lesser) if it has the greater number of races scored
|
||||||
*/
|
*/
|
||||||
protected int compareByNumberOfRacesScored(int o1NumberOfRacesScored, int o2NumberOfRacesScored) {
|
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.base.RaceColumn;
|
||||||
import com.sap.sailing.domain.common.MaxPointsReason;
|
import com.sap.sailing.domain.common.MaxPointsReason;
|
||||||
import com.sap.sailing.domain.common.ScoringSchemeType;
|
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() {
|
public ScoringSchemeType getType() {
|
||||||
return ScoringSchemeType.LOW_POINT;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
+7
-4
@@ -11,10 +11,13 @@ public abstract class AbstractRaceTrackerImpl implements RaceTracker {
|
|||||||
@Override
|
@Override
|
||||||
public Set<RegattaAndRaceIdentifier> getRaceIdentifiers() {
|
public Set<RegattaAndRaceIdentifier> getRaceIdentifiers() {
|
||||||
Set<RegattaAndRaceIdentifier> result = new HashSet<RegattaAndRaceIdentifier>();
|
Set<RegattaAndRaceIdentifier> result = new HashSet<RegattaAndRaceIdentifier>();
|
||||||
for (RaceDefinition race : getRaces()) {
|
final Set<RaceDefinition> races = getRaces();
|
||||||
TrackedRace trackedRace = getTrackedRegatta().getTrackedRace(race);
|
if (races != null) {
|
||||||
if (trackedRace != null) {
|
for (RaceDefinition race : races) {
|
||||||
result.add(trackedRace.getRaceIdentifier());
|
TrackedRace trackedRace = getTrackedRegatta().getTrackedRace(race);
|
||||||
|
if (trackedRace != null) {
|
||||||
|
result.add(trackedRace.getRaceIdentifier());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@@ -47,11 +47,11 @@ public interface RaceTracker {
|
|||||||
com.sap.sailing.domain.base.Regatta getRegatta();
|
com.sap.sailing.domain.base.Regatta getRegatta();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the races being tracked by this tracker. Non-blocking call that returns <code>null</code> if
|
* Returns the races being tracked by this tracker. Non-blocking call that returns <code>null</code> or an empty set
|
||||||
* the {@link RaceDefinition} for a TracTrac Event hasn't been created yet, e.g., because the course definition
|
* 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).
|
* hasn't been received yet or the listener for receiving course information hasn't been registered (yet). Also
|
||||||
* Also returns races that have been removed from containing structures which may lead this tracker to no
|
* returns races that have been removed from containing structures which may lead this tracker to no longer update
|
||||||
* longer update their {@link TrackedRace} with new data.
|
* their {@link TrackedRace} with new data.
|
||||||
*/
|
*/
|
||||||
Set<RaceDefinition> getRaces();
|
Set<RaceDefinition> getRaces();
|
||||||
|
|
||||||
|
|||||||
+7
-2
@@ -1,14 +1,17 @@
|
|||||||
package com.sap.sailing.domain.tracking.impl;
|
package com.sap.sailing.domain.tracking.impl;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.logging.Logger;
|
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.Competitor;
|
||||||
import com.sap.sailing.domain.base.Leg;
|
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.Timed;
|
||||||
import com.sap.sailing.domain.base.Waypoint;
|
import com.sap.sailing.domain.base.Waypoint;
|
||||||
import com.sap.sailing.domain.base.impl.MillisecondsTimePoint;
|
import com.sap.sailing.domain.base.impl.MillisecondsTimePoint;
|
||||||
@@ -334,7 +337,9 @@ public class CrossTrackErrorCache extends AbstractRaceChangeListener {
|
|||||||
@Override
|
@Override
|
||||||
public void markPositionChanged(GPSFix fix, Mark mark) {
|
public void markPositionChanged(GPSFix fix, Mark mark) {
|
||||||
TimePoint from = owner.getOrCreateTrack(mark).getEstimatedPositionTimePeriodAffectedBy(fix).getA();
|
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);
|
invalidate(competitor, from);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+28
-15
@@ -21,6 +21,7 @@ import com.sap.sailing.domain.base.RaceDefinition;
|
|||||||
import com.sap.sailing.domain.base.Waypoint;
|
import com.sap.sailing.domain.base.Waypoint;
|
||||||
import com.sap.sailing.domain.base.impl.MillisecondsTimePoint;
|
import com.sap.sailing.domain.base.impl.MillisecondsTimePoint;
|
||||||
import com.sap.sailing.domain.common.TimePoint;
|
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.WindSource;
|
||||||
import com.sap.sailing.domain.common.WindSourceType;
|
import com.sap.sailing.domain.common.WindSourceType;
|
||||||
import com.sap.sailing.domain.common.impl.Util;
|
import com.sap.sailing.domain.common.impl.Util;
|
||||||
@@ -238,7 +239,7 @@ DynamicTrackedRace, GPSTrackListener<Competitor, GPSFixMoving> {
|
|||||||
listener.windSourcesToExcludeChanged(windSourcesToExclude);
|
listener.windSourcesToExcludeChanged(windSourcesToExclude);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.log(Level.SEVERE, "RaceChangeListener " + listener + " threw exception " + e.getMessage());
|
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);
|
listener.raceTimesChanged(startOfTracking, endOfTracking, startTimeReceived);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.log(Level.SEVERE, "RaceChangeListener " + listener + " threw exception " + e.getMessage());
|
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);
|
listener.markPositionChanged(fix, mark);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.log(Level.SEVERE, "RaceChangeListener " + listener + " threw exception " + e.getMessage());
|
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);
|
listener.competitorPositionChanged(fix, competitor);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.log(Level.SEVERE, "RaceChangeListener " + listener + " threw exception " + e.getMessage());
|
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);
|
listener.statusChanged(status);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.log(Level.SEVERE, "RaceChangeListener " + listener + " threw exception " + e.getMessage());
|
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);
|
listener.windDataReceived(wind, windSource);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.log(Level.SEVERE, "RaceChangeListener " + listener + " threw exception " + e.getMessage());
|
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);
|
newMillisecondsOverWhichToAverageSpeed);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.log(Level.SEVERE, "RaceChangeListener " + listener + " threw exception " + e.getMessage());
|
logger.log(Level.SEVERE, "RaceChangeListener " + listener + " threw exception " + e.getMessage());
|
||||||
logger.throwing(DynamicTrackedRaceImpl.class.getName(),
|
logger.log(Level.SEVERE,
|
||||||
"notifyListenersSpeedAveragingChanged(long, long)", e);
|
"notifyListenersSpeedAveragingChanged(long, long)", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -347,7 +348,7 @@ DynamicTrackedRace, GPSTrackListener<Competitor, GPSFixMoving> {
|
|||||||
newMillisecondsOverWhichToAverageWind);
|
newMillisecondsOverWhichToAverageWind);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.log(Level.SEVERE, "RaceChangeListener " + listener + " threw exception " + e.getMessage());
|
logger.log(Level.SEVERE, "RaceChangeListener " + listener + " threw exception " + e.getMessage());
|
||||||
logger.throwing(DynamicTrackedRaceImpl.class.getName(),
|
logger.log(Level.SEVERE,
|
||||||
"notifyListenersWindAveragingChanged(long, long)", e);
|
"notifyListenersWindAveragingChanged(long, long)", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -363,7 +364,7 @@ DynamicTrackedRace, GPSTrackListener<Competitor, GPSFixMoving> {
|
|||||||
listener.delayToLiveChanged(delayToLiveInMillis);
|
listener.delayToLiveChanged(delayToLiveInMillis);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.log(Level.SEVERE, "RaceChangeListener " + listener + " threw exception " + e.getMessage());
|
logger.log(Level.SEVERE, "RaceChangeListener " + listener + " threw exception " + e.getMessage());
|
||||||
logger.throwing(DynamicTrackedRaceImpl.class.getName(),
|
logger.log(Level.SEVERE,
|
||||||
"notifyListenersDelayToLiveChanged(long)", e);
|
"notifyListenersDelayToLiveChanged(long)", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -379,7 +380,7 @@ DynamicTrackedRace, GPSTrackListener<Competitor, GPSFixMoving> {
|
|||||||
listener.windDataRemoved(wind, windSource);
|
listener.windDataRemoved(wind, windSource);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.log(Level.SEVERE, "RaceChangeListener " + listener + " threw exception " + e.getMessage());
|
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);
|
listener.markPassingReceived(competitor, oldMarkPassings, markPassings);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.log(Level.SEVERE, "RaceChangeListener " + listener + " threw exception " + e.getMessage());
|
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,10 +568,22 @@ DynamicTrackedRace, GPSTrackListener<Competitor, GPSFixMoving> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void recordWind(Wind wind, WindSource windSource) {
|
public void recordWind(Wind wind, WindSource windSource) {
|
||||||
getOrCreateWindTrack(windSource).add(wind);
|
// 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?
|
||||||
updated(/* time point */null); // wind events shouldn't advance race time
|
TimePoint startOfRace = getStartOfRace();
|
||||||
triggerManeuverCacheRecalculationForAllCompetitors();
|
TimePoint startOfTracking = getStartOfTracking();
|
||||||
notifyListeners(wind, windSource);
|
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
|
@Override
|
||||||
|
|||||||
+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
|
getRace().getCourse().lockForRead(); // ensure the course doesn't change, particularly lose the leg we're interested in, while we're running
|
||||||
try {
|
try {
|
||||||
for (Competitor competitor : getRace().getCompetitors()) {
|
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 was set to null this indicates there was an exception; no need for further calculations, return null
|
||||||
if (bearings != null && leg != null) {
|
if (bearings != null && leg != null) {
|
||||||
TrackedLeg trackedLeg = getTrackedLeg(leg.getLeg());
|
TrackedLeg trackedLeg = getTrackedLeg(leg.getLeg());
|
||||||
@@ -1756,7 +1764,12 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected void triggerManeuverCacheRecalculationForAllCompetitors() {
|
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);
|
triggerManeuverCacheRecalculation(competitor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1768,6 +1781,8 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Triple<TimePoint, TimePoint, List<Maneuver>> computeManeuvers(Competitor competitor) throws NoWindException {
|
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
|
// compute the maneuvers for competitor
|
||||||
Triple<TimePoint, TimePoint, List<Maneuver>> result = null;
|
Triple<TimePoint, TimePoint, List<Maneuver>> result = null;
|
||||||
NavigableSet<MarkPassing> markPassings = getMarkPassings(competitor);
|
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);
|
result = new Triple<TimePoint, TimePoint, List<Maneuver>>(extendedFrom, extendedTo, extendedResultForCache);
|
||||||
} // else competitor has no fixes to consider; remove any maneuver cache entry
|
} // else competitor has no fixes to consider; remove any maneuver cache entry
|
||||||
} // else competitor hasn't started yet; 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;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -115,7 +115,7 @@ public class WindTrackImpl extends TrackImpl<Wind> implements WindTrack {
|
|||||||
listener.windDataReceived(wind);
|
listener.windDataReceived(wind);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.log(Level.SEVERE, "WindListener " + listener + " threw exception " + e.getMessage());
|
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);
|
listener.windAveragingChanged(oldMillisecondsOverWhichToAverage, newMillisecondsOverWhichToAverage);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.log(Level.SEVERE, "WindListener " + listener + " threw exception " + e.getMessage());
|
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);
|
listener.windDataRemoved(wind);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.log(Level.SEVERE, "WindListener " + listener + " threw exception " + e.getMessage());
|
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.LinkedBlockingQueue;
|
||||||
import java.util.concurrent.ThreadPoolExecutor;
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||||
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import com.sap.sailing.util.impl.SmartFutureCache.UpdateInterval;
|
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>
|
* {@link #ongoingManeuverCacheRecalculations}. This will let {@link Future#cancel(boolean)} return <code>false</code>
|
||||||
* should it be called on that Future.
|
* 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 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;
|
private final CacheUpdater<K, V, U> cacheUpdateComputer;
|
||||||
|
|
||||||
@@ -92,6 +103,8 @@ public class SmartFutureCache<K, V, U extends UpdateInterval<U>> {
|
|||||||
private boolean suspended;
|
private boolean suspended;
|
||||||
|
|
||||||
private final Map<K, U> triggeredWhileSuspended;
|
private final Map<K, U> triggeredWhileSuspended;
|
||||||
|
|
||||||
|
private int smartFutureCacheTaskReuseCounter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An immutable "interval" description for a cache update
|
* 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
|
* 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
|
* 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)
|
* @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 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);
|
super(callable);
|
||||||
|
callable.setCallable(this);
|
||||||
|
this.key = key;
|
||||||
this.updateInterval = updateInterval;
|
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() {
|
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() {
|
public U getUpdateInterval() {
|
||||||
return updateInterval;
|
return updateInterval;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public SmartFutureCache(CacheUpdater<K, V, U> cacheUpdateComputer, String nameForLocks) {
|
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.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.cacheUpdateComputer = cacheUpdateComputer;
|
||||||
this.locksForKeys = new ConcurrentHashMap<K, NamedReentrantReadWriteLock>();
|
this.locksForKeys = new ConcurrentHashMap<K, NamedReentrantReadWriteLock>();
|
||||||
this.nameForLocks = nameForLocks;
|
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) {
|
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
|
// no Future contained in it is in cancelled state
|
||||||
synchronized (ongoingRecalculations) {
|
synchronized (ongoingRecalculations) {
|
||||||
final U oldUpdateInterval;
|
final U oldUpdateInterval;
|
||||||
final U joinedUpdateInterval;
|
final U joinedUpdateInterval;
|
||||||
|
FutureTaskWithCancelBlocking oldFuture = null;
|
||||||
if (suspended) {
|
if (suspended) {
|
||||||
oldUpdateInterval = triggeredWhileSuspended.get(key);
|
oldUpdateInterval = triggeredWhileSuspended.get(key);
|
||||||
} else {
|
} else {
|
||||||
FutureTaskWithCancelBlocking<V, U> oldFuture = ongoingRecalculations.get(key);
|
oldFuture = ongoingRecalculations.get(key);
|
||||||
if (oldFuture != null) {
|
|
||||||
oldFuture.cancel(/* mayInterruptIfRunning */false);
|
|
||||||
}
|
|
||||||
oldUpdateInterval = oldFuture == null ? null : oldFuture.getUpdateInterval();
|
oldUpdateInterval = oldFuture == null ? null : oldFuture.getUpdateInterval();
|
||||||
}
|
}
|
||||||
if (oldUpdateInterval == null) {
|
if (oldUpdateInterval == null) {
|
||||||
@@ -268,42 +379,41 @@ public class SmartFutureCache<K, V, U extends UpdateInterval<U>> {
|
|||||||
if (suspended) {
|
if (suspended) {
|
||||||
triggeredWhileSuspended.put(key, joinedUpdateInterval);
|
triggeredWhileSuspended.put(key, joinedUpdateInterval);
|
||||||
} else {
|
} 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
|
* Creates a {@link FutureTask} for the (re-)calculation of the cache entry for <code>key</code> across update
|
||||||
* <code>joinedUpdateInterval</code>, enters it into {@link #ongoingRecalculations} and schedules its execution with
|
* interval <code>joinedUpdateInterval</code>, enters it into {@link #ongoingRecalculations} and schedules its
|
||||||
* {@link #recalculator}. The method synchronizes on {@link #ongoingRecalculations}.
|
* 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) {
|
synchronized (ongoingRecalculations) {
|
||||||
final FutureTaskWithCancelBlocking<V, U> future;
|
final FutureTaskWithCancelBlocking future = new FutureTaskWithCancelBlocking(key,
|
||||||
future = new FutureTaskWithCancelBlocking<V, U>(new Callable<V>() {
|
joinedUpdateInterval, callerWaitsSynchronouslyForResult, callerThread);
|
||||||
@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);
|
|
||||||
ongoingRecalculations.put(key, future);
|
ongoingRecalculations.put(key, future);
|
||||||
recalculator.execute(future);
|
recalculator.execute(future);
|
||||||
}
|
}
|
||||||
@@ -318,16 +428,22 @@ public class SmartFutureCache<K, V, U extends UpdateInterval<U>> {
|
|||||||
public V get(K key, boolean waitForLatest) {
|
public V get(K key, boolean waitForLatest) {
|
||||||
V value = null;
|
V value = null;
|
||||||
if (waitForLatest) {
|
if (waitForLatest) {
|
||||||
FutureTaskWithCancelBlocking<V, U> future;
|
FutureTaskWithCancelBlocking future;
|
||||||
synchronized (ongoingRecalculations) {
|
synchronized (ongoingRecalculations) {
|
||||||
if (suspended && waitForLatest) {
|
if (suspended && waitForLatest) {
|
||||||
final boolean wasTriggeredWhileSuspended = triggeredWhileSuspended.containsKey(key); // update interval may have deliberately been null
|
final boolean wasTriggeredWhileSuspended = triggeredWhileSuspended.containsKey(key); // update interval may have deliberately been null
|
||||||
if (wasTriggeredWhileSuspended) {
|
if (wasTriggeredWhileSuspended) {
|
||||||
// If suspended and the caller wants to get the latest results, and during suspend recalculations were
|
// If suspended and the caller wants to get the latest results, and while suspended recalculations were
|
||||||
// triggered, and execute them now.
|
// triggered, execute them now.
|
||||||
// This will enter the new future into ongoingRecalculations, and therefore this method
|
// This will enter the new future into ongoingRecalculations, and therefore this method
|
||||||
// will wait for its completion below.
|
// 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
|
// 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 {
|
try {
|
||||||
value = future.get();
|
value = future.get();
|
||||||
} catch (InterruptedException | ExecutionException e) {
|
} catch (InterruptedException | ExecutionException e) {
|
||||||
logger.throwing(SmartFutureCache.class.getName(), "get", e);
|
logger.log(Level.SEVERE, "get", e);
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
} // else no calculation currently going on; value has been fetched from latest cache entry
|
} // 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) {
|
protected void cache(final K key, V value) {
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
cache.remove(key);
|
cache.remove(key);
|
||||||
|
locksForKeys.remove(key);
|
||||||
} else {
|
} else {
|
||||||
cache.put(key, value);
|
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,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,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[] {
|
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)
|
* @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
|
* @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() {
|
private static void usage() {
|
||||||
System.out.println("Usage: java "+UDPMirror.class.getName()+" [-v] <listeningport> hostname1 port1 [hostname2 port2]*");
|
System.out.println("Usage: java "+UDPMirror.class.getName()+" [-v] <listeningport> hostname1 port1 [hostname2 port2]*");
|
||||||
System.out.println(" -v\tPrint packets received to stdout");
|
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.net.DatagramPacket;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.logging.Logger;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
@@ -13,6 +14,7 @@ import com.sap.sailing.expeditionconnector.UDPExpeditionReceiver;
|
|||||||
import com.sap.sailing.udpconnector.UDPMessageParser;
|
import com.sap.sailing.udpconnector.UDPMessageParser;
|
||||||
|
|
||||||
public class ExpeditionMessageParser implements UDPMessageParser<ExpeditionMessage> {
|
public class ExpeditionMessageParser implements UDPMessageParser<ExpeditionMessage> {
|
||||||
|
private static final Logger logger = Logger.getLogger(ExpeditionMessageParser.class.getName());
|
||||||
|
|
||||||
private UDPExpeditionReceiver receiver;
|
private UDPExpeditionReceiver receiver;
|
||||||
|
|
||||||
@@ -25,7 +27,7 @@ public class ExpeditionMessageParser implements UDPMessageParser<ExpeditionMessa
|
|||||||
String packetAsString = new String(p.getData(), p.getOffset(), p.getLength()).trim();
|
String packetAsString = new String(p.getData(), p.getOffset(), p.getLength()).trim();
|
||||||
if (packetAsString.length() > 0) {
|
if (packetAsString.length() > 0) {
|
||||||
Pattern completeLinePattern = Pattern
|
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);
|
Matcher m = completeLinePattern.matcher(packetAsString);
|
||||||
boolean valid = m.matches();
|
boolean valid = m.matches();
|
||||||
if (valid) {
|
if (valid) {
|
||||||
@@ -62,7 +64,7 @@ public class ExpeditionMessageParser implements UDPMessageParser<ExpeditionMessa
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
} else {
|
} else {
|
||||||
System.err.println("Unparsable expedition message: " + packetAsString);
|
logger.warning("Unparsable expedition message: " + packetAsString);
|
||||||
return null; // couldn't even parse
|
return null; // couldn't even parse
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -72,7 +74,12 @@ public class ExpeditionMessageParser implements UDPMessageParser<ExpeditionMessa
|
|||||||
|
|
||||||
private boolean checksumOk(int checksum, String packetAsString) {
|
private boolean checksumOk(int checksum, String packetAsString) {
|
||||||
int b = 0;
|
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;
|
b ^= stringByte;
|
||||||
}
|
}
|
||||||
return b == checksum;
|
return b == checksum;
|
||||||
|
|||||||
@@ -1,72 +1,73 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<?pde version="3.5"?>
|
<?pde version="3.5"?>
|
||||||
|
|
||||||
<product name="SAP Race Analysis" uid="raceanalysis.product.id" id="raceanalysis.product" application="raceanalysis.application" version="1.0.0" useFeatures="true" includeLaunchers="false">
|
<product name="SAP Race Analysis" uid="raceanalysis.product.id" id="raceanalysis.product" application="raceanalysis.application" version="1.0.0" useFeatures="true" includeLaunchers="false">
|
||||||
|
|
||||||
<configIni use="default">
|
<configIni use="default">
|
||||||
</configIni>
|
</configIni>
|
||||||
|
|
||||||
<launcherArgs>
|
<launcherArgs>
|
||||||
<programArgs>-os ${target.os} -ws ${target.ws} -arch ${target.arch} -nl ${target.nl} -consoleLog -console -clean</programArgs>
|
<programArgs>-os ${target.os} -ws ${target.ws} -arch ${target.arch} -nl ${target.nl} -consoleLog -console -clean</programArgs>
|
||||||
<vmArgs>-Declipse.ignoreApp=true -Dosgi.noShutdown=true
|
<vmArgs>-Declipse.ignoreApp=true -Dosgi.noShutdown=true
|
||||||
-Xmx1024m -Djetty.home=configuration/jetty
|
-Xmx1024m -Djetty.home=configuration/jetty
|
||||||
-XX:+UseMembar</vmArgs>
|
-XX:+UseMembar</vmArgs>
|
||||||
</launcherArgs>
|
</launcherArgs>
|
||||||
|
|
||||||
<windowImages/>
|
<windowImages/>
|
||||||
|
|
||||||
<launcher>
|
<launcher>
|
||||||
<solaris/>
|
<solaris/>
|
||||||
<win useIco="false">
|
<win useIco="false">
|
||||||
<bmp/>
|
<bmp/>
|
||||||
</win>
|
</win>
|
||||||
</launcher>
|
</launcher>
|
||||||
|
|
||||||
<vm>
|
<vm>
|
||||||
</vm>
|
</vm>
|
||||||
|
|
||||||
<plugins>
|
<plugins>
|
||||||
</plugins>
|
</plugins>
|
||||||
|
|
||||||
<features>
|
<features>
|
||||||
<feature id="com.sap.sailing.feature" version="1.0.0.qualifier"/>
|
<feature id="com.sap.sailing.feature" version="1.0.0.qualifier"/>
|
||||||
<feature id="com.sap.sailing.feature.runtime" version="1.0.0.qualifier"/>
|
<feature id="com.sap.sailing.feature.runtime" version="1.0.0.qualifier"/>
|
||||||
</features>
|
</features>
|
||||||
|
|
||||||
<configurations>
|
<configurations>
|
||||||
<plugin id="com.sap.sailing.ess40.resultimport" autoStart="true" startLevel="3" />
|
<plugin id="com.sap.sailing.ess40.resultimport" autoStart="true" startLevel="3" />
|
||||||
<plugin id="com.sap.sailing.freg.resultimport" autoStart="true" startLevel="3" />
|
<plugin id="com.sap.sailing.freg.resultimport" autoStart="true" startLevel="3" />
|
||||||
<plugin id="com.sap.sailing.gwt.ui" autoStart="true" startLevel="4" />
|
<plugin id="com.sap.sailing.gwt.ui" autoStart="true" startLevel="4" />
|
||||||
<plugin id="com.sap.sailing.kiworesultimport" autoStart="true" startLevel="3" />
|
<plugin id="com.sap.sailing.kiworesultimport" autoStart="true" startLevel="3" />
|
||||||
<plugin id="com.sap.sailing.monitoring" autoStart="true" startLevel="5" />
|
<plugin id="com.sap.sailing.monitoring" autoStart="true" startLevel="5" />
|
||||||
<plugin id="com.sap.sailing.server" autoStart="true" startLevel="4" />
|
<plugin id="com.sap.sailing.server" autoStart="true" startLevel="4" />
|
||||||
<plugin id="com.sap.sailing.server.gateway" autoStart="true" startLevel="4" />
|
<plugin id="com.sap.sailing.server.gateway" autoStart="true" startLevel="4" />
|
||||||
<plugin id="com.sap.sailing.server.gateway.ess40" autoStart="true" startLevel="4" />
|
<plugin id="com.sap.sailing.server.gateway.ess40" autoStart="true" startLevel="4" />
|
||||||
<plugin id="com.sap.sailing.server.replication" autoStart="true" startLevel="4" />
|
<plugin id="com.sap.sailing.server.replication" autoStart="true" startLevel="4" />
|
||||||
<plugin id="com.sap.sailing.www" autoStart="true" startLevel="4" />
|
<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.www.events" autoStart="true" startLevel="4" />
|
||||||
<plugin id="com.sap.sailing.xcelsiusadapter" autoStart="true" startLevel="4" />
|
<plugin id="com.sap.sailing.xcelsiusadapter" autoStart="true" startLevel="4" />
|
||||||
<plugin id="org.eclipse.equinox.common" autoStart="true" startLevel="2" />
|
<plugin id="com.sap.sailing.xrr.resultimport" autoStart="true" startLevel="3" />
|
||||||
<plugin id="org.eclipse.equinox.simpleconfigurator" autoStart="true" startLevel="2" />
|
<plugin id="org.eclipse.equinox.common" autoStart="true" startLevel="2" />
|
||||||
<plugin id="org.eclipse.jetty.continuation" autoStart="true" startLevel="4" />
|
<plugin id="org.eclipse.equinox.simpleconfigurator" autoStart="true" startLevel="2" />
|
||||||
<plugin id="org.eclipse.jetty.deploy" autoStart="true" startLevel="4" />
|
<plugin id="org.eclipse.jetty.continuation" autoStart="true" startLevel="4" />
|
||||||
<plugin id="org.eclipse.jetty.http" autoStart="true" startLevel="4" />
|
<plugin id="org.eclipse.jetty.deploy" autoStart="true" startLevel="4" />
|
||||||
<plugin id="org.eclipse.jetty.io" autoStart="true" startLevel="4" />
|
<plugin id="org.eclipse.jetty.http" autoStart="true" startLevel="4" />
|
||||||
<plugin id="org.eclipse.jetty.jmx" autoStart="true" startLevel="4" />
|
<plugin id="org.eclipse.jetty.io" autoStart="true" startLevel="4" />
|
||||||
<plugin id="org.eclipse.jetty.nested" autoStart="true" startLevel="4" />
|
<plugin id="org.eclipse.jetty.jmx" autoStart="true" startLevel="4" />
|
||||||
<plugin id="org.eclipse.jetty.osgi.boot" autoStart="true" startLevel="3" />
|
<plugin id="org.eclipse.jetty.nested" autoStart="true" startLevel="4" />
|
||||||
<plugin id="org.eclipse.jetty.security" autoStart="true" startLevel="4" />
|
<plugin id="org.eclipse.jetty.osgi.boot" autoStart="true" startLevel="3" />
|
||||||
<plugin id="org.eclipse.jetty.server" autoStart="true" startLevel="4" />
|
<plugin id="org.eclipse.jetty.security" autoStart="true" startLevel="4" />
|
||||||
<plugin id="org.eclipse.jetty.servlet" autoStart="true" startLevel="4" />
|
<plugin id="org.eclipse.jetty.server" autoStart="true" startLevel="4" />
|
||||||
<plugin id="org.eclipse.jetty.util" autoStart="true" startLevel="4" />
|
<plugin id="org.eclipse.jetty.servlet" autoStart="true" startLevel="4" />
|
||||||
<plugin id="org.eclipse.jetty.webapp" autoStart="true" startLevel="4" />
|
<plugin id="org.eclipse.jetty.util" autoStart="true" startLevel="4" />
|
||||||
<plugin id="org.eclipse.jetty.xml" autoStart="true" startLevel="4" />
|
<plugin id="org.eclipse.jetty.webapp" autoStart="true" startLevel="4" />
|
||||||
<plugin id="org.mortbay.jetty.util" autoStart="true" startLevel="4" />
|
<plugin id="org.eclipse.jetty.xml" autoStart="true" startLevel="4" />
|
||||||
<property name="eclipse.ignoreApp" value="true" />
|
<plugin id="org.mortbay.jetty.util" autoStart="true" startLevel="4" />
|
||||||
<property name="expedition.udp.port" value="2010" />
|
<property name="eclipse.ignoreApp" value="true" />
|
||||||
<property name="mongo.port" value="10200" />
|
<property name="expedition.udp.port" value="2010" />
|
||||||
<property name="osgi.noShutdown" value="true" />
|
<property name="mongo.port" value="10200" />
|
||||||
<property name="replication.exchangeName" value="sapsailinganalytics-dev" />
|
<property name="osgi.noShutdown" value="true" />
|
||||||
</configurations>
|
<property name="replication.exchangeName" value="sapsailinganalytics-dev" />
|
||||||
|
</configurations>
|
||||||
</product>
|
|
||||||
|
</product>
|
||||||
@@ -1,233 +1,240 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<feature
|
<feature
|
||||||
id="com.sap.sailing.feature"
|
id="com.sap.sailing.feature"
|
||||||
label="SAP Sailing Server"
|
label="SAP Sailing Server"
|
||||||
version="1.0.0.qualifier"
|
version="1.0.0.qualifier"
|
||||||
provider-name="SAP">
|
provider-name="SAP">
|
||||||
|
|
||||||
<description url="http://www.sap.com">
|
<description url="http://www.sap.com">
|
||||||
The SAP Sailing Server.
|
The SAP Sailing Server.
|
||||||
|
|
||||||
Provides connectivity to a TracTrac live GPS feed as well as a live feed from an Expedition system.
|
Provides connectivity to a TracTrac live GPS feed as well as a live feed from an Expedition system.
|
||||||
Computes leaderboard information for sailing races and offers RESTful APIs to all of this.
|
Computes leaderboard information for sailing races and offers RESTful APIs to all of this.
|
||||||
</description>
|
</description>
|
||||||
|
|
||||||
<copyright url="http://www.example.com/copyright">
|
<copyright url="http://www.example.com/copyright">
|
||||||
[Enter Copyright Description here.]
|
[Enter Copyright Description here.]
|
||||||
</copyright>
|
</copyright>
|
||||||
|
|
||||||
<license url="http://www.example.com/license">
|
<license url="http://www.example.com/license">
|
||||||
[Enter License Description here.]
|
[Enter License Description here.]
|
||||||
</license>
|
</license>
|
||||||
|
|
||||||
<requires>
|
<requires>
|
||||||
<import plugin="com.googlecode.java-diff-utils" version="1.3.0" match="greaterOrEqual"/>
|
<import plugin="com.googlecode.java-diff-utils" version="1.3.0" match="greaterOrEqual"/>
|
||||||
<import plugin="org.eclipse.osgi"/>
|
<import plugin="org.eclipse.osgi"/>
|
||||||
<import plugin="javax.servlet" version="2.5.0" match="greaterOrEqual"/>
|
<import plugin="javax.servlet" version="2.5.0" match="greaterOrEqual"/>
|
||||||
<import plugin="com.mongodb.driver" version="2.6.2" match="greaterOrEqual"/>
|
<import plugin="com.mongodb.driver" version="2.6.2" match="greaterOrEqual"/>
|
||||||
<import plugin="com.google.gwt.osgi" version="2.4.0" match="greaterOrEqual"/>
|
<import plugin="com.google.gwt.osgi" version="2.4.0" match="greaterOrEqual"/>
|
||||||
<import plugin="com.sap.sailing.domain.common"/>
|
<import plugin="com.sap.sailing.domain.common"/>
|
||||||
<import plugin="org.json.simple" version="1.1.0" match="greaterOrEqual"/>
|
<import plugin="org.json.simple" version="1.1.0" match="greaterOrEqual"/>
|
||||||
<import plugin="com.sap.sailing.operationaltransformation"/>
|
<import plugin="com.sap.sailing.operationaltransformation"/>
|
||||||
<import plugin="org.moxieapps.gwt.highcharts" version="1.1.4" match="greaterOrEqual"/>
|
<import plugin="org.moxieapps.gwt.highcharts" version="1.1.4" match="greaterOrEqual"/>
|
||||||
<import plugin="com.sap.sailing.geocoding"/>
|
<import plugin="com.sap.sailing.geocoding"/>
|
||||||
<import plugin="org.apache.geronimo.specs.geronimo-jms_1.1_spec"/>
|
<import plugin="org.apache.geronimo.specs.geronimo-jms_1.1_spec"/>
|
||||||
<import plugin="com.rabbitmq.client"/>
|
<import plugin="com.rabbitmq.client"/>
|
||||||
<import plugin="org.apache.geronimo.specs.geronimo-j2ee-management_1.1_spec"/>
|
<import plugin="org.apache.geronimo.specs.geronimo-j2ee-management_1.1_spec"/>
|
||||||
</requires>
|
</requires>
|
||||||
|
|
||||||
<plugin
|
<plugin
|
||||||
id="com.sap.sailing.domain"
|
id="com.sap.sailing.domain"
|
||||||
download-size="0"
|
download-size="0"
|
||||||
install-size="0"
|
install-size="0"
|
||||||
version="0.0.0"
|
version="0.0.0"
|
||||||
unpack="false"/>
|
unpack="false"/>
|
||||||
|
|
||||||
<plugin
|
<plugin
|
||||||
id="com.sap.sailing.domain.tractracadapter"
|
id="com.sap.sailing.domain.tractracadapter"
|
||||||
download-size="0"
|
download-size="0"
|
||||||
install-size="0"
|
install-size="0"
|
||||||
version="0.0.0"
|
version="0.0.0"
|
||||||
unpack="false"/>
|
unpack="false"/>
|
||||||
|
|
||||||
<plugin
|
<plugin
|
||||||
id="com.sap.sailing.expeditionconnector"
|
id="com.sap.sailing.expeditionconnector"
|
||||||
download-size="0"
|
download-size="0"
|
||||||
install-size="0"
|
install-size="0"
|
||||||
version="0.0.0"
|
version="0.0.0"
|
||||||
unpack="false"/>
|
unpack="false"/>
|
||||||
|
|
||||||
<plugin
|
<plugin
|
||||||
id="com.sap.sailing.server"
|
id="com.sap.sailing.server"
|
||||||
download-size="0"
|
download-size="0"
|
||||||
install-size="0"
|
install-size="0"
|
||||||
version="0.0.0"
|
version="0.0.0"
|
||||||
unpack="false"/>
|
unpack="false"/>
|
||||||
|
|
||||||
<plugin
|
<plugin
|
||||||
id="com.sap.sailing.server.gateway"
|
id="com.sap.sailing.server.gateway"
|
||||||
download-size="0"
|
download-size="0"
|
||||||
install-size="0"
|
install-size="0"
|
||||||
version="0.0.0"
|
version="0.0.0"
|
||||||
unpack="false"/>
|
unpack="false"/>
|
||||||
|
|
||||||
<plugin
|
<plugin
|
||||||
id="com.sap.sailing.server.gateway.ess40"
|
id="com.sap.sailing.server.gateway.ess40"
|
||||||
download-size="0"
|
download-size="0"
|
||||||
install-size="0"
|
install-size="0"
|
||||||
version="0.0.0"
|
version="0.0.0"
|
||||||
unpack="false"/>
|
unpack="false"/>
|
||||||
|
|
||||||
<plugin
|
<plugin
|
||||||
id="com.tractrac.clientmodule"
|
id="com.tractrac.clientmodule"
|
||||||
download-size="0"
|
download-size="0"
|
||||||
install-size="0"
|
install-size="0"
|
||||||
version="0.0.0"
|
version="0.0.0"
|
||||||
unpack="false"/>
|
unpack="false"/>
|
||||||
|
|
||||||
<plugin
|
<plugin
|
||||||
id="org.json.simple"
|
id="org.json.simple"
|
||||||
download-size="0"
|
download-size="0"
|
||||||
install-size="0"
|
install-size="0"
|
||||||
version="0.0.0"
|
version="0.0.0"
|
||||||
unpack="false"/>
|
unpack="false"/>
|
||||||
|
|
||||||
<plugin
|
<plugin
|
||||||
id="com.sap.sailing.declination"
|
id="com.sap.sailing.declination"
|
||||||
download-size="0"
|
download-size="0"
|
||||||
install-size="0"
|
install-size="0"
|
||||||
version="0.0.0"
|
version="0.0.0"
|
||||||
unpack="false"/>
|
unpack="false"/>
|
||||||
|
|
||||||
<plugin
|
<plugin
|
||||||
id="com.mongodb.driver"
|
id="com.mongodb.driver"
|
||||||
download-size="0"
|
download-size="0"
|
||||||
install-size="0"
|
install-size="0"
|
||||||
version="0.0.0"
|
version="0.0.0"
|
||||||
unpack="false"/>
|
unpack="false"/>
|
||||||
|
|
||||||
<plugin
|
<plugin
|
||||||
id="com.sap.sailing.mongodb"
|
id="com.sap.sailing.mongodb"
|
||||||
download-size="0"
|
download-size="0"
|
||||||
install-size="0"
|
install-size="0"
|
||||||
version="0.0.0"
|
version="0.0.0"
|
||||||
unpack="false"/>
|
unpack="false"/>
|
||||||
|
|
||||||
<plugin
|
<plugin
|
||||||
id="com.sap.sailing.domain.persistence"
|
id="com.sap.sailing.domain.persistence"
|
||||||
download-size="0"
|
download-size="0"
|
||||||
install-size="0"
|
install-size="0"
|
||||||
version="0.0.0"
|
version="0.0.0"
|
||||||
unpack="false"/>
|
unpack="false"/>
|
||||||
|
|
||||||
<plugin
|
<plugin
|
||||||
id="com.sap.sailing.domain.swisstimingadapter"
|
id="com.sap.sailing.domain.swisstimingadapter"
|
||||||
download-size="0"
|
download-size="0"
|
||||||
install-size="0"
|
install-size="0"
|
||||||
version="0.0.0"
|
version="0.0.0"
|
||||||
unpack="false"/>
|
unpack="false"/>
|
||||||
|
|
||||||
<plugin
|
<plugin
|
||||||
id="com.sap.sailing.domain.swisstimingadapter.persistence"
|
id="com.sap.sailing.domain.swisstimingadapter.persistence"
|
||||||
download-size="0"
|
download-size="0"
|
||||||
install-size="0"
|
install-size="0"
|
||||||
version="0.0.0"
|
version="0.0.0"
|
||||||
unpack="false"/>
|
unpack="false"/>
|
||||||
|
|
||||||
<plugin
|
<plugin
|
||||||
id="com.sap.sailing.domain.swisstimingreplayadapter"
|
id="com.sap.sailing.domain.swisstimingreplayadapter"
|
||||||
download-size="0"
|
download-size="0"
|
||||||
install-size="0"
|
install-size="0"
|
||||||
version="0.0.0"
|
version="0.0.0"
|
||||||
unpack="false"/>
|
unpack="false"/>
|
||||||
|
|
||||||
<plugin
|
<plugin
|
||||||
id="com.sap.sailing.domain.tractracadapter.persistence"
|
id="com.sap.sailing.domain.tractracadapter.persistence"
|
||||||
download-size="0"
|
download-size="0"
|
||||||
install-size="0"
|
install-size="0"
|
||||||
version="0.0.0"
|
version="0.0.0"
|
||||||
unpack="false"/>
|
unpack="false"/>
|
||||||
|
|
||||||
<plugin
|
<plugin
|
||||||
id="com.sap.sailing.gwt.ui"
|
id="com.sap.sailing.gwt.ui"
|
||||||
download-size="0"
|
download-size="0"
|
||||||
install-size="0"
|
install-size="0"
|
||||||
version="0.0.0"
|
version="0.0.0"
|
||||||
unpack="false"/>
|
unpack="false"/>
|
||||||
|
|
||||||
<plugin
|
<plugin
|
||||||
id="com.sap.sailing.udpconnector"
|
id="com.sap.sailing.udpconnector"
|
||||||
download-size="0"
|
download-size="0"
|
||||||
install-size="0"
|
install-size="0"
|
||||||
version="0.0.0"
|
version="0.0.0"
|
||||||
unpack="false"/>
|
unpack="false"/>
|
||||||
|
|
||||||
<plugin
|
<plugin
|
||||||
id="com.sap.sailing.xcelsiusadapter"
|
id="com.sap.sailing.xcelsiusadapter"
|
||||||
download-size="0"
|
download-size="0"
|
||||||
install-size="0"
|
install-size="0"
|
||||||
version="0.0.0"
|
version="0.0.0"
|
||||||
unpack="false"/>
|
unpack="false"/>
|
||||||
|
|
||||||
<plugin
|
<plugin
|
||||||
id="com.google.gwt.osgi"
|
id="com.google.gwt.osgi"
|
||||||
download-size="0"
|
download-size="0"
|
||||||
install-size="0"
|
install-size="0"
|
||||||
version="0.0.0"
|
version="0.0.0"
|
||||||
unpack="false"/>
|
unpack="false"/>
|
||||||
|
|
||||||
<plugin
|
<plugin
|
||||||
id="com.googlecode.java-diff-utils"
|
id="com.googlecode.java-diff-utils"
|
||||||
download-size="0"
|
download-size="0"
|
||||||
install-size="0"
|
install-size="0"
|
||||||
version="0.0.0"
|
version="0.0.0"
|
||||||
unpack="false"/>
|
unpack="false"/>
|
||||||
|
|
||||||
<plugin
|
<plugin
|
||||||
id="com.sap.sailing.www"
|
id="com.sap.sailing.www"
|
||||||
download-size="0"
|
download-size="0"
|
||||||
install-size="0"
|
install-size="0"
|
||||||
version="0.0.0"/>
|
version="0.0.0"/>
|
||||||
|
|
||||||
<plugin
|
<plugin
|
||||||
id="com.sap.sailing.www.events"
|
id="com.sap.sailing.www.events"
|
||||||
download-size="0"
|
download-size="0"
|
||||||
install-size="0"
|
install-size="0"
|
||||||
version="0.0.0"/>
|
version="0.0.0"/>
|
||||||
|
|
||||||
<plugin
|
<plugin
|
||||||
id="com.sap.sailing.server.replication"
|
id="com.sap.sailing.server.replication"
|
||||||
download-size="0"
|
download-size="0"
|
||||||
install-size="0"
|
install-size="0"
|
||||||
version="0.0.0"
|
version="0.0.0"
|
||||||
unpack="false"/>
|
unpack="false"/>
|
||||||
|
|
||||||
<plugin
|
<plugin
|
||||||
id="com.sap.sailing.kiworesultimport"
|
id="com.sap.sailing.kiworesultimport"
|
||||||
download-size="0"
|
download-size="0"
|
||||||
install-size="0"
|
install-size="0"
|
||||||
version="0.0.0"
|
version="0.0.0"
|
||||||
unpack="false"/>
|
unpack="false"/>
|
||||||
|
|
||||||
<plugin
|
<plugin
|
||||||
id="com.sap.sailing.ess40.resultimport"
|
id="com.sap.sailing.ess40.resultimport"
|
||||||
download-size="0"
|
download-size="0"
|
||||||
install-size="0"
|
install-size="0"
|
||||||
version="0.0.0"
|
version="0.0.0"
|
||||||
unpack="false"/>
|
unpack="false"/>
|
||||||
|
|
||||||
<plugin
|
<plugin
|
||||||
id="com.sap.sailing.freg.resultimport"
|
id="com.sap.sailing.freg.resultimport"
|
||||||
download-size="0"
|
download-size="0"
|
||||||
install-size="0"
|
install-size="0"
|
||||||
version="0.0.0"
|
version="0.0.0"
|
||||||
unpack="false"/>
|
unpack="false"/>
|
||||||
|
|
||||||
<plugin
|
<plugin
|
||||||
id="com.sap.sailing.monitoring"
|
id="com.sap.sailing.monitoring"
|
||||||
download-size="0"
|
download-size="0"
|
||||||
install-size="0"
|
install-size="0"
|
||||||
version="0.0.0"
|
version="0.0.0"
|
||||||
unpack="false"/>
|
unpack="false"/>
|
||||||
|
|
||||||
</feature>
|
<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 LeaderboardDTO lb = null;
|
||||||
private static final String LEADERBOARDNAME = "test";
|
private static final String LEADERBOARDNAME = "test";
|
||||||
private static final String DEFAULT_FLEET_NAME = "Default";
|
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 SailingServiceImpl service;
|
||||||
private LeaderboardDTO leaderboardOriginalDTO;
|
private LeaderboardDTO leaderboardOriginalDTO;
|
||||||
private LeaderboardDTO leaderboardDTO;
|
private LeaderboardDTO leaderboardDTO;
|
||||||
|
|||||||
@@ -131,3 +131,7 @@ body, table td, select, button {
|
|||||||
.gwt-DialogBox {
|
.gwt-DialogBox {
|
||||||
z-index: 20;
|
z-index: 20;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bold {
|
||||||
|
font-family: "OpenSansCondensedBold", sans-serif;
|
||||||
|
}
|
||||||
@@ -176,8 +176,7 @@ input.magnifier {
|
|||||||
margin: 0 0 25px 0;
|
margin: 0 0 25px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.leaderboardGroupPanel-LeaderboardsTableLabel {
|
.leaderboardGroupPanel-LeaderboardsLabel {
|
||||||
margin: 0 0 10px;
|
|
||||||
font-size: 17px;
|
font-size: 17px;
|
||||||
font-weight: 500;
|
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));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
+20
-15
@@ -1,5 +1,7 @@
|
|||||||
package com.sap.sailing.gwt.ui.adminconsole;
|
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.DoubleBox;
|
||||||
import com.google.gwt.user.client.ui.Grid;
|
import com.google.gwt.user.client.ui.Grid;
|
||||||
import com.google.gwt.user.client.ui.Label;
|
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.RaceDTO;
|
||||||
import com.sap.sailing.gwt.ui.shared.WindDTO;
|
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 StringMessages stringMessages;
|
||||||
|
|
||||||
private final DoubleBox speedInKnotsBox;
|
private final DoubleBox speedInKnotsBox;
|
||||||
@@ -23,7 +25,7 @@ public class WindSettingDialog extends DataEntryDialog<WindDTO> {
|
|||||||
private final DateBox timeBox;
|
private final DateBox timeBox;
|
||||||
|
|
||||||
protected static class WindDataValidator implements Validator<WindDTO> {
|
protected static class WindDataValidator implements Validator<WindDTO> {
|
||||||
protected final StringMessages stringMessages;
|
private final StringMessages stringMessages;
|
||||||
|
|
||||||
public WindDataValidator(StringMessages stringMessages){
|
public WindDataValidator(StringMessages stringMessages){
|
||||||
super();
|
super();
|
||||||
@@ -42,24 +44,26 @@ public class WindSettingDialog extends DataEntryDialog<WindDTO> {
|
|||||||
errorMessage = stringMessages.pleaseEnterAValue();
|
errorMessage = stringMessages.pleaseEnterAValue();
|
||||||
} else if(windDTO.trueWindFromDeg != null && (windDTO.trueWindFromDeg < 0.0 || windDTO.trueWindFromDeg > 360.0)){
|
} else if(windDTO.trueWindFromDeg != null && (windDTO.trueWindFromDeg < 0.0 || windDTO.trueWindFromDeg > 360.0)){
|
||||||
errorMessage = stringMessages.valueMustBeBetweenMinMax(stringMessages.fromDeg(), "0", "360");
|
errorMessage = stringMessages.valueMustBeBetweenMinMax(stringMessages.fromDeg(), "0", "360");
|
||||||
|
} else if(windDTO.measureTimepoint == null) {
|
||||||
|
errorMessage = stringMessages.pleaseEnterAValue();
|
||||||
} else if(windDTO.position != null) {
|
} else if(windDTO.position != null) {
|
||||||
if(windDTO.position.latDeg < -90.0 || windDTO.position.latDeg > 90.0){
|
if(windDTO.position.latDeg < -90.0 || windDTO.position.latDeg > 90.0){
|
||||||
errorMessage = stringMessages.valueMustBeBetweenMinMax(stringMessages.latitude(), "-90", "90");
|
errorMessage = stringMessages.valueMustBeBetweenMinMax(stringMessages.latitude(), "-90", "90");
|
||||||
} else if(windDTO.position.lngDeg < -180.0 || windDTO.position.lngDeg > 180.0){
|
} else if(windDTO.position.lngDeg < -180.0 || windDTO.position.lngDeg > 180.0){
|
||||||
errorMessage = stringMessages.valueMustBeBetweenMinMax(stringMessages.longitude(), "-180", "180");
|
errorMessage = stringMessages.valueMustBeBetweenMinMax(stringMessages.longitude(), "-180", "180");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return errorMessage;
|
return errorMessage;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
super(stringMessages.actionAddWindData(), null, stringMessages.ok(), stringMessages.cancel(), new WindDataValidator(stringMessages), callback);
|
||||||
this.stringMessages = stringMessages;
|
this.stringMessages = stringMessages;
|
||||||
speedInKnotsBox = createDoubleBox(5);
|
speedInKnotsBox = createDoubleBox(5);
|
||||||
fromInDegBox = 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);
|
PositionDTO positionDTO = courseDTO.waypointPositions.get(0);
|
||||||
latDegBox = createDoubleBox(positionDTO.latDeg, 10);
|
latDegBox = createDoubleBox(positionDTO.latDeg, 10);
|
||||||
lngDegBox = createDoubleBox(positionDTO.lngDeg, 10);
|
lngDegBox = createDoubleBox(positionDTO.lngDeg, 10);
|
||||||
@@ -67,10 +71,11 @@ public class WindSettingDialog extends DataEntryDialog<WindDTO> {
|
|||||||
latDegBox = createDoubleBox(10);
|
latDegBox = createDoubleBox(10);
|
||||||
lngDegBox = createDoubleBox(10);
|
lngDegBox = createDoubleBox(10);
|
||||||
}
|
}
|
||||||
if(race.startOfRace != null) {
|
if(race.trackedRace != null && race.trackedRace.startOfTracking != null) {
|
||||||
timeBox = createDateBox(race.startOfRace.getTime(), 20);
|
timeBox = createDateBox(race.trackedRace.startOfTracking.getTime(), 20);
|
||||||
} else {
|
} 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(0, 1, speedInKnotsBox);
|
||||||
grid.setWidget(1, 0, new Label(stringMessages.fromDeg() + ":"));
|
grid.setWidget(1, 0, new Label(stringMessages.fromDeg() + ":"));
|
||||||
grid.setWidget(1, 1, fromInDegBox);
|
grid.setWidget(1, 1, fromInDegBox);
|
||||||
grid.setWidget(2, 0, new Label(stringMessages.latitude() + " (" + stringMessages.optional() + "):"));
|
grid.setWidget(2, 0, new Label(stringMessages.time() + ":"));
|
||||||
grid.setWidget(2, 1, latDegBox);
|
grid.setWidget(2, 1, timeBox);
|
||||||
grid.setWidget(3, 0, new Label(stringMessages.longitude() + " (" + stringMessages.optional() + "):"));
|
grid.setWidget(3, 0, new Label(stringMessages.latitude() + " (" + stringMessages.optional() + "):"));
|
||||||
grid.setWidget(3, 1, lngDegBox);
|
grid.setWidget(3, 1, latDegBox);
|
||||||
grid.setWidget(4, 0, new Label(stringMessages.time() + " (" + stringMessages.optional() + "):"));
|
grid.setWidget(4, 0, new Label(stringMessages.longitude() + " (" + stringMessages.optional() + "):"));
|
||||||
grid.setWidget(4, 1, timeBox);
|
grid.setWidget(4, 1, lngDegBox);
|
||||||
|
|
||||||
return mainPanel;
|
return mainPanel;
|
||||||
}
|
}
|
||||||
@@ -100,7 +105,7 @@ public class WindSettingDialog extends DataEntryDialog<WindDTO> {
|
|||||||
|
|
||||||
result.trueWindSpeedInKnots = speedInKnotsBox.getValue();
|
result.trueWindSpeedInKnots = speedInKnotsBox.getValue();
|
||||||
result.trueWindFromDeg = fromInDegBox.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) {
|
if (latDegBox.getValue() != null && lngDegBox.getValue() != null) {
|
||||||
result.position = new PositionDTO(latDegBox.getValue(), lngDegBox.getValue());
|
result.position = new PositionDTO(latDegBox.getValue(), lngDegBox.getValue());
|
||||||
}
|
}
|
||||||
+2
-2
@@ -63,7 +63,7 @@ public class AdminConsoleEntryPoint extends AbstractEntryPoint implements Regatt
|
|||||||
tabPanel.add(swisstimingEventManagementPanel, stringMessages.swissTimingEvents(), false);
|
tabPanel.add(swisstimingEventManagementPanel, stringMessages.swissTimingEvents(), false);
|
||||||
regattaDisplayers.add(swisstimingEventManagementPanel);
|
regattaDisplayers.add(swisstimingEventManagementPanel);
|
||||||
|
|
||||||
CreateSwissTimingRacePanel createSwissTimingRacePanel = new CreateSwissTimingRacePanel(sailingService,this,stringMessages);
|
CreateSwissTimingRacePanel createSwissTimingRacePanel = new CreateSwissTimingRacePanel(sailingService, this, stringMessages);
|
||||||
//createSwissTimingRacePanel.ensureDebugId("CreateSwissTimingRace");
|
//createSwissTimingRacePanel.ensureDebugId("CreateSwissTimingRace");
|
||||||
createSwissTimingRacePanel.setSize("90%", "90%");
|
createSwissTimingRacePanel.setSize("90%", "90%");
|
||||||
tabPanel.add(createSwissTimingRacePanel,"Create SwissTiming race",false);
|
tabPanel.add(createSwissTimingRacePanel,"Create SwissTiming race",false);
|
||||||
@@ -96,7 +96,7 @@ public class AdminConsoleEntryPoint extends AbstractEntryPoint implements Regatt
|
|||||||
|
|
||||||
final LeaderboardConfigPanel leaderboardConfigPanel = new LeaderboardConfigPanel(sailingService, this, this, stringMessages,
|
final LeaderboardConfigPanel leaderboardConfigPanel = new LeaderboardConfigPanel(sailingService, this, this, stringMessages,
|
||||||
/* showRaceDetails */ true);
|
/* showRaceDetails */ true);
|
||||||
//leaderboardConfigPanel.ensureDebugId("LeaderboardConfiguration");
|
leaderboardConfigPanel.ensureDebugId("LeaderboardConfiguration");
|
||||||
leaderboardConfigPanel.setSize("90%", "90%");
|
leaderboardConfigPanel.setSize("90%", "90%");
|
||||||
tabPanel.add(leaderboardConfigPanel, stringMessages.leaderboardConfiguration(), /* asHTML */ false);
|
tabPanel.add(leaderboardConfigPanel, stringMessages.leaderboardConfiguration(), /* asHTML */ false);
|
||||||
regattaDisplayers.add(leaderboardConfigPanel);
|
regattaDisplayers.add(leaderboardConfigPanel);
|
||||||
|
|||||||
+25
-24
@@ -1,24 +1,25 @@
|
|||||||
package com.sap.sailing.gwt.ui.adminconsole;
|
package com.sap.sailing.gwt.ui.adminconsole;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|
||||||
import com.sap.sailing.gwt.ui.client.ErrorReporter;
|
import com.sap.sailing.gwt.ui.client.ErrorReporter;
|
||||||
import com.sap.sailing.gwt.ui.client.StringMessages;
|
import com.sap.sailing.gwt.ui.client.StringMessages;
|
||||||
import com.sap.sailing.gwt.ui.shared.EventDTO;
|
import com.sap.sailing.gwt.ui.shared.EventDTO;
|
||||||
import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO;
|
import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO;
|
||||||
|
|
||||||
public class FlexibleLeaderboardCreateDialog extends FlexibleLeaderboardDialog {
|
public class FlexibleLeaderboardCreateDialog extends FlexibleLeaderboardDialog {
|
||||||
|
|
||||||
public FlexibleLeaderboardCreateDialog(Collection<StrippedLeaderboardDTO> existingLeaderboards, StringMessages stringMessages,
|
public FlexibleLeaderboardCreateDialog(Collection<StrippedLeaderboardDTO> existingLeaderboards, StringMessages stringMessages,
|
||||||
Collection<EventDTO> existingEvents, ErrorReporter errorReporter, DialogCallback<LeaderboardDescriptor> callback) {
|
Collection<EventDTO> existingEvents, ErrorReporter errorReporter, DialogCallback<LeaderboardDescriptor> callback) {
|
||||||
super(stringMessages.createFlexibleLeaderboard(), new LeaderboardDescriptor(), stringMessages, existingEvents, errorReporter, new FlexibleLeaderboardDialog.LeaderboardParameterValidator(stringMessages, existingLeaderboards), callback);
|
super(stringMessages.createFlexibleLeaderboard(), new LeaderboardDescriptor(), stringMessages, existingEvents, errorReporter, new FlexibleLeaderboardDialog.LeaderboardParameterValidator(stringMessages, existingLeaderboards), callback);
|
||||||
nameTextBox = createTextBox(null);
|
nameTextBox = createTextBox(null);
|
||||||
displayNameTextBox = createTextBox(null);
|
nameTextBox.ensureDebugId("LeaderboardNameField");
|
||||||
nameTextBox.setVisibleLength(50);
|
displayNameTextBox = createTextBox(null);
|
||||||
displayNameTextBox.setVisibleLength(50);
|
nameTextBox.setVisibleLength(50);
|
||||||
|
displayNameTextBox.setVisibleLength(50);
|
||||||
scoringSchemeListBox = createScoringSchemeListBox(this, stringMessages);
|
|
||||||
sailingEventsListBox = createSailingEventListBox();
|
scoringSchemeListBox = createScoringSchemeListBox(this, stringMessages);
|
||||||
discardThresholdBoxes = initEmptyDiscardThresholdBoxes(this);
|
sailingEventsListBox = createSailingEventListBox();
|
||||||
}
|
discardThresholdBoxes = initEmptyDiscardThresholdBoxes(this);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
+1131
-1127
File diff suppressed because it is too large.
Load diff
+1
-1
@@ -138,7 +138,7 @@ public class RaceColumnInLeaderboardDialog extends DataEntryDialog<RaceColumnInL
|
|||||||
super.show();
|
super.show();
|
||||||
if (isRegattaLeaderboard) {
|
if (isRegattaLeaderboard) {
|
||||||
explicitFactorBox.setFocus(true);
|
explicitFactorBox.setFocus(true);
|
||||||
explicitFactorBox.setSelectionRange(0, explicitFactorBox.getText().length());
|
explicitFactorBox.selectAll();
|
||||||
} else {
|
} else {
|
||||||
raceNameBox.setFocus(true);
|
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.user.client.ui.VerticalPanel;
|
||||||
import com.google.gwt.view.client.ListDataProvider;
|
import com.google.gwt.view.client.ListDataProvider;
|
||||||
import com.google.gwt.view.client.MultiSelectionModel;
|
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.ErrorReporter;
|
||||||
import com.sap.sailing.gwt.ui.client.RaceSelectionModel;
|
import com.sap.sailing.gwt.ui.client.RaceSelectionModel;
|
||||||
import com.sap.sailing.gwt.ui.client.RegattaRefresher;
|
import com.sap.sailing.gwt.ui.client.RegattaRefresher;
|
||||||
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
|
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
|
||||||
import com.sap.sailing.gwt.ui.client.StringMessages;
|
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.SwissTimingConfigurationDTO;
|
||||||
import com.sap.sailing.gwt.ui.shared.SwissTimingRaceRecordDTO;
|
import com.sap.sailing.gwt.ui.shared.SwissTimingRaceRecordDTO;
|
||||||
|
|
||||||
@@ -138,14 +141,31 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
TextColumn<SwissTimingRaceRecordDTO> raceNameColumn = new TextColumn<SwissTimingRaceRecordDTO>() {
|
TextColumn<SwissTimingRaceRecordDTO> raceNameColumn = new TextColumn<SwissTimingRaceRecordDTO>() {
|
||||||
@Override
|
@Override
|
||||||
public String getValue(SwissTimingRaceRecordDTO object) {
|
public String getValue(SwissTimingRaceRecordDTO object) {
|
||||||
return object.ID;
|
return object.ID;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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>() {
|
TextColumn<SwissTimingRaceRecordDTO> raceStartTrackingColumn = new TextColumn<SwissTimingRaceRecordDTO>() {
|
||||||
@Override
|
@Override
|
||||||
public String getValue(SwissTimingRaceRecordDTO object) {
|
public String getValue(SwissTimingRaceRecordDTO object) {
|
||||||
@@ -153,7 +173,6 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
HorizontalPanel racesSplitPanel = new HorizontalPanel();
|
HorizontalPanel racesSplitPanel = new HorizontalPanel();
|
||||||
mainPanel.add(racesSplitPanel);
|
mainPanel.add(racesSplitPanel);
|
||||||
|
|
||||||
@@ -179,6 +198,15 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane
|
|||||||
filterPanel.setSpacing(5);
|
filterPanel.setSpacing(5);
|
||||||
racesPanel.add(filterPanel);
|
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() + ":");
|
Label lblFilterEvents = new Label(stringConstants.filterRacesByName() + ":");
|
||||||
filterPanel.add(lblFilterEvents);
|
filterPanel.add(lblFilterEvents);
|
||||||
filterPanel.setCellVerticalAlignment(lblFilterEvents, HasVerticalAlignment.ALIGN_MIDDLE);
|
filterPanel.setCellVerticalAlignment(lblFilterEvents, HasVerticalAlignment.ALIGN_MIDDLE);
|
||||||
@@ -205,6 +233,8 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane
|
|||||||
AdminConsoleTableResources tableRes = GWT.create(AdminConsoleTableResources.class);
|
AdminConsoleTableResources tableRes = GWT.create(AdminConsoleTableResources.class);
|
||||||
raceTable = new CellTable<SwissTimingRaceRecordDTO>(/* pageSize */ 10000, tableRes);
|
raceTable = new CellTable<SwissTimingRaceRecordDTO>(/* pageSize */ 10000, tableRes);
|
||||||
raceTable.addColumn(raceNameColumn, stringConstants.name());
|
raceTable.addColumn(raceNameColumn, stringConstants.name());
|
||||||
|
raceTable.addColumn(raceDescriptionColumn, stringConstants.description());
|
||||||
|
raceTable.addColumn(boatClassColumn, stringConstants.boatClass());
|
||||||
raceTable.addColumn(raceStartTrackingColumn, stringConstants.raceStartTimeColumn());
|
raceTable.addColumn(raceStartTrackingColumn, stringConstants.raceStartTimeColumn());
|
||||||
raceTable.setWidth("300px");
|
raceTable.setWidth("300px");
|
||||||
raceTable.setSelectionModel(new MultiSelectionModel<SwissTimingRaceRecordDTO>() {});
|
raceTable.setSelectionModel(new MultiSelectionModel<SwissTimingRaceRecordDTO>() {});
|
||||||
@@ -345,8 +375,13 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane
|
|||||||
selectedRaces.add(race);
|
selectedRaces.add(race);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
RegattaDTO selectedRegatta = getSelectedRegatta();
|
||||||
|
RegattaIdentifier regattaIdentifier = null;
|
||||||
|
if (selectedRegatta != null) {
|
||||||
|
regattaIdentifier = new RegattaName(selectedRegatta.name);
|
||||||
|
}
|
||||||
sailingService.trackWithSwissTiming(
|
sailingService.trackWithSwissTiming(
|
||||||
/* regattaToAddTo */null, // TODO allow user to select a pre-defined regatta
|
/* regattaToAddTo */ regattaIdentifier,
|
||||||
selectedRaces, hostname, port, /* canSendRequests */false, trackWind, correctWindByDeclination,
|
selectedRaces, hostname, port, /* canSendRequests */false, trackWind, correctWindByDeclination,
|
||||||
new AsyncCallback<Void>() {
|
new AsyncCallback<Void>() {
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
+7
-18
@@ -186,9 +186,8 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel {
|
|||||||
layoutTable.setWidget(2, 0, hostnameLabel);
|
layoutTable.setWidget(2, 0, hostnameLabel);
|
||||||
layoutTable.setWidget(2, 1, this.hostnameTextBox);
|
layoutTable.setWidget(2, 1, this.hostnameTextBox);
|
||||||
|
|
||||||
// Regatta name
|
// Event name
|
||||||
Label eventNameLabel = new Label(this.stringMessages.eventName() + ":");
|
Label eventNameLabel = new Label(this.stringMessages.eventName() + ":");
|
||||||
|
|
||||||
this.eventNameTextBox = new TextBox();
|
this.eventNameTextBox = new TextBox();
|
||||||
this.eventNameTextBox.ensureDebugId("RegattaName");
|
this.eventNameTextBox.ensureDebugId("RegattaName");
|
||||||
this.eventNameTextBox.setText("event_2011...");
|
this.eventNameTextBox.setText("event_2011...");
|
||||||
@@ -198,7 +197,6 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel {
|
|||||||
synchURIs();
|
synchURIs();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
layoutTable.setWidget(3, 0, eventNameLabel);
|
layoutTable.setWidget(3, 0, eventNameLabel);
|
||||||
layoutTable.setWidget(3, 1, this.eventNameTextBox);
|
layoutTable.setWidget(3, 1, this.eventNameTextBox);
|
||||||
|
|
||||||
@@ -338,9 +336,8 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel {
|
|||||||
//columnFormatter.setWidth(1, "80%");
|
//columnFormatter.setWidth(1, "80%");
|
||||||
|
|
||||||
// Regatta
|
// Regatta
|
||||||
Label regattaForTrackingLabel = new Label("Regatta used for the tracked race:");
|
Label regattaForTrackingLabel = new Label(stringMessages.regattaUsedForTheTrackedRace());
|
||||||
regattaForTrackingLabel.setWordWrap(false);
|
regattaForTrackingLabel.setWordWrap(false);
|
||||||
|
|
||||||
layoutTable.setWidget(0, 0, regattaForTrackingLabel);
|
layoutTable.setWidget(0, 0, regattaForTrackingLabel);
|
||||||
layoutTable.setWidget(0, 1, getAvailableRegattasListBox());
|
layoutTable.setWidget(0, 1, getAvailableRegattasListBox());
|
||||||
|
|
||||||
@@ -619,11 +616,9 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel {
|
|||||||
String storedURI = this.storedURITextBox.getValue();
|
String storedURI = this.storedURITextBox.getValue();
|
||||||
RegattaDTO selectedRegatta = getSelectedRegatta();
|
RegattaDTO selectedRegatta = getSelectedRegatta();
|
||||||
RegattaIdentifier regattaIdentifier = null;
|
RegattaIdentifier regattaIdentifier = null;
|
||||||
|
|
||||||
if (selectedRegatta != null) {
|
if (selectedRegatta != null) {
|
||||||
regattaIdentifier = new RegattaName(selectedRegatta.name);
|
regattaIdentifier = new RegattaName(selectedRegatta.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the assigned regatta makes sense
|
// Check if the assigned regatta makes sense
|
||||||
List<TracTracRaceRecordDTO> allRaces = this.raceList.getList();
|
List<TracTracRaceRecordDTO> allRaces = this.raceList.getList();
|
||||||
List<TracTracRaceRecordDTO> racesWithNotMatchingBoatClasses = new ArrayList<TracTracRaceRecordDTO>();
|
List<TracTracRaceRecordDTO> racesWithNotMatchingBoatClasses = new ArrayList<TracTracRaceRecordDTO>();
|
||||||
@@ -631,33 +626,27 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel {
|
|||||||
|
|
||||||
for (TracTracRaceRecordDTO race : allRaces) {
|
for (TracTracRaceRecordDTO race : allRaces) {
|
||||||
if (selectionModel.isSelected(race)) {
|
if (selectionModel.isSelected(race)) {
|
||||||
if(!checkBoatClassMatch(race, selectedRegatta))
|
if (!checkBoatClassMatch(race, selectedRegatta))
|
||||||
racesWithNotMatchingBoatClasses.add(race);
|
racesWithNotMatchingBoatClasses.add(race);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (racesWithNotMatchingBoatClasses.size() > 0) {
|
||||||
if(racesWithNotMatchingBoatClasses.size() > 0) {
|
|
||||||
StringBuilder builder = new StringBuilder(100 + racesWithNotMatchingBoatClasses.size() * 30);
|
StringBuilder builder = new StringBuilder(100 + racesWithNotMatchingBoatClasses.size() * 30);
|
||||||
|
|
||||||
builder.append("WARNING\n");
|
builder.append("WARNING\n");
|
||||||
|
if (selectedRegatta != null) {
|
||||||
if(selectedRegatta != null) {
|
|
||||||
builder.append(this.stringMessages.boatClassDoesNotMatchSelectedRegatta(selectedRegatta.boatClass.name,
|
builder.append(this.stringMessages.boatClassDoesNotMatchSelectedRegatta(selectedRegatta.boatClass.name,
|
||||||
selectedRegatta.name));
|
selectedRegatta.name));
|
||||||
} else {
|
} else {
|
||||||
builder.append(this.stringMessages.regattaExistForSelectedBoatClass());
|
builder.append(this.stringMessages.regattaExistForSelectedBoatClass());
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.append("\n\n");
|
builder.append("\n\n");
|
||||||
builder.append(this.stringMessages.races());
|
builder.append(this.stringMessages.races());
|
||||||
builder.append("\n");
|
builder.append("\n");
|
||||||
|
for (TracTracRaceRecordDTO record: racesWithNotMatchingBoatClasses) {
|
||||||
for(TracTracRaceRecordDTO record: racesWithNotMatchingBoatClasses) {
|
|
||||||
builder.append(record.name);
|
builder.append(record.name);
|
||||||
builder.append("\n");
|
builder.append("\n");
|
||||||
}
|
}
|
||||||
|
if (!Window.confirm(builder.toString())) {
|
||||||
if(!Window.confirm(builder.toString())) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -109,7 +109,7 @@ public class WindPanel extends FormPanel implements RegattaDisplayer, WindShower
|
|||||||
RegattaAndRaceIdentifier selectedRace = getSelectedRace();
|
RegattaAndRaceIdentifier selectedRace = getSelectedRace();
|
||||||
if(selectedRace != null) {
|
if(selectedRace != null) {
|
||||||
final RaceDTO race = trackedRacesListComposite.getRaceByIdentifier(selectedRace);
|
final RaceDTO race = trackedRacesListComposite.getRaceByIdentifier(selectedRace);
|
||||||
sailingService.getCoursePositions(selectedRace, race.startOfRace, new AsyncCallback<CoursePositionsDTO>() {
|
sailingService.getCoursePositions(selectedRace, race.trackedRace.startOfTracking, new AsyncCallback<CoursePositionsDTO>() {
|
||||||
@Override
|
@Override
|
||||||
public void onFailure(Throwable caught) {
|
public void onFailure(Throwable caught) {
|
||||||
showWindSettingDialog(race, null);
|
showWindSettingDialog(race, null);
|
||||||
@@ -215,7 +215,7 @@ public class WindPanel extends FormPanel implements RegattaDisplayer, WindShower
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void showWindSettingDialog(RaceDTO race, CoursePositionsDTO course) {
|
private void showWindSettingDialog(RaceDTO race, CoursePositionsDTO course) {
|
||||||
WindSettingDialog windSettingDialog = new WindSettingDialog(race, course, stringMessages,
|
AddWindFixDialog windSettingDialog = new AddWindFixDialog(race, course, stringMessages,
|
||||||
new DialogCallback<WindDTO>() {
|
new DialogCallback<WindDTO>() {
|
||||||
@Override
|
@Override
|
||||||
public void cancel() {
|
public void cancel() {
|
||||||
|
|||||||
+16
-9
@@ -88,9 +88,11 @@ public abstract class DataEntryDialog<T> {
|
|||||||
this.validator = validator;
|
this.validator = validator;
|
||||||
okButton = new Button(okButtonName);
|
okButton = new Button(okButtonName);
|
||||||
okButton.getElement().getStyle().setMargin(3, Unit.PX);
|
okButton.getElement().getStyle().setMargin(3, Unit.PX);
|
||||||
|
okButton.ensureDebugId("OkButton");
|
||||||
FlowPanel dialogFPanel = new FlowPanel();
|
FlowPanel dialogFPanel = new FlowPanel();
|
||||||
dialogFPanel.setWidth("100%");
|
dialogFPanel.setWidth("100%");
|
||||||
statusLabel = new Label();
|
statusLabel = new Label();
|
||||||
|
statusLabel.ensureDebugId("StatusLabel");
|
||||||
dialogFPanel.add(statusLabel);
|
dialogFPanel.add(statusLabel);
|
||||||
if (message != null) {
|
if (message != null) {
|
||||||
Label messageLabel = new Label(message);
|
Label messageLabel = new Label(message);
|
||||||
@@ -107,6 +109,7 @@ public abstract class DataEntryDialog<T> {
|
|||||||
buttonPanel.add(okButton);
|
buttonPanel.add(okButton);
|
||||||
cancelButton = new Button(cancelButtonName);
|
cancelButton = new Button(cancelButtonName);
|
||||||
cancelButton.getElement().getStyle().setMargin(3, Unit.PX);
|
cancelButton.getElement().getStyle().setMargin(3, Unit.PX);
|
||||||
|
cancelButton.ensureDebugId("CancelButton");
|
||||||
buttonPanel.add(cancelButton);
|
buttonPanel.add(cancelButton);
|
||||||
cancelButton.addClickHandler(new ClickHandler() {
|
cancelButton.addClickHandler(new ClickHandler() {
|
||||||
@Override
|
@Override
|
||||||
@@ -238,19 +241,20 @@ public abstract class DataEntryDialog<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private DateBox createDateBoxInternal(Date initialDate, int visibleLength) {
|
private DateBox createDateBoxInternal(Date initialDate, int visibleLength) {
|
||||||
DateBox textBox = new DateBox();
|
DateBox dateBox = new DateBox();
|
||||||
textBox.getTextBox().setVisibleLength(visibleLength);
|
dateBox.getTextBox().setVisibleLength(visibleLength);
|
||||||
textBox.setValue(initialDate);
|
dateBox.setFireNullValues(true);
|
||||||
AbstractEntryPoint.addFocusUponKeyUpToggler(textBox.getTextBox());
|
dateBox.setValue(initialDate);
|
||||||
textBox.getTextBox().addChangeHandler(new ChangeHandler() {
|
AbstractEntryPoint.addFocusUponKeyUpToggler(dateBox.getTextBox());
|
||||||
|
dateBox.addValueChangeHandler(new ValueChangeHandler<Date>() {
|
||||||
@Override
|
@Override
|
||||||
public void onChange(ChangeEvent event) {
|
public void onValueChange(ValueChangeEvent<Date> event) {
|
||||||
validate();
|
validate();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
AbstractEntryPoint.linkEnterToButton(getOkButton(), textBox.getTextBox());
|
AbstractEntryPoint.linkEnterToButton(getOkButton(), dateBox.getTextBox());
|
||||||
AbstractEntryPoint.linkEscapeToButton(getCancelButton(), textBox.getTextBox());
|
AbstractEntryPoint.linkEscapeToButton(getCancelButton(), dateBox.getTextBox());
|
||||||
return textBox;
|
return dateBox;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -405,4 +409,7 @@ public abstract class DataEntryDialog<T> {
|
|||||||
dateEntryDialog.center();
|
dateEntryDialog.center();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ensureDebugId(String debugId) {
|
||||||
|
dateEntryDialog.ensureDebugId(debugId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+6
-8
@@ -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
|
// in case the race is not tracked anymore we reset the time slider
|
||||||
resetTimeSlider();
|
resetTimeSlider();
|
||||||
} else {
|
} else {
|
||||||
// check if the live delay is already been set
|
// timer will only accept this update if the delay hasn't been updated explicitly
|
||||||
if (!isUserExplicitlyChangedLivePlayDelay() && timer.getLivePlayDelayInMillis() != raceTimesInfo.delayToLiveInMs) {
|
timer.setLivePlayDelayInMillis(raceTimesInfo.delayToLiveInMs);
|
||||||
timer.setLivePlayDelayInMillis(raceTimesInfo.delayToLiveInMs);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((raceTimesInfo.startOfTracking != null || raceTimesInfo.startOfRace != null) &&
|
if ((raceTimesInfo.startOfTracking != null || raceTimesInfo.startOfRace != null) &&
|
||||||
(raceTimesInfo.newestTrackingEvent != null || raceTimesInfo.endOfRace != 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
|
// we set here the min and max of the time slider, the start and end of the race as well as the known
|
||||||
@@ -108,14 +105,15 @@ public class RaceTimePanel extends TimePanel<RaceTimePanelSettings> implements R
|
|||||||
@Override
|
@Override
|
||||||
protected boolean isLiveModeToBeMadePossible() {
|
protected boolean isLiveModeToBeMadePossible() {
|
||||||
long livePlayDelayInMillis = timer.getLivePlayDelayInMillis();
|
long livePlayDelayInMillis = timer.getLivePlayDelayInMillis();
|
||||||
long eventTimeoutTolerance = 60 * 1000; // 60s
|
long eventTimeoutTolerance = 60 * 1000; // 60s
|
||||||
|
long timeBeforeRaceStartTolerance = 3 * 60 * 1000; // 3min
|
||||||
long liveTimePointInMillis = System.currentTimeMillis() - livePlayDelayInMillis;
|
long liveTimePointInMillis = System.currentTimeMillis() - livePlayDelayInMillis;
|
||||||
RaceTimesInfoDTO lastRaceTimesInfo = raceTimesInfoProvider != null ? raceTimesInfoProvider.getRaceTimesInfo(selectedRace) : null;
|
RaceTimesInfoDTO lastRaceTimesInfo = raceTimesInfoProvider != null ? raceTimesInfoProvider.getRaceTimesInfo(selectedRace) : null;
|
||||||
return lastRaceTimesInfo != null &&
|
return lastRaceTimesInfo != null &&
|
||||||
lastRaceTimesInfo.newestTrackingEvent != null &&
|
lastRaceTimesInfo.newestTrackingEvent != null &&
|
||||||
liveTimePointInMillis < lastRaceTimesInfo.newestTrackingEvent.getTime() + eventTimeoutTolerance &&
|
liveTimePointInMillis < lastRaceTimesInfo.newestTrackingEvent.getTime() + eventTimeoutTolerance &&
|
||||||
lastRaceTimesInfo.startOfTracking != null &&
|
((lastRaceTimesInfo.startOfTracking != null && liveTimePointInMillis > lastRaceTimesInfo.startOfTracking.getTime())||
|
||||||
liveTimePointInMillis > lastRaceTimesInfo.startOfTracking.getTime();
|
(lastRaceTimesInfo.startOfRace != null && liveTimePointInMillis > lastRaceTimesInfo.startOfRace.getTime() - timeBeforeRaceStartTolerance));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
+4
@@ -367,6 +367,7 @@ public interface StringMessages extends Messages {
|
|||||||
String insertWaypointAfterSelected();
|
String insertWaypointAfterSelected();
|
||||||
String scoringSchemeLowPointSystem();
|
String scoringSchemeLowPointSystem();
|
||||||
String scoringSchemeHighPointSystem();
|
String scoringSchemeHighPointSystem();
|
||||||
|
String scoringSchemeLowPointWinnerGetsZero();
|
||||||
String scoringSystem();
|
String scoringSystem();
|
||||||
String createFlexibleLeaderboard();
|
String createFlexibleLeaderboard();
|
||||||
String createRegattaLeaderboard();
|
String createRegattaLeaderboard();
|
||||||
@@ -520,4 +521,7 @@ public interface StringMessages extends Messages {
|
|||||||
String correctScoreFor(String competitorName, String raceColumnName);
|
String correctScoreFor(String competitorName, String raceColumnName);
|
||||||
String penaltyOrRedress();
|
String penaltyOrRedress();
|
||||||
String netScore();
|
String netScore();
|
||||||
|
String timePointMustBeAfterStartOfTracking();
|
||||||
|
String timePointMustBeBeforeEndOfTracking();
|
||||||
|
String regattaUsedForTheTrackedRace();
|
||||||
}
|
}
|
||||||
+5
-1
@@ -395,6 +395,7 @@ scoringSchemeLowPointSystem=Low Point System
|
|||||||
scoringSchemeHighPointSystem=High Point System
|
scoringSchemeHighPointSystem=High Point System
|
||||||
scoringSchemeHighPointEssOverall=High Point System ESS Overall
|
scoringSchemeHighPointEssOverall=High Point System ESS Overall
|
||||||
scoringSchemeHighPointLastBreaksTie=High Point System, Last Race Breaks Tie
|
scoringSchemeHighPointLastBreaksTie=High Point System, Last Race Breaks Tie
|
||||||
|
scoringSchemeLowPointWinnerGetsZero=Low Point System, Winner Gets Zero
|
||||||
scoringSystem=Scoring system
|
scoringSystem=Scoring system
|
||||||
createFlexibleLeaderboard=Create flexible leaderboard
|
createFlexibleLeaderboard=Create flexible leaderboard
|
||||||
createRegattaLeaderboard=Create regatta leaderboard
|
createRegattaLeaderboard=Create regatta leaderboard
|
||||||
@@ -521,4 +522,7 @@ cannotAddRacesToRegattaLeaderboardButOnlyToRegatta=This is a regatta leaderboard
|
|||||||
correctScore=Correct Score
|
correctScore=Correct Score
|
||||||
correctScoreFor=Correct score for competitor {0} in race {1}
|
correctScoreFor=Correct score for competitor {0} in race {1}
|
||||||
penaltyOrRedress=Penalty/Redress
|
penaltyOrRedress=Penalty/Redress
|
||||||
netScore=Net points
|
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
|
||||||
+5
-1
@@ -396,6 +396,7 @@ scoringSchemeLowPointSystem=Low Point System
|
|||||||
scoringSchemeHighPointSystem=High Point System
|
scoringSchemeHighPointSystem=High Point System
|
||||||
scoringSchemeHighPointEssOverall=High Point System ESS Gesamtwertung
|
scoringSchemeHighPointEssOverall=High Point System ESS Gesamtwertung
|
||||||
scoringSchemeHighPointLastBreaksTie=High Point System, letztes Rennen entscheidet bei Gleichstand
|
scoringSchemeHighPointLastBreaksTie=High Point System, letztes Rennen entscheidet bei Gleichstand
|
||||||
|
scoringSchemeLowPointWinnerGetsZero=Low Point System, Gewinner erhält 0 Punkte
|
||||||
scoringSystem=Bepunktungssystem
|
scoringSystem=Bepunktungssystem
|
||||||
createFlexibleLeaderboard=Flexible Rangliste anlegen
|
createFlexibleLeaderboard=Flexible Rangliste anlegen
|
||||||
createRegattaLeaderboard=Rangliste für Regatta anlegen
|
createRegattaLeaderboard=Rangliste für Regatta anlegen
|
||||||
@@ -521,4 +522,7 @@ cannotAddRacesToRegattaLeaderboardButOnlyToRegatta=Dies ist eine Regatta-Ergebni
|
|||||||
correctScore=Ergebnis bearbeiten
|
correctScore=Ergebnis bearbeiten
|
||||||
correctScoreFor=Ergebnis für Teilnehmer {0} in Rennen {1} bearbeiten
|
correctScoreFor=Ergebnis für Teilnehmer {0} in Rennen {1} bearbeiten
|
||||||
penaltyOrRedress=Strafe/Gutschrift
|
penaltyOrRedress=Strafe/Gutschrift
|
||||||
netScore=Nettoergebnis
|
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 slowDownButton;
|
||||||
private final Button speedUpButton;
|
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
|
* 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.addTimeListener(this);
|
||||||
timer.addPlayStateListener(this);
|
timer.addPlayStateListener(this);
|
||||||
timeRangeProvider.addTimeRangeChangeListener(this);
|
timeRangeProvider.addTimeRangeChangeListener(this);
|
||||||
userExplicitlyChangedLivePlayDelay = false;
|
|
||||||
FlowPanel timePanelInnerWrapper = new FlowPanel();
|
FlowPanel timePanelInnerWrapper = new FlowPanel();
|
||||||
timePanelInnerWrapper.setStyleName("timePanelInnerWrapper");
|
timePanelInnerWrapper.setStyleName("timePanelInnerWrapper");
|
||||||
timePanelInnerWrapper.setSize("100%", "100%");
|
timePanelInnerWrapper.setSize("100%", "100%");
|
||||||
@@ -460,16 +452,12 @@ public class TimePanel<T extends TimePanelSettings> extends FormPanel implements
|
|||||||
return new TimePanelSettingsDialogComponent<T>(getSettings(), stringMessages);
|
return new TimePanelSettingsDialogComponent<T>(getSettings(), stringMessages);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean isUserExplicitlyChangedLivePlayDelay() {
|
|
||||||
return userExplicitlyChangedLivePlayDelay;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateSettings(T newSettings) {
|
public void updateSettings(T newSettings) {
|
||||||
boolean delayChanged = newSettings.getDelayToLivePlayInSeconds() != getSettings().getDelayToLivePlayInSeconds();
|
boolean delayChanged = newSettings.getDelayToLivePlayInSeconds() != getSettings().getDelayToLivePlayInSeconds();
|
||||||
if (delayChanged) {
|
if (delayChanged) {
|
||||||
userExplicitlyChangedLivePlayDelay = true;
|
// explicit change always goes through and disables further automatic delay updates in timer
|
||||||
timer.setLivePlayDelayInMillis(1000l * newSettings.getDelayToLivePlayInSeconds());
|
timer.setLivePlayDelayInMillisExplicitly(1000l * newSettings.getDelayToLivePlayInSeconds());
|
||||||
if (timer.getPlayMode() == PlayModes.Live) {
|
if (timer.getPlayMode() == PlayModes.Live) {
|
||||||
timeDelayLabel.setText(String.valueOf(newSettings.getDelayToLivePlayInSeconds()) + " s");
|
timeDelayLabel.setText(String.valueOf(newSettings.getDelayToLivePlayInSeconds()) + " s");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,6 +78,13 @@ public class Timer {
|
|||||||
* Set to <code>true</code> to enable the timer to advance the max time automatically if the current time gets greater than the max time
|
* Set to <code>true</code> to enable the timer to advance the max time automatically if the current time gets greater than the max time
|
||||||
*/
|
*/
|
||||||
private boolean autoAdvance;
|
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
|
* The timer can run in two different modes: Live and Replay
|
||||||
@@ -261,6 +268,21 @@ public class Timer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setLivePlayDelayInMillis(long delayInMilliseconds) {
|
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) {
|
if (this.livePlayDelayInMillis != delayInMilliseconds) {
|
||||||
this.livePlayDelayInMillis = delayInMilliseconds;
|
this.livePlayDelayInMillis = delayInMilliseconds;
|
||||||
if (getPlayMode() == PlayModes.Live) {
|
if (getPlayMode() == PlayModes.Live) {
|
||||||
|
|||||||
+5
-2
@@ -24,10 +24,13 @@ public abstract class AbstractRaceColumnSelection implements RaceColumnSelection
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Iterable<RaceColumnDTO> getSelectedRaceColumnsOrderedAsInLeaderboard(LeaderboardDTO leaderboard) {
|
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>();
|
List<RaceColumnDTO> result = new ArrayList<RaceColumnDTO>();
|
||||||
for (RaceColumnDTO column : leaderboard.getRaceList()) {
|
for (RaceColumnDTO column : leaderboard.getRaceList()) {
|
||||||
if (Util.contains(selectedColumns, column)) {
|
if (Util.contains(namesOfSelectedRaceColumns, column.name)) {
|
||||||
result.add(column);
|
result.add(column);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+19
-14
@@ -4,7 +4,6 @@ import java.util.ArrayList;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -15,18 +14,22 @@ import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
|
|||||||
import com.sap.sailing.gwt.ui.client.RaceTimesInfoProvider;
|
import com.sap.sailing.gwt.ui.client.RaceTimesInfoProvider;
|
||||||
import com.sap.sailing.gwt.ui.client.RaceTimesInfoProviderListener;
|
import com.sap.sailing.gwt.ui.client.RaceTimesInfoProviderListener;
|
||||||
import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettings.RaceColumnSelectionStrategies;
|
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.FleetDTO;
|
||||||
import com.sap.sailing.gwt.ui.shared.LeaderboardDTO;
|
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.RaceColumnDTO;
|
||||||
import com.sap.sailing.gwt.ui.shared.RaceTimesInfoDTO;
|
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
|
* 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
|
* <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)
|
* @author Axel Uhl (D043530)
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public class LastNRacesColumnSelection extends AbstractRaceColumnSelection implements RaceTimesInfoProviderListener {
|
public class LastNRacesColumnSelection extends AbstractRaceColumnSelection implements RaceTimesInfoProviderListener {
|
||||||
private final int numberOfLastRacesToShow;
|
private final int numberOfLastRacesToShow;
|
||||||
@@ -87,33 +90,35 @@ public class LastNRacesColumnSelection extends AbstractRaceColumnSelection imple
|
|||||||
this.raceTimesInfo = raceTimesInfo;
|
this.raceTimesInfo = raceTimesInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Iterable<RaceColumnDTO> getRaceColumnsFromNewestToOldest(LeaderboardDTO leaderboard,
|
private Iterable<RaceColumnDTO> getRaceColumnsFromNewestToOldest(final LeaderboardDTO leaderboard,
|
||||||
Map<RegattaAndRaceIdentifier, RaceTimesInfoDTO> raceTimesInfo) {
|
Map<RegattaAndRaceIdentifier, RaceTimesInfoDTO> raceTimesInfo) {
|
||||||
final Map<RaceColumnDTO, Date> latestStarts = new HashMap<RaceColumnDTO, Date>();
|
|
||||||
List<RaceColumnDTO> columns = new ArrayList<RaceColumnDTO>(leaderboard.getRaceList());
|
List<RaceColumnDTO> columns = new ArrayList<RaceColumnDTO>(leaderboard.getRaceList());
|
||||||
for (Iterator<RaceColumnDTO> i=columns.iterator(); i.hasNext(); ) {
|
for (Iterator<RaceColumnDTO> i=columns.iterator(); i.hasNext(); ) {
|
||||||
RaceColumnDTO column = i.next();
|
RaceColumnDTO column = i.next();
|
||||||
Date latestStart = getLatestStart(column);
|
if (getLatestStart(column) == null && !hasScoreCorrections(leaderboard, column)) {
|
||||||
if (latestStart == null) {
|
|
||||||
i.remove();
|
i.remove();
|
||||||
} else {
|
|
||||||
latestStarts.put(column, latestStart);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Comparator<RaceColumnDTO> comparator = new Comparator<RaceColumnDTO>() {
|
Comparator<RaceColumnDTO> comparator = new Comparator<RaceColumnDTO>() {
|
||||||
@Override
|
@Override
|
||||||
public int compare(RaceColumnDTO o1, RaceColumnDTO o2) {
|
public int compare(RaceColumnDTO o1, RaceColumnDTO o2) {
|
||||||
int result;
|
return leaderboard.getRaceList().indexOf(o2) - leaderboard.getRaceList().indexOf(o1);
|
||||||
Date latestStartO1 = latestStarts.get(o1);
|
|
||||||
Date latestStartO2 = latestStarts.get(o2);
|
|
||||||
result = latestStartO2.compareTo(latestStartO1);
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Collections.sort(columns, comparator);
|
Collections.sort(columns, comparator);
|
||||||
return columns;
|
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) {
|
private Date getLatestStart(RaceColumnDTO column) {
|
||||||
Date latestStart = null;
|
Date latestStart = null;
|
||||||
for (FleetDTO fleet : column.getFleets()) {
|
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_REFRESH_INTERVAL_MILLIS = "refreshIntervalMillis";
|
||||||
private static final String PARAM_SHOW_OVERALL_LEADERBOARDS_ON_SAME_PAGE = "showOverallLeaderboardsOnSamePage";
|
private static final String PARAM_SHOW_OVERALL_LEADERBOARDS_ON_SAME_PAGE = "showOverallLeaderboardsOnSamePage";
|
||||||
private static final String PARAM_DELAY_TO_LIVE_MILLIS = "delayToLiveMillis";
|
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";
|
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);
|
final String zoomTo = Window.Location.getParameter(PARAM_ZOOM_TO);
|
||||||
if (zoomTo != null) {
|
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 Label liveRaceLabel;
|
||||||
|
|
||||||
private final DateTimeFormat dateFormatter = DateTimeFormat.getFormat(DateTimeFormat.PredefinedFormat.DATE_LONG);
|
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;
|
private boolean isEmbedded = false;
|
||||||
|
|
||||||
@@ -430,7 +430,7 @@ public class LeaderboardPanel extends FormPanel implements TimeListener, PlaySta
|
|||||||
timer.setRefreshInterval(newSettings.getDelayBetweenAutoAdvancesInMilliseconds());
|
timer.setRefreshInterval(newSettings.getDelayBetweenAutoAdvancesInMilliseconds());
|
||||||
}
|
}
|
||||||
if (newSettings.getDelayInMilliseconds() != null) {
|
if (newSettings.getDelayInMilliseconds() != null) {
|
||||||
setDelayInMilliseconds(newSettings.getDelayInMilliseconds());
|
timer.setLivePlayDelayInMillisExplicitly(newSettings.getDelayInMilliseconds());
|
||||||
}
|
}
|
||||||
for (ExpandableSortableColumn<?> expandableSortableColumn : columnsToExpandAgain) {
|
for (ExpandableSortableColumn<?> expandableSortableColumn : columnsToExpandAgain) {
|
||||||
expandableSortableColumn.toggleExpansion();
|
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
|
* 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
|
* {@link RaceColumnDTO#getStartDate(FleetDTO) start time}, automatically adjusts the delay accordingly unless it
|
||||||
* changes} were performed, automatically adjusts the delay accordingly.
|
* was manually explicitly updated.
|
||||||
*/
|
*/
|
||||||
private void adjustDelayToLive() {
|
private void adjustDelayToLive() {
|
||||||
if (!settingsUpdatedExplicitly && leaderboard.getDelayToLiveInMillisForLatestRace() != null) {
|
if (leaderboard.getDelayToLiveInMillisForLatestRace() != null) {
|
||||||
setDelayInMilliseconds(leaderboard.getDelayToLiveInMillisForLatestRace());
|
setDelayInMilliseconds(leaderboard.getDelayToLiveInMillisForLatestRace());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-6
@@ -4,18 +4,20 @@ import com.sap.sailing.domain.common.ScoringSchemeType;
|
|||||||
import com.sap.sailing.gwt.ui.client.StringMessages;
|
import com.sap.sailing.gwt.ui.client.StringMessages;
|
||||||
|
|
||||||
public class ScoringSchemeTypeFormatter {
|
public class ScoringSchemeTypeFormatter {
|
||||||
public static String format(ScoringSchemeType scoringSchemeType, StringMessages stringConstants) {
|
public static String format(ScoringSchemeType scoringSchemeType, StringMessages stringMessages) {
|
||||||
switch (scoringSchemeType) {
|
switch (scoringSchemeType) {
|
||||||
case LOW_POINT:
|
case LOW_POINT:
|
||||||
return stringConstants.scoringSchemeLowPointSystem();
|
return stringMessages.scoringSchemeLowPointSystem();
|
||||||
case HIGH_POINT:
|
case HIGH_POINT:
|
||||||
return stringConstants.scoringSchemeHighPointSystem();
|
return stringMessages.scoringSchemeHighPointSystem();
|
||||||
case HIGH_POINT_ESS_OVERALL:
|
case HIGH_POINT_ESS_OVERALL:
|
||||||
return stringConstants.scoringSchemeHighPointEssOverall();
|
return stringMessages.scoringSchemeHighPointEssOverall();
|
||||||
case HIGH_POINT_LAST_BREAKS_TIE:
|
case HIGH_POINT_LAST_BREAKS_TIE:
|
||||||
return stringConstants.scoringSchemeHighPointLastBreaksTie();
|
return stringMessages.scoringSchemeHighPointLastBreaksTie();
|
||||||
case HIGH_POINT_FIRST_GETS_TEN:
|
case HIGH_POINT_FIRST_GETS_TEN:
|
||||||
return stringConstants.scoringSchemeHighPointFirstGetsTen();
|
return stringMessages.scoringSchemeHighPointFirstGetsTen();
|
||||||
|
case LOW_POINT_WINNER_GETS_ZERO:
|
||||||
|
return stringMessages.scoringSchemeLowPointWinnerGetsZero();
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
+7
@@ -60,4 +60,11 @@ public class EditScoreDialog extends DataEntryDialog<Pair<MaxPointsReason, Doubl
|
|||||||
grid.setWidget(1, 1, netPointsBox);
|
grid.setWidget(1, 1, netPointsBox);
|
||||||
return grid;
|
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 prefix;
|
||||||
String suffix;
|
String suffix;
|
||||||
if (netPointsCorrected) {
|
if (netPointsCorrected) {
|
||||||
prefix = "<b>";
|
prefix = "<b class='bold'>";
|
||||||
suffix = "</b>";
|
suffix = "</b>";
|
||||||
} else {
|
} else {
|
||||||
prefix = "";
|
prefix = "";
|
||||||
|
|||||||
+28
-18
@@ -251,12 +251,16 @@ public class MatchAndApplyScoreCorrectionsDialog extends DataEntryDialog<BulkSco
|
|||||||
if (officialSailID != null && raceNameOrNumber != null) {
|
if (officialSailID != null && raceNameOrNumber != null) {
|
||||||
ScoreCorrectionEntryDTO officialCorrectionEntry = regattaScoreCorrection
|
ScoreCorrectionEntryDTO officialCorrectionEntry = regattaScoreCorrection
|
||||||
.getScoreCorrectionsByRaceNameOrNumber().get(raceNameOrNumber).get(officialSailID);
|
.getScoreCorrectionsByRaceNameOrNumber().get(raceNameOrNumber).get(officialSailID);
|
||||||
result.addMaxPointsReasonUpdate(competitor, raceColumn,
|
// the competitor may not have an official result for this race, for example, if in case of split fleets
|
||||||
officialCorrectionEntry.getMaxPointsReason());
|
// the competitor raced in a different fleet
|
||||||
if (officialCorrectionEntry.getScore() != null) {
|
if (officialCorrectionEntry != null) {
|
||||||
double officialTotalPoints = officialCorrectionEntry.getScore().doubleValue();
|
result.addMaxPointsReasonUpdate(competitor, raceColumn,
|
||||||
double officialNetPoints = officialTotalPoints / raceColumn.getEffectiveFactor();
|
officialCorrectionEntry.getMaxPointsReason());
|
||||||
result.addScoreUpdate(competitor, raceColumn, officialNetPoints);
|
if (officialCorrectionEntry.getScore() != null) {
|
||||||
|
double officialTotalPoints = officialCorrectionEntry.getScore().doubleValue();
|
||||||
|
double officialNetPoints = officialTotalPoints / raceColumn.getEffectiveFactor();
|
||||||
|
result.addScoreUpdate(competitor, raceColumn, officialNetPoints);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -308,36 +312,42 @@ public class MatchAndApplyScoreCorrectionsDialog extends DataEntryDialog<BulkSco
|
|||||||
ScoreCorrectionEntryDTO officialCorrectionEntry =
|
ScoreCorrectionEntryDTO officialCorrectionEntry =
|
||||||
regattaScoreCorrection.getScoreCorrectionsByRaceNameOrNumber()
|
regattaScoreCorrection.getScoreCorrectionsByRaceNameOrNumber()
|
||||||
.get(raceNameOrNumber).get(officialSailID);
|
.get(raceNameOrNumber).get(officialSailID);
|
||||||
final Double officialTotalPoints = officialCorrectionEntry.isDiscarded() ? new Double(0) : officialCorrectionEntry.getScore();
|
final Double officialTotalPoints = officialCorrectionEntry == null ? null :
|
||||||
final Double officialNetPoints = officialCorrectionEntry.getScore() == null ? null :
|
officialCorrectionEntry.isDiscarded() ? new Double(0) : officialCorrectionEntry.getScore();
|
||||||
|
final Double officialNetPoints = officialCorrectionEntry == null ? null :
|
||||||
|
officialCorrectionEntry.getScore() == null ? null :
|
||||||
officialCorrectionEntry.getScore() / raceColumn.getEffectiveFactor();
|
officialCorrectionEntry.getScore() / raceColumn.getEffectiveFactor();
|
||||||
|
final MaxPointsReason officialMaxPointsReason = officialCorrectionEntry == null ? null :
|
||||||
|
officialCorrectionEntry.getMaxPointsReason();
|
||||||
SafeHtmlBuilder sb = new SafeHtmlBuilder();
|
SafeHtmlBuilder sb = new SafeHtmlBuilder();
|
||||||
boolean entriesDiffer =
|
// entries not considered "different' in case we haven't got an entry for the competitor/race at all;
|
||||||
((officialNetPoints == null && entry.netPoints != null) || (officialNetPoints != null && entry.netPoints != null && !new Double(entry.netPoints).equals(officialNetPoints))) ||
|
// those non-existing "entries" will be ignored by getResults anyhow
|
||||||
((officialTotalPoints == null && entry.totalPoints != null) || (officialTotalPoints != null && entry.totalPoints != null && !new Double(entry.totalPoints).equals(officialTotalPoints))) ||
|
boolean entriesDiffer = officialCorrectionEntry != null &&
|
||||||
((officialCorrectionEntry.getMaxPointsReason() == null && entry.reasonForMaxPoints != MaxPointsReason.NONE) ||
|
(((officialNetPoints == null && entry.netPoints != null) || (officialNetPoints != null && (entry.netPoints == null || !new Double(entry.netPoints).equals(officialNetPoints)))) ||
|
||||||
officialCorrectionEntry.getMaxPointsReason() != null && officialCorrectionEntry.getMaxPointsReason() != entry.reasonForMaxPoints);
|
((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) {
|
if (entriesDiffer) {
|
||||||
sb.appendHtmlConstant("<span style=\"color: #0000FF;\"><b>");
|
sb.appendHtmlConstant("<span style=\"color: #0000FF;\"><b>");
|
||||||
}
|
}
|
||||||
if (officialNetPoints == null) {
|
if (officialNetPoints == null) {
|
||||||
sb.append(0);
|
sb.appendEscaped("null");
|
||||||
} else {
|
} else {
|
||||||
sb.append(officialNetPoints);
|
sb.append(officialNetPoints);
|
||||||
}
|
}
|
||||||
sb.appendEscaped("/");
|
sb.appendEscaped("/");
|
||||||
if (officialTotalPoints == null) {
|
if (officialTotalPoints == null) {
|
||||||
sb.append(0);
|
sb.appendEscaped("null");
|
||||||
} else {
|
} else {
|
||||||
sb.append(officialTotalPoints);
|
sb.append(officialTotalPoints);
|
||||||
}
|
}
|
||||||
sb.appendEscaped("/");
|
sb.appendEscaped("/");
|
||||||
if (officialCorrectionEntry.getMaxPointsReason() != null) {
|
if (officialMaxPointsReason != null) {
|
||||||
sb.appendEscaped(officialCorrectionEntry.getMaxPointsReason().name());
|
sb.appendEscaped(officialMaxPointsReason.name());
|
||||||
} else {
|
} else {
|
||||||
sb.appendEscaped(MaxPointsReason.NONE.name());
|
sb.appendEscaped(MaxPointsReason.NONE.name());
|
||||||
}
|
}
|
||||||
if (officialCorrectionEntry.isDiscarded()) {
|
if (officialCorrectionEntry != null && officialCorrectionEntry.isDiscarded()) {
|
||||||
sb.appendEscaped("/discarded");
|
sb.appendEscaped("/discarded");
|
||||||
}
|
}
|
||||||
if (entriesDiffer) {
|
if (entriesDiffer) {
|
||||||
|
|||||||
+19
-13
@@ -88,11 +88,11 @@ public class MediaSelector implements /*RaceTimesInfoProviderListener,*/ PlaySta
|
|||||||
Collection<MediaTrack> reachableVideoTracks = new ArrayList<MediaTrack>();
|
Collection<MediaTrack> reachableVideoTracks = new ArrayList<MediaTrack>();
|
||||||
Collection<MediaTrack> reachableAudioTracks = new ArrayList<MediaTrack>();
|
Collection<MediaTrack> reachableAudioTracks = new ArrayList<MediaTrack>();
|
||||||
for (MediaTrack mediaTrack : mediaTracks) {
|
for (MediaTrack mediaTrack : mediaTracks) {
|
||||||
if (MediaTrack.Status.REACHABLE.equals(mediaTrack.status)) {
|
if (isPotentiallyPlayable(mediaTrack)) {
|
||||||
switch (mediaTrack.mimeType.mediaType) {
|
switch (mediaTrack.mimeType.mediaType) {
|
||||||
case video:
|
case video:
|
||||||
reachableVideoTracks.add(mediaTrack);
|
reachableVideoTracks.add(mediaTrack);
|
||||||
case audio: //intentionall fall through
|
case audio: //intentional fall through
|
||||||
reachableAudioTracks.add(mediaTrack);
|
reachableAudioTracks.add(mediaTrack);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -129,20 +129,19 @@ 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) {
|
private void setStatus(final MediaTrack mediaTrack) {
|
||||||
if (!mediaTrack.isYoutube()) {
|
if (!mediaTrack.isYoutube()) {
|
||||||
Audio audio = Audio.createIfSupported();
|
Audio audio = Audio.createIfSupported();
|
||||||
if (audio != null) {
|
if (audio != null) {
|
||||||
AudioElement mediaReachableTester = audio.getAudioElement();
|
AudioElement mediaReachableTester = audio.getAudioElement();
|
||||||
String canPlay = mediaReachableTester.canPlayType(mediaTrack.typeToString());
|
addLoadMetadataHandler(mediaReachableTester, mediaTrack);
|
||||||
if (MediaElement.CAN_PLAY_PROBABLY.equals(canPlay) || MediaElement.CAN_PLAY_MAYBE.equals(canPlay)) {
|
mediaReachableTester.setPreload(MediaElement.PRELOAD_METADATA);
|
||||||
addLoadMetadataHandler(mediaReachableTester, mediaTrack);
|
mediaReachableTester.setSrc(mediaTrack.url);
|
||||||
mediaReachableTester.setPreload(MediaElement.PRELOAD_METADATA);
|
mediaReachableTester.load();
|
||||||
mediaReachableTester.setSrc(mediaTrack.url);
|
|
||||||
mediaReachableTester.load();
|
|
||||||
} else {
|
|
||||||
mediaTrack.status = Status.CANNOT_PLAY;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
mediaTrack.status = Status.CANNOT_PLAY;
|
mediaTrack.status = Status.CANNOT_PLAY;
|
||||||
}
|
}
|
||||||
@@ -156,11 +155,18 @@ public class MediaSelector implements /*RaceTimesInfoProviderListener,*/ PlaySta
|
|||||||
mediaElement.addEventListener('loadedmetadata', function() {
|
mediaElement.addEventListener('loadedmetadata', function() {
|
||||||
that.@com.sap.sailing.gwt.ui.raceboard.MediaSelector::loadedmetadata(Lcom/sap/sailing/gwt/ui/shared/media/MediaTrack;)(mediaTrack);
|
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) {
|
public void loadedmetadata(MediaTrack mediaTrack) {
|
||||||
mediaTrack.status = Status.REACHABLE;
|
mediaTrack.status = Status.REACHABLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void mediaError(MediaTrack mediaTrack) {
|
||||||
|
mediaTrack.status = Status.NOT_REACHABLE;
|
||||||
|
}
|
||||||
|
|
||||||
private void playDefault() {
|
private void playDefault() {
|
||||||
MediaTrack defaultVideo = getDefaultVideo();
|
MediaTrack defaultVideo = getDefaultVideo();
|
||||||
@@ -176,7 +182,7 @@ public class MediaSelector implements /*RaceTimesInfoProviderListener,*/ PlaySta
|
|||||||
private MediaTrack getDefaultAudio() {
|
private MediaTrack getDefaultAudio() {
|
||||||
//TODO: implement a better heuristic than just taking the first to come
|
//TODO: implement a better heuristic than just taking the first to come
|
||||||
for (MediaTrack mediaTrack : mediaTracks) {
|
for (MediaTrack mediaTrack : mediaTracks) {
|
||||||
if (MediaType.audio.equals(mediaTrack.mimeType.mediaType)) {
|
if (MediaType.audio.equals(mediaTrack.mimeType.mediaType) && isPotentiallyPlayable(mediaTrack)) {
|
||||||
return mediaTrack;
|
return mediaTrack;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -185,7 +191,7 @@ public class MediaSelector implements /*RaceTimesInfoProviderListener,*/ PlaySta
|
|||||||
|
|
||||||
private MediaTrack getDefaultVideo() {
|
private MediaTrack getDefaultVideo() {
|
||||||
for (MediaTrack mediaTrack : mediaTracks) {
|
for (MediaTrack mediaTrack : mediaTracks) {
|
||||||
if (MediaType.video.equals(mediaTrack.mimeType.mediaType)) {
|
if (MediaType.video.equals(mediaTrack.mimeType.mediaType) && isPotentiallyPlayable(mediaTrack)) {
|
||||||
return mediaTrack;
|
return mediaTrack;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-3
@@ -44,6 +44,7 @@ import com.sap.sailing.domain.tracking.TrackedRace;
|
|||||||
import com.sap.sailing.domain.tracking.TrackedRaceStatus;
|
import com.sap.sailing.domain.tracking.TrackedRaceStatus;
|
||||||
import com.sap.sailing.domain.tracking.Wind;
|
import com.sap.sailing.domain.tracking.Wind;
|
||||||
import com.sap.sailing.gwt.ui.shared.LeaderboardDTO;
|
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
|
* Caches the expensive to compute {@link LeaderboardDTO} results of a
|
||||||
@@ -402,12 +403,21 @@ public class LeaderboardDTOCache {
|
|||||||
future = map.get(key);
|
future = map.get(key);
|
||||||
}
|
}
|
||||||
if (future == null) {
|
if (future == null) {
|
||||||
|
final Thread callerThread = Thread.currentThread();
|
||||||
future = new FutureTask<LeaderboardDTO>(new Callable<LeaderboardDTO>() {
|
future = new FutureTask<LeaderboardDTO>(new Callable<LeaderboardDTO>() {
|
||||||
@Override
|
@Override
|
||||||
public LeaderboardDTO call() throws Exception {
|
public LeaderboardDTO call() throws Exception {
|
||||||
LeaderboardDTO result = sailingService.computeLeaderboardByName(leaderboard, adjustedTimePoint,
|
// The outer getLeaderboardByName(...) method will always wait for this future's completion.
|
||||||
namesOfRaceColumnsForWhichToLoadLegDetails, waitForLatestAnalyses);
|
// Therefore, it's safe to propagate the calling thread's locks to this one:
|
||||||
return result;
|
LockUtil.propagateLockSetFrom(callerThread);
|
||||||
|
try {
|
||||||
|
LeaderboardDTO result = sailingService.computeLeaderboardByName(leaderboard,
|
||||||
|
adjustedTimePoint, namesOfRaceColumnsForWhichToLoadLegDetails,
|
||||||
|
waitForLatestAnalyses);
|
||||||
|
return result;
|
||||||
|
} finally {
|
||||||
|
LockUtil.unpropagateLockSetFrom(callerThread);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
computeLeadearboardByNameExecutor.execute(future);
|
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.CountryCode;
|
||||||
import com.sap.sailing.domain.common.DetailType;
|
import com.sap.sailing.domain.common.DetailType;
|
||||||
import com.sap.sailing.domain.common.Distance;
|
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.LegType;
|
||||||
import com.sap.sailing.domain.common.ManeuverType;
|
import com.sap.sailing.domain.common.ManeuverType;
|
||||||
import com.sap.sailing.domain.common.MaxPointsReason;
|
import com.sap.sailing.domain.common.MaxPointsReason;
|
||||||
@@ -347,7 +348,11 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
|||||||
/* workQueue */ new LinkedBlockingQueue<Runnable>());
|
/* workQueue */ new LinkedBlockingQueue<Runnable>());
|
||||||
raceDetailsExecutor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
|
raceDetailsExecutor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
|
||||||
leaderboardByNameLiveUpdaters = new HashMap<String, LiveLeaderboardUpdater>();
|
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 {
|
private void writeObject(ObjectOutputStream oos) throws IOException {
|
||||||
@@ -509,7 +514,8 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
|||||||
RegattaAndRaceIdentifier raceIdentifier = null;
|
RegattaAndRaceIdentifier raceIdentifier = null;
|
||||||
RaceDTO race = null;
|
RaceDTO race = null;
|
||||||
TrackedRace trackedRace = raceColumn.getTrackedRace(fleet);
|
TrackedRace trackedRace = raceColumn.getTrackedRace(fleet);
|
||||||
final FleetDTO fleetDTO = convertToFleetDTO(fleet);
|
|
||||||
|
final FleetDTO fleetDTO = convertToFleetDTO(raceColumn, fleet);
|
||||||
if (trackedRace != null) {
|
if (trackedRace != null) {
|
||||||
raceIdentifier = new RegattaNameAndRaceName(trackedRace.getTrackedRegatta().getRegatta()
|
raceIdentifier = new RegattaNameAndRaceName(trackedRace.getTrackedRegatta().getRegatta()
|
||||||
.getName(), trackedRace.getRace().getName());
|
.getName(), trackedRace.getRace().getName());
|
||||||
@@ -577,7 +583,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
|||||||
public LeaderboardEntryDTO call() {
|
public LeaderboardEntryDTO call() {
|
||||||
try {
|
try {
|
||||||
Entry entry = leaderboard.getEntry(competitor, raceColumn, timePoint);
|
Entry entry = leaderboard.getEntry(competitor, raceColumn, timePoint);
|
||||||
return getLeaderboardEntryDTO(entry, raceColumn.getTrackedRace(competitor), competitor, timePoint,
|
return getLeaderboardEntryDTO(entry, raceColumn, competitor, timePoint,
|
||||||
namesOfRaceColumnsForWhichToLoadLegDetails != null
|
namesOfRaceColumnsForWhichToLoadLegDetails != null
|
||||||
&& namesOfRaceColumnsForWhichToLoadLegDetails.contains(raceColumn.getName()),
|
&& namesOfRaceColumnsForWhichToLoadLegDetails.contains(raceColumn.getName()),
|
||||||
waitForLatestAnalyses, legRanksCache);
|
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");
|
(System.currentTimeMillis()-startOfRequestHandling)+"ms");
|
||||||
return result;
|
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
|
* may not reflect all data already received; otherwise, the method will always block for the latest
|
||||||
* cache updates to have happened before returning.
|
* 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,
|
TimePoint timePoint, boolean addLegDetails, boolean waitForLatestAnalyses,
|
||||||
Map<Leg, LinkedHashMap<Competitor, Integer>> legRanksCache) throws NoWindException {
|
Map<Leg, LinkedHashMap<Competitor, Integer>> legRanksCache) throws NoWindException {
|
||||||
LeaderboardEntryDTO entryDTO = new LeaderboardEntryDTO();
|
LeaderboardEntryDTO entryDTO = new LeaderboardEntryDTO();
|
||||||
|
TrackedRace trackedRace = raceColumn.getTrackedRace(competitor);
|
||||||
entryDTO.race = trackedRace == null ? null : trackedRace.getRaceIdentifier();
|
entryDTO.race = trackedRace == null ? null : trackedRace.getRaceIdentifier();
|
||||||
entryDTO.netPoints = entry.getNetPoints();
|
entryDTO.netPoints = entry.getNetPoints();
|
||||||
entryDTO.netPointsCorrected = entry.isNetPointsCorrected();
|
entryDTO.netPointsCorrected = entry.isNetPointsCorrected();
|
||||||
@@ -671,7 +678,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
final Fleet fleet = entry.getFleet();
|
final Fleet fleet = entry.getFleet();
|
||||||
entryDTO.fleet = fleet == null ? null : convertToFleetDTO(fleet);
|
entryDTO.fleet = fleet == null ? null : convertToFleetDTO(raceColumn, fleet);
|
||||||
return entryDTO;
|
return entryDTO;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1068,7 +1075,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
|||||||
private SeriesDTO convertToSeriesDTO(Series series) {
|
private SeriesDTO convertToSeriesDTO(Series series) {
|
||||||
List<FleetDTO> fleets = new ArrayList<FleetDTO>();
|
List<FleetDTO> fleets = new ArrayList<FleetDTO>();
|
||||||
for (Fleet fleet : series.getFleets()) {
|
for (Fleet fleet : series.getFleets()) {
|
||||||
fleets.add(convertToFleetDTO(fleet));
|
fleets.add(convertToFleetDTO(series, fleet));
|
||||||
}
|
}
|
||||||
List<RaceColumnDTO> raceColumns = new ArrayList<RaceColumnDTO>();
|
List<RaceColumnDTO> raceColumns = new ArrayList<RaceColumnDTO>();
|
||||||
for (RaceColumnInSeries raceColumn : series.getRaceColumns()) {
|
for (RaceColumnInSeries raceColumn : series.getRaceColumns()) {
|
||||||
@@ -1082,8 +1089,16 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private FleetDTO convertToFleetDTO(Fleet fleet) {
|
private FleetDTO convertToFleetDTO(Series series, Fleet fleet) {
|
||||||
return new FleetDTO(fleet.getName(), fleet.getOrdering(), fleet.getColor());
|
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) {
|
private List<RaceWithCompetitorsDTO> convertToRaceDTOs(Regatta regatta) {
|
||||||
@@ -2132,7 +2147,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
|||||||
latestTimePointAfterQueryTimePointWhenATrackedRaceWasLive = liveTimePointForTrackedRace;
|
latestTimePointAfterQueryTimePointWhenATrackedRaceWasLive = liveTimePointForTrackedRace;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
final FleetDTO fleetDTO = convertToFleetDTO(fleet);
|
final FleetDTO fleetDTO = convertToFleetDTO(raceColumn, fleet);
|
||||||
RaceColumnDTO raceColumnDTO = leaderboardDTO.addRace(raceColumn.getName(), raceColumn.getExplicitFactor(), raceColumn.getFactor(),
|
RaceColumnDTO raceColumnDTO = leaderboardDTO.addRace(raceColumn.getName(), raceColumn.getExplicitFactor(), raceColumn.getFactor(),
|
||||||
fleetDTO, raceColumn.isMedalRace(), raceIdentifier, raceDTO);
|
fleetDTO, raceColumn.isMedalRace(), raceIdentifier, raceDTO);
|
||||||
if (latestTimePointAfterQueryTimePointWhenATrackedRaceWasLive != null) {
|
if (latestTimePointAfterQueryTimePointWhenATrackedRaceWasLive != null) {
|
||||||
@@ -2407,7 +2422,11 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
|||||||
throws UnknownHostException, IOException, InterruptedException, ParseException {
|
throws UnknownHostException, IOException, InterruptedException, ParseException {
|
||||||
List<SwissTimingRaceRecordDTO> result = new ArrayList<SwissTimingRaceRecordDTO>();
|
List<SwissTimingRaceRecordDTO> result = new ArrayList<SwissTimingRaceRecordDTO>();
|
||||||
for (com.sap.sailing.domain.swisstimingadapter.RaceRecord rr : getService().getSwissTimingRaceRecords(hostname, port, canSendRequests)) {
|
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;
|
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,
|
public void trackWithSwissTiming(RegattaIdentifier regattaToAddTo, Iterable<SwissTimingRaceRecordDTO> rrs, String hostname, int port,
|
||||||
boolean canSendRequests, boolean trackWind, final boolean correctWindByDeclination) throws Exception {
|
boolean canSendRequests, boolean trackWind, final boolean correctWindByDeclination) throws Exception {
|
||||||
for (SwissTimingRaceRecordDTO rr : rrs) {
|
for (SwissTimingRaceRecordDTO rr : rrs) {
|
||||||
|
|
||||||
final RacesHandle raceHandle = getService().addSwissTimingRace(regattaToAddTo, rr.ID, hostname, port,
|
final RacesHandle raceHandle = getService().addSwissTimingRace(regattaToAddTo, rr.ID, hostname, port,
|
||||||
canSendRequests,
|
canSendRequests,
|
||||||
MongoWindStoreFactory.INSTANCE.getMongoWindStore(mongoObjectFactory, domainObjectFactory),
|
MongoWindStoreFactory.INSTANCE.getMongoWindStore(mongoObjectFactory, domainObjectFactory),
|
||||||
@@ -2476,11 +2494,11 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
|||||||
regatta = getService().createRegatta(
|
regatta = getService().createRegatta(
|
||||||
replayRaceDTO.rsc,
|
replayRaceDTO.rsc,
|
||||||
boatClass.trim(),
|
boatClass.trim(),
|
||||||
RegattaImpl.getFullName(replayRaceDTO.rsc, replayRaceDTO.boat_class),
|
RegattaImpl.getDefaultName(replayRaceDTO.rsc, replayRaceDTO.boat_class),
|
||||||
Collections.singletonList(new SeriesImpl(
|
Collections.singletonList(new SeriesImpl(
|
||||||
"Default",
|
LeaderboardNameConstants.DEFAULT_SERIES_NAME,
|
||||||
/* isMedal */false,
|
/* isMedal */false,
|
||||||
Collections.singletonList(new FleetImpl("Default")),
|
Collections.singletonList(new FleetImpl(LeaderboardNameConstants.DEFAULT_FLEET_NAME)),
|
||||||
/* race column names */ new ArrayList<String>(), getService())),
|
/* race column names */ new ArrayList<String>(), getService())),
|
||||||
false,
|
false,
|
||||||
baseDomainFactory.createScoringScheme(ScoringSchemeType.LOW_POINT), null);
|
baseDomainFactory.createScoringScheme(ScoringSchemeType.LOW_POINT), null);
|
||||||
|
|||||||
+20
-5
@@ -7,10 +7,14 @@ public class FleetDTO extends NamedDTO implements IsSerializable {
|
|||||||
private Color color;
|
private Color color;
|
||||||
private int orderNo;
|
private int orderNo;
|
||||||
|
|
||||||
|
/** the name of the series the fleet belongs to */
|
||||||
|
private String seriesName;
|
||||||
|
|
||||||
public FleetDTO() {}
|
public FleetDTO() {}
|
||||||
|
|
||||||
public FleetDTO(String fleetName, int orderNo, Color color) {
|
public FleetDTO(String fleetName, String seriesName, int orderNo, Color color) {
|
||||||
super(fleetName);
|
super(fleetName);
|
||||||
|
this.seriesName = seriesName;
|
||||||
this.orderNo = orderNo;
|
this.orderNo = orderNo;
|
||||||
this.color = color;
|
this.color = color;
|
||||||
}
|
}
|
||||||
@@ -31,12 +35,17 @@ public class FleetDTO extends NamedDTO implements IsSerializable {
|
|||||||
this.orderNo = orderNo;
|
this.orderNo = orderNo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getSeriesName() {
|
||||||
|
return seriesName;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
final int prime = 31;
|
final int prime = 31;
|
||||||
int result = super.hashCode();
|
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 + orderNo;
|
||||||
|
result = prime * result + ((seriesName == null) ? 0 : seriesName.hashCode());
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,13 +58,19 @@ public class FleetDTO extends NamedDTO implements IsSerializable {
|
|||||||
if (getClass() != obj.getClass())
|
if (getClass() != obj.getClass())
|
||||||
return false;
|
return false;
|
||||||
FleetDTO other = (FleetDTO) obj;
|
FleetDTO other = (FleetDTO) obj;
|
||||||
if (color == null) {
|
if (name == null) {
|
||||||
if (other.color != null)
|
if (other.name != null)
|
||||||
return false;
|
return false;
|
||||||
} else if (!color.equals(other.color))
|
} else if (!name.equals(other.name))
|
||||||
return false;
|
return false;
|
||||||
if (orderNo != other.orderNo)
|
if (orderNo != other.orderNo)
|
||||||
return false;
|
return false;
|
||||||
|
if (seriesName == null) {
|
||||||
|
if (other.seriesName != null)
|
||||||
|
return false;
|
||||||
|
} else if (!seriesName.equals(other.seriesName))
|
||||||
|
return false;
|
||||||
return true;
|
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