diff --git a/home.md b/home.md
index 0d852aee27c..2ff9a805a82 100644
--- a/home.md
+++ b/home.md
@@ -1,146 +1,148 @@
-# Welcome to the SAP Sailing Wiki
-
-This is the
Wiki where useful information regarding this project can be found.
-
-### The Pitch
-
-Like businesses, sailors need the latest information to make strategic decisions - but they need it even faster. One wrong tack, a false estimation of the current, or the slightest wind shift can cost the skipper the entire race. As premium sponsor of the Kieler Woche 2011, and co-sponsor of Sailing Team Germany (STG), SAP is showing how innovative IT solutions providing real time data analysis can give teams the competitive edge.
-
-SAP is at the center of today’s technology revolution, developing innovations that not only help businesses run like never before, but also improve the lives of people everywhere. As market leader in enterprise application software, SAP SE (NYSE: SAP) helps companies of all sizes and industries run better. From back office to boardroom, warehouse to storefront, desktop to mobile device – SAP empowers people and organizations to work together more efficiently and use business insight more effectively to stay ahead of the competition. SAP applications and services enable more than 258,000 customers to operate profitably, adapt continuously, and grow sustainably.
-
-# Table of Contents
-
-## Information
-
-### General
-
-* [[Information about this Wiki and HowTo|wiki/info/general/wiki]]
-* [[General Project Information|wiki/info/general/general-information]]
-* [[Inventory|wiki/info/general/inventar-liste]]
-
-### Landscape and Development
-
-* [[Architecture and Infrastructure|wiki/info/landscape/architecture-and-infrastructure]]
- * [[Runtime Environment|wiki/info/landscape/runtime-environment]]
- * [[Basic architectual principles|wiki/info/landscape/basic-architectural-principles]]
- * [[User Management|wiki/info/landscape/usermanagement]]
- * [[Development Environment|wiki/info/landscape/development-environment]]
- * [[Production Environment|wiki/info/landscape/production-environment]]
- * [[Typical Development Scenarios|wiki/info/landscape/typical-development-scenarios]]
-* [[RaceLog Tracking Server Architecture|wiki/info/landscape/server]]
- * Environment Overview [[PDF|wiki/info/mobile/event-tracking/architecture.pdf]] | [[SVG|wiki/info/mobile/event-tracking/architecture.svg]]
-* Amazon
- * [[Amazon EC2|wiki/info/landscape/amazon-ec2]]
- * [[EC2 Backup Strategy|wiki/info/landscape/amazon-ec2-backup-strategy]]
- * [[Creating an EC2 image from scratch|wiki/info/landscape/creating-ec2-image-from-scratch]]
- * [[EC2 mail relaying|wiki/info/landscape/mail-relaying]]
-* [[Building and Deploying|wiki/info/landscape/building-and-deploying]]
-* [[Data Mining Architecture|wiki/info/landscape/data-mining-architecture]]
-* [[Typical Data Mining Scenarios|wiki/info/landscape/typical-data-mining-scenarios]]
-* [[Create clickable UI prototypes with Axure|wiki/info/landscape/ui-clickable-prototypes]]
-* [[Webdesign|wiki/info/landscape/webdesign]]
-
-### Mobile
-
-* [[Mobile Development|wiki/info/mobile/mobile-development]]
-* Tracking App
- * [[Tracking App Specification|wiki/info/mobile/app-spec/app-spec]]
- * [[Event Tracking|wiki/info/mobile/event-tracking/event-tracking]]
- * [[Steps for setting up Smartphone Tracking|wiki/info/mobile/smartphone-tracking-steps]]
- * [[Tracking App Prototype Architecture|wiki/info/mobile/app]]
-* Racecommittee App
- * [[Racecommittee App|wiki/info/mobile/racecommittee-app]]
- * [[Environment|wiki/info/mobile/racecommittee-app-environment]]
- * [[Administrator|wiki/info/mobile/racecommittee-app-administrator]]
- * [[User|wiki/info/mobile/racecommittee-app-user]]
-* [[Android and Release Build|wiki/info/mobile/android-and-release-build]]
-* [[Mobile Sailing Analytics|wiki/info/mobile/mobilesailinganalytics]]
-* [[Push Notifications|wiki/info/mobile/push-notifications]]
-
-### API
-
-* [[Web Services API|wiki/info/api/sailing-webservices]]
-* [[API v1|wiki/info/api/api-v1]]
-* [[Tracking App API|wiki/info/api/tracking-app-api]]
-* [[Training API v1 draft|wiki/info/api/training-api-v1-draft]]
-
-### Security
- * [[Fortify Tests|wiki/info/security/fortify]]
- * [[SSL / HTTPS Support|wiki/info/security/ssl-support]]
-
-### Miscellaneous
-
-* [[Big Data - Numbers|wiki/info/misc/big-data-numbers]]
-* [[Data Quality|wiki/info/misc/data-quality]]
-* [[Sailing Domain Algorithms|wiki/info/misc/sailing-domain-algorithms]]
-* [[Google Analytics (Web Page Tracking)|wiki/info/misc/ganalytics]]
-* [[S3 Development Sample|wiki/info/misc/s3-sample]]
-
-* [[FAQ for League Operators|wiki/info/misc/league-operators-faq]]
-* [[Prionet related information|wiki/info/misc/prionet-related-information]]
-* [[TracTrac Workshop 2013|wiki/info/misc/tractracworkshop2013]]
-
-## HowTo
-
-* [[Onboarding|wiki/howto/onboarding]]
-
-
-### Development
-
-* [[Create boat graphics for the 2D race viewer|wiki/howto/development/boatgraphicssvg]]
-* [[Continuous Integration with Hudson/Jenkins|wiki/howto/development/ci]]
-* [[Dispatch|wiki/howto/development/dispatch]]
-* [[Working with GWT UI Binder|wiki/howto/development/gwt-ui-binder]]
-* [[Java De(Serialization) and Circular Dependencies|wiki/howto/development/java-de-serialization-and-circular-dependencies]]
-* [[JMX Support|wiki/howto/development/jmx]]
-* [[Working with GWT Locally|wiki/howto/development/local-gwt]]
-* [[Log File Analysis|wiki/howto/development/log-file-analysis]]
-* [[UI Tests with Selenium|wiki/howto/development/selenium-ui-tests]]
-* [[Profiling|wiki/howto/development/profiling]]
-* [[Working with GWT Super Dev Mode|wiki/howto/development/super-dev-mode]]
-
-### For Event Managers
-
-* [[Set up local network with replication server|wiki/howto/eventmanagers/event-network-with-replica]]
-* [[Operating Igtimi WindBots|wiki/howto/eventmanagers/windbot-operations]]
-* [[Linking Race Videos|wiki/howto/eventmanagers/linking-race-videos]]
-* [[Import official results|wiki/howto/eventmanagers/results-import]]
-
-### Setup
-
-* [[Configure Races on Server|wiki/howto/setup/configure-races-on-server]]
-* [[Setting up internal Jenkins on SAP Monsoon|wiki/howto/setup/setting-up-jenkins-on-sap-monsoon]]
-
-### Miscellaneous
-
-* [[Cook Book|wiki/howto/misc/cook-book]]
-* [[Polars|wiki/howto/misc/polars]]
-* [[Server Replication|wiki/howto/misc/server-replication]]
-* [[TracTrac|wiki/howto/misc/tractrac-lifecycle]]
-* [[UI Tests|wiki/howto/misc/ui-tests-tutorial]]
-* [[Uploading Media Content|wiki/howto/misc/uploading-media-content]]
-* [[Monitoring Apache and RabbitMQ|wiki/misc/monitoring-apache-and-rabbitmq]]
-
-## Projects
-* [[Analytics on a stick|wiki/projects/analytics-on-a-stick]]
-
-## Events and Planning
-* [[Project Planning (bigger development)|wiki/events/planning]]
-* [[General Event Planning|wiki/events/general-event-planning]]
-* [[Information about Extreme Sailing Series|wiki/events/extreme-sailing-series]]
-* [[Travemünder Woche 2014 event page|wiki/events/tw2014]]
-* [[505 worlds Kiel 2014 event page|wiki/events/505-worlds-kiel-2014]]
-* [[Kieler Woche event page|wiki/events/kieler-woche-2015]]
-* [[Charleston Race Week 2016|wiki/events/Charleston-Race-Week-2016]]
-* [[Sailing Leagues 2016|wiki/events/sailing-Leagues-2016]]
-
-## Planning
-* [[Overview|https://wiki.sapsailing.com/pages/wiki/planning/]]
-
-## Internal services (not related to wiki but useful)
-
-* [Bugzilla Issue Tracking System](http://bugzilla.sapsailing.com/bugzilla/)
-* [GIT Repository (SAP)](ssh://git.wdf.sap.corp:29418/SAPSail/sapsailingcapture.git)
-* [Maven Repository Browser](http://maven.sapsailing.com/maven/) (see [[how to setup repository for Android builds|wiki/info/mobile/racecommittee-app-environment]])
-* [Main Sailing Website](http://www.sapsailing.com)
-* [Visitor Statistics](http://analysis.sapsailing.com/)
\ No newline at end of file
+# Welcome to the SAP Sailing Wiki
+
+This is the
Wiki where useful information regarding this project can be found.
+
+### The Pitch
+
+Like businesses, sailors need the latest information to make strategic decisions - but they need it even faster. One wrong tack, a false estimation of the current, or the slightest wind shift can cost the skipper the entire race. As premium sponsor of the Kieler Woche 2011, and co-sponsor of Sailing Team Germany (STG), SAP is showing how innovative IT solutions providing real time data analysis can give teams the competitive edge.
+
+SAP is at the center of today’s technology revolution, developing innovations that not only help businesses run like never before, but also improve the lives of people everywhere. As market leader in enterprise application software, SAP SE (NYSE: SAP) helps companies of all sizes and industries run better. From back office to boardroom, warehouse to storefront, desktop to mobile device – SAP empowers people and organizations to work together more efficiently and use business insight more effectively to stay ahead of the competition. SAP applications and services enable more than 258,000 customers to operate profitably, adapt continuously, and grow sustainably.
+
+# Table of Contents
+
+## Information
+
+### General
+
+* [[Information about this Wiki and HowTo|wiki/info/general/wiki]]
+* [[General Project Information|wiki/info/general/general-information]]
+* [[Inventory|wiki/info/general/inventar-liste]]
+
+### Landscape and Development
+
+* [[Architecture and Infrastructure|wiki/info/landscape/architecture-and-infrastructure]]
+ * [[Runtime Environment|wiki/info/landscape/runtime-environment]]
+ * [[Basic architectual principles|wiki/info/landscape/basic-architectural-principles]]
+ * [[User Management|wiki/info/landscape/usermanagement]]
+ * [[Development Environment|wiki/info/landscape/development-environment]]
+ * [[Production Environment|wiki/info/landscape/production-environment]]
+ * [[Typical Development Scenarios|wiki/info/landscape/typical-development-scenarios]]
+* [[RaceLog Tracking Server Architecture|wiki/info/landscape/server]]
+ * Environment Overview [[PDF|wiki/info/mobile/event-tracking/architecture.pdf]] | [[SVG|wiki/info/mobile/event-tracking/architecture.svg]]
+* Amazon
+ * [[Amazon EC2|wiki/info/landscape/amazon-ec2]]
+ * [[EC2 Backup Strategy|wiki/info/landscape/amazon-ec2-backup-strategy]]
+ * [[Creating an EC2 image from scratch|wiki/info/landscape/creating-ec2-image-from-scratch]]
+ * [[EC2 mail relaying|wiki/info/landscape/mail-relaying]]
+* [[Building and Deploying|wiki/info/landscape/building-and-deploying]]
+* [[Data Mining Architecture|wiki/info/landscape/data-mining-architecture]]
+* [[Typical Data Mining Scenarios|wiki/info/landscape/typical-data-mining-scenarios]]
+* [[Create clickable UI prototypes with Axure|wiki/info/landscape/ui-clickable-prototypes]]
+* [[Webdesign|wiki/info/landscape/webdesign]]
+
+### Mobile
+
+* [[Mobile Development|wiki/info/mobile/mobile-development]]
+* Tracking App
+ * [[Tracking App Specification|wiki/info/mobile/app-spec/app-spec]]
+ * [[Event Tracking|wiki/info/mobile/event-tracking/event-tracking]]
+ * [[Steps for setting up Smartphone Tracking|wiki/info/mobile/smartphone-tracking-steps]]
+ * [[Tracking App Prototype Architecture|wiki/info/mobile/app]]
+* Racecommittee App
+ * [[Racecommittee App|wiki/info/mobile/racecommittee-app]]
+ * [[Environment|wiki/info/mobile/racecommittee-app-environment]]
+ * [[Administrator|wiki/info/mobile/racecommittee-app-administrator]]
+ * [[User|wiki/info/mobile/racecommittee-app-user]]
+* [[Android and Release Build|wiki/info/mobile/android-and-release-build]]
+* [[Energy consumption of mobile apps|wiki/info/mobile/energy-consumption]]
+* [[Data consumption of mobile apps|wiki/info/mobile/data-consumption]]
+* [[Mobile Sailing Analytics|wiki/info/mobile/mobilesailinganalytics]]
+* [[Push Notifications|wiki/info/mobile/push-notifications]]
+
+### API
+
+* [[Web Services API|wiki/info/api/sailing-webservices]]
+* [[API v1|wiki/info/api/api-v1]]
+* [[Tracking App API|wiki/info/api/tracking-app-api]]
+* [[Training API v1 draft|wiki/info/api/training-api-v1-draft]]
+
+### Security
+ * [[Fortify Tests|wiki/info/security/fortify]]
+ * [[SSL / HTTPS Support|wiki/info/security/ssl-support]]
+
+### Miscellaneous
+
+* [[Big Data - Numbers|wiki/info/misc/big-data-numbers]]
+* [[Data Quality|wiki/info/misc/data-quality]]
+* [[Sailing Domain Algorithms|wiki/info/misc/sailing-domain-algorithms]]
+* [[Google Analytics (Web Page Tracking)|wiki/info/misc/ganalytics]]
+* [[S3 Development Sample|wiki/info/misc/s3-sample]]
+
+* [[FAQ for League Operators|wiki/info/misc/league-operators-faq]]
+* [[Prionet related information|wiki/info/misc/prionet-related-information]]
+* [[TracTrac Workshop 2013|wiki/info/misc/tractracworkshop2013]]
+
+## HowTo
+
+* [[Onboarding|wiki/howto/onboarding]]
+
+
+### Development
+
+* [[Create boat graphics for the 2D race viewer|wiki/howto/development/boatgraphicssvg]]
+* [[Continuous Integration with Hudson/Jenkins|wiki/howto/development/ci]]
+* [[Dispatch|wiki/howto/development/dispatch]]
+* [[Working with GWT UI Binder|wiki/howto/development/gwt-ui-binder]]
+* [[Java De(Serialization) and Circular Dependencies|wiki/howto/development/java-de-serialization-and-circular-dependencies]]
+* [[JMX Support|wiki/howto/development/jmx]]
+* [[Working with GWT Locally|wiki/howto/development/local-gwt]]
+* [[Log File Analysis|wiki/howto/development/log-file-analysis]]
+* [[UI Tests with Selenium|wiki/howto/development/selenium-ui-tests]]
+* [[Profiling|wiki/howto/development/profiling]]
+* [[Working with GWT Super Dev Mode|wiki/howto/development/super-dev-mode]]
+
+### For Event Managers
+
+* [[Set up local network with replication server|wiki/howto/eventmanagers/event-network-with-replica]]
+* [[Operating Igtimi WindBots|wiki/howto/eventmanagers/windbot-operations]]
+* [[Linking Race Videos|wiki/howto/eventmanagers/linking-race-videos]]
+* [[Import official results|wiki/howto/eventmanagers/results-import]]
+
+### Setup
+
+* [[Configure Races on Server|wiki/howto/setup/configure-races-on-server]]
+* [[Setting up internal Jenkins on SAP Monsoon|wiki/howto/setup/setting-up-jenkins-on-sap-monsoon]]
+
+### Miscellaneous
+
+* [[Cook Book|wiki/howto/misc/cook-book]]
+* [[Polars|wiki/howto/misc/polars]]
+* [[Server Replication|wiki/howto/misc/server-replication]]
+* [[TracTrac|wiki/howto/misc/tractrac-lifecycle]]
+* [[UI Tests|wiki/howto/misc/ui-tests-tutorial]]
+* [[Uploading Media Content|wiki/howto/misc/uploading-media-content]]
+* [[Monitoring Apache and RabbitMQ|wiki/misc/monitoring-apache-and-rabbitmq]]
+
+## Projects
+* [[Analytics on a stick|wiki/projects/analytics-on-a-stick]]
+
+## Events and Planning
+* [[Project Planning (bigger development)|wiki/events/planning]]
+* [[General Event Planning|wiki/events/general-event-planning]]
+* [[Information about Extreme Sailing Series|wiki/events/extreme-sailing-series]]
+* [[Travemünder Woche 2014 event page|wiki/events/tw2014]]
+* [[505 worlds Kiel 2014 event page|wiki/events/505-worlds-kiel-2014]]
+* [[Kieler Woche event page|wiki/events/kieler-woche-2015]]
+* [[Charleston Race Week 2016|wiki/events/Charleston-Race-Week-2016]]
+* [[Sailing Leagues 2016|wiki/events/sailing-Leagues-2016]]
+
+## Planning
+* [[Overview|https://wiki.sapsailing.com/pages/wiki/planning/]]
+
+## Internal services (not related to wiki but useful)
+
+* [Bugzilla Issue Tracking System](http://bugzilla.sapsailing.com/bugzilla/)
+* [GIT Repository (SAP)](ssh://git.wdf.sap.corp:29418/SAPSail/sapsailingcapture.git)
+* [Maven Repository Browser](http://maven.sapsailing.com/maven/) (see [[how to setup repository for Android builds|wiki/info/mobile/racecommittee-app-environment]])
+* [Main Sailing Website](http://www.sapsailing.com)
+* [Visitor Statistics](http://analysis.sapsailing.com/)
diff --git a/ios/SAPTracker/src/xcode/SAPTracker/Base.lproj/Localizable.strings b/ios/SAPTracker/src/xcode/SAPTracker/Base.lproj/Localizable.strings
index 14e49d15af8..6e3a025ff81 100644
Binary files a/ios/SAPTracker/src/xcode/SAPTracker/Base.lproj/Localizable.strings and b/ios/SAPTracker/src/xcode/SAPTracker/Base.lproj/Localizable.strings differ
diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/sensordata/BravoSensorDataMetadata.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/sensordata/BravoSensorDataMetadata.java
index c7a7e10f408..969737c9f5e 100644
--- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/sensordata/BravoSensorDataMetadata.java
+++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/sensordata/BravoSensorDataMetadata.java
@@ -24,6 +24,7 @@ public enum BravoSensorDataMetadata {
"BravoNet_Node0x0A0_Voltage", "BravoNet_Node0x0A0_Temperature", "BravoNet_Node0x0A0_Current"));
public final int rideHeightColumn = getColumnIndex(RIDE_HEIGHT);
+ public final int columnCount = columns.size();
public List getColumns() {
return columns;
diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/tracking/impl/DoubleVectorFixImpl.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/tracking/impl/DoubleVectorFixImpl.java
index 67edd307e56..9c6b1ca99f3 100644
--- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/tracking/impl/DoubleVectorFixImpl.java
+++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/tracking/impl/DoubleVectorFixImpl.java
@@ -1,5 +1,7 @@
package com.sap.sailing.domain.common.tracking.impl;
+import java.util.Arrays;
+
import com.sap.sailing.domain.common.tracking.DoubleVectorFix;
import com.sap.sse.common.TimePoint;
@@ -34,4 +36,31 @@ public class DoubleVectorFixImpl implements DoubleVectorFix {
return fixData;
}
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + Arrays.hashCode(fixData);
+ result = prime * result + ((timePoint == null) ? 0 : timePoint.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ DoubleVectorFixImpl other = (DoubleVectorFixImpl) obj;
+ if (!Arrays.equals(fixData, other.fixData))
+ return false;
+ if (timePoint == null) {
+ if (other.timePoint != null)
+ return false;
+ } else if (!timePoint.equals(other.timePoint))
+ return false;
+ return true;
+ }
}
diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/racelog/tracking/impl/MongoSensorFixStoreImpl.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/racelog/tracking/impl/MongoSensorFixStoreImpl.java
index 48b3411d21a..c7e72a6e978 100644
--- a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/racelog/tracking/impl/MongoSensorFixStoreImpl.java
+++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/racelog/tracking/impl/MongoSensorFixStoreImpl.java
@@ -125,56 +125,53 @@ public class MongoSensorFixStoreImpl implements MongoSensorFixStore {
*/
@Override
public void storeFixes(DeviceIdentifier device, Iterable fixes) {
- if (Util.isEmpty(fixes)) {
- return;
- }
- try {
- final Object dbDeviceId = storeDeviceId(deviceServiceFinder, device);
- final int nrOfTotalFixes = Util.size(fixes);
- final ArrayList dbFixes = new ArrayList<>(nrOfTotalFixes);
-
- TimePoint newFrom = null;
- TimePoint newTo = null;
- for (FixT fix : fixes) {
- String type = fix.getClass().getName();
- FixMongoHandler mongoHandler = findService(type);
- Object fixObject = mongoHandler.transformForth(fix);
- DBObject entry = new BasicDBObjectBuilder().add(FieldNames.DEVICE_ID.name(), dbDeviceId)
- .add(FieldNames.GPSFIX_TYPE.name(), type).add(FieldNames.GPSFIX.name(), fixObject).get();
- mongoOF.storeTimed(fix, entry);
- dbFixes.add(entry);
- TimePoint fixTP = fix.getTimePoint();
- if (newFrom == null || newFrom.after(fixTP)) {
- newFrom = fixTP;
+ if (!Util.isEmpty(fixes)) {
+ try {
+ final Object dbDeviceId = storeDeviceId(deviceServiceFinder, device);
+ final int nrOfTotalFixes = Util.size(fixes);
+ final ArrayList dbFixes = new ArrayList<>(nrOfTotalFixes);
+
+ TimePoint newFrom = null;
+ TimePoint newTo = null;
+ for (FixT fix : fixes) {
+ String type = fix.getClass().getName();
+ FixMongoHandler mongoHandler = findService(type);
+ Object fixObject = mongoHandler.transformForth(fix);
+ DBObject entry = new BasicDBObjectBuilder().add(FieldNames.DEVICE_ID.name(), dbDeviceId)
+ .add(FieldNames.GPSFIX_TYPE.name(), type).add(FieldNames.GPSFIX.name(), fixObject).get();
+ mongoOF.storeTimed(fix, entry);
+ dbFixes.add(entry);
+ TimePoint fixTP = fix.getTimePoint();
+ if (newFrom == null || newFrom.after(fixTP)) {
+ newFrom = fixTP;
+ }
+ if (newTo == null || newTo.before(fixTP)) {
+ newTo = fixTP;
+ }
}
- if (newTo == null || newTo.before(fixTP)) {
- newTo = fixTP;
+ fixesCollection.insert(dbFixes);
+ final BasicDBObject updateOperation = new BasicDBObject();
+ final BasicDBObject newMetadata = new BasicDBObject();
+ newMetadata.put(FieldNames.DEVICE_ID.name(), dbDeviceId);
+
+ TimeRange oldTimeRange = getTimeRangeCoveredByFixes(device);
+ if (oldTimeRange != null) {
+ newFrom = oldTimeRange.from().before(newFrom) ? oldTimeRange.from() : newFrom;
+ newTo = oldTimeRange.to().after(newTo) ? oldTimeRange.to() : newTo;
}
+ final TimeRange newTimeRange = new TimeRangeImpl(newFrom, newTo);
+
+ storeTimeRange(newTimeRange, newMetadata, FieldNames.TIMERANGE);
+ updateOperation.append("$set", newMetadata);
+ updateOperation.append("$inc", new BasicDBObject(FieldNames.NUM_FIXES.name(), nrOfTotalFixes));
+ metadataCollection.update(getDeviceQuery(device), updateOperation, /* create if not existent */ true,
+ /* update multiple */ false);
+ } catch (TransformationException e) {
+ logger.log(Level.WARNING, "Could not store fix in MongoDB");
+ e.printStackTrace();
}
- fixesCollection.insert(dbFixes);
- final BasicDBObject updateOperation = new BasicDBObject();
- final BasicDBObject newMetadata = new BasicDBObject();
- newMetadata.put(FieldNames.DEVICE_ID.name(), dbDeviceId);
-
- TimeRange oldTimeRange = getTimeRangeCoveredByFixes(device);
- if (oldTimeRange != null) {
- newFrom = oldTimeRange.from().before(newFrom) ? oldTimeRange.from() : newFrom;
- newTo = oldTimeRange.to().after(newTo) ? oldTimeRange.to() : newTo;
- }
- final TimeRange newTimeRange = new TimeRangeImpl(newFrom, newTo);
-
- storeTimeRange(newTimeRange, newMetadata, FieldNames.TIMERANGE);
- updateOperation.append("$set", newMetadata);
- updateOperation.append("$inc", new BasicDBObject(FieldNames.NUM_FIXES.name(), nrOfTotalFixes));
- metadataCollection.update(getDeviceQuery(device), updateOperation, /* create if not existent */ true,
- /* update multiple */ false);
-
-
- } catch (TransformationException e) {
- logger.log(Level.WARNING, "Could not store fix in MongoDB");
- e.printStackTrace();
+ notifyListeners(device, fixes);
}
- notifyListeners(device, fixes);
}
@Override
diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/AbstractGPSFixStoreTest.java b/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/AbstractGPSFixStoreTest.java
index ed21eaf7f14..cef4a6b47c7 100644
--- a/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/AbstractGPSFixStoreTest.java
+++ b/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/AbstractGPSFixStoreTest.java
@@ -71,6 +71,8 @@ public class AbstractGPSFixStoreTest {
MongoObjectFactoryImpl mongoOF = (MongoObjectFactoryImpl) service.getMongoObjectFactory();
mongoOF.getGPSFixCollection().drop();
mongoOF.getGPSFixMetadataCollection().drop();
+ mongoOF.getRaceLogCollection().drop();
+ mongoOF.getRegattaLogCollection().drop();
}
protected void map(RegattaLog regattaLog, Competitor comp, DeviceIdentifier device, long from, long to) {
diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/SensorFixStoreAndLoadTest.java b/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/SensorFixStoreAndLoadTest.java
new file mode 100644
index 00000000000..0fe9590a085
--- /dev/null
+++ b/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/SensorFixStoreAndLoadTest.java
@@ -0,0 +1,657 @@
+package com.sap.sailing.domain.racelogtracking.test.impl;
+
+import static com.sap.sse.common.Util.size;
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNull;
+import static org.mockito.Mockito.mock;
+
+import java.io.Serializable;
+import java.net.UnknownHostException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.UUID;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.mongodb.DB;
+import com.mongodb.MongoException;
+import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor;
+import com.sap.sailing.domain.abstractlog.impl.LogEventAuthorImpl;
+import com.sap.sailing.domain.abstractlog.race.RaceLog;
+import com.sap.sailing.domain.abstractlog.race.analyzing.impl.RaceLogResolver;
+import com.sap.sailing.domain.abstractlog.race.impl.RaceLogEndOfTrackingEventImpl;
+import com.sap.sailing.domain.abstractlog.race.impl.RaceLogImpl;
+import com.sap.sailing.domain.abstractlog.race.impl.RaceLogStartOfTrackingEventImpl;
+import com.sap.sailing.domain.abstractlog.regatta.MappingEventVisitor;
+import com.sap.sailing.domain.abstractlog.regatta.RegattaLog;
+import com.sap.sailing.domain.abstractlog.regatta.RegattaLogEventVisitor;
+import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceCompetitorSensorDataMappingEvent;
+import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceMappingEvent;
+import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceMappingEventImpl;
+import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogCloseOpenEndedDeviceMappingEventImpl;
+import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogDefineMarkEventImpl;
+import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogDeviceCompetitorBravoMappingEventImpl;
+import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogRevokeEventImpl;
+import com.sap.sailing.domain.abstractlog.regatta.impl.RegattaLogImpl;
+import com.sap.sailing.domain.base.BoatClass;
+import com.sap.sailing.domain.base.Competitor;
+import com.sap.sailing.domain.base.Course;
+import com.sap.sailing.domain.base.DomainFactory;
+import com.sap.sailing.domain.base.Mark;
+import com.sap.sailing.domain.base.RaceDefinition;
+import com.sap.sailing.domain.base.Sideline;
+import com.sap.sailing.domain.base.Waypoint;
+import com.sap.sailing.domain.base.impl.CourseImpl;
+import com.sap.sailing.domain.base.impl.RaceDefinitionImpl;
+import com.sap.sailing.domain.base.impl.RegattaImpl;
+import com.sap.sailing.domain.base.impl.WaypointImpl;
+import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
+import com.sap.sailing.domain.common.impl.DegreePosition;
+import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
+import com.sap.sailing.domain.common.sensordata.BravoSensorDataMetadata;
+import com.sap.sailing.domain.common.tracking.DoubleVectorFix;
+import com.sap.sailing.domain.common.tracking.GPSFixMoving;
+import com.sap.sailing.domain.common.tracking.SensorFix;
+import com.sap.sailing.domain.common.tracking.impl.DoubleVectorFixImpl;
+import com.sap.sailing.domain.common.tracking.impl.GPSFixMovingImpl;
+import com.sap.sailing.domain.persistence.PersistenceFactory;
+import com.sap.sailing.domain.persistence.impl.CollectionNames;
+import com.sap.sailing.domain.persistence.racelog.tracking.impl.MongoSensorFixStoreImpl;
+import com.sap.sailing.domain.racelog.impl.EmptyRaceLogStore;
+import com.sap.sailing.domain.racelog.tracking.SensorFixMapper;
+import com.sap.sailing.domain.racelog.tracking.SensorFixStore;
+import com.sap.sailing.domain.racelog.tracking.test.mock.MockSmartphoneImeiServiceFinderFactory;
+import com.sap.sailing.domain.racelog.tracking.test.mock.SmartphoneImeiIdentifier;
+import com.sap.sailing.domain.racelogsensortracking.SensorFixMapperFactory;
+import com.sap.sailing.domain.racelogtracking.DeviceIdentifier;
+import com.sap.sailing.domain.racelogtracking.impl.BravoDataFixMapper;
+import com.sap.sailing.domain.racelogtracking.impl.fixtracker.FixLoaderAndTracker;
+import com.sap.sailing.domain.ranking.OneDesignRankingMetric;
+import com.sap.sailing.domain.regattalog.impl.EmptyRegattaLogStore;
+import com.sap.sailing.domain.tracking.BravoFixTrack;
+import com.sap.sailing.domain.tracking.DynamicSensorFixTrack;
+import com.sap.sailing.domain.tracking.DynamicTrack;
+import com.sap.sailing.domain.tracking.DynamicTrackedRace;
+import com.sap.sailing.domain.tracking.DynamicTrackedRegatta;
+import com.sap.sailing.domain.tracking.Track;
+import com.sap.sailing.domain.tracking.impl.DynamicTrackedRaceImpl;
+import com.sap.sailing.domain.tracking.impl.DynamicTrackedRegattaImpl;
+import com.sap.sailing.domain.tracking.impl.EmptyWindStore;
+import com.sap.sailing.domain.tracking.impl.SensorFixTrackImpl;
+import com.sap.sse.common.TimePoint;
+import com.sap.sse.common.Timed;
+import com.sap.sse.common.WithID;
+import com.sap.sse.common.impl.MillisecondsTimePoint;
+
+public class SensorFixStoreAndLoadTest {
+ private static final long START_OF_TRACKING = 100;
+ private static final long MID_OF_TRACKING = 200;
+ private static final long END_OF_TRACKING = 300;
+ private static final long FIX_TIMESTAMP = 110;
+ private static final long FIX_TIMESTAMP2 = 120;
+ private static final long FIX_TIMESTAMP3 = 210;
+ private static final long AFTER_LAST_FIX = FIX_TIMESTAMP3 + 1;
+ private static final double FIX_RIDE_HEIGHT = 1337.0;
+ private static final double FIX_RIDE_HEIGHT2 = 1338.0;
+ private static final double FIX_RIDE_HEIGHT3 = 1336.0;
+ private static final double FIX_TEST_VALUE = 12.0;
+ protected final MockSmartphoneImeiServiceFinderFactory serviceFinderFactory = new MockSmartphoneImeiServiceFinderFactory();
+ protected final DeviceIdentifier device = new SmartphoneImeiIdentifier("a");
+ protected final DeviceIdentifier deviceTest = new SmartphoneImeiIdentifier("b");
+ protected RaceLog raceLog;
+ protected RegattaLog regattaLog;
+ protected SensorFixStore store;
+ protected final Competitor comp = DomainFactory.INSTANCE.getOrCreateCompetitor("comp", "comp", null, null, null,
+ null, null, /* timeOnTimeFactor */ null, /* timeOnDistanceAllowanceInSecondsPerNauticalMile */ null, null);
+ protected final Competitor comp2 = DomainFactory.INSTANCE.getOrCreateCompetitor("comp2", "comp2", null, null, null,
+ null, null, /* timeOnTimeFactor */ null, /* timeOnDistanceAllowanceInSecondsPerNauticalMile */ null, null);
+ protected final Mark mark = DomainFactory.INSTANCE.getOrCreateMark("mark");
+ protected final Mark mark2 = DomainFactory.INSTANCE.getOrCreateMark("mark2");
+ private final BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("49er");
+
+ protected final AbstractLogEventAuthor author = new LogEventAuthorImpl("author", 0);
+ private DynamicTrackedRace trackedRace;
+
+ protected GPSFixMoving createFix(long millis, double lat, double lng, double knots, double degrees) {
+ return new GPSFixMovingImpl(new DegreePosition(lat, lng), new MillisecondsTimePoint(millis),
+ new KnotSpeedWithBearingImpl(knots, new DegreeBearingImpl(degrees)));
+ }
+
+ @Before
+ public void setUp() throws UnknownHostException, MongoException {
+ dropPersistedData();
+ raceLog = new RaceLogImpl("racelog");
+ raceLog.add(new RaceLogStartOfTrackingEventImpl(new MillisecondsTimePoint(START_OF_TRACKING), author, 0));
+ raceLog.add(new RaceLogEndOfTrackingEventImpl(new MillisecondsTimePoint(END_OF_TRACKING), author, 0));
+
+ regattaLog = new RegattaLogImpl("regattalog");
+
+ store = new MongoSensorFixStoreImpl(PersistenceFactory.INSTANCE.getDefaultMongoObjectFactory(),
+ PersistenceFactory.INSTANCE.getDefaultDomainObjectFactory(), serviceFinderFactory);
+
+ regattaLog.add(new RegattaLogDefineMarkEventImpl(new MillisecondsTimePoint(1), author,
+ new MillisecondsTimePoint(1), 0, mark));
+ regattaLog.add(new RegattaLogDefineMarkEventImpl(new MillisecondsTimePoint(2), author,
+ new MillisecondsTimePoint(1), 0, mark2));
+ Course course = new CourseImpl("course",
+ Arrays.asList(new Waypoint[] { new WaypointImpl(mark), new WaypointImpl(mark2) }));
+ RaceDefinition race = new RaceDefinitionImpl("race", course, boatClass, Arrays.asList(comp, comp2));
+ DynamicTrackedRegatta regatta = new DynamicTrackedRegattaImpl(new RegattaImpl(EmptyRaceLogStore.INSTANCE,
+ EmptyRegattaLogStore.INSTANCE, RegattaImpl.getDefaultName("regatta", boatClass.getName()), boatClass,
+ /* startDate */ null, /* endDate */null, null, null, "a", null));
+ trackedRace = new DynamicTrackedRaceImpl(regatta, race, Collections. emptyList(),
+ EmptyWindStore.INSTANCE, 0, 0, 0, /* useMarkPassingCalculator */ false, OneDesignRankingMetric::new,
+ mock(RaceLogResolver.class));
+ }
+
+ private void dropPersistedData() {
+ DB db = PersistenceFactory.INSTANCE.getDefaultMongoObjectFactory().getDatabase();
+ db.getCollection(CollectionNames.GPS_FIXES.name()).drop();
+ db.getCollection(CollectionNames.GPS_FIXES_METADATA.name()).drop();
+ db.getCollection(CollectionNames.REGATTA_LOGS.name()).drop();
+ db.getCollection(CollectionNames.RACE_LOGS.name()).drop();
+ }
+
+ @After
+ public void after() {
+ dropPersistedData();
+ }
+
+ @Test
+ public void testLoadAlreadyAddedFixes() throws InterruptedException {
+ regattaLog.add(new RegattaLogDeviceCompetitorBravoMappingEventImpl(new MillisecondsTimePoint(3), author, comp,
+ device, new MillisecondsTimePoint(START_OF_TRACKING), new MillisecondsTimePoint(MID_OF_TRACKING)));
+
+ addBravoFixes();
+
+ FixLoaderAndTracker fixLoaderAndTracker = createFixLoaderAndTracker();
+
+ trackedRace.attachRaceLog(raceLog);
+ trackedRace.attachRegattaLog(regattaLog);
+
+ trackedRace.waitForLoadingToFinish();
+
+ testNumberOfRawFixes(trackedRace.getSensorTrack(comp, BravoFixTrack.TRACK_NAME), 2);
+
+ fixLoaderAndTracker.stop(true);
+ }
+
+ @Test
+ public void testAddFixesWhileTracking() throws InterruptedException {
+ regattaLog.add(new RegattaLogDeviceCompetitorBravoMappingEventImpl(new MillisecondsTimePoint(3), author, comp,
+ device, new MillisecondsTimePoint(START_OF_TRACKING), new MillisecondsTimePoint(MID_OF_TRACKING)));
+
+ FixLoaderAndTracker fixLoaderAndTracker = createFixLoaderAndTracker();
+
+ trackedRace.attachRaceLog(raceLog);
+ trackedRace.attachRegattaLog(regattaLog);
+
+ addBravoFixes();
+
+ trackedRace.waitForLoadingToFinish();
+
+ testNumberOfRawFixes(trackedRace.getSensorTrack(comp, BravoFixTrack.TRACK_NAME), 2);
+
+ fixLoaderAndTracker.stop(true);
+ }
+
+ @Test
+ public void testNoFixesAreLoadedIfNoStoredFixIsInTimeRange() throws InterruptedException {
+ regattaLog.add(new RegattaLogDeviceCompetitorBravoMappingEventImpl(new MillisecondsTimePoint(3), author, comp,
+ device, new MillisecondsTimePoint(AFTER_LAST_FIX), new MillisecondsTimePoint(END_OF_TRACKING)));
+
+ addBravoFixes();
+
+ FixLoaderAndTracker fixLoaderAndTracker = createFixLoaderAndTracker();
+
+ trackedRace.attachRaceLog(raceLog);
+ trackedRace.attachRegattaLog(regattaLog);
+
+ trackedRace.waitForLoadingToFinish();
+
+ testNumberOfRawFixes(trackedRace.getSensorTrack(comp, BravoFixTrack.TRACK_NAME), 0);
+
+ fixLoaderAndTracker.stop(true);
+ }
+
+ @Test
+ public void testFixesNotInMappedTimeRangeAreIgnoredWhileTracking() throws InterruptedException {
+ regattaLog.add(new RegattaLogDeviceCompetitorBravoMappingEventImpl(new MillisecondsTimePoint(3), author, comp,
+ device, new MillisecondsTimePoint(AFTER_LAST_FIX), new MillisecondsTimePoint(END_OF_TRACKING)));
+
+ FixLoaderAndTracker fixLoaderAndTracker = createFixLoaderAndTracker();
+
+ trackedRace.attachRaceLog(raceLog);
+ trackedRace.attachRegattaLog(regattaLog);
+
+ addBravoFixes();
+
+ trackedRace.waitForLoadingToFinish();
+
+ testNumberOfRawFixes(trackedRace.getSensorTrack(comp, BravoFixTrack.TRACK_NAME), 0);
+
+ fixLoaderAndTracker.stop(true);
+ }
+
+ @Test
+ public void testCompetitorWithoutMappingHasNoTrack() throws InterruptedException {
+ regattaLog.add(new RegattaLogDeviceCompetitorBravoMappingEventImpl(new MillisecondsTimePoint(3), author, comp,
+ device, new MillisecondsTimePoint(START_OF_TRACKING), new MillisecondsTimePoint(MID_OF_TRACKING)));
+
+ addBravoFixes();
+
+ FixLoaderAndTracker fixLoaderAndTracker = createFixLoaderAndTracker();
+
+ trackedRace.attachRaceLog(raceLog);
+ trackedRace.attachRegattaLog(regattaLog);
+
+ trackedRace.waitForLoadingToFinish();
+
+ assertNull(trackedRace.getSensorTrack(comp2, BravoFixTrack.TRACK_NAME));
+
+ fixLoaderAndTracker.stop(true);
+ }
+
+ @Test
+ public void testDeviceIsMappedToDifferentCompetitorsInDifferentTimeRanges() throws InterruptedException {
+ regattaLog.add(new RegattaLogDeviceCompetitorBravoMappingEventImpl(new MillisecondsTimePoint(3), author, comp,
+ device, new MillisecondsTimePoint(START_OF_TRACKING), new MillisecondsTimePoint(MID_OF_TRACKING)));
+ regattaLog.add(new RegattaLogDeviceCompetitorBravoMappingEventImpl(new MillisecondsTimePoint(3), author, comp2,
+ device, new MillisecondsTimePoint(MID_OF_TRACKING + 1), new MillisecondsTimePoint(END_OF_TRACKING)));
+
+ addBravoFixes();
+
+ FixLoaderAndTracker fixLoaderAndTracker = createFixLoaderAndTracker();
+
+ trackedRace.attachRaceLog(raceLog);
+ trackedRace.attachRegattaLog(regattaLog);
+
+ trackedRace.waitForLoadingToFinish();
+
+ testNumberOfRawFixes(trackedRace.getSensorTrack(comp, BravoFixTrack.TRACK_NAME), 2);
+ testNumberOfRawFixes(trackedRace.getSensorTrack(comp2, BravoFixTrack.TRACK_NAME), 1);
+
+ fixLoaderAndTracker.stop(true);
+ }
+
+ @Test
+ public void testMultipleMappingsForOneDeviceAndCompetitor() throws InterruptedException {
+ regattaLog.add(new RegattaLogDeviceCompetitorBravoMappingEventImpl(new MillisecondsTimePoint(3), author, comp,
+ device, new MillisecondsTimePoint(START_OF_TRACKING), new MillisecondsTimePoint(MID_OF_TRACKING)));
+ regattaLog.add(new RegattaLogDeviceCompetitorBravoMappingEventImpl(new MillisecondsTimePoint(3), author, comp,
+ device, new MillisecondsTimePoint(MID_OF_TRACKING + 1), new MillisecondsTimePoint(END_OF_TRACKING)));
+
+ addBravoFixes();
+
+ FixLoaderAndTracker fixLoaderAndTracker = createFixLoaderAndTracker();
+
+ trackedRace.attachRaceLog(raceLog);
+ trackedRace.attachRegattaLog(regattaLog);
+
+ trackedRace.waitForLoadingToFinish();
+
+ testNumberOfRawFixes(trackedRace.getSensorTrack(comp, BravoFixTrack.TRACK_NAME), 3);
+
+ fixLoaderAndTracker.stop(true);
+ }
+
+ @Test
+ public void testNothingLoadedForRevokedMapping() throws InterruptedException {
+ RegattaLogDeviceCompetitorBravoMappingEventImpl mappingEvent = new RegattaLogDeviceCompetitorBravoMappingEventImpl(
+ new MillisecondsTimePoint(3), author, comp, device, new MillisecondsTimePoint(START_OF_TRACKING),
+ new MillisecondsTimePoint(MID_OF_TRACKING));
+ regattaLog.add(mappingEvent);
+ regattaLog.add(new RegattaLogRevokeEventImpl(author, mappingEvent, "Test purposes"));
+
+ FixLoaderAndTracker fixLoaderAndTracker = createFixLoaderAndTracker();
+
+ trackedRace.attachRaceLog(raceLog);
+ trackedRace.attachRegattaLog(regattaLog);
+
+ addBravoFixes();
+
+ trackedRace.waitForLoadingToFinish();
+
+ assertNull(trackedRace.getSensorTrack(comp, BravoFixTrack.TRACK_NAME));
+
+ fixLoaderAndTracker.stop(true);
+ }
+
+ @Test
+ public void testFixesAreLoadedIfThereIsOneRevokedAndOneNonRevokedMapping() throws InterruptedException {
+ // revoked mapping timestamp 100-300
+ RegattaLogDeviceCompetitorBravoMappingEventImpl mappingEvent = new RegattaLogDeviceCompetitorBravoMappingEventImpl(
+ new MillisecondsTimePoint(3), author, comp, device, new MillisecondsTimePoint(START_OF_TRACKING),
+ new MillisecondsTimePoint(END_OF_TRACKING));
+ regattaLog.add(mappingEvent);
+ regattaLog.add(new RegattaLogRevokeEventImpl(author, mappingEvent, "Test purposes"));
+
+ // non-revoked mapping timestamp 100-200
+ regattaLog.add(new RegattaLogDeviceCompetitorBravoMappingEventImpl(new MillisecondsTimePoint(3), author, comp,
+ device, new MillisecondsTimePoint(START_OF_TRACKING), new MillisecondsTimePoint(MID_OF_TRACKING)));
+
+ FixLoaderAndTracker fixLoaderAndTracker = createFixLoaderAndTracker();
+
+ trackedRace.attachRaceLog(raceLog);
+ trackedRace.attachRegattaLog(regattaLog);
+
+ addBravoFixes();
+
+ trackedRace.waitForLoadingToFinish();
+
+ // Only Fixes from 100 to 200 may be included
+ testNumberOfRawFixes(trackedRace.getSensorTrack(comp, BravoFixTrack.TRACK_NAME), 2);
+
+ fixLoaderAndTracker.stop(true);
+ }
+
+ @Test
+ public void testLoadFixesWhenClosingEventIsRevoked() throws InterruptedException {
+ UUID mappingEventId = UUID.randomUUID();
+ regattaLog.add(new RegattaLogDeviceCompetitorBravoMappingEventImpl(MillisecondsTimePoint.now(),
+ new MillisecondsTimePoint(3), author, mappingEventId, comp, device, new MillisecondsTimePoint(START_OF_TRACKING),
+ null));
+ RegattaLogCloseOpenEndedDeviceMappingEventImpl closeEvent = new RegattaLogCloseOpenEndedDeviceMappingEventImpl(
+ new MillisecondsTimePoint(4), author, mappingEventId, new MillisecondsTimePoint(MID_OF_TRACKING));
+ regattaLog.add(closeEvent);
+
+ addBravoFixes();
+
+ FixLoaderAndTracker fixLoaderAndTracker = createFixLoaderAndTracker();
+
+ trackedRace.attachRaceLog(raceLog);
+ trackedRace.attachRegattaLog(regattaLog);
+
+ trackedRace.waitForLoadingToFinish();
+
+ BravoFixTrack bravoFixTrack = trackedRace.getSensorTrack(comp, BravoFixTrack.TRACK_NAME);
+
+ testNumberOfRawFixes(bravoFixTrack, 2);
+
+ regattaLog.add(new RegattaLogRevokeEventImpl(author, closeEvent, "Test purposes"));
+
+ trackedRace.waitForLoadingToFinish();
+ testNumberOfRawFixes(bravoFixTrack, 3);
+
+ fixLoaderAndTracker.stop(true);
+ }
+
+ @Test
+ public void testBravoFixIsCorrectlyWrapped() throws InterruptedException {
+ regattaLog.add(new RegattaLogDeviceCompetitorBravoMappingEventImpl(new MillisecondsTimePoint(3), author, comp,
+ device, new MillisecondsTimePoint(START_OF_TRACKING), new MillisecondsTimePoint(MID_OF_TRACKING)));
+
+ addBravoFixes();
+
+ FixLoaderAndTracker fixLoaderAndTracker = createFixLoaderAndTracker();
+
+ trackedRace.attachRaceLog(raceLog);
+ trackedRace.attachRegattaLog(regattaLog);
+
+ trackedRace.waitForLoadingToFinish();
+
+ BravoFixTrack bravoFixTrack = trackedRace.getSensorTrack(comp, BravoFixTrack.TRACK_NAME);
+ assertEquals(FIX_RIDE_HEIGHT,
+ bravoFixTrack.getFirstFixAtOrAfter(new MillisecondsTimePoint(FIX_TIMESTAMP)).getRideHeight());
+
+ fixLoaderAndTracker.stop(true);
+ }
+
+ @Test
+ public void testMultipleFixTypesAreLoadedInSeparateTracks() throws InterruptedException {
+ regattaLog.add(new RegattaLogDeviceCompetitorTestMappingEventImpl(new MillisecondsTimePoint(1), author, comp,
+ deviceTest, new MillisecondsTimePoint(START_OF_TRACKING), new MillisecondsTimePoint(MID_OF_TRACKING)));
+ regattaLog.add(new RegattaLogDeviceCompetitorBravoMappingEventImpl(new MillisecondsTimePoint(3), author, comp,
+ device, new MillisecondsTimePoint(START_OF_TRACKING), new MillisecondsTimePoint(MID_OF_TRACKING)));
+
+ addTestFixes();
+ addBravoFixes();
+
+ FixLoaderAndTracker fixLoaderAndTracker = createFixLoaderAndTracker();
+
+ trackedRace.attachRaceLog(raceLog);
+ trackedRace.attachRegattaLog(regattaLog);
+
+ trackedRace.waitForLoadingToFinish();
+
+ testNumberOfRawFixes(trackedRace.getSensorTrack(comp, BravoFixTrack.TRACK_NAME), 2);
+ testNumberOfRawFixes(trackedRace.getSensorTrack(comp, TestFixTrackImpl.TRACK_NAME), 1);
+
+ fixLoaderAndTracker.stop(true);
+ }
+
+ @Test
+ public void testMultipleFixTypesAreLoadedInSeparateTracksWhileTracking() throws InterruptedException {
+ regattaLog.add(new RegattaLogDeviceCompetitorTestMappingEventImpl(new MillisecondsTimePoint(1), author, comp,
+ deviceTest, new MillisecondsTimePoint(START_OF_TRACKING), new MillisecondsTimePoint(MID_OF_TRACKING)));
+ regattaLog.add(new RegattaLogDeviceCompetitorBravoMappingEventImpl(new MillisecondsTimePoint(3), author, comp,
+ device, new MillisecondsTimePoint(START_OF_TRACKING), new MillisecondsTimePoint(MID_OF_TRACKING)));
+
+
+ FixLoaderAndTracker fixLoaderAndTracker = createFixLoaderAndTracker();
+
+ trackedRace.attachRaceLog(raceLog);
+ trackedRace.attachRegattaLog(regattaLog);
+
+ addTestFixes();
+ addBravoFixes();
+
+ trackedRace.waitForLoadingToFinish();
+
+ testNumberOfRawFixes(trackedRace.getSensorTrack(comp, TestFixTrackImpl.TRACK_NAME), 1);
+ testNumberOfRawFixes(trackedRace.getSensorTrack(comp, BravoFixTrack.TRACK_NAME), 2);
+
+ fixLoaderAndTracker.stop(true);
+ }
+
+ @Test
+ public void testMultipleFixTypesAreMappedCorrectly() throws InterruptedException {
+ regattaLog.add(new RegattaLogDeviceCompetitorTestMappingEventImpl(new MillisecondsTimePoint(1), author, comp,
+ deviceTest, new MillisecondsTimePoint(START_OF_TRACKING), new MillisecondsTimePoint(END_OF_TRACKING)));
+ regattaLog.add(new RegattaLogDeviceCompetitorBravoMappingEventImpl(new MillisecondsTimePoint(3), author, comp,
+ device, new MillisecondsTimePoint(START_OF_TRACKING), new MillisecondsTimePoint(END_OF_TRACKING)));
+
+
+ FixLoaderAndTracker fixLoaderAndTracker = createFixLoaderAndTracker();
+
+ trackedRace.attachRaceLog(raceLog);
+ trackedRace.attachRegattaLog(regattaLog);
+
+ addTestFixes();
+ addBravoFixes();
+
+ trackedRace.waitForLoadingToFinish();
+
+ BravoFixTrack bravoFixTrack = trackedRace.getSensorTrack(comp, BravoFixTrack.TRACK_NAME);
+ assertEquals(FIX_RIDE_HEIGHT,
+ bravoFixTrack.getFirstFixAtOrAfter(new MillisecondsTimePoint(FIX_TIMESTAMP)).getRideHeight());
+ assertEquals(FIX_RIDE_HEIGHT2,
+ bravoFixTrack.getFirstFixAtOrAfter(new MillisecondsTimePoint(FIX_TIMESTAMP2)).getRideHeight());
+ assertEquals(FIX_RIDE_HEIGHT3,
+ bravoFixTrack.getFirstFixAtOrAfter(new MillisecondsTimePoint(FIX_TIMESTAMP3)).getRideHeight());
+
+ TestFixTrackImpl testFixTrack = trackedRace.getSensorTrack(comp, TestFixTrackImpl.TRACK_NAME);
+ assertEquals(FIX_TEST_VALUE,
+ testFixTrack.getFirstFixAtOrAfter(new MillisecondsTimePoint(FIX_TIMESTAMP)).getTestValue());
+
+ fixLoaderAndTracker.stop(true);
+ }
+
+ private void addBravoFixes() {
+ store.storeFix(device, createBravoDoubleVectorFixWithRideHeight(FIX_TIMESTAMP, FIX_RIDE_HEIGHT));
+ store.storeFix(device, createBravoDoubleVectorFixWithRideHeight(FIX_TIMESTAMP2, FIX_RIDE_HEIGHT2));
+ store.storeFix(device, createBravoDoubleVectorFixWithRideHeight(FIX_TIMESTAMP3, FIX_RIDE_HEIGHT3));
+ }
+
+ private FixLoaderAndTracker createFixLoaderAndTracker() {
+
+ return new FixLoaderAndTracker(trackedRace, store, new SensorFixMapperFactory() {
+ private TestDataFixMapper testDataFixMapper = new TestDataFixMapper();
+ private BravoDataFixMapper bravoDataFixMapper = new BravoDataFixMapper();
+
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ @Override
+ public > SensorFixMapper createCompetitorMapper(
+ Class extends RegattaLogDeviceMappingEvent>> eventType) {
+ if(bravoDataFixMapper.isResponsibleFor(eventType)) {
+ return (SensorFixMapper) bravoDataFixMapper;
+ }
+ if(testDataFixMapper.isResponsibleFor(eventType)) {
+ return (SensorFixMapper) testDataFixMapper;
+ }
+ throw new IllegalArgumentException("Unknown event type");
+ }
+ });
+ }
+
+ protected void testNumberOfRawFixes(Track> track, long expected) {
+ track.lockForRead();
+ assertEquals(expected, size(track.getRawFixes()));
+ track.unlockAfterRead();
+ }
+
+ private DoubleVectorFix createBravoDoubleVectorFixWithRideHeight(long timestamp, double rideHeight) {
+ double[] fixData = new double[BravoSensorDataMetadata.INSTANCE.columnCount];
+ fixData[BravoSensorDataMetadata.INSTANCE.rideHeightColumn] = rideHeight;
+ return new DoubleVectorFixImpl(new MillisecondsTimePoint(timestamp), fixData);
+ }
+
+ private void addTestFixes() {
+ store.storeFix(deviceTest, createTestDoubleVectorFixWithTestValue(FIX_TIMESTAMP, FIX_TEST_VALUE));
+ }
+
+ private DoubleVectorFix createTestDoubleVectorFixWithTestValue(long timestamp, double testValue) {
+ double[] fixData = new double[TestFixImpl.COLUMNS.size()];
+ fixData[TestFixImpl.TEST_COLUMN_INDEX] = testValue;
+ return new DoubleVectorFixImpl(new MillisecondsTimePoint(timestamp), fixData);
+ }
+
+ private static class TestFixImpl implements SensorFix {
+ private static final long serialVersionUID = 2033254212013220L;
+
+ public static final String TEST_COLUMN = "testColumn";
+ public static final int TEST_COLUMN_INDEX = 1;
+
+ public static final List COLUMNS = Collections.unmodifiableList(Arrays.asList("blub", TEST_COLUMN));
+
+ private final DoubleVectorFix fix;
+
+ public TestFixImpl(DoubleVectorFix fix) {
+ this.fix = fix;
+ }
+
+ @Override
+ public double get(String valueName) {
+ return fix.get(COLUMNS.indexOf(valueName));
+ }
+
+ @Override
+ public TimePoint getTimePoint() {
+ return fix.getTimePoint();
+ }
+
+ public double getTestValue() {
+ return fix.get(TEST_COLUMN_INDEX);
+ }
+
+ }
+
+ public class TestFixTrackImpl extends SensorFixTrackImpl {
+ private static final long serialVersionUID = 5517848726454386L;
+
+ public static final String TRACK_NAME = "TestFixTrack";
+
+ public TestFixTrackImpl(ItemType trackedItem, String trackName) {
+ super(trackedItem, trackName, TestFixImpl.COLUMNS,
+ TRACK_NAME + " for " + trackedItem);
+ }
+
+ public Double getTextValue(TimePoint timePoint) {
+ TestFixImpl fixAfter = getFirstFixAtOrAfter(timePoint);
+ if (fixAfter != null && fixAfter.getTimePoint().compareTo(timePoint) == 0) {
+ // exact match of timepoint -> no interpolation necessary
+ return fixAfter.getTestValue();
+ }
+ TestFixImpl fixBefore = getLastFixAtOrBefore(timePoint);
+ if (fixBefore != null && fixBefore.getTimePoint().compareTo(timePoint) == 0) {
+ // exact match of timepoint -> no interpolation necessary
+ return fixBefore.getTestValue();
+ }
+ if (fixAfter == null || fixBefore == null) {
+ // the fix is out of the TimeRange where we have fixes
+ return null;
+ }
+ return fixBefore.getTestValue();
+ }
+ }
+
+ public class TestDataFixMapper implements SensorFixMapper, Competitor> {
+
+ @Override
+ public DynamicSensorFixTrack getTrack(DynamicTrackedRace race, Competitor key) {
+ return race.getOrCreateSensorTrack(key, TestFixTrackImpl.TRACK_NAME,
+ () -> new TestFixTrackImpl(key, TestFixTrackImpl.TRACK_NAME));
+ }
+
+ @Override
+ public TestFixImpl map(DoubleVectorFix fix) {
+ return new TestFixImpl(fix);
+ }
+
+ @Override
+ public boolean isResponsibleFor(Class extends RegattaLogDeviceMappingEvent>> eventType) {
+ return RegattaLogDeviceCompetitorTestMappingEventImpl.class.isAssignableFrom(eventType);
+ }
+ }
+
+ public class RegattaLogDeviceCompetitorTestMappingEventImpl extends RegattaLogDeviceMappingEventImpl
+ implements RegattaLogDeviceCompetitorSensorDataMappingEvent {
+ private static final long serialVersionUID = -14940305448048753L;
+
+
+ public RegattaLogDeviceCompetitorTestMappingEventImpl(TimePoint createdAt, TimePoint logicalTimePoint,
+ AbstractLogEventAuthor author, Serializable pId, Competitor mappedTo, DeviceIdentifier device,
+ TimePoint from, TimePoint to) {
+ super(createdAt, logicalTimePoint, author, pId, mappedTo, device, from, to);
+ }
+
+ public RegattaLogDeviceCompetitorTestMappingEventImpl(TimePoint logicalTimePoint, AbstractLogEventAuthor author,
+ Competitor mappedTo, DeviceIdentifier device, TimePoint from, TimePoint to) {
+ super(logicalTimePoint, author, mappedTo, device, from, to);
+ }
+
+ @Override
+ public void accept(RegattaLogEventVisitor visitor) {
+ visitor.visit(this);
+ }
+
+ @Override
+ public void accept(MappingEventVisitor visitor) {
+ visitor.visit(this);
+ }
+ }
+
+ @Test
+ public void testThatMappingsOutsideOfTheTrackedIntervalDontCauseLoadingToFail() throws InterruptedException {
+ regattaLog.add(new RegattaLogDeviceCompetitorBravoMappingEventImpl(new MillisecondsTimePoint(3), author, comp,
+ device, new MillisecondsTimePoint(START_OF_TRACKING), new MillisecondsTimePoint(MID_OF_TRACKING)));
+ regattaLog.add(new RegattaLogDeviceCompetitorBravoMappingEventImpl(new MillisecondsTimePoint(3), author, comp,
+ device, new MillisecondsTimePoint(10), new MillisecondsTimePoint(20)));
+ regattaLog.add(new RegattaLogDeviceCompetitorBravoMappingEventImpl(new MillisecondsTimePoint(3), author, comp2,
+ deviceTest, new MillisecondsTimePoint(START_OF_TRACKING), new MillisecondsTimePoint(MID_OF_TRACKING)));
+ regattaLog.add(new RegattaLogDeviceCompetitorBravoMappingEventImpl(new MillisecondsTimePoint(3), author, comp2,
+ deviceTest, new MillisecondsTimePoint(10), new MillisecondsTimePoint(20)));
+ addBravoFixes();
+ FixLoaderAndTracker fixLoaderAndTracker = createFixLoaderAndTracker();
+ trackedRace.attachRaceLog(raceLog);
+ trackedRace.attachRegattaLog(regattaLog);
+ trackedRace.waitForLoadingToFinish();
+ assertNotNull(trackedRace.getSensorTrack(comp, BravoFixTrack.TRACK_NAME));
+ assertNotNull(trackedRace.getSensorTrack(comp2, BravoFixTrack.TRACK_NAME));
+ fixLoaderAndTracker.stop(true);
+ }
+}
diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/SensorFixStoreTest.java b/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/SensorFixStoreTest.java
new file mode 100644
index 00000000000..eb670d2c9cf
--- /dev/null
+++ b/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/SensorFixStoreTest.java
@@ -0,0 +1,301 @@
+package com.sap.sailing.domain.racelogtracking.test.impl;
+
+import static org.junit.Assert.assertEquals;
+
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import com.mongodb.DB;
+import com.mongodb.MongoException;
+import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
+import com.sap.sailing.domain.common.impl.DegreePosition;
+import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
+import com.sap.sailing.domain.common.racelog.tracking.TransformationException;
+import com.sap.sailing.domain.common.sensordata.BravoSensorDataMetadata;
+import com.sap.sailing.domain.common.tracking.DoubleVectorFix;
+import com.sap.sailing.domain.common.tracking.GPSFixMoving;
+import com.sap.sailing.domain.common.tracking.impl.DoubleVectorFixImpl;
+import com.sap.sailing.domain.common.tracking.impl.GPSFixMovingImpl;
+import com.sap.sailing.domain.persistence.PersistenceFactory;
+import com.sap.sailing.domain.persistence.impl.CollectionNames;
+import com.sap.sailing.domain.persistence.racelog.tracking.impl.MongoSensorFixStoreImpl;
+import com.sap.sailing.domain.racelog.tracking.FixReceivedListener;
+import com.sap.sailing.domain.racelog.tracking.SensorFixStore;
+import com.sap.sailing.domain.racelog.tracking.test.mock.MockSmartphoneImeiServiceFinderFactory;
+import com.sap.sailing.domain.racelog.tracking.test.mock.SmartphoneImeiIdentifier;
+import com.sap.sailing.domain.racelogtracking.DeviceIdentifier;
+import com.sap.sse.common.Timed;
+import com.sap.sse.common.impl.MillisecondsTimePoint;
+
+public class SensorFixStoreTest {
+ private static final long FIX_TIMESTAMP = 110;
+ private static final long FIX_TIMESTAMP2 = 120;
+ private static final double FIX_RIDE_HEIGHT = 1337.0;
+ private static final double FIX_RIDE_HEIGHT2 = 1338.0;
+ protected final MockSmartphoneImeiServiceFinderFactory serviceFinderFactory = new MockSmartphoneImeiServiceFinderFactory();
+ protected final DeviceIdentifier device = new SmartphoneImeiIdentifier("a");
+ protected final DeviceIdentifier device2 = new SmartphoneImeiIdentifier("b");
+ protected SensorFixStore store;
+
+ protected GPSFixMoving createFix(long millis, double lat, double lng, double knots, double degrees) {
+ return new GPSFixMovingImpl(new DegreePosition(lat, lng), new MillisecondsTimePoint(millis),
+ new KnotSpeedWithBearingImpl(knots, new DegreeBearingImpl(degrees)));
+ }
+
+ @Before
+ public void setUp() throws UnknownHostException, MongoException {
+ dropPersistedData();
+ newStore();
+
+ }
+
+ private void newStore() {
+ store = new MongoSensorFixStoreImpl(PersistenceFactory.INSTANCE.getDefaultMongoObjectFactory(),
+ PersistenceFactory.INSTANCE.getDefaultDomainObjectFactory(), serviceFinderFactory);
+ }
+
+ @After
+ public void after() {
+ dropPersistedData();
+ }
+
+ private void dropPersistedData() {
+ DB db = PersistenceFactory.INSTANCE.getDefaultMongoObjectFactory().getDatabase();
+ db.getCollection(CollectionNames.GPS_FIXES.name()).drop();
+ db.getCollection(CollectionNames.GPS_FIXES_METADATA.name()).drop();
+ }
+
+ @Test
+ public void testFixIsPersisted() throws Exception {
+ addBravoFix(device, FIX_TIMESTAMP, FIX_RIDE_HEIGHT);
+ assertEquals(1, store.getNumberOfFixes(device));
+ }
+
+ /**
+ * Ensures that no local caching in the store makes in seem to work but data will be lost on restart of the system.
+ */
+ @Test
+ public void testFixIsFoundByOtherStoreInstance() throws Exception {
+ addBravoFix(device, FIX_TIMESTAMP, FIX_RIDE_HEIGHT);
+
+ newStore();
+ assertEquals(1, store.getNumberOfFixes(device));
+ }
+
+ @Test
+ public void testFixDataIsPreservedOnStore() throws Exception {
+ DoubleVectorFix fix = addBravoFix(device, FIX_TIMESTAMP, FIX_RIDE_HEIGHT);
+
+ verifySingleFix(fix, 100, 200, device, true);
+ }
+
+ @Test
+ public void testFixWithinExclusiveBoundsIsLoaded() throws Exception {
+ DoubleVectorFix fix = addBravoFix(device, FIX_TIMESTAMP, FIX_RIDE_HEIGHT);
+ verifySingleFix(fix, FIX_TIMESTAMP - 1, FIX_TIMESTAMP + 1, device, false);
+ }
+
+ @Test
+ public void testFixOnInclusiveLowerBoundIsLoaded() throws Exception {
+ DoubleVectorFix fix = addBravoFix(device, FIX_TIMESTAMP, FIX_RIDE_HEIGHT);
+ verifySingleFix(fix, FIX_TIMESTAMP, FIX_TIMESTAMP + 1, device, true);
+ }
+
+ @Test
+ public void testFixOnInclusiveUpperBoundIsLoaded() throws Exception {
+ DoubleVectorFix fix = addBravoFix(device, FIX_TIMESTAMP, FIX_RIDE_HEIGHT);
+ verifySingleFix(fix, FIX_TIMESTAMP-1, FIX_TIMESTAMP, device, true);
+ }
+
+ @Test
+ public void testFixesOnExclusiveBoundsArentLoaded() throws Exception {
+ addBravoFix(device, FIX_TIMESTAMP, FIX_RIDE_HEIGHT);
+ addBravoFix(device, FIX_TIMESTAMP2, FIX_RIDE_HEIGHT2);
+ verifyNoFix(FIX_TIMESTAMP, FIX_TIMESTAMP2, device, false);
+ }
+
+ @Test
+ public void testSingleListenerIsNotified() throws Exception {
+ FixReceivedListener listener = mockFixReceivedListener();
+ store.addListener(listener, device);
+ DoubleVectorFix doubleVectorFix = addBravoFix(device, FIX_TIMESTAMP, FIX_RIDE_HEIGHT);
+
+ Mockito.verify(listener, Mockito.times(1)).fixReceived(device, doubleVectorFix);
+ }
+
+ @Test
+ public void testMultipleListenersForSameDeviceAreNotified() throws Exception {
+ FixReceivedListener listener1 = mockFixReceivedListener();
+ FixReceivedListener listener2 = mockFixReceivedListener();
+ store.addListener(listener1, device);
+ store.addListener(listener2, device);
+ DoubleVectorFix doubleVectorFix = addBravoFix(device, FIX_TIMESTAMP, FIX_RIDE_HEIGHT);
+
+ Mockito.verify(listener1, Mockito.times(1)).fixReceived(device, doubleVectorFix);
+ Mockito.verify(listener2, Mockito.times(1)).fixReceived(device, doubleVectorFix);
+ }
+
+ @Test
+ public void testListenerForNonMatchingDeviceIsNotNotified() throws Exception {
+ FixReceivedListener listener = mockFixReceivedListener();
+ store.addListener(listener, device);
+ addBravoFix(device2, FIX_TIMESTAMP, FIX_RIDE_HEIGHT);
+
+ Mockito.verifyZeroInteractions(listener);
+ }
+
+ @Test
+ public void testOnlyListenerForMatchingDeviceIsNotified() throws Exception {
+ FixReceivedListener listener1 = mockFixReceivedListener();
+ FixReceivedListener listener2 = mockFixReceivedListener();
+ store.addListener(listener1, device);
+ store.addListener(listener2, device2);
+ DoubleVectorFix doubleVectorFix = addBravoFix(device, FIX_TIMESTAMP, FIX_RIDE_HEIGHT);
+
+ Mockito.verify(listener1, Mockito.times(1)).fixReceived(device, doubleVectorFix);
+ Mockito.verifyZeroInteractions(listener2);
+ }
+
+ @Test
+ public void testListenerForMatchingDeviceIsNotifiedMultipleTimes() throws Exception {
+ FixReceivedListener listener = mockFixReceivedListener();
+ store.addListener(listener, device);
+ DoubleVectorFix doubleVectorFix1 = addBravoFix(device, FIX_TIMESTAMP, FIX_RIDE_HEIGHT);
+ DoubleVectorFix doubleVectorFix2 = addBravoFix(device, FIX_TIMESTAMP2, FIX_RIDE_HEIGHT2);
+
+ Mockito.verify(listener, Mockito.times(1)).fixReceived(device, doubleVectorFix1);
+ Mockito.verify(listener, Mockito.times(1)).fixReceived(device, doubleVectorFix2);
+ Mockito.verifyNoMoreInteractions(listener);
+ }
+
+ @Test
+ public void testAfterRemoveListenerIsNotNotified() throws Exception {
+ FixReceivedListener listener = mockFixReceivedListener();
+ store.addListener(listener, device);
+ store.removeListener(listener);
+ addBravoFix(device, FIX_TIMESTAMP, FIX_RIDE_HEIGHT);
+ Mockito.verifyZeroInteractions(listener);
+ }
+
+ @Test
+ public void testAfterRemoveForDeviceListenerIsNotNotified() throws Exception {
+ FixReceivedListener listener = mockFixReceivedListener();
+ store.addListener(listener, device);
+ store.removeListener(listener, device);
+ addBravoFix(device, FIX_TIMESTAMP, FIX_RIDE_HEIGHT);
+ Mockito.verifyZeroInteractions(listener);
+ }
+
+ @Test
+ public void testAfterRemoveForNonMatchingDeviceListenerIsNotified() throws Exception {
+ FixReceivedListener listener = mockFixReceivedListener();
+ store.addListener(listener, device);
+ store.removeListener(listener, device2);
+ DoubleVectorFix doubleVectorFix = addBravoFix(device, FIX_TIMESTAMP, FIX_RIDE_HEIGHT);
+ Mockito.verify(listener, Mockito.times(1)).fixReceived(device, doubleVectorFix);
+ }
+
+ @Test
+ public void testListenerForMultipleDevicesIsNotifiedMultipleTimes() throws Exception {
+ FixReceivedListener listener = mockFixReceivedListener();
+ store.addListener(listener, device);
+ store.addListener(listener, device2);
+ DoubleVectorFix doubleVectorFix1 = addBravoFix(device, FIX_TIMESTAMP, FIX_RIDE_HEIGHT);
+ DoubleVectorFix doubleVectorFix2 = addBravoFix(device2, FIX_TIMESTAMP2, FIX_RIDE_HEIGHT2);
+ Mockito.verify(listener, Mockito.times(1)).fixReceived(device, doubleVectorFix1);
+ Mockito.verify(listener, Mockito.times(1)).fixReceived(device2, doubleVectorFix2);
+ Mockito.verifyNoMoreInteractions(listener);
+ }
+
+ @Test
+ public void testAfterRemoveListenerForMultipleDevicesIsNotNotified() throws Exception {
+ FixReceivedListener listener = mockFixReceivedListener();
+ store.addListener(listener, device);
+ store.addListener(listener, device2);
+ store.removeListener(listener);
+ addBravoFix(device, FIX_TIMESTAMP, FIX_RIDE_HEIGHT);
+ addBravoFix(device2, FIX_TIMESTAMP2, FIX_RIDE_HEIGHT2);
+ Mockito.verifyZeroInteractions(listener);
+ }
+
+ @Test
+ public void testAfterRemoveListenerForOneDevicesIsNotifiedForOtherDevice() throws Exception {
+ FixReceivedListener listener = mockFixReceivedListener();
+ store.addListener(listener, device);
+ store.addListener(listener, device2);
+ store.removeListener(listener, device);
+ addBravoFix(device, FIX_TIMESTAMP, FIX_RIDE_HEIGHT);
+ DoubleVectorFix doubleVectorFix2 = addBravoFix(device2, FIX_TIMESTAMP2, FIX_RIDE_HEIGHT2);
+ Mockito.verify(listener, Mockito.times(1)).fixReceived(device2, doubleVectorFix2);
+ Mockito.verifyNoMoreInteractions(listener);
+ }
+
+ @Test
+ public void testAfterRemoveListenerOtherListenerIsNotified() throws Exception {
+ FixReceivedListener listener1 = mockFixReceivedListener();
+ FixReceivedListener listener2 = mockFixReceivedListener();
+ store.addListener(listener1, device);
+ store.addListener(listener2, device);
+ store.removeListener(listener1);
+ DoubleVectorFix doubleVectorFix = addBravoFix(device, FIX_TIMESTAMP, FIX_RIDE_HEIGHT);
+ Mockito.verify(listener2, Mockito.times(1)).fixReceived(device, doubleVectorFix);
+ Mockito.verifyNoMoreInteractions(listener2);
+ Mockito.verifyZeroInteractions(listener1);
+ }
+
+ @Test
+ public void testAfterRemoveForDeviceListenerOtherListenerIsNotified() throws Exception {
+ FixReceivedListener listener1 = mockFixReceivedListener();
+ FixReceivedListener listener2 = mockFixReceivedListener();
+ store.addListener(listener1, device);
+ store.addListener(listener2, device);
+ store.removeListener(listener1, device);
+ DoubleVectorFix doubleVectorFix = addBravoFix(device, FIX_TIMESTAMP, FIX_RIDE_HEIGHT);
+ Mockito.verify(listener2, Mockito.times(1)).fixReceived(device, doubleVectorFix);
+ Mockito.verifyNoMoreInteractions(listener2);
+ Mockito.verifyZeroInteractions(listener1);
+ }
+
+ @SuppressWarnings("unchecked")
+ private FixReceivedListener mockFixReceivedListener() {
+ FixReceivedListener listener = Mockito.mock(FixReceivedListener.class);
+ return listener;
+ }
+
+ private void verifySingleFix(Timed expectedFix, long start, long end, DeviceIdentifier device, boolean inclusive) throws Exception {
+ List loadedFixes = loadFixes(start, end, device, inclusive);
+ assertEquals(1, loadedFixes.size());
+ assertEquals(expectedFix, loadedFixes.get(0));
+ }
+
+ private void verifyNoFix(long start, long end, DeviceIdentifier device, boolean inclusive) throws Exception {
+ List loadedFixes = loadFixes(start, end, device, inclusive);
+ assertEquals(0, loadedFixes.size());
+ }
+
+ private List loadFixes(long start, long end, DeviceIdentifier device, boolean inclusive)
+ throws TransformationException {
+ List loadedFixes = new ArrayList<>();
+
+ store.loadFixes(loadedFixes::add, device, new MillisecondsTimePoint(start), new MillisecondsTimePoint(end), inclusive);
+ return loadedFixes;
+ }
+
+ private DoubleVectorFix addBravoFix(DeviceIdentifier device, long timestamp, double rideHeight) {
+ DoubleVectorFix fix = createBravoDoubleVectorFixWithRideHeight(timestamp, rideHeight);
+ store.storeFix(device, fix);
+ return fix;
+ }
+
+ private DoubleVectorFix createBravoDoubleVectorFixWithRideHeight(long timestamp, double rideHeight) {
+ double[] fixData = new double[BravoSensorDataMetadata.INSTANCE.columnCount];
+ fixData[BravoSensorDataMetadata.INSTANCE.rideHeightColumn] = rideHeight;
+ return new DoubleVectorFixImpl(new MillisecondsTimePoint(timestamp), fixData);
+ }
+}
diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/src/com/sap/sailing/domain/racelog/tracking/test/mock/MockGPSFixMongoServiceFinder.java b/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/src/com/sap/sailing/domain/racelog/tracking/test/mock/MockGPSFixMongoServiceFinder.java
index f7cc5ebfb8f..587b128c16d 100644
--- a/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/src/com/sap/sailing/domain/racelog/tracking/test/mock/MockGPSFixMongoServiceFinder.java
+++ b/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/src/com/sap/sailing/domain/racelog/tracking/test/mock/MockGPSFixMongoServiceFinder.java
@@ -6,11 +6,13 @@ import java.util.Set;
import com.sap.sailing.domain.common.tracking.impl.CompactGPSFixImpl;
import com.sap.sailing.domain.common.tracking.impl.CompactGPSFixMovingImpl;
+import com.sap.sailing.domain.common.tracking.impl.DoubleVectorFixImpl;
import com.sap.sailing.domain.common.tracking.impl.GPSFixImpl;
import com.sap.sailing.domain.common.tracking.impl.GPSFixMovingImpl;
import com.sap.sailing.domain.persistence.impl.DomainObjectFactoryImpl;
import com.sap.sailing.domain.persistence.impl.MongoObjectFactoryImpl;
import com.sap.sailing.domain.persistence.racelog.tracking.FixMongoHandler;
+import com.sap.sailing.domain.persistence.racelog.tracking.impl.DoubleVectorFixMongoHandlerImpl;
import com.sap.sailing.domain.persistence.racelog.tracking.impl.GPSFixMongoHandlerImpl;
import com.sap.sailing.domain.persistence.racelog.tracking.impl.GPSFixMovingMongoHandlerImpl;
import com.sap.sse.common.TypeBasedServiceFinder;
@@ -20,6 +22,7 @@ public class MockGPSFixMongoServiceFinder implements TypeBasedServiceFinder movingHandler = new GPSFixMovingMongoHandlerImpl(mof, dof);
private final FixMongoHandler> handler = new GPSFixMongoHandlerImpl(mof, dof);
+ private final FixMongoHandler> doubleVectorFixHandler = new DoubleVectorFixMongoHandlerImpl(mof, dof);
@Override
public FixMongoHandler> findService(String fixType) {
@@ -27,6 +30,7 @@ public class MockGPSFixMongoServiceFinder implements TypeBasedServiceFinder mapping) {
+ if(timeRangeToLoad == null) {
+ return;
+ }
if (preemptiveStopRequested.get()) {
return;
}
@@ -194,8 +197,8 @@ public class FixLoaderAndTracker implements TrackingDataLoader {
try {
@SuppressWarnings({ "unchecked" })
DeviceMapping competitorMapping = (DeviceMapping) mapping;
- gpsFixStore.loadCompetitorTrack(track, competitorMapping, getStartOfTracking(),
- getEndOfTracking());
+ gpsFixStore.loadCompetitorTrack(track, competitorMapping, timeRangeToLoad.from(),
+ timeRangeToLoad.to());
} catch (TransformationException | NoCorrespondingServiceRegisteredException e) {
logger.log(Level.WARNING, "Could not load competitor track " + mapping.getMappedTo());
}
@@ -208,12 +211,10 @@ public class FixLoaderAndTracker implements TrackingDataLoader {
try {
@SuppressWarnings("unchecked")
DeviceMapping markMapping = (DeviceMapping) mapping;
- TimePoint from = getStartOfTracking();
- TimePoint to = getEndOfTracking();
- gpsFixStore.loadMarkTrack(track, markMapping, from, to);
+ gpsFixStore.loadMarkTrack(track, markMapping, timeRangeToLoad.from(), timeRangeToLoad.to());
if (track.getFirstRawFix() == null) {
- logger.fine("Loading mark positions from outside of start/end of tracking interval (" + from
- + ".." + to + ") because no fixes were found in that interval");
+ logger.fine("Loading mark positions from outside of start/end of tracking interval (" + timeRangeToLoad.from()
+ + ".." + timeRangeToLoad.to() + ") because no fixes were found in that interval");
// got an empty track for the mark; try again without constraining the mapping interval
// by start/end of tracking to at least attempt to get fixes at all in case there were any
// within the device mapping interval specified
@@ -264,12 +265,10 @@ public class FixLoaderAndTracker implements TrackingDataLoader {
}
}
- // TEMP: kommt von RaceLogSensorFixTracker
protected TimePoint getStartOfTracking() {
return trackedRace.getStartOfTracking();
}
- // TEMP: kommt von RaceLogSensorFixTracker
protected TimePoint getEndOfTracking() {
return trackedRace.getEndOfTracking();
}
@@ -277,10 +276,7 @@ public class FixLoaderAndTracker implements TrackingDataLoader {
protected void loadFixesForExtendedTimeRange(TimePoint loadFixesFrom, TimePoint loadFixesTo) {
final TimeRangeImpl extendedTimeRange = new TimeRangeImpl(loadFixesFrom, loadFixesTo);
deviceMappings.forEachMapping((mapping) -> {
- TimeRange timeRangeToLoad = extendedTimeRange.intersection(mapping.getTimeRange());
- if (timeRangeToLoad != null && !preemptiveStopRequested.get()) {
- loadFixes(timeRangeToLoad, mapping);
- }
+ loadFixes(extendedTimeRange.intersection(mapping.getTimeRange()), mapping);
});
}
@@ -292,7 +288,6 @@ public class FixLoaderAndTracker implements TrackingDataLoader {
* @param updateCallback
* the {@link Runnable} callback used to run the update
*/
- // TODO Consider using a thread pool here, after merging bug3864 into master
private void updateAsyncInternal(final Runnable updateCallback) {
synchronized (FixLoaderAndTracker.this) {
activeLoaders.incrementAndGet();
@@ -301,9 +296,9 @@ public class FixLoaderAndTracker implements TrackingDataLoader {
ThreadPoolUtil.INSTANCE.getDefaultForegroundTaskThreadPoolExecutor().execute(new Runnable() {
@Override
public void run() {
+ trackedRace.lockForSerializationRead();
try {
if (!preemptiveStopRequested.get()) {
- trackedRace.lockForSerializationRead();
setStatusAndProgress(TrackedRaceStatusEnum.LOADING, 0.5);
synchronized (FixLoaderAndTracker.this) {
FixLoaderAndTracker.this.notifyAll();
@@ -311,15 +306,18 @@ public class FixLoaderAndTracker implements TrackingDataLoader {
updateCallback.run();
}
} finally {
- synchronized (FixLoaderAndTracker.this) {
- int currentActiveLoaders = activeLoaders.decrementAndGet();
- FixLoaderAndTracker.this.notifyAll();
- if (currentActiveLoaders == 0) {
- setStatusAndProgress(stopRequested.get() ? TrackedRaceStatusEnum.FINISHED
- : TrackedRaceStatusEnum.TRACKING, 1.0);
+ try {
+ synchronized (FixLoaderAndTracker.this) {
+ int currentActiveLoaders = activeLoaders.decrementAndGet();
+ FixLoaderAndTracker.this.notifyAll();
+ if (currentActiveLoaders == 0) {
+ setStatusAndProgress(stopRequested.get() ? TrackedRaceStatusEnum.FINISHED
+ : TrackedRaceStatusEnum.TRACKING, 1.0);
+ }
}
+ } finally {
+ trackedRace.unlockAfterSerializationRead();
}
- trackedRace.unlockAfterSerializationRead();
}
}
});
@@ -350,7 +348,7 @@ public class FixLoaderAndTracker implements TrackingDataLoader {
@Override
protected void mappingRemoved(DeviceMappingWithRegattaLogEvent mapping) {
final DeviceIdentifier device = mapping.getDevice();
- if(!hasMappingForDevice(device)) {
+ if (!hasMappingForDevice(device)) {
sensorFixStore.removeListener(listener, device);
}
// TODO if tracks are always associated to only one device mapping, we could remove tracks here
@@ -366,8 +364,22 @@ public class FixLoaderAndTracker implements TrackingDataLoader {
@Override
protected void mappingChanged(DeviceMappingWithRegattaLogEvent oldMapping,
DeviceMappingWithRegattaLogEvent newMapping) {
- // TODO can the new time range be bigger than the old one? In this case we would need to load the
- // additional time range.
+ final TimeRange newTimeRange = newMapping.getTimeRange();
+ final TimeRange oldTimeRange = oldMapping.getTimeRange();
+ if(newTimeRange.endsAfter(oldTimeRange)) {
+ final TimePoint oldTo= oldTimeRange.to();
+ final TimePoint newFrom = newTimeRange.from();
+ final TimePoint from = oldTo.after(newFrom) ? oldTo : newFrom;
+ final TimeRange rangeToLoad = new TimeRangeImpl(from, newTimeRange.to());
+ loadFixes(rangeToLoad, newMapping);
+ }
+ if(newTimeRange.startsBefore(oldTimeRange)) {
+ final TimePoint oldFrom= oldTimeRange.from();
+ final TimePoint newTo = newTimeRange.to();
+ final TimePoint to = oldFrom.after(newTo) ? oldFrom : newTo;
+ final TimeRange rangeToLoad = new TimeRangeImpl(newTimeRange.from(), to);
+ loadFixes(rangeToLoad, newMapping);
+ }
}
}
}
diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/fixtracker/RaceLogFixTrackerManager.java b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/fixtracker/RaceLogFixTrackerManager.java
index 5efe9c08bf4..d0edf58d240 100644
--- a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/fixtracker/RaceLogFixTrackerManager.java
+++ b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/fixtracker/RaceLogFixTrackerManager.java
@@ -120,7 +120,7 @@ public class RaceLogFixTrackerManager implements TrackingDataLoader {
stopTrackerIfStillRunning(preemptive);
trackedRace.removeListener(raceChangeListener);
synchronized (knownRaceLogs) {
- for (RaceLog raceLog : trackedRace.getAttachedRaceLogs()) {
+ for (RaceLog raceLog : knownRaceLogs) {
removeRaceLogUnlocked(raceLog);
}
}
@@ -142,10 +142,12 @@ public class RaceLogFixTrackerManager implements TrackingDataLoader {
}
boolean isForTracking() {
- for (RaceLog raceLog : trackedRace.getAttachedRaceLogs()) {
- RaceLogTrackingState raceLogTrackingState = new RaceLogTrackingStateAnalyzer(raceLog).analyze();
- if (raceLogTrackingState.isForTracking()) {
- return true;
+ synchronized (knownRaceLogs) {
+ for (RaceLog raceLog : knownRaceLogs) {
+ RaceLogTrackingState raceLogTrackingState = new RaceLogTrackingStateAnalyzer(raceLog).analyze();
+ if (raceLogTrackingState.isForTracking()) {
+ return true;
+ }
}
}
return false;
diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/fixtracker/RegattaLogDeviceMappings.java b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/fixtracker/RegattaLogDeviceMappings.java
index a7b5e30b265..e61d9abd4a9 100644
--- a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/fixtracker/RegattaLogDeviceMappings.java
+++ b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/fixtracker/RegattaLogDeviceMappings.java
@@ -48,7 +48,7 @@ public abstract class RegattaLogDeviceMappings {
* sure to not produce a memory leak.
*/
private final Set knownRegattaLogs = new HashSet<>();
-
+
/**
* Lock object to be used when accessing {@link #knownRegattaLogs}.
*/
@@ -127,16 +127,16 @@ public abstract class RegattaLogDeviceMappings {
knownRegattaLogs.forEach((log) -> log.removeListener(regattaLogEventVisitor));
knownRegattaLogs.clear();
});
- }
+ }
protected void updateMappings() {
try {
- updateMappings(true);
+ updateMappingsInternal();
} catch (Exception e) {
logger.log(Level.WARNING, "Could not update device mappings", e);
}
}
-
+
/**
* Calls the given callback for every known mapping that's currently known.
*
@@ -163,11 +163,11 @@ public abstract class RegattaLogDeviceMappings {
public void forEachMapping(BiConsumer> callback) {
LockUtil.executeWithReadLock(mappingsLock, () -> {
for (Map.Entry>> entry : mappings.entrySet()) {
- ItemT item = entry.getKey();
- for (DeviceMappingWithRegattaLogEvent mapping : entry.getValue()) {
- callback.accept(item, mapping);
- }
+ ItemT item = entry.getKey();
+ for (DeviceMappingWithRegattaLogEvent mapping : entry.getValue()) {
+ callback.accept(item, mapping);
}
+ }
});
}
@@ -187,13 +187,13 @@ public abstract class RegattaLogDeviceMappings {
Consumer> callback) {
LockUtil.executeWithReadLock(mappingsLock, () -> {
List> mappingsForDevice = mappingsByDevice.get(device);
- if (mappingsForDevice != null) {
- for (DeviceMappingWithRegattaLogEvent mapping : mappingsForDevice) {
- if (mapping.getTimeRange().includes(timePoint)) {
- callback.accept(mapping);
- }
+ if (mappingsForDevice != null) {
+ for (DeviceMappingWithRegattaLogEvent mapping : mappingsForDevice) {
+ if (mapping.getTimeRange().includes(timePoint)) {
+ callback.accept(mapping);
}
}
+ }
});
}
@@ -236,9 +236,7 @@ public abstract class RegattaLogDeviceMappings {
*
* @throws DoesNotHaveRegattaLogException
*/
- private final > void updateMappings(boolean loadIfNotCovered) {
- // TODO remove fixes, if mappings have been removed
- // check if there are new time ranges not covered so far
+ private final > void updateMappingsInternal() {
final Map>> newMappings = calculateMappings();
final Map>> oldMappings = new HashMap<>();
LockUtil.lockForWrite(mappingsLock);
@@ -261,18 +259,20 @@ public abstract class RegattaLogDeviceMappings {
} finally {
LockUtil.unlockAfterWrite(mappingsLock);
}
- if (loadIfNotCovered) {
- calculateDiff(oldMappings, newMappings);
- }
+ calculateDiff(oldMappings, newMappings);
}
+ /**
+ * Calculates and applies the mapping changes by removing listeners no longer needed for the mappings removed,
+ * and by loading and adding the fixes for extended or added mappings.
+ */
private void calculateDiff(Map>> previousMappings,
Map>> newMappings) {
Set itemsToProcess = new HashSet(previousMappings.keySet());
itemsToProcess.addAll(newMappings.keySet());
- for(ItemT item : itemsToProcess) {
- if(!newMappings.containsKey(item)) {
- previousMappings.get(item).forEach(this::mappingRemoved);
+ for (ItemT item : itemsToProcess) {
+ if (!newMappings.containsKey(item)) {
+ previousMappings.get(item).forEach(this::mappingRemovedInternal);
} else {
final List> oldMappings = previousMappings.containsKey(item)
? previousMappings.get(item) : Collections.emptyList();
@@ -281,22 +281,30 @@ public abstract class RegattaLogDeviceMappings {
DeviceMappingWithRegattaLogEvent oldMapping = findAndRemoveMapping(newMapping,
oldMappings);
if (oldMapping == null) {
- mappingAdded(newMapping);
- } else if (newMapping.getTimeRange().equals(oldMapping.getTimeRange())) {
- mappingChanged(oldMapping, newMapping);
+ mappingAddedInternal(newMapping);
+ } else if (!newMapping.getTimeRange().equals(oldMapping.getTimeRange())) {
+ mappingChangedInternal(oldMapping, newMapping);
}
}
- oldMappings.forEach(this::mappingRemoved);
+ oldMappings.forEach(this::mappingRemovedInternal);
}
}
}
-
+
/**
* Called when a {@link DeviceMapping} was removed.
*
* @param mapping the removed mapping
*/
protected abstract void mappingRemoved(DeviceMappingWithRegattaLogEvent mapping);
+
+ private void mappingRemovedInternal(DeviceMappingWithRegattaLogEvent mapping) {
+ try {
+ mappingRemoved(mapping);
+ } catch(Exception e) {
+ logger.log(Level.SEVERE, "error while removing mapping " + mapping, e);
+ }
+ }
/**
* Called when a {@link DeviceMapping} was added.
@@ -304,6 +312,14 @@ public abstract class RegattaLogDeviceMappings {
* @param mapping the new mapping
*/
protected abstract void mappingAdded(DeviceMappingWithRegattaLogEvent mapping);
+
+ private void mappingAddedInternal(DeviceMappingWithRegattaLogEvent mapping) {
+ try {
+ mappingAdded(mapping);
+ } catch(Exception e) {
+ logger.log(Level.SEVERE, "error while adding mapping " + mapping, e);
+ }
+ }
/**
* Called when a {@link DeviceMapping} was changed regarding its mapped time range.
@@ -315,6 +331,15 @@ public abstract class RegattaLogDeviceMappings {
protected abstract void mappingChanged(DeviceMappingWithRegattaLogEvent oldMapping,
DeviceMappingWithRegattaLogEvent newMapping);
+ private void mappingChangedInternal(DeviceMappingWithRegattaLogEvent oldMapping,
+ DeviceMappingWithRegattaLogEvent newMapping) {
+ try {
+ mappingChanged(oldMapping, newMapping);
+ } catch(Exception e) {
+ logger.log(Level.SEVERE, "error while changing mapping old: " + oldMapping + "; new: " + newMapping, e);
+ }
+ }
+
private DeviceMappingWithRegattaLogEvent findAndRemoveMapping(
DeviceMappingWithRegattaLogEvent mappingToFind,
List> newItemsToProcess) {
diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/regatta/tracking/analyzing/impl/BaseRegattaLogDeviceMappingFinder.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/regatta/tracking/analyzing/impl/BaseRegattaLogDeviceMappingFinder.java
index 0399e83c387..76774039688 100644
--- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/regatta/tracking/analyzing/impl/BaseRegattaLogDeviceMappingFinder.java
+++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/regatta/tracking/analyzing/impl/BaseRegattaLogDeviceMappingFinder.java
@@ -65,7 +65,7 @@ public abstract class BaseRegattaLogDeviceMappingFinder
if (!endOfFirstHalf.before(from)) {
log.add(createDeviceMappingEvent(item, event.getAuthor(), from, endOfFirstHalf, event.getDevice()));
}
- if (!to.before(startOfSecondHalf)) {
+ if (to == null || !to.before(startOfSecondHalf)) {
log.add(createDeviceMappingEvent(item, event.getAuthor(), startOfSecondHalf, to,
event.getDevice()));
}
diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/WaypointImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/WaypointImpl.java
index 7c262b59976..bb16d391cba 100644
--- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/WaypointImpl.java
+++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/WaypointImpl.java
@@ -46,7 +46,7 @@ public class WaypointImpl implements Waypoint {
@Override
public String toString() {
- return getName();
+ return (getName()==null?"":getName()) + " ("+getPassingInstructions()+")";
}
@Override
diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/impl/TrackImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/impl/TrackImpl.java
index b2ad7e825b0..113fa50e5ae 100644
--- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/impl/TrackImpl.java
+++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/impl/TrackImpl.java
@@ -107,12 +107,12 @@ public class TrackImpl implements Track {
}
/**
- * Callers that want to iterate over the collection returned need to synchronize on this object to
- * avoid {@link ConcurrentModificationException}s.
+ * Callers that want to iterate over the collection returned need to use {@link #lockForRead()} and
+ * {@link #unlockAfterRead()} to avoid {@link ConcurrentModificationException}s.
*
- * @return the smoothened fixes ordered by their time points; this implementation simply delegates to {@link #getInternalRawFixes()} because for
- * only {@link Timed} fixes we can't know how to remove outliers. Subclasses that constrain the
- * FixType may provide smoothening implementations.
+ * @return the smoothened fixes ordered by their time points; this implementation simply delegates to
+ * {@link #getInternalRawFixes()} because for only {@link Timed} fixes we can't know how to remove outliers.
+ * Subclasses that constrain the FixType may provide smoothening implementations.
*/
protected NavigableSet getInternalFixes() {
NavigableSet result = getInternalRawFixes();
diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/ConcurrencyTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/ConcurrencyTest.java
index 0204088e717..e7d08ff101a 100755
--- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/ConcurrencyTest.java
+++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/ConcurrencyTest.java
@@ -10,7 +10,6 @@ import java.net.URI;
import java.net.URISyntaxException;
import java.text.ParseException;
import java.util.List;
-import java.util.concurrent.Executors;
import java.util.logging.Logger;
import org.junit.Before;
@@ -30,6 +29,7 @@ import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tractracadapter.ReceiverType;
import com.sap.sailing.domain.tractracadapter.TracTracConnectionConstants;
import com.sap.sse.common.impl.MillisecondsTimePoint;
+import com.sap.sse.util.ThreadPoolUtil;
import com.tractrac.model.lib.api.event.CreateModelException;
import com.tractrac.subscription.lib.api.SubscriberInitializationException;
@@ -77,7 +77,7 @@ public class ConcurrencyTest extends OnlineTracTracBasedTest {
logger.info("1 thread: "+duration+"ns");
logger.info("number of approximation points: "+approximation1.size());
- dp = new DouglasPeucker(teamAquaTrack, Executors.newFixedThreadPool(numberOfCPUs));
+ dp = new DouglasPeucker(teamAquaTrack, ThreadPoolUtil.INSTANCE.getDefaultBackgroundTaskThreadPoolExecutor());
start = System.nanoTime();
List approximation2 = dp.approximate(new MeterDistance(3), firstMarkPassing.getTimePoint(), lastMarkPassing.getTimePoint());
duration = System.nanoTime()-start;
diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/RegattaLogAttachOnTrackedRaceTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/RegattaLogAttachOnTrackedRaceTest.java
new file mode 100644
index 00000000000..b9db265ecec
--- /dev/null
+++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/RegattaLogAttachOnTrackedRaceTest.java
@@ -0,0 +1,80 @@
+package com.sap.sailing.domain.test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.sap.sailing.domain.abstractlog.regatta.RegattaLog;
+import com.sap.sailing.domain.abstractlog.regatta.impl.RegattaLogImpl;
+import com.sap.sailing.domain.base.Fleet;
+import com.sap.sailing.domain.base.RaceColumn;
+import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard;
+import com.sap.sailing.domain.leaderboard.impl.FlexibleLeaderboardImpl;
+import com.sap.sailing.domain.leaderboard.impl.LowPoint;
+import com.sap.sailing.domain.racelog.impl.EmptyRaceLogStore;
+import com.sap.sailing.domain.regattalike.RegattaLikeIdentifier;
+import com.sap.sailing.domain.regattalog.RegattaLogStore;
+import com.sap.sailing.domain.test.mock.MockedTrackedRace;
+import com.sap.sailing.domain.tracking.TrackedRace;
+import com.sap.sse.common.Util;
+
+public class RegattaLogAttachOnTrackedRaceTest {
+
+ private class MyMockedRegattaLogStore implements RegattaLogStore {
+ private Map regattaLogs = new HashMap<>();
+ @Override
+ public RegattaLog getRegattaLog(RegattaLikeIdentifier regattaLikeId, boolean ignoreCache) {
+ if(!regattaLogs.containsKey(regattaLikeId)) {
+ regattaLogs.put(regattaLikeId, new RegattaLogImpl(regattaLikeId));
+ }
+ return regattaLogs.get(regattaLikeId);
+ }
+
+ @Override
+ public void removeRegattaLog(RegattaLikeIdentifier regattaLikeId) {
+ }
+
+ @Override
+ public void addImportedRegattaLog(RegattaLog regattaLog, RegattaLikeIdentifier identifier) {
+ }
+ }
+
+ private class MyMockedTrackedRace extends MockedTrackedRace {
+ private static final long serialVersionUID = -5450144964435682919L;
+
+ private RegattaLog regattaLog;
+ @Override
+ public void attachRegattaLog(RegattaLog regattaLog) {
+ this.regattaLog = regattaLog;
+ }
+
+ @Override
+ public Iterable getAttachedRegattaLogs() {
+ return Collections.singletonList(regattaLog);
+ }
+ }
+
+ private FlexibleLeaderboard leaderboard;
+
+ @Before
+ public void setUp() {
+ leaderboard = new FlexibleLeaderboardImpl(EmptyRaceLogStore.INSTANCE, new MyMockedRegattaLogStore(), "", null, new LowPoint(), null);
+ }
+
+ @Test
+ public void testAttachRegattaLogIsCalledWhenTrackedRaceIsSetToARaceColumn() {
+ RaceColumn column = leaderboard.addRaceColumn("R1", false);
+ TrackedRace trackedRace = new MyMockedTrackedRace();
+ Fleet defaultFleet = Util.get(column.getFleets(), 0);
+ column.setTrackedRace(defaultFleet, trackedRace);
+ assertEquals(1, Util.size(trackedRace.getAttachedRegattaLogs()));
+ assertSame(leaderboard.getRegattaLog(), Util.get(trackedRace.getAttachedRegattaLogs(), 0));
+ }
+
+}
diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/tracking/impl/BravoFixTrackSerializationTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/tracking/impl/BravoFixTrackSerializationTest.java
index 546eb8e0925..2a0f19e5877 100644
--- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/tracking/impl/BravoFixTrackSerializationTest.java
+++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/tracking/impl/BravoFixTrackSerializationTest.java
@@ -116,7 +116,7 @@ public class BravoFixTrackSerializationTest {
}
private void addOrReplaceBravoFixToTrack(boolean replace) {
- double[] fixData = new double[BravoSensorDataMetadata.INSTANCE.getColumns().size()];
+ double[] fixData = new double[BravoSensorDataMetadata.INSTANCE.columnCount];
fixData[BravoSensorDataMetadata.INSTANCE.rideHeightColumn] = rideHeight;
track.add(new BravoFixImpl(new DoubleVectorFixImpl(timePoint, fixData)), replace);
}
diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/AbstractRaceExecutionOrderProvider.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/AbstractRaceExecutionOrderProvider.java
index 07da1de7bed..34caffcaf8d 100755
--- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/AbstractRaceExecutionOrderProvider.java
+++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/AbstractRaceExecutionOrderProvider.java
@@ -43,6 +43,10 @@ public abstract class AbstractRaceExecutionOrderProvider implements RaceExecutio
protected abstract Map> getRaceColumnsOfSeries();
+ /**
+ * For all tracked races found on any of the {@link #getRaceColumnsOfSeries() race columns} produces a valid and
+ * potentially empty set.
+ */
private Map> reloadAndGetPreviousRacesByRace() {
final Map> previousRacesByRace = new HashMap<>();
for (Entry> raceColumnsInSeries : getRaceColumnsOfSeries().entrySet()) {
@@ -96,11 +100,13 @@ public abstract class AbstractRaceExecutionOrderProvider implements RaceExecutio
@Override
public Set getPreviousRacesInExecutionOrder(TrackedRace race) {
- Set result = Collections.emptySet();
+ final Set result;
final Map> previousRacesByRace = getPreviousRacesByRace();
if (previousRacesByRace != null) {
result = previousRacesByRace.get(race);
- }
+ } else {
+ result = Collections.emptySet();
+ }
return result;
}
}
diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/DouglasPeucker.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/DouglasPeucker.java
index 9971762470e..efd8c9a53a8 100755
--- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/DouglasPeucker.java
+++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/DouglasPeucker.java
@@ -6,12 +6,9 @@ import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
-import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Executor;
import java.util.concurrent.Future;
-import java.util.concurrent.FutureTask;
-import java.util.concurrent.RunnableFuture;
+import java.util.concurrent.ScheduledExecutorService;
import com.sap.sailing.domain.common.Bearing;
import com.sap.sailing.domain.common.Distance;
@@ -31,7 +28,7 @@ import com.sap.sse.common.Util;
public class DouglasPeucker {
private final GPSFixTrack track;
- private final Executor executor;
+ private final ScheduledExecutorService executor;
public DouglasPeucker(GPSFixTrack track) {
this.track = track;
@@ -42,7 +39,7 @@ public class DouglasPeucker {
* Allows clients to specify a non-default, optionally multi-threaded executor that will be used to perform the
* high-effort computations.
*/
- public DouglasPeucker(GPSFixTrack track, Executor executor) {
+ public DouglasPeucker(GPSFixTrack track, ScheduledExecutorService executor) {
this.track = track;
this.executor = executor;
}
@@ -96,15 +93,8 @@ public class DouglasPeucker {
if (fix.getTimePoint().compareTo(to) > 0) {
break;
}
- RunnableFuture> crossTrackErrorFuture = new FutureTask>(
- new Callable>() {
- @Override
- public Util.Pair call() throws Exception {
- return new Util.Pair(fix, fix.getPosition().absoluteCrossTrackError(
- fromPosition, bearing));
- }
- });
- executor.execute(crossTrackErrorFuture);
+ Future> crossTrackErrorFuture = executor.submit(
+ ()->new Util.Pair(fix, fix.getPosition().absoluteCrossTrackError(fromPosition, bearing)));
crossTrackErrorFutures.add(crossTrackErrorFuture);
}
for (Future> crossTrackErrorFuture : crossTrackErrorFutures) {
diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/caching/LeaderboardDTOCache.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/caching/LeaderboardDTOCache.java
index a4c9e684d01..eb6d8f44056 100755
--- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/caching/LeaderboardDTOCache.java
+++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/caching/LeaderboardDTOCache.java
@@ -23,6 +23,7 @@ import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util;
import com.sap.sse.concurrent.LockUtil;
import com.sap.sse.concurrent.NamedReentrantReadWriteLock;
+import com.sap.sse.util.impl.FutureTaskWithTracingGet;
/**
* Caches the expensive to compute {@link LeaderboardDTO} results of a
@@ -140,15 +141,16 @@ public class LeaderboardDTOCache implements LeaderboardCache {
LockUtil.unlockAfterRead(leaderboardCacheLock);
}
if (future == null) {
- future = new FutureTask(new Callable() {
- @Override
- public LeaderboardDTO call() throws Exception {
- LeaderboardDTO result = leaderboard.computeDTO(adjustedTimePoint,
- namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails,
- waitForLatestAnalyses, trackedRegattaRegistry, baseDomainFactory, /* fillTotalPointsUncorrected */ false);
- return result;
- }
- });
+ future = new FutureTaskWithTracingGet(LeaderboardDTOCache.class.getName()+" for leaderboard "+leaderboard.getName(),
+ new Callable() {
+ @Override
+ public LeaderboardDTO call() throws Exception {
+ LeaderboardDTO result = leaderboard.computeDTO(adjustedTimePoint,
+ namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails,
+ waitForLatestAnalyses, trackedRegattaRegistry, baseDomainFactory, /* fillTotalPointsUncorrected */ false);
+ return result;
+ }
+ });
// The add(Leaderboard) method that the cache manager calls back on this class does nothing, so no synchronization required
this.leaderboardCacheManager.add(leaderboard); // ensure the leaderboard is tracked for changes to invalidate
LockUtil.lockForWrite(leaderboardCacheLock);
diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractSimpleLeaderboardImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractSimpleLeaderboardImpl.java
index a60ec26b586..083e5915d18 100644
--- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractSimpleLeaderboardImpl.java
+++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractSimpleLeaderboardImpl.java
@@ -21,7 +21,6 @@ import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
-import java.util.concurrent.FutureTask;
import java.util.concurrent.RunnableFuture;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -97,6 +96,7 @@ import com.sap.sse.common.impl.MillisecondsTimePoint;
import com.sap.sse.concurrent.LockUtil;
import com.sap.sse.concurrent.NamedReentrantReadWriteLock;
import com.sap.sse.util.ThreadPoolUtil;
+import com.sap.sse.util.impl.FutureTaskWithTracingGet;
/**
* Base implementation for various types of leaderboards. The {@link RaceColumnListener} implementation forwards events
@@ -311,7 +311,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
this.suppressedCompetitors = new HashSet();
this.suppressedCompetitorsLock = new NamedReentrantReadWriteLock("suppressedCompetitorsLock", /* fair */ false);
this.raceColumnListeners = new RaceColumnListeners();
- this.raceDetailsAtEndOfTrackingCache = new HashMap, RunnableFuture>();
+ this.raceDetailsAtEndOfTrackingCache = new HashMap<>();
initTransientFields();
}
@@ -330,7 +330,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
}
private void initTransientFields() {
- this.raceDetailsAtEndOfTrackingCache = new HashMap, RunnableFuture>();
+ this.raceDetailsAtEndOfTrackingCache = new HashMap<>();
this.cacheInvalidationListeners = new HashSet();
// When many updates are triggered in a short period of time by a single thread, ensure that the single thread
// providing the updates is not outperformed by all the re-calculations happening here. Leave at least one
@@ -1225,7 +1225,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
}
// Now create the race columns and, as a future task, set their competitorsFromBestToWorst, then wait for all these
// futures to finish:
- Map>> competitorsFromBestToWorstTasks = new HashMap<>();
+ Map>> competitorsFromBestToWorstTasks = new HashMap<>();
for (final RaceColumn raceColumn : this.getRaceColumns()) {
boolean isMetaLeaderboardColumn = raceColumn instanceof MetaLeaderboardColumn;
RaceColumnDTO raceColumnDTO = result.createEmptyRaceColumn(raceColumn.getName(), raceColumn.isMedalRace(),
@@ -1253,13 +1253,12 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getSeries().getName() : null,
fleetDTO, raceColumn.isMedalRace(), raceIdentifier, race, isMetaLeaderboardColumn);
}
- FutureTask> task = new FutureTask>(
+ Future> task = executor.submit(
() -> baseDomainFactory.getCompetitorDTOList(AbstractSimpleLeaderboardImpl.this.getCompetitorsFromBestToWorst(raceColumn, timePoint)));
- executor.execute(task);
competitorsFromBestToWorstTasks.put(raceColumn, task);
}
// wait for the competitor orderings to have been computed for all race columns before continuing; subsequent tasks may depend on these data
- for (Map.Entry>> raceColumnAndTaskToJoin : competitorsFromBestToWorstTasks.entrySet()) {
+ for (Map.Entry>> raceColumnAndTaskToJoin : competitorsFromBestToWorstTasks.entrySet()) {
try {
result.setCompetitorsFromBestToWorst(raceColumnAndTaskToJoin.getKey().getName(), raceColumnAndTaskToJoin.getValue().get());
} catch (InterruptedException e) {
@@ -1323,7 +1322,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
Map> futuresForColumnName = new HashMap>();
final Set discardedRaceColumns = getResultDiscardingRule().getDiscardedRaceColumns(competitor, this, getRaceColumns(), timePoint);
for (final RaceColumn raceColumn : this.getRaceColumns()) {
- RunnableFuture future = new FutureTask(() -> {
+ Future future = executor.submit(() -> {
Entry entry = AbstractSimpleLeaderboardImpl.this.getEntry(competitor, raceColumn, timePoint, discardedRaceColumns);
return getLeaderboardEntryDTO(entry, raceColumn, competitor, timePoint,
namesOfRaceColumnsForWhichToLoadLegDetails != null
@@ -1331,7 +1330,6 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
.getName()), waitForLatestAnalyses, legRanksCache, baseDomainFactory,
fillTotalPointsUncorrected, cache);
});
- executor.execute(future);
futuresForColumnName.put(raceColumn.getName(), future);
}
for (Map.Entry> raceColumnNameAndFuture : futuresForColumnName.entrySet()) {
@@ -1611,7 +1609,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
raceDetails = raceDetailsAtEndOfTrackingCache.get(key);
if (raceDetails == null) {
needToRunRaceDetails = true;
- raceDetails = new FutureTask(new Callable() {
+ raceDetails = new FutureTaskWithTracingGet("RaceDetails for "+trackedRace, new Callable() {
@Override
public RaceDetails call() throws Exception {
TimePoint end = trackedRace.getEndOfRace();
diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/MarkPassingCalculator.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/MarkPassingCalculator.java
index ed4b3280062..32f29af56d5 100755
--- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/MarkPassingCalculator.java
+++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/MarkPassingCalculator.java
@@ -9,7 +9,6 @@ import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -173,7 +172,7 @@ public class MarkPassingCalculator {
+ " while waiting for new GPSFixes");
}
listener.getQueue().drainTo(allNewFixInsertions);
- logger.finer("MPC for "+raceName+" recieved "+ allNewFixInsertions.size()+" new updates.");
+ logger.fine("MPC for "+raceName+" received "+ allNewFixInsertions.size()+" new updates.");
for (StorePositionUpdateStrategy fixInsertion : allNewFixInsertions) {
if (listener.isEndMarker(fixInsertion)) {
logger.info("Stopping "+MarkPassingCalculator.this+"'s listener for race "+raceName);
@@ -212,8 +211,7 @@ public class MarkPassingCalculator {
}
executor.invokeAll(tasks);
}
- updateManuallySetMarkPassings(fixedMarkPassings, removedFixedMarkPassings, suppressedMarkPassings,
- unsuppressedMarkPassings);
+ updateManuallySetMarkPassings(fixedMarkPassings, removedFixedMarkPassings, suppressedMarkPassings, unsuppressedMarkPassings);
computeMarkPasses(competitorFixes, markFixes);
competitorFixes.clear();
markFixes.clear();
@@ -284,20 +282,35 @@ public class MarkPassingCalculator {
fixes.addAll(fixesAffectedByNewMarkFixes.getValue());
}
}
- List> tasks = new ArrayList<>();
- for (Entry> c : combinedCompetitorFixes.entrySet()) {
- tasks.add(Executors.callable(new ComputeMarkPassings(c.getKey(), c.getValue())));
+ List> tasks = new ArrayList<>();
+ for (final Entry> c : combinedCompetitorFixes.entrySet()) {
+ final Runnable runnable = new ComputeMarkPassings(c.getKey(), c.getValue());
+ tasks.add(new Callable() {
+ @Override
+ public Void call() throws Exception {
+ runnable.run();
+ return null;
+ }
+
+ /**
+ * A reasonable toString implementation helps identifying these tasks in log messages for long-running tasks
+ */
+ @Override
+ public String toString() {
+ return "Mark passing calculation for competitor "+c.getKey()+" with "+c.getValue().size()+" fixes";
+ }
+ });
}
try {
executor.invokeAll(tasks);
} catch (InterruptedException e) {
- e.printStackTrace();
+ logger.log(Level.INFO, "mark passing calculation interrupted", e);
}
}
private class ComputeMarkPassings implements Runnable {
- Competitor c;
- Iterable fixes;
+ final Competitor c;
+ final Iterable fixes;
public ComputeMarkPassings(Competitor c, Iterable fixes) {
this.c = c;
@@ -306,12 +319,22 @@ public class MarkPassingCalculator {
@Override
public void run() {
- logger.finer("Calculating MarkPassings for race "+raceName+", competitor " + c + " (" + Util.size(fixes) + " new fixes)");
- Util.Pair, Iterable> candidateDeltas = finder.getCandidateDeltas(c, fixes);
- logger.finer("Received " + Util.size(candidateDeltas.getA()) + " new Candidates for race "+raceName+
- " and competitor "+c+" and will remove "
- + Util.size(candidateDeltas.getB()) + " old Candidates for " + c);
- chooser.calculateMarkPassDeltas(c, candidateDeltas.getA(), candidateDeltas.getB());
+ try {
+ if (logger.isLoggable(Level.FINER)) {
+ logger.finer("Calculating MarkPassings for race "+raceName+", competitor " + c + " (" + Util.size(fixes) + " new fixes)");
+ }
+ Util.Pair, Iterable> candidateDeltas = finder.getCandidateDeltas(c, fixes);
+ if (logger.isLoggable(Level.FINER)) {
+ logger.finer("Received " + Util.size(candidateDeltas.getA()) + " new Candidates for race "+raceName+
+ " and competitor "+c+" and will remove "
+ + Util.size(candidateDeltas.getB()) + " old Candidates for " + c);
+ }
+ chooser.calculateMarkPassDeltas(c, candidateDeltas.getA(), candidateDeltas.getB());
+ } catch (Exception e) {
+ // make sure the exception is logged and not only "swallowed" and suppressed by an invokeAll(...) statement
+ logger.log(Level.SEVERE, "Exception trying to compute mark passings for competitor "+c, e);
+ throw e;
+ }
}
}
diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateFinderImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateFinderImpl.java
index c15b26cfaa9..b82874e0663 100755
--- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateFinderImpl.java
+++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateFinderImpl.java
@@ -47,6 +47,7 @@ import com.sap.sse.common.Timed;
import com.sap.sse.common.Util;
import com.sap.sse.common.Util.Pair;
import com.sap.sse.common.impl.TimeRangeImpl;
+import com.sap.sse.concurrent.LockUtil;
import com.sap.sse.util.ThreadPoolUtil;
/**
@@ -383,24 +384,30 @@ public class CandidateFinderImpl implements CandidateFinder {
changedWaypoints.add(w);
}
}
- final Set> tasks = new HashSet<>();;
+ final Set> tasks = new HashSet<>();
+ final Thread executingThread = Thread.currentThread(); // most likely the MarkPassingCalculator.Listen thread
for (Competitor c : race.getRace().getCompetitors()) {
tasks.add(()->{
- List badCans = new ArrayList<>();
- List newCans = new ArrayList<>();
- for (Waypoint w : changedWaypoints) {
- Map, Candidate> xteCans = getXteCandidates(c, w);
- badCans.addAll(xteCans.values());
- xteCans.clear();
- Map distanceCans = getDistanceCandidates(c, w);
- badCans.addAll(distanceCans.values());
- distanceCans.clear();
+ LockUtil.propagateLockSetFrom(executingThread); // "inherit" the course read lock from the "calling" thread into the thread pool executor thread
+ try {
+ List badCans = new ArrayList<>();
+ List newCans = new ArrayList<>();
+ for (Waypoint w : changedWaypoints) {
+ Map, Candidate> xteCans = getXteCandidates(c, w);
+ badCans.addAll(xteCans.values());
+ xteCans.clear();
+ Map distanceCans = getDistanceCandidates(c, w);
+ badCans.addAll(distanceCans.values());
+ distanceCans.clear();
+ }
+ Set allFixes = getAllFixes(c);
+ newCans.addAll(checkForDistanceCandidateChanges(c, allFixes, changedWaypoints).getA());
+ newCans.addAll(checkForXTECandidatesChanges(c, allFixes, changedWaypoints).getA());
+ result.put(c, new Util.Pair, List>(newCans, badCans));
+ return null;
+ } finally {
+ LockUtil.unpropagateLockSetFrom(executingThread); // "return" the course read lock from the thread pool executor thread to the "calling" thread
}
- Set allFixes = getAllFixes(c);
- newCans.addAll(checkForDistanceCandidateChanges(c, allFixes, changedWaypoints).getA());
- newCans.addAll(checkForXTECandidatesChanges(c, allFixes, changedWaypoints).getA());
- result.put(c, new Util.Pair, List>(newCans, badCans));
- return null;
});
}
executor.invokeAll(tasks);
@@ -513,8 +520,8 @@ public class CandidateFinderImpl implements CandidateFinder {
private Set getAllFixes(Competitor c) {
Set fixes = new TreeSet(comp);
DynamicGPSFixTrack track = race.getTrack(c);
+ track.lockForRead();
try {
- track.lockForRead();
for (GPSFix fix : track.getFixes(
timeRangeForValidCandidates.from(), /* fromInclusive */ true,
timeRangeForValidCandidates.to(), /* toInclusive */ true)) {
@@ -542,8 +549,8 @@ public class CandidateFinderImpl implements CandidateFinder {
affectedFixes.add(fix);
GPSFix fixBefore;
GPSFix fixAfter;
+ track.lockForRead();
try {
- track.lockForRead();
TimePoint timePoint = fix.getTimePoint();
fixBefore = track.getLastFixBefore(timePoint);
fixAfter = track.getFirstFixAfter(timePoint);
@@ -1206,19 +1213,24 @@ public class CandidateFinderImpl implements CandidateFinder {
* @return an average of the estimated legs before and after w.
*/
private Distance getAverageLengthOfAdjacentLegs(TimePoint t, Waypoint w) {
- Course course = race.getRace().getCourse();
- if (w == course.getFirstWaypoint()) {
- return race.getTrackedLegStartingAt(w).getGreatCircleDistance(t);
+ final Distance result;
+ final Course course = race.getRace().getCourse();
+ if (course.getNumberOfWaypoints() < 2) {
+ result = null;
+ } else if (w == course.getFirstWaypoint()) {
+ result = race.getTrackedLegStartingAt(w).getGreatCircleDistance(t);
} else if (w == course.getLastWaypoint()) {
- return race.getTrackedLegFinishingAt(w).getGreatCircleDistance(t);
+ result = race.getTrackedLegFinishingAt(w).getGreatCircleDistance(t);
} else {
Distance before = race.getTrackedLegStartingAt(w).getGreatCircleDistance(t);
Distance after = race.getTrackedLegFinishingAt(w).getGreatCircleDistance(t);
if (after != null && before != null) {
- return new MeterDistance(before.add(after).getMeters() / 2);
+ result = new MeterDistance(before.add(after).getMeters() / 2);
+ } else {
+ result = null;
}
- return null;
}
+ return result;
}
/**
@@ -1414,8 +1426,9 @@ public class CandidateFinderImpl implements CandidateFinder {
final TimePoint newNonInferredStartTime = race.getStartOfRace(/* inferred */ false);
final TimePoint newTimePointWhenToStartConsideringCandidates = getTimePointWhenToStartConsideringCandidates(newNonInferredStartTime);
if (!Util.equalsWithNull(newTimePointWhenToStartConsideringCandidates, timeRangeForValidCandidates.from())) {
+ final TimePoint oldTimePointWhenToStartConsideringCandidates = timeRangeForValidCandidates.from();
timeRangeForValidCandidates = new TimeRangeImpl(newTimePointWhenToStartConsideringCandidates, timeRangeForValidCandidates.to());
- result = updateCandiatesAfterRaceTimeRangeChanged(newTimePointWhenToStartConsideringCandidates, timeRangeForValidCandidates.from());
+ result = updateCandiatesAfterRaceTimeRangeChanged(newTimePointWhenToStartConsideringCandidates, oldTimePointWhenToStartConsideringCandidates);
} else {
result = Collections.emptyMap();
}
@@ -1427,12 +1440,10 @@ public class CandidateFinderImpl implements CandidateFinder {
TimePoint oldFinishedTime, TimePoint newFinishedTime) {
final Map, Iterable>> result;
final TimePoint newTimePointWhenToFinishConsideringCandidates = getTimePointWhenToFinishConsideringCandidates(newFinishedTime);
- final TimePoint oldTimePointWhenToFinishConsideringCandidates = timeRangeForValidCandidates.to();
if (!Util.equalsWithNull(timeRangeForValidCandidates.to(), newTimePointWhenToFinishConsideringCandidates)) {
+ final TimePoint oldTimePointWhenToFinishConsideringCandidates = timeRangeForValidCandidates.to();
timeRangeForValidCandidates = new TimeRangeImpl(timeRangeForValidCandidates.from(), newTimePointWhenToFinishConsideringCandidates);
- result = updateCandiatesAfterRaceTimeRangeChanged(
- oldTimePointWhenToFinishConsideringCandidates,
- newTimePointWhenToFinishConsideringCandidates);
+ result = updateCandiatesAfterRaceTimeRangeChanged(oldTimePointWhenToFinishConsideringCandidates, newTimePointWhenToFinishConsideringCandidates);
} else {
result = Collections.emptyMap();
}
diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/trackimport/DoubleVectorFixImporter.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/trackimport/DoubleVectorFixImporter.java
index 6a44a51156b..0e1e75cdf6f 100644
--- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/trackimport/DoubleVectorFixImporter.java
+++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/trackimport/DoubleVectorFixImporter.java
@@ -37,7 +37,7 @@ public interface DoubleVectorFixImporter {
* @throws IOException
* if there is a problem while reading the file
*/
- void importFixes(InputStream inputStream, Callback callback, String sourceName)
+ void importFixes(InputStream inputStream, Callback callback, String filename, String sourceName)
throws FormatNotSupportedException, IOException;
/**
diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRaceWithWindEssentials.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRaceWithWindEssentials.java
index 5f84849d6a6..ce7bf54732c 100644
--- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRaceWithWindEssentials.java
+++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRaceWithWindEssentials.java
@@ -23,7 +23,7 @@ public abstract class TrackedRaceWithWindEssentials implements TrackedRace {
private static final Logger logger = Logger.getLogger(TrackedRaceWithWindEssentials.class.getName());
/**
- * Serializing an instance of this class has to serialized the various data structures holding the tracked race's
+ * Serializing an instance of this class has to serialize the various data structures holding the tracked race's
* state. When a race is currently on, these structures change very frequently, and
* {@link ConcurrentModificationException}s during serialization will be the norm rather than the exception. To
* avoid this, all modifications to any data structure that is not in itself synchronized obtains this lock's
@@ -146,7 +146,4 @@ public abstract class TrackedRaceWithWindEssentials implements TrackedRace {
protected NamedReentrantReadWriteLock getSerializationLock() {
return serializationLock;
}
-
-
-
}
diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/GPSFixTrackImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/GPSFixTrackImpl.java
index b89c60c9421..2600557528a 100755
--- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/GPSFixTrackImpl.java
+++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/GPSFixTrackImpl.java
@@ -1075,8 +1075,10 @@ public class GPSFixTrackImpl extends MappedTra
unlockAfterRead();
}
}
- for (GPSTrackListener listener : getListeners()) {
- listener.gpsFixReceived(fix, getTrackedItem(), firstFixInTrack);
+ if (result) {
+ for (GPSTrackListener listener : getListeners()) {
+ listener.gpsFixReceived(fix, getTrackedItem(), firstFixInTrack);
+ }
}
return result;
}
diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackBasedEstimationWindTrackImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackBasedEstimationWindTrackImpl.java
index d3f198f7562..4ce552698a5 100644
--- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackBasedEstimationWindTrackImpl.java
+++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackBasedEstimationWindTrackImpl.java
@@ -28,6 +28,8 @@ import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
import com.sap.sailing.domain.common.impl.WindImpl;
import com.sap.sailing.domain.common.tracking.GPSFix;
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
+import com.sap.sailing.domain.common.tracking.SensorFix;
+import com.sap.sailing.domain.tracking.DynamicSensorFixTrack;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.TrackedRaceStatus;
@@ -740,6 +742,16 @@ public class TrackBasedEstimationWindTrackImpl extends VirtualWindTrackImpl {
scheduleCacheRefresh(startOfInvalidation, endOfInvalidation);
}
}
+
+ @Override
+ public void competitorSensorFixAdded(Competitor competitor, String trackName, SensorFix fix) {
+ // no action required
+ }
+
+ @Override
+ public void competitorSensorTrackAdded(DynamicSensorFixTrack track) {
+ // no action required
+ }
}
/**
diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java
index 8bb6f3e2098..9973e347fb1 100644
--- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java
+++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java
@@ -158,6 +158,7 @@ import com.sap.sse.util.SmartFutureCache;
import com.sap.sse.util.SmartFutureCache.AbstractCacheUpdater;
import com.sap.sse.util.SmartFutureCache.EmptyUpdateInterval;
import com.sap.sse.util.impl.ArrayListNavigableSet;
+import com.sap.sse.util.impl.FutureTaskWithTracingGet;
import difflib.DiffUtils;
import difflib.Patch;
@@ -750,7 +751,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
/**
* Monitor object to synchronize access to the {@link #updateStartAndEndOfTracking(boolean)} method. See bug 3922.
*/
- private final Serializable updateStartAndEndOfTrackingMonitor = "";
+ private final Serializable updateStartAndEndOfTrackingMonitor = "updateStartAndEndOfTrackingMonitor";
/**
* Updates the start and end of tracking in the following precedence order:
@@ -762,10 +763,12 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
*
*/
public void updateStartAndEndOfTracking(boolean waitForGPSFixesToLoad) {
+ final TimePoint oldStartOfTracking;
+ final TimePoint oldEndOfTracking;
synchronized (updateStartAndEndOfTrackingMonitor) {
final Pair trackingTimesFromRaceLog = this.getTrackingTimesFromRaceLogs();
- TimePoint oldStartOfTracking = getStartOfTracking();
- TimePoint oldEndOfTracking = getEndOfTracking();
+ oldStartOfTracking = getStartOfTracking();
+ oldEndOfTracking = getEndOfTracking();
boolean startOfTrackingFound = false;
boolean endOfTrackingFound = false;
// check race log
@@ -801,9 +804,9 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
endOfTrackingFound = true;
}
}
- startOfTrackingChanged(oldStartOfTracking, waitForGPSFixesToLoad);
- endOfTrackingChanged(oldEndOfTracking, waitForGPSFixesToLoad);
}
+ startOfTrackingChanged(oldStartOfTracking, waitForGPSFixesToLoad);
+ endOfTrackingChanged(oldEndOfTracking, waitForGPSFixesToLoad);
}
@Override
@@ -1873,7 +1876,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
synchronized (directionFromStartToNextMarkCache) {
future = directionFromStartToNextMarkCache.get(at);
if (future == null) {
- newFuture = new FutureTask(new Callable() {
+ newFuture = new FutureTaskWithTracingGet("getDirectionFromStartToNextMark for "+this, new Callable() {
@Override
public Wind call() {
Wind result;
diff --git a/java/com.sap.sailing.expeditionconnector/src/com/sap/sailing/expeditionconnector/ExpeditionWindTrackerFactory.java b/java/com.sap.sailing.expeditionconnector/src/com/sap/sailing/expeditionconnector/ExpeditionWindTrackerFactory.java
index a30719ea1bf..961d030e3db 100755
--- a/java/com.sap.sailing.expeditionconnector/src/com/sap/sailing/expeditionconnector/ExpeditionWindTrackerFactory.java
+++ b/java/com.sap.sailing.expeditionconnector/src/com/sap/sailing/expeditionconnector/ExpeditionWindTrackerFactory.java
@@ -96,4 +96,9 @@ public class ExpeditionWindTrackerFactory implements WindTrackerFactory {
windReceivers.remove(windTracker.getReceiver().getPort());
}
}
+
+ @Override
+ public String toString() {
+ return "ExpeditionWindTrackerFactory [defaultPort=" + defaultPort + "]";
+ }
}
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/place/start/DesktopStartView.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/place/start/DesktopStartView.java
index 6df38e1b7ed..bb42e9bafcb 100644
--- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/place/start/DesktopStartView.java
+++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/place/start/DesktopStartView.java
@@ -28,6 +28,7 @@ import com.sap.sailing.gwt.autoplay.client.place.player.AutoPlayerConfiguration;
import com.sap.sailing.gwt.autoplay.client.shared.leaderboard.LeaderboardWithHeaderPerspectiveLifecycle;
import com.sap.sailing.gwt.autoplay.client.shared.leaderboard.LeaderboardWithHeaderPerspectiveSettings;
import com.sap.sailing.gwt.common.authentication.FixedSailingAuthentication;
+import com.sap.sailing.gwt.common.authentication.SAPSailingHeaderWithAuthentication;
import com.sap.sailing.gwt.common.client.SharedResources;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.raceboard.RaceBoardPerspectiveLifecycle;
@@ -44,7 +45,6 @@ import com.sap.sse.gwt.client.shared.perspective.PerspectiveCompositeLifecycleTa
import com.sap.sse.gwt.client.shared.perspective.PerspectiveCompositeSettings;
import com.sap.sse.gwt.client.shared.perspective.PerspectiveLifecycle;
import com.sap.sse.gwt.client.shared.perspective.PerspectiveLifecycleWithAllSettings;
-import com.sap.sse.security.ui.authentication.generic.sapheader.SAPHeaderWithAuthentication;
import com.sap.sse.security.ui.client.UserService;
public class DesktopStartView extends Composite implements StartView {
@@ -53,7 +53,7 @@ public class DesktopStartView extends Composite implements StartView {
interface StartPageViewUiBinder extends UiBinder {
}
- @UiField(provided=true) SAPHeaderWithAuthentication sapHeader;
+ @UiField(provided=true) SAPSailingHeaderWithAuthentication sapHeader;
@UiField(provided=true) ListBox localeSelectionBox;
@UiField(provided=true) ListBox eventSelectionBox;
@UiField(provided=true) ListBox leaderboardSelectionBox;
@@ -82,8 +82,7 @@ public class DesktopStartView extends Composite implements StartView {
this.eventBus = eventBus;
this.events = new ArrayList();
- sapHeader = new SAPHeaderWithAuthentication(StringMessages.INSTANCE.sapSailingAnalytics(),
- StringMessages.INSTANCE.autoplayConfiguration());
+ sapHeader = new SAPSailingHeaderWithAuthentication(StringMessages.INSTANCE.autoplayConfiguration());
new FixedSailingAuthentication(userService, sapHeader.getAuthenticationMenuView());
eventSelectionBox = new ListBox();
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/shared/header/SAPHeaderComponent.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/shared/header/SAPHeaderComponent.java
index d8df0b472aa..cb71942d676 100644
--- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/shared/header/SAPHeaderComponent.java
+++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/shared/header/SAPHeaderComponent.java
@@ -4,23 +4,23 @@ import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.RepeatingCommand;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.gwt.common.authentication.FixedSailingAuthentication;
+import com.sap.sailing.gwt.common.authentication.SAPSailingHeaderWithAuthentication;
import com.sap.sailing.gwt.common.client.FullscreenUtil;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sse.gwt.client.shared.components.AbstractCompositeComponent;
-import com.sap.sse.security.ui.authentication.generic.sapheader.SAPHeaderWithAuthentication;
import com.sap.sse.security.ui.client.UserService;
public class SAPHeaderComponent extends AbstractCompositeComponent {
private SAPHeaderComponentSettings settings;
private final SAPHeaderComponentLifecycle componentLifecycle;
- private final SAPHeaderWithAuthentication sapHeader;
+ private final SAPSailingHeaderWithAuthentication sapHeader;
public SAPHeaderComponent(SAPHeaderComponentLifecycle componentLifecycle, UserService userService, SAPHeaderComponentSettings settings,
StringMessages stringMessages, boolean startInAutoScreenMode) {
this.componentLifecycle = componentLifecycle;
this.settings = settings;
- this.sapHeader = new SAPHeaderWithAuthentication(stringMessages.sapSailingAnalytics(), settings.getTitle());
+ this.sapHeader = new SAPSailingHeaderWithAuthentication(settings.getTitle());
new FixedSailingAuthentication(userService, sapHeader.getAuthenticationMenuView());
initWidget(sapHeader);
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/authentication/SAPSailingHeaderWithAuthentication.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/authentication/SAPSailingHeaderWithAuthentication.java
new file mode 100644
index 00000000000..20101cccdec
--- /dev/null
+++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/authentication/SAPSailingHeaderWithAuthentication.java
@@ -0,0 +1,20 @@
+package com.sap.sailing.gwt.common.authentication;
+
+import com.sap.sailing.gwt.ui.client.StringMessages;
+import com.sap.sse.security.ui.authentication.generic.sapheader.SAPHeaderWithAuthentication;
+
+public class SAPSailingHeaderWithAuthentication extends SAPHeaderWithAuthentication {
+
+ private static final String SAP_SAILING_URL = "http://www.sapsailing.com";
+ private static final String SAP_SAILING_APP_NAME = StringMessages.INSTANCE.sapSailingAnalytics();
+
+
+ public SAPSailingHeaderWithAuthentication() {
+ super(SAP_SAILING_APP_NAME, SAP_SAILING_URL);
+ }
+
+ public SAPSailingHeaderWithAuthentication(String headerText) {
+ super(SAP_SAILING_APP_NAME, SAP_SAILING_URL, headerText);
+ }
+
+}
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/regattaoverview/client/RegattaOverviewEntryPoint.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/regattaoverview/client/RegattaOverviewEntryPoint.java
index f5653f8e579..00827fb9fa8 100644
--- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/regattaoverview/client/RegattaOverviewEntryPoint.java
+++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/regattaoverview/client/RegattaOverviewEntryPoint.java
@@ -14,6 +14,7 @@ import com.google.gwt.user.client.ui.Panel;
import com.google.gwt.user.client.ui.RootLayoutPanel;
import com.google.gwt.user.client.ui.ScrollPanel;
import com.sap.sailing.gwt.common.authentication.FixedSailingAuthentication;
+import com.sap.sailing.gwt.common.authentication.SAPSailingHeaderWithAuthentication;
import com.sap.sailing.gwt.common.client.SharedResources;
import com.sap.sailing.gwt.regattaoverview.client.RegattaRaceStatesComponent.EntryHandler;
import com.sap.sailing.gwt.settings.client.regattaoverview.RegattaOverviewBaseSettings;
@@ -21,7 +22,6 @@ import com.sap.sailing.gwt.settings.client.regattaoverview.RegattaRaceStatesSett
import com.sap.sailing.gwt.ui.client.AbstractSailingEntryPoint;
import com.sap.sailing.gwt.ui.shared.RegattaOverviewEntryDTO;
import com.sap.sse.gwt.settings.SettingsToUrlSerializer;
-import com.sap.sse.security.ui.authentication.generic.sapheader.SAPHeaderWithAuthentication;
public class RegattaOverviewEntryPoint extends AbstractSailingEntryPoint {
@@ -33,7 +33,7 @@ public class RegattaOverviewEntryPoint extends AbstractSailingEntryPoint {
private final Label clockLabel = new Label();
private final RegattaOverviewResources.LocalCss style = RegattaOverviewResources.INSTANCE.css();
- private SAPHeaderWithAuthentication siteHeader;
+ private SAPSailingHeaderWithAuthentication siteHeader;
@Override
public void doOnModuleLoad() {
@@ -50,8 +50,7 @@ public class RegattaOverviewEntryPoint extends AbstractSailingEntryPoint {
clockLabel.addStyleName(style.clockLabel());
- siteHeader = new SAPHeaderWithAuthentication(getStringMessages()
- .sapSailingAnalytics());
+ siteHeader = new SAPSailingHeaderWithAuthentication();
new FixedSailingAuthentication(getUserService(), siteHeader.getAuthenticationMenuView());
siteHeader.addWidgetToRightSide(clockLabel);
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractFileImportWidget.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractFileImportWidget.java
index 22f61ee64c9..d462a8ddd9a 100644
--- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractFileImportWidget.java
+++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractFileImportWidget.java
@@ -41,6 +41,7 @@ public abstract class AbstractFileImportWidget extends Composite {
protected final SailingServiceAsync sailingService;
private final StringMessages stringMessages;
private final ErrorReporter errorReporter;
+ private boolean shouldClearListOnNewUploadComplete = true;
protected AbstractFileImportWidget(String formActionUrl, StringMessages stringMessages,
TrackFileImportDeviceIdentifierTableWrapper table, SailingServiceAsync sailingService,
@@ -73,7 +74,9 @@ public abstract class AbstractFileImportWidget extends Composite {
@UiHandler("formPanelUi")
void onFileImportComplete(SubmitCompleteEvent event) {
- table.getDataProvider().getList().clear();
+ if (shouldClearListOnNewUploadComplete) {
+ table.getDataProvider().getList().clear();
+ }
String[] uuids = event.getResults().split("\\n");
if (uuids.length != 1 || (uuids.length > 0 && !uuids[0].isEmpty())) {
sailingService.getTrackFileImportDeviceIds(Arrays.asList(uuids),
@@ -106,4 +109,7 @@ public abstract class AbstractFileImportWidget extends Composite {
protected abstract void getImporterTypes(AsyncCallback> callback);
+ protected void setShouldClearListOnNewUploadComplete(boolean shouldClearListOnNewUploadComplete) {
+ this.shouldClearListOnNewUploadComplete = shouldClearListOnNewUploadComplete;
+ }
}
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleEntryPoint.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleEntryPoint.java
index 00d781e3259..226aaa74264 100755
--- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleEntryPoint.java
+++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleEntryPoint.java
@@ -16,6 +16,7 @@ import com.sap.sailing.domain.common.security.Permission;
import com.sap.sailing.domain.common.security.Roles;
import com.sap.sailing.domain.common.security.SailingPermissionsForRoleProvider;
import com.sap.sailing.gwt.common.authentication.FixedSailingAuthentication;
+import com.sap.sailing.gwt.common.authentication.SAPSailingHeaderWithAuthentication;
import com.sap.sailing.gwt.ui.client.AbstractSailingEntryPoint;
import com.sap.sailing.gwt.ui.client.LeaderboardGroupsDisplayer;
import com.sap.sailing.gwt.ui.client.LeaderboardGroupsRefresher;
@@ -45,7 +46,6 @@ import com.sap.sse.security.ui.authentication.decorator.AuthorizedContentDecorat
import com.sap.sse.security.ui.authentication.decorator.WidgetFactory;
import com.sap.sse.security.ui.authentication.generic.GenericAuthentication;
import com.sap.sse.security.ui.authentication.generic.GenericAuthorizedContentDecorator;
-import com.sap.sse.security.ui.authentication.generic.sapheader.SAPHeaderWithAuthentication;
import com.sap.sse.security.ui.client.component.UserManagementPanel;
import com.sap.sse.security.ui.client.i18n.StringMessages;
@@ -66,8 +66,7 @@ public class AdminConsoleEntryPoint extends AbstractSailingEntryPoint implements
private void createUI() {
HeaderPanel headerPanel = new HeaderPanel();
- SAPHeaderWithAuthentication header = new SAPHeaderWithAuthentication(getStringMessages().sapSailingAnalytics(),
- getStringMessages().administration());
+ SAPSailingHeaderWithAuthentication header = new SAPSailingHeaderWithAuthentication(getStringMessages().administration());
GenericAuthentication genericSailingAuthentication = new FixedSailingAuthentication(getUserService(), header.getAuthenticationMenuView());
AuthorizedContentDecorator authorizedContentDecorator = new GenericAuthorizedContentDecorator(genericSailingAuthentication);
authorizedContentDecorator.setContentWidgetFactory(new WidgetFactory() {
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaLogImportSensorDataAndAddMappingsDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaLogImportSensorDataAndAddMappingsDialog.java
index 6691ddf6c7c..1db0f615e1e 100644
--- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaLogImportSensorDataAndAddMappingsDialog.java
+++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaLogImportSensorDataAndAddMappingsDialog.java
@@ -2,9 +2,7 @@ package com.sap.sailing.gwt.ui.adminconsole;
import java.util.ArrayList;
import java.util.Collection;
-import java.util.HashMap;
import java.util.List;
-import java.util.Map;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.CaptionPanel;
@@ -12,7 +10,6 @@ import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.view.client.SelectionChangeEvent;
-import com.google.gwt.view.client.SingleSelectionModel;
import com.sap.sailing.domain.common.dto.CompetitorDTO;
import com.sap.sailing.domain.common.racelog.tracking.MappableToDevice;
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
@@ -31,12 +28,6 @@ public class RegattaLogImportSensorDataAndAddMappingsDialog extends DataEntryDia
protected final CompetitorTableWrapper> competitorTable;
private final StringMessages stringMessages;
- private final Map mappings = new HashMap<>();
-
- private TrackFileImportDeviceIdentifierDTO deviceToSelect;
- private CompetitorDTO compToSelect;
- private boolean inInstableTransitionState = false;
-
public RegattaLogImportSensorDataAndAddMappingsDialog(SailingServiceAsync sailingService,
final ErrorReporter errorReporter, final StringMessages stringMessages, String leaderboardName,
DialogCallback> callback) {
@@ -50,11 +41,16 @@ public class RegattaLogImportSensorDataAndAddMappingsDialog extends DataEntryDia
}, true, callback);
this.stringMessages = stringMessages;
deviceIdTable = new TrackFileImportDeviceIdentifierTableWrapper(sailingService, stringMessages, errorReporter);
+ deviceIdTable.removeTrackNameColumn();
+
importWidget = new SensorDataImportWidget(deviceIdTable, stringMessages, sailingService, errorReporter);
deviceIdTable.getSelectionModel().addSelectionChangeHandler(new SelectionChangeEvent.Handler() {
@Override
public void onSelectionChange(SelectionChangeEvent event) {
- deviceSelectionChanged(deviceIdTable.getSelectionModel().getSelectedObject());
+ CompetitorDTO mappedComp = deviceIdTable.getMappedCompetitorForCurrentSelection();
+ if (mappedComp != null) {
+ competitorTable.getSelectionModel().setSelected(mappedComp, true);
+ }
}
});
competitorTable = new CompetitorTableWrapper<>(sailingService, stringMessages, errorReporter, /* multiSelection */
@@ -63,7 +59,8 @@ public class RegattaLogImportSensorDataAndAddMappingsDialog extends DataEntryDia
competitorTable.getSelectionModel().addSelectionChangeHandler(new SelectionChangeEvent.Handler() {
@Override
public void onSelectionChange(SelectionChangeEvent event) {
- mappedToSelectionChanged(competitorTable.getSelectionModel().getSelectedObject());
+ deviceIdTable.didSelectCompetitorForMapping(competitorTable.getSelectionModel().getSelectedObject());
+ validate();
}
});
@@ -87,50 +84,6 @@ public class RegattaLogImportSensorDataAndAddMappingsDialog extends DataEntryDia
});
}
- private static void selectOrClear(SingleSelectionModel selectionModel, T object) {
- if (object == null) {
- selectionModel.clear();
- } else {
- selectionModel.setSelected(object, true);
- }
- }
-
- /**
- * Avoid programmatic deselections that re-trigger the selection listeners and lead to a loop.
- */
- private void select() {
- if (inInstableTransitionState) {
- if (deviceIdTable.getSelectionModel().getSelectedObject() == deviceToSelect
- && competitorTable.getSelectionModel().getSelectedObject() == compToSelect) {
- inInstableTransitionState = false;
- }
- } else {
- inInstableTransitionState = true;
- selectOrClear(deviceIdTable.getSelectionModel(), deviceToSelect);
- selectOrClear(competitorTable.getSelectionModel(), compToSelect);
- }
- }
-
- private void mappedToSelectionChanged(CompetitorDTO mappedTo) {
- if (!inInstableTransitionState) {
- TrackFileImportDeviceIdentifierDTO device = deviceIdTable.getSelectionModel().getSelectedObject();
- if (device != null) {
- mappings.put(device, mappedTo);
- }
- compToSelect = mappedTo;
- }
- select();
- validate();
- }
-
- private void deviceSelectionChanged(TrackFileImportDeviceIdentifierDTO deviceId) {
- if (!inInstableTransitionState) {
- deviceToSelect = deviceId;
- compToSelect = deviceId != null ? mappings.get(deviceId) : null;
- }
- select();
- }
-
@Override
protected Widget getAdditionalWidget() {
HorizontalPanel panel = new HorizontalPanel();
@@ -150,9 +103,9 @@ public class RegattaLogImportSensorDataAndAddMappingsDialog extends DataEntryDia
protected Collection getResult() {
List result = new ArrayList<>();
String dataType = importWidget.getSelectedImporterType();
- for (TrackFileImportDeviceIdentifierDTO device : mappings.keySet()) {
+ for (TrackFileImportDeviceIdentifierDTO device : deviceIdTable.getMappings().keySet()) {
DeviceIdentifierDTO deviceIdDto = new DeviceIdentifierDTO("FILE", device.uuidAsString);
- MappableToDevice mappedTo = mappings.get(device);
+ MappableToDevice mappedTo = deviceIdTable.getMappings().get(device);
result.add(new TypedDeviceMappingDTO(deviceIdDto, device.from, device.to, mappedTo, null, dataType));
}
return result;
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SensorDataImportWidget.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SensorDataImportWidget.java
index c6ff1885e74..20ec0834486 100644
--- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SensorDataImportWidget.java
+++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SensorDataImportWidget.java
@@ -14,6 +14,7 @@ public class SensorDataImportWidget extends AbstractFileImportWidget implements
final SailingServiceAsync sailingService, final ErrorReporter errorReporter) {
super("/sailingserver/sensordata/import", stringMessages, table, sailingService, errorReporter);
setMultipleFileUploadEnabled(true);
+ setShouldClearListOnNewUploadComplete(false);
}
@Override
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SimulationPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SimulationPanel.java
new file mode 100644
index 00000000000..ff4a621350f
--- /dev/null
+++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SimulationPanel.java
@@ -0,0 +1,60 @@
+package com.sap.sailing.gwt.ui.adminconsole;
+
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.user.client.ui.CheckBox;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.HorizontalPanel;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.TextBox;
+import com.google.gwt.user.client.ui.VerticalPanel;
+import com.sap.sailing.gwt.ui.client.StringMessages;
+
+/**
+ * Can be used in {@link AbstractEventManagementPanel}s to enable some sort of time-delayed race simulation.
+ * Provides a checkbox (see {@link #isSimulate()}) that enables simulation and an offset in minutes (see
+ * {@link #getOffsetToStartTimeInMinutes()}).
+ *
+ * @author Axel Uhl (d043530)
+ *
+ */
+public class SimulationPanel extends VerticalPanel {
+ private static final String ZERO_AS_STRING = "0";
+ final CheckBox simulateWithStartTimeNowCheckBox;
+ final TextBox offsetToStartTimeOfSimulatedRaceTextBox;
+
+ public SimulationPanel(StringMessages stringMessages) {
+ offsetToStartTimeOfSimulatedRaceTextBox = new TextBox();
+ offsetToStartTimeOfSimulatedRaceTextBox.setWidth("40px");
+ offsetToStartTimeOfSimulatedRaceTextBox.setEnabled(false);
+ offsetToStartTimeOfSimulatedRaceTextBox.setValue(ZERO_AS_STRING);
+ simulateWithStartTimeNowCheckBox = new CheckBox(stringMessages.simulateAsLiveRace());
+ simulateWithStartTimeNowCheckBox.ensureDebugId("SimulateWithStartTimeNowCheckBox");
+ simulateWithStartTimeNowCheckBox.setWordWrap(false);
+ simulateWithStartTimeNowCheckBox.setValue(Boolean.FALSE);
+ simulateWithStartTimeNowCheckBox.addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event) {
+ offsetToStartTimeOfSimulatedRaceTextBox.setEnabled(simulateWithStartTimeNowCheckBox.getValue());
+ offsetToStartTimeOfSimulatedRaceTextBox.setFocus(simulateWithStartTimeNowCheckBox.getValue());
+ }
+ });
+
+ final FlowPanel simulateAsLiveRacePanel = new FlowPanel();
+ simulateAsLiveRacePanel.add(simulateWithStartTimeNowCheckBox);
+ final Label offsetToStartLabel = new Label(stringMessages.simulateWithOffset());
+ final HorizontalPanel simulateWithOffsetPanel = new HorizontalPanel();
+ simulateWithOffsetPanel.add(offsetToStartLabel);
+ simulateWithOffsetPanel.add(offsetToStartTimeOfSimulatedRaceTextBox);
+ add(simulateAsLiveRacePanel);
+ add(simulateWithOffsetPanel);
+ }
+
+ public boolean isSimulate() {
+ return simulateWithStartTimeNowCheckBox.getValue();
+ }
+
+ public String getOffsetToStartTimeInMinutes() {
+ return offsetToStartTimeOfSimulatedRaceTextBox.getValue();
+ }
+}
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TracTracEventManagementPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TracTracEventManagementPanel.java
index 416a780ba56..5e4e5272b6d 100755
--- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TracTracEventManagementPanel.java
+++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TracTracEventManagementPanel.java
@@ -23,7 +23,6 @@ import com.google.gwt.user.client.ui.CaptionPanel;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.FlexTable.FlexCellFormatter;
-import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.HTMLTable.ColumnFormatter;
import com.google.gwt.user.client.ui.HorizontalPanel;
@@ -85,7 +84,6 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel {
private LabeledAbstractFilterablePanel racesFilterablePanel;
private FlushableCellTable racesTable;
- private static final String ZERO_AS_STRING = "0";
public TracTracEventManagementPanel(final SailingServiceAsync sailingService, ErrorReporter errorReporter,
RegattaRefresher regattaRefresher, StringMessages stringMessages) {
@@ -275,32 +273,7 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel {
correctWindCheckBox.setWordWrap(false);
correctWindCheckBox.setValue(Boolean.TRUE);
- final TextBox offsetToStartTimeOfSimulatedRaceTextBox = new TextBox();
- offsetToStartTimeOfSimulatedRaceTextBox.setWidth("40px");
- offsetToStartTimeOfSimulatedRaceTextBox.setEnabled(false);
- offsetToStartTimeOfSimulatedRaceTextBox.setValue(ZERO_AS_STRING);
-
- final CheckBox simulateWithStartTimeNowCheckBox = new CheckBox(stringMessages.simulateAsLiveRace());
- simulateWithStartTimeNowCheckBox.ensureDebugId("SimulateWithStartTimeNowCheckBox");
- simulateWithStartTimeNowCheckBox.setWordWrap(false);
- simulateWithStartTimeNowCheckBox.setValue(Boolean.FALSE);
- simulateWithStartTimeNowCheckBox.addClickHandler(new ClickHandler() {
- @Override
- public void onClick(ClickEvent event) {
- offsetToStartTimeOfSimulatedRaceTextBox.setEnabled(simulateWithStartTimeNowCheckBox.getValue());
- offsetToStartTimeOfSimulatedRaceTextBox.setFocus(simulateWithStartTimeNowCheckBox.getValue());
- }
- });
-
- final FlowPanel simulateAsLiveRacePanel = new FlowPanel();
- simulateAsLiveRacePanel.add(simulateWithStartTimeNowCheckBox);
-
- final Label offsetToStartLabel = new Label(stringMessages.simulateWithOffset());
-
- final HorizontalPanel simulateWithOffsetPanel = new HorizontalPanel();
- simulateWithOffsetPanel.add(offsetToStartLabel);
- simulateWithOffsetPanel.add(offsetToStartTimeOfSimulatedRaceTextBox);
-
+ final SimulationPanel simulationPanel = new SimulationPanel(stringMessages);
final CheckBox ignoreTracTracMarkPassingsCheckbox = new CheckBox(stringMessages.useInternalAlgorithm());
ignoreTracTracMarkPassingsCheckbox.setWordWrap(false);
ignoreTracTracMarkPassingsCheckbox.setValue(Boolean.FALSE);
@@ -308,8 +281,7 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel {
layoutTable.setWidget(++row, 0, trackSettingsLabel);
layoutTable.setWidget(row, 1, trackWindCheckBox);
layoutTable.setWidget(++row, 1, correctWindCheckBox);
- layoutTable.setWidget(++row, 1, simulateAsLiveRacePanel);
- layoutTable.setWidget(++row, 1, simulateWithOffsetPanel);
+ layoutTable.setWidget(++row, 1, simulationPanel);
layoutTable.setWidget(++row, 1, ignoreTracTracMarkPassingsCheckbox);
// Filter
@@ -413,8 +385,8 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel {
@Override
public void onClick(ClickEvent event) {
Duration offsetToStartTimeOfSimulatedRace = null;
- if(simulateWithStartTimeNowCheckBox.getValue().booleanValue()) {
- offsetToStartTimeOfSimulatedRace = getMillisecondsDurationFromMinutesAsString(offsetToStartTimeOfSimulatedRaceTextBox.getValue());
+ if (simulationPanel.isSimulate()) {
+ offsetToStartTimeOfSimulatedRace = getMillisecondsDurationFromMinutesAsString(simulationPanel.getOffsetToStartTimeInMinutes());
}
trackSelectedRaces(trackWindCheckBox.getValue(), correctWindCheckBox.getValue(),
offsetToStartTimeOfSimulatedRace, ignoreTracTracMarkPassingsCheckbox.getValue());
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TrackFileImportDeviceIdentifierTableWrapper.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TrackFileImportDeviceIdentifierTableWrapper.java
index 12d730da684..b2217694978 100644
--- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TrackFileImportDeviceIdentifierTableWrapper.java
+++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TrackFileImportDeviceIdentifierTableWrapper.java
@@ -1,6 +1,18 @@
package com.sap.sailing.gwt.ui.adminconsole;
+import java.util.HashMap;
+import java.util.Map;
+
+import com.google.gwt.dom.builder.shared.TableCellBuilder;
+import com.google.gwt.dom.builder.shared.TableRowBuilder;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
+import com.google.gwt.safehtml.shared.SafeHtmlUtils;
+import com.google.gwt.user.cellview.client.AbstractCellTable;
+import com.google.gwt.user.cellview.client.AbstractCellTable.Style;
+import com.google.gwt.user.cellview.client.DefaultCellTableBuilder;
+import com.google.gwt.user.cellview.client.RowStyles;
import com.google.gwt.user.cellview.client.TextColumn;
+import com.sap.sailing.domain.common.dto.CompetitorDTO;
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.common.client.DateAndTimeFormatterUtil;
@@ -9,11 +21,13 @@ import com.sap.sse.gwt.client.ErrorReporter;
import com.sap.sse.gwt.client.celltable.EntityIdentityComparator;
import com.sap.sse.gwt.client.celltable.RefreshableSingleSelectionModel;
-public class TrackFileImportDeviceIdentifierTableWrapper extends TableWrapper> {
-
- public TrackFileImportDeviceIdentifierTableWrapper(SailingServiceAsync sailingService, StringMessages stringMessages,
- ErrorReporter errorReporter) {
+public class TrackFileImportDeviceIdentifierTableWrapper extends
+ TableWrapper> {
+ private TextColumn trackNameColumn;
+ private final Map mappings = new HashMap<>();
+
+ public TrackFileImportDeviceIdentifierTableWrapper(SailingServiceAsync sailingService,
+ StringMessages stringMessages, ErrorReporter errorReporter) {
super(sailingService, stringMessages, errorReporter, /* multiSelection */ false, /* enablePager */ true,
new EntityIdentityComparator() {
@Override
@@ -21,54 +35,49 @@ RefreshableSingleSelectionModel> {
TrackFileImportDeviceIdentifierDTO dto2) {
return dto1.uuidAsString.equals(dto2.uuidAsString);
}
+
@Override
public int hashCode(TrackFileImportDeviceIdentifierDTO t) {
return t.uuidAsString.hashCode();
}
});
-
TextColumn uuidColumn = new TextColumn() {
@Override
public String getValue(TrackFileImportDeviceIdentifierDTO object) {
return object.uuidAsString;
}
};
-
TextColumn fileNameColumn = new TextColumn() {
@Override
public String getValue(TrackFileImportDeviceIdentifierDTO object) {
return object.fileName;
}
};
-
- TextColumn trackNameColumn = new TextColumn() {
+ trackNameColumn = new TextColumn() {
@Override
public String getValue(TrackFileImportDeviceIdentifierDTO object) {
return object.trackName;
}
};
-
TextColumn fromColumn = new TextColumn() {
@Override
public String getValue(TrackFileImportDeviceIdentifierDTO object) {
return DateAndTimeFormatterUtil.formatDateAndTime(object.from);
}
};
-
TextColumn toColumn = new TextColumn() {
@Override
public String getValue(TrackFileImportDeviceIdentifierDTO object) {
return DateAndTimeFormatterUtil.formatDateAndTime(object.to);
}
};
-
TextColumn numberOfFixesColumn = new TextColumn() {
@Override
public String getValue(TrackFileImportDeviceIdentifierDTO object) {
return "" + object.numFixes;
}
};
-
+ table.setTableBuilder(new MappingTableBuilder(table));
table.addColumn(uuidColumn, "UUID");
table.addColumn(fileNameColumn, "Filename");
table.addColumn(trackNameColumn, "Trackname");
@@ -77,4 +86,87 @@ RefreshableSingleSelectionModel> {
table.addColumn(numberOfFixesColumn, "# fixes");
}
+ public void removeTrackNameColumn() {
+ table.removeColumn(trackNameColumn);
+ }
+
+ class MappingTableBuilder extends DefaultCellTableBuilder {
+ private final String evenRowStyle;
+ private final String oddRowStyle;
+ private final String cellStyle;
+ private final String evenCellStyle;
+ private final String oddCellStyle;
+ private final String firstColumnStyle;
+ private final AbstractCellTable cellTable;
+
+ public MappingTableBuilder(AbstractCellTable cellTable) {
+ super(cellTable);
+ this.cellTable = cellTable;
+ Style style = cellTable.getResources().style();
+ evenRowStyle = style.evenRow();
+ oddRowStyle = style.oddRow();
+ cellStyle = style.cell();
+ evenCellStyle = " " + style.evenRowCell();
+ oddCellStyle = " " + style.oddRowCell();
+ firstColumnStyle = " " + style.firstColumn();
+ }
+
+ @Override
+ public void buildRowImpl(TrackFileImportDeviceIdentifierDTO rowValue, int absRowIndex) {
+ super.buildRowImpl(rowValue, absRowIndex);
+ boolean isEven = absRowIndex % 2 == 0;
+
+ StringBuilder trClasses = new StringBuilder(isEven ? evenRowStyle : oddRowStyle);
+ RowStyles rowStyles = cellTable.getRowStyles();
+ if (rowStyles != null) {
+ String extraRowStyles = rowStyles.getStyleNames(rowValue, absRowIndex);
+ if (extraRowStyles != null) {
+ trClasses.append(" ").append(extraRowStyles);
+ }
+ }
+
+ StringBuilder tdClasses = new StringBuilder(cellStyle);
+ tdClasses.append(isEven ? evenCellStyle : oddCellStyle);
+
+ TableRowBuilder tr = startRow();
+ tr.className(trClasses.toString());
+ tr.startTD().className(new StringBuilder(tdClasses).append(firstColumnStyle).toString())
+ .html(SafeHtmlUtils.fromSafeConstant(" ")).end();
+ TableCellBuilder td = tr.startTD();
+ td.className(tdClasses.toString());
+ td.colSpan(getColumns().size() - 1);
+ CompetitorDTO competitorDTO = mappings.get(rowValue);
+ if (competitorDTO == null) {
+ td.html(new SafeHtmlBuilder().appendEscaped("--").toSafeHtml());
+ } else {
+ td.html(new SafeHtmlBuilder().appendEscaped(competitorDTO.getName()).toSafeHtml());
+ }
+ td.end();
+ tr.end();
+ }
+ }
+
+ public CompetitorDTO getMappedCompetitorForCurrentSelection() {
+ TrackFileImportDeviceIdentifierDTO selectedObject = getSelectionModel().getSelectedObject();
+ if (selectedObject != null) {
+ return mappings.get(selectedObject);
+ }
+ return null;
+ }
+
+ public void didSelectCompetitorForMapping(CompetitorDTO competitor) {
+ TrackFileImportDeviceIdentifierDTO selectedObject = getSelectionModel().getSelectedObject();
+ if (selectedObject != null) {
+ if (competitor != null) {
+ mappings.put(selectedObject, competitor);
+ } else {
+ mappings.remove(selectedObject);
+ }
+ }
+ table.redraw();
+ }
+
+ public Map getMappings() {
+ return mappings;
+ }
}
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboardedit/LeaderboardEditPage.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboardedit/LeaderboardEditPage.java
index 47ceeda55ed..fdc69aa2711 100644
--- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboardedit/LeaderboardEditPage.java
+++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboardedit/LeaderboardEditPage.java
@@ -1,71 +1,71 @@
-package com.sap.sailing.gwt.ui.leaderboardedit;
-
-import java.util.List;
-
-import com.google.gwt.dom.client.Style.Position;
-import com.google.gwt.dom.client.Style.Unit;
-import com.google.gwt.user.client.Window;
-import com.google.gwt.user.client.rpc.AsyncCallback;
-import com.google.gwt.user.client.ui.Label;
-import com.google.gwt.user.client.ui.RootPanel;
-import com.google.gwt.user.client.ui.Widget;
-import com.sap.sailing.domain.common.security.Permission;
-import com.sap.sailing.domain.common.security.SailingPermissionsForRoleProvider;
-import com.sap.sailing.gwt.common.authentication.FixedSailingAuthentication;
-import com.sap.sailing.gwt.ui.client.AbstractSailingEntryPoint;
-import com.sap.sse.gwt.client.async.AsyncActionsExecutor;
-import com.sap.sse.gwt.client.async.MarkedAsyncCallback;
-import com.sap.sse.security.ui.authentication.decorator.AuthorizedContentDecorator;
-import com.sap.sse.security.ui.authentication.decorator.WidgetFactory;
-import com.sap.sse.security.ui.authentication.generic.GenericAuthentication;
-import com.sap.sse.security.ui.authentication.generic.GenericAuthorizedContentDecorator;
-import com.sap.sse.security.ui.authentication.generic.sapheader.SAPHeaderWithAuthentication;
-
-public class LeaderboardEditPage extends AbstractSailingEntryPoint {
- @Override
- protected void doOnModuleLoad() {
- super.doOnModuleLoad();
-
- sailingService.getLeaderboardNames(new MarkedAsyncCallback>(
- new AsyncCallback>() {
- @Override
- public void onSuccess(List leaderboardNames) {
- final String leaderboardName = Window.Location.getParameter("name");
- if (leaderboardNames.contains(leaderboardName)) {
-
- SAPHeaderWithAuthentication header = initHeader();
- GenericAuthentication genericSailingAuthentication = new FixedSailingAuthentication(getUserService(), header.getAuthenticationMenuView());
- AuthorizedContentDecorator authorizedContentDecorator = new GenericAuthorizedContentDecorator(genericSailingAuthentication);
- authorizedContentDecorator.setPermissionToCheck(Permission.MANAGE_LEADERBOARD_RESULTS, SailingPermissionsForRoleProvider.INSTANCE);
- authorizedContentDecorator.setContentWidgetFactory(new WidgetFactory() {
- @Override
- public Widget get() {
- EditableLeaderboardPanel leaderboardPanel = new EditableLeaderboardPanel(sailingService, new AsyncActionsExecutor(), leaderboardName, null,
- LeaderboardEditPage.this, getStringMessages(), userAgent);
- leaderboardPanel.ensureDebugId("EditableLeaderboardPanel");
- return leaderboardPanel;
- }
- });
-
- RootPanel.get().add(authorizedContentDecorator);
- RootPanel.get().add(header);
- } else {
- RootPanel.get().add(new Label(getStringMessages().noSuchLeaderboard()));
- }
- }
- @Override
- public void onFailure(Throwable t) {
- reportError("Error trying to obtain list of leaderboard names: "+t.getMessage());
- }
- }));
- }
-
- private SAPHeaderWithAuthentication initHeader() {
- SAPHeaderWithAuthentication header = new SAPHeaderWithAuthentication(getStringMessages().sapSailingAnalytics(),
- getStringMessages().editScores());
- header.getElement().getStyle().setPosition(Position.FIXED);
- header.getElement().getStyle().setTop(0, Unit.PX);
- header.getElement().getStyle().setWidth(100, Unit.PCT);
- return header;
- }
-}
+package com.sap.sailing.gwt.ui.leaderboardedit;
+
+import java.util.List;
+
+import com.google.gwt.dom.client.Style.Position;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.rpc.AsyncCallback;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.RootPanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.sap.sailing.domain.common.security.Permission;
+import com.sap.sailing.domain.common.security.SailingPermissionsForRoleProvider;
+import com.sap.sailing.gwt.common.authentication.FixedSailingAuthentication;
+import com.sap.sailing.gwt.common.authentication.SAPSailingHeaderWithAuthentication;
+import com.sap.sailing.gwt.ui.client.AbstractSailingEntryPoint;
+import com.sap.sse.gwt.client.async.AsyncActionsExecutor;
+import com.sap.sse.gwt.client.async.MarkedAsyncCallback;
+import com.sap.sse.security.ui.authentication.decorator.AuthorizedContentDecorator;
+import com.sap.sse.security.ui.authentication.decorator.WidgetFactory;
+import com.sap.sse.security.ui.authentication.generic.GenericAuthentication;
+import com.sap.sse.security.ui.authentication.generic.GenericAuthorizedContentDecorator;
+import com.sap.sse.security.ui.authentication.generic.sapheader.SAPHeaderWithAuthentication;
+
+public class LeaderboardEditPage extends AbstractSailingEntryPoint {
+ @Override
+ protected void doOnModuleLoad() {
+ super.doOnModuleLoad();
+
+ sailingService.getLeaderboardNames(new MarkedAsyncCallback>(
+ new AsyncCallback>() {
+ @Override
+ public void onSuccess(List leaderboardNames) {
+ final String leaderboardName = Window.Location.getParameter("name");
+ if (leaderboardNames.contains(leaderboardName)) {
+
+ SAPHeaderWithAuthentication header = initHeader();
+ GenericAuthentication genericSailingAuthentication = new FixedSailingAuthentication(getUserService(), header.getAuthenticationMenuView());
+ AuthorizedContentDecorator authorizedContentDecorator = new GenericAuthorizedContentDecorator(genericSailingAuthentication);
+ authorizedContentDecorator.setPermissionToCheck(Permission.MANAGE_LEADERBOARD_RESULTS, SailingPermissionsForRoleProvider.INSTANCE);
+ authorizedContentDecorator.setContentWidgetFactory(new WidgetFactory() {
+ @Override
+ public Widget get() {
+ EditableLeaderboardPanel leaderboardPanel = new EditableLeaderboardPanel(sailingService, new AsyncActionsExecutor(), leaderboardName, null,
+ LeaderboardEditPage.this, getStringMessages(), userAgent);
+ leaderboardPanel.ensureDebugId("EditableLeaderboardPanel");
+ return leaderboardPanel;
+ }
+ });
+
+ RootPanel.get().add(authorizedContentDecorator);
+ RootPanel.get().add(header);
+ } else {
+ RootPanel.get().add(new Label(getStringMessages().noSuchLeaderboard()));
+ }
+ }
+ @Override
+ public void onFailure(Throwable t) {
+ reportError("Error trying to obtain list of leaderboard names: "+t.getMessage());
+ }
+ }));
+ }
+
+ private SAPHeaderWithAuthentication initHeader() {
+ SAPSailingHeaderWithAuthentication header = new SAPSailingHeaderWithAuthentication(getStringMessages().editScores());
+ header.getElement().getStyle().setPosition(Position.FIXED);
+ header.getElement().getStyle().setTop(0, Unit.PX);
+ header.getElement().getStyle().setWidth(100, Unit.PCT);
+ return header;
+ }
+}
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java
index 7fc9263cb34..420a98df543 100644
--- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java
+++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java
@@ -1139,7 +1139,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
* can be obtained within this timeout, wind for the race will be tracked; otherwise, the method returns without
* taking any effect.
*/
- private void startTrackingWind(RaceHandle raceHandle, boolean correctByDeclination, long timeoutInMilliseconds) throws Exception {
+ private void startTrackingWind(RaceHandle raceHandle, boolean correctByDeclination, long timeoutInMilliseconds) {
Regatta regatta = raceHandle.getRegatta();
if (regatta != null) {
RaceDefinition race = raceHandle.getRace(timeoutInMilliseconds);
@@ -2173,7 +2173,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
} else {
actualTimePoint = timePoint;
}
- RaceDefinition race = trackedRace.getRace();
+ final RaceDefinition race = trackedRace.getRace();
int rank = 1;
final List competitorsFromBestToWorst = trackedRace.getCompetitorsFromBestToWorst(actualTimePoint);
for (Competitor competitor : competitorsFromBestToWorst) {
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SimulatorServiceImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SimulatorServiceImpl.java
index 2a2a72e011f..6f6fba2e3e7 100644
--- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SimulatorServiceImpl.java
+++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SimulatorServiceImpl.java
@@ -10,10 +10,7 @@ import java.util.Map.Entry;
import java.util.NavigableMap;
import java.util.Set;
import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Executor;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
+import java.util.concurrent.ScheduledExecutorService;
import java.util.logging.Logger;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;
@@ -86,7 +83,7 @@ import com.sap.sse.common.Duration;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util;
import com.sap.sse.common.impl.MillisecondsTimePoint;
-import com.sap.sse.util.impl.ThreadFactoryWithPriority;
+import com.sap.sse.util.ThreadPoolUtil;
public class SimulatorServiceImpl extends RemoteServiceServlet implements SimulatorService {
@@ -107,12 +104,7 @@ public class SimulatorServiceImpl extends RemoteServiceServlet implements Simula
private SpeedWithBearing averageWind = null;
public SimulatorServiceImpl() {
- final int THREAD_POOL_SIZE = Math.max(Runtime.getRuntime().availableProcessors()/2, 3);
- Executor simulatorExecutor = new ThreadPoolExecutor(/* corePoolSize */THREAD_POOL_SIZE,
- /* maximumPoolSize */THREAD_POOL_SIZE,
- /* keepAliveTime */60, TimeUnit.SECONDS,
- /* workQueue */new LinkedBlockingQueue(),
- new ThreadFactoryWithPriority(Thread.NORM_PRIORITY, /* daemon */ true));
+ final ScheduledExecutorService simulatorExecutor = ThreadPoolUtil.INSTANCE.getDefaultForegroundTaskThreadPoolExecutor();
// TODO: initialize smart-future-cache for simulation-results and add to simulation-service
simulationService = SimulationServiceFactory.INSTANCE.getService(simulatorExecutor, null);
}
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/SimulatorEntryPoint.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/SimulatorEntryPoint.java
index 4029f742f53..5f5561271f9 100644
--- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/SimulatorEntryPoint.java
+++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/SimulatorEntryPoint.java
@@ -10,6 +10,7 @@ import com.google.gwt.user.client.ui.DockLayoutPanel;
import com.google.gwt.user.client.ui.RootLayoutPanel;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.gwt.common.authentication.FixedSailingAuthentication;
+import com.sap.sailing.gwt.common.authentication.SAPSailingHeaderWithAuthentication;
import com.sap.sailing.gwt.ui.client.AbstractSailingEntryPoint;
import com.sap.sailing.gwt.ui.client.RemoteServiceMappingConstants;
import com.sap.sailing.gwt.ui.client.SimulatorService;
@@ -21,7 +22,6 @@ import com.sap.sse.security.ui.authentication.decorator.AuthorizedContentDecorat
import com.sap.sse.security.ui.authentication.decorator.WidgetFactory;
import com.sap.sse.security.ui.authentication.generic.GenericAuthentication;
import com.sap.sse.security.ui.authentication.generic.GenericAuthorizedContentDecorator;
-import com.sap.sse.security.ui.authentication.generic.sapheader.SAPHeaderWithAuthentication;
public class SimulatorEntryPoint extends AbstractSailingEntryPoint {
@@ -187,8 +187,7 @@ public class SimulatorEntryPoint extends AbstractSailingEntryPoint {
}
private void createSimulatorPanel() {
- SAPHeaderWithAuthentication header = new SAPHeaderWithAuthentication(getStringMessages()
- .sapSailingAnalytics(), getStringMessages().strategySimulatorTitle());
+ SAPSailingHeaderWithAuthentication header = new SAPSailingHeaderWithAuthentication(getStringMessages().strategySimulatorTitle());
GenericAuthentication genericSailingAuthentication = new FixedSailingAuthentication(getUserService(), header.getAuthenticationMenuView());
AuthorizedContentDecorator authorizedContentDecorator = new GenericAuthorizedContentDecorator(genericSailingAuthentication);
diff --git a/java/com.sap.sailing.manage2sail.resultimport.test/resources/YES_29er_XRR.xml b/java/com.sap.sailing.manage2sail.resultimport.test/resources/YES_29er_XRR.xml
index 6415684c895..7eb876b64f4 100644
--- a/java/com.sap.sailing.manage2sail.resultimport.test/resources/YES_29er_XRR.xml
+++ b/java/com.sap.sailing.manage2sail.resultimport.test/resources/YES_29er_XRR.xml
@@ -218,7 +218,7 @@
-
+
diff --git a/java/com.sap.sailing.manage2sail.resultimport.test/src/com/sap/sailing/manage2sail/resultimport/test/ParserTest.java b/java/com.sap.sailing.manage2sail.resultimport.test/src/com/sap/sailing/manage2sail/resultimport/test/ParserTest.java
index c9587971f1f..5a957651261 100755
--- a/java/com.sap.sailing.manage2sail.resultimport.test/src/com/sap/sailing/manage2sail/resultimport/test/ParserTest.java
+++ b/java/com.sap.sailing.manage2sail.resultimport.test/src/com/sap/sailing/manage2sail/resultimport/test/ParserTest.java
@@ -2,6 +2,7 @@ package com.sap.sailing.manage2sail.resultimport.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
import static org.mockito.Mockito.mock;
import java.io.File;
@@ -10,7 +11,9 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Date;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -22,7 +25,9 @@ import javax.xml.parsers.ParserConfigurationException;
import org.junit.Test;
import org.xml.sax.SAXException;
+import com.sap.sailing.domain.common.MaxPointsReason;
import com.sap.sailing.domain.common.RegattaScoreCorrections;
+import com.sap.sailing.domain.common.RegattaScoreCorrections.ScoreCorrectionForCompetitorInRace;
import com.sap.sailing.domain.common.RegattaScoreCorrections.ScoreCorrectionsForRace;
import com.sap.sailing.domain.persistence.DomainObjectFactory;
import com.sap.sailing.domain.persistence.MongoObjectFactory;
@@ -128,11 +133,52 @@ public class ParserTest {
}
assertNotNull(resultFor29er);
+ int numberOfRaceColumnsWithScores = 8; // R9 is in 'planned' state and has no results
RegattaScoreCorrections _29erResult = scoreCorrectionProvider.getScoreCorrections(YES_EVENT_NAME, ISAF_ID_29ER , resultFor29er.getB());
assertNotNull(_29erResult);
Iterable scoreCorrectionsForRaces = _29erResult.getScoreCorrectionsForRaces();
assertNotNull(scoreCorrectionsForRaces);
- assertEquals(8, Util.size(scoreCorrectionsForRaces));
+ assertEquals(numberOfRaceColumnsWithScores, Util.size(scoreCorrectionsForRaces));
+
+ Set raceNamesOrNumbers = new HashSet<>();
+ for (ScoreCorrectionsForRace scoreCorrectionForRace : scoreCorrectionsForRaces) {
+ raceNamesOrNumbers.add(scoreCorrectionForRace.getRaceNameOrNumber());
+ }
+ assertEquals(new HashSet(Arrays.asList(new String[] { "1", "2", "3", "4", "5", "6", "7", "8" })), raceNamesOrNumbers);
+ {
+ final ScoreCorrectionsForRace resultsForR2 = Util.get(scoreCorrectionsForRaces, 1);
+ ScoreCorrectionForCompetitorInRace scoreCorrectionR2ForDEN63 = resultsForR2.getScoreCorrectionForCompetitor("DEN 63");
+ assertEquals(0, scoreCorrectionR2ForDEN63.getPoints(), 29.00000001);
+ assertSame(MaxPointsReason.UFD, scoreCorrectionR2ForDEN63.getMaxPointsReason());
+ }
+ {
+ final ScoreCorrectionsForRace resultsForR5 = Util.get(scoreCorrectionsForRaces, 4);
+ ScoreCorrectionForCompetitorInRace scoreCorrectionR5ForDEN63 = resultsForR5.getScoreCorrectionForCompetitor("DEN 63");
+ assertEquals(0, scoreCorrectionR5ForDEN63.getPoints(), 35.00000001);
+ assertSame(MaxPointsReason.BFD, scoreCorrectionR5ForDEN63.getMaxPointsReason());
+ }
+
}
+/**
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ */
}
diff --git a/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/serialization/impl/FlatGPSFixJsonSerializer.java b/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/serialization/impl/FlatGPSFixJsonSerializer.java
index 86059c4429f..689a03fe977 100644
--- a/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/serialization/impl/FlatGPSFixJsonSerializer.java
+++ b/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/serialization/impl/FlatGPSFixJsonSerializer.java
@@ -17,8 +17,12 @@ public class FlatGPSFixJsonSerializer implements JsonSerializer {
result.put(FIELD_LON_DEG, object.getPosition().getLngDeg());
result.put(FIELD_LAT_DEG, object.getPosition().getLatDeg());
result.put(FIELD_TIME_MILLIS, object.getTimePoint().asMillis());
- // TODO the default accuracy is 1.0; when fixes ever have an accuracy, report it here
- result.put(FIELD_ACCURACY, 1.0);
+ // TODO Insert Accuracy if someday available from the server
+ // Buoy Pinger app will interpret no accuracy in JSON message as MarkPingInfo.NOT_SET_BY_USER (-1) and store that in DB
+ // For the user these values are displayed as R.string.unknown ("n/a");
+ // (CheckinManager in Buoy Pinger can accept messages with and without accuracy)
+
+ // result.put(FIELD_ACCURACY, XXXXXXXXX);
return result;
}
}
diff --git a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/trackfiles/impl/GPSFixImportTest.java b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/trackfiles/impl/GPSFixImportTest.java
index f1092ae3abd..b9f844c78f5 100755
--- a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/trackfiles/impl/GPSFixImportTest.java
+++ b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/trackfiles/impl/GPSFixImportTest.java
@@ -4,11 +4,15 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.io.BufferedInputStream;
+import java.io.File;
import java.io.IOException;
import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import java.util.Collection;
+import org.apache.commons.fileupload.FileItem;
import org.junit.Test;
import com.sap.sailing.domain.common.tracking.GPSFix;
@@ -30,32 +34,33 @@ public class GPSFixImportTest {
@Test
public void testReReadingEOFFromBufferedInputStream() throws IOException {
- InputStream in = getClass().getResourceAsStream("/Cardiff Race17 - COMPETITORS.gpx");
- BufferedInputStreamWithPublicBuffer bis = new BufferedInputStreamWithPublicBuffer(in);
- bis.mark(10*1024*1024); // the file has ~2MB, so 10MB is much more than twice the size, so should never be required
- int size = 0;
- while (bis.read() != -1) {
- size++;
+ try (InputStream in = getClass().getResourceAsStream("/Cardiff Race17 - COMPETITORS.gpx");
+ BufferedInputStreamWithPublicBuffer bis = new BufferedInputStreamWithPublicBuffer(in)) {
+ bis.mark(10 * 1024 * 1024); // the file has ~2MB, so 10MB is much more than twice the size, so should never
+ // be required
+ int size = 0;
+ while (bis.read() != -1) {
+ size++;
+ }
+ bis.reset();
+ int secondSize = 0;
+ while (bis.read() != -1) {
+ secondSize++;
+ }
+ assertEquals(size, secondSize);
+ assertTrue(bis.getBuffer().length < 8 * 1024 * 1024);
+ bis.reset();
+ int thirdSize = 0;
+ while (bis.read() != -1) {
+ thirdSize++;
+ }
+ assertEquals(size, thirdSize);
+ assertTrue(bis.getBuffer().length < 8 * 1024 * 1024);
+ // Now try to read EOF multiple times
+ for (int i = 1; i < 1000; i++) {
+ assertEquals(-1, bis.read());
+ }
}
- bis.reset();
- int secondSize = 0;
- while (bis.read() != -1) {
- secondSize++;
- }
- assertEquals(size, secondSize);
- assertTrue(bis.getBuffer().length < 8*1024*1024);
- bis.reset();
- int thirdSize = 0;
- while (bis.read() != -1) {
- thirdSize++;
- }
- assertEquals(size, thirdSize);
- assertTrue(bis.getBuffer().length < 8*1024*1024);
- // Now try to read EOF multiple times
- for (int i=1; i<1000; i++) {
- assertEquals(-1, bis.read());
- }
- bis.close();
}
@Test
@@ -73,8 +78,81 @@ public class GPSFixImportTest {
public void storeFix(GPSFix fix, DeviceIdentifier deviceIdentifier) {
}
};
- InputStream in = getClass().getResourceAsStream("/Cardiff Race17 - COMPETITORS.gpx");
- servlet.importFiles(Arrays.asList(new Pair<>("test.gpx", in)), new AlwaysFailingGPSFixImporter(-1));
+ FileItem fi = new FileItem() {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public void write(File file) throws Exception {
+ }
+
+ @Override
+ public void setFormField(boolean state) {
+ }
+
+ @Override
+ public void setFieldName(String name) {
+ }
+
+ @Override
+ public boolean isInMemory() {
+ return false;
+ }
+
+ @Override
+ public boolean isFormField() {
+ return false;
+ }
+
+ @Override
+ public String getString(String encoding) throws UnsupportedEncodingException {
+ return null;
+ }
+
+ @Override
+ public String getString() {
+ return null;
+ }
+
+ @Override
+ public long getSize() {
+ return 0;
+ }
+
+ @Override
+ public OutputStream getOutputStream() throws IOException {
+ return null;
+ }
+
+ @Override
+ public String getName() {
+ return "Cardiff Race17 - COMPETITORS.gpx";
+ }
+
+ @Override
+ public InputStream getInputStream() throws IOException {
+ return getClass().getResourceAsStream("/" + getName());
+ }
+
+ @Override
+ public String getFieldName() {
+ return null;
+ }
+
+ @Override
+ public String getContentType() {
+ return null;
+ }
+
+ @Override
+ public byte[] get() {
+ return null;
+ }
+
+ @Override
+ public void delete() {
+ }
+ };
+ servlet.importFiles(Arrays.asList(new Pair<>("test.gpx", fi)), new AlwaysFailingGPSFixImporter(-1));
// getting to here without errors is good enough
}
}
diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/GPSFixesResource.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/GPSFixesResource.java
index f8ef67471ec..6109f18251a 100644
--- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/GPSFixesResource.java
+++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/GPSFixesResource.java
@@ -52,9 +52,7 @@ public class GPSFixesResource extends AbstractSailingServerResource {
List fixes = data.getB();
try {
- for (GPSFixMoving fix : fixes) {
- getService().getSensorFixStore().storeFix(device, fix);
- }
+ getService().getSensorFixStore().storeFixes(device, fixes);
logger.log(Level.INFO, "Added " + fixes.size() + " fixes for device " + device.toString() + " to store");
} catch (NoCorrespondingServiceRegisteredException e) {
logger.log(Level.WARNING, "Could not store fix for device " + device);
@@ -62,5 +60,4 @@ public class GPSFixesResource extends AbstractSailingServerResource {
return Response.ok().build();
}
-}
-
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/trackfiles/impl/SensorDataImportServlet.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/trackfiles/impl/SensorDataImportServlet.java
index 422f0c50572..cf1a661dccd 100644
--- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/trackfiles/impl/SensorDataImportServlet.java
+++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/trackfiles/impl/SensorDataImportServlet.java
@@ -2,7 +2,6 @@ package com.sap.sailing.server.gateway.trackfiles.impl;
import java.io.BufferedInputStream;
import java.io.IOException;
-import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
@@ -32,13 +31,12 @@ import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util.Pair;
/**
- * Import servlet for sensor data files. Importers are located through the OSGi service registry and mathed against the
- * name provided by the upload formular.
+ * Import servlet for sensor data files. Importers are located through the OSGi service registry and matched against the
+ * name provided by the upload form.
*/
public class SensorDataImportServlet extends AbstractFileUploadServlet {
private static final long serialVersionUID = 1120226743039934620L;
private static final Logger logger = Logger.getLogger(SensorDataImportServlet.class.getName());
- private static final int READ_BUFFER_SIZE = 1024 * 1024 * 1024;
public void storeFixes(Iterable fixes, DeviceIdentifier deviceIdentifier) {
try {
@@ -54,21 +52,16 @@ public class SensorDataImportServlet extends AbstractFileUploadServlet {
*
* @throws IOException
*/
- private Iterable importFiles(Iterable> files)
+ private Iterable importFiles(Iterable> files)
throws IOException {
final Set deviceIds = new HashSet<>();
final Map from = new HashMap<>();
final Map