Merge branch 'master' into translation

This commit is contained in:
Axel Uhl committed 2016-09-29 17:18:19 +02:00
commit 7bdc6e61d1
105 files changed
+3306 -1126

No files matched your search

+148 -146
View File
@@ -1,146 +1,148 @@
# Welcome to the SAP Sailing Wiki # Welcome to the SAP Sailing Wiki
This is the <img src="https://www.sapsailing.com/images/sap-logo_grey.png"/> Wiki where useful information regarding this project can be found. This is the <img src="https://www.sapsailing.com/images/sap-logo_grey.png"/> Wiki where useful information regarding this project can be found.
### The Pitch ### 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. 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. 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 # Table of Contents
## Information ## Information
### General ### General
* [[Information about this Wiki and HowTo|wiki/info/general/wiki]] * [[Information about this Wiki and HowTo|wiki/info/general/wiki]]
* [[General Project Information|wiki/info/general/general-information]] * [[General Project Information|wiki/info/general/general-information]]
* [[Inventory|wiki/info/general/inventar-liste]] * [[Inventory|wiki/info/general/inventar-liste]]
### Landscape and Development ### Landscape and Development
* [[Architecture and Infrastructure|wiki/info/landscape/architecture-and-infrastructure]] * [[Architecture and Infrastructure|wiki/info/landscape/architecture-and-infrastructure]]
* [[Runtime Environment|wiki/info/landscape/runtime-environment]] * [[Runtime Environment|wiki/info/landscape/runtime-environment]]
* [[Basic architectual principles|wiki/info/landscape/basic-architectural-principles]] * [[Basic architectual principles|wiki/info/landscape/basic-architectural-principles]]
* [[User Management|wiki/info/landscape/usermanagement]] * [[User Management|wiki/info/landscape/usermanagement]]
* [[Development Environment|wiki/info/landscape/development-environment]] * [[Development Environment|wiki/info/landscape/development-environment]]
* [[Production Environment|wiki/info/landscape/production-environment]] * [[Production Environment|wiki/info/landscape/production-environment]]
* [[Typical Development Scenarios|wiki/info/landscape/typical-development-scenarios]] * [[Typical Development Scenarios|wiki/info/landscape/typical-development-scenarios]]
* [[RaceLog Tracking Server Architecture|wiki/info/landscape/server]] * [[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]] * Environment Overview [[PDF|wiki/info/mobile/event-tracking/architecture.pdf]] | [[SVG|wiki/info/mobile/event-tracking/architecture.svg]]
* Amazon * Amazon
* [[Amazon EC2|wiki/info/landscape/amazon-ec2]] * [[Amazon EC2|wiki/info/landscape/amazon-ec2]]
* [[EC2 Backup Strategy|wiki/info/landscape/amazon-ec2-backup-strategy]] * [[EC2 Backup Strategy|wiki/info/landscape/amazon-ec2-backup-strategy]]
* [[Creating an EC2 image from scratch|wiki/info/landscape/creating-ec2-image-from-scratch]] * [[Creating an EC2 image from scratch|wiki/info/landscape/creating-ec2-image-from-scratch]]
* [[EC2 mail relaying|wiki/info/landscape/mail-relaying]] * [[EC2 mail relaying|wiki/info/landscape/mail-relaying]]
* [[Building and Deploying|wiki/info/landscape/building-and-deploying]] * [[Building and Deploying|wiki/info/landscape/building-and-deploying]]
* [[Data Mining Architecture|wiki/info/landscape/data-mining-architecture]] * [[Data Mining Architecture|wiki/info/landscape/data-mining-architecture]]
* [[Typical Data Mining Scenarios|wiki/info/landscape/typical-data-mining-scenarios]] * [[Typical Data Mining Scenarios|wiki/info/landscape/typical-data-mining-scenarios]]
* [[Create clickable UI prototypes with Axure|wiki/info/landscape/ui-clickable-prototypes]] * [[Create clickable UI prototypes with Axure|wiki/info/landscape/ui-clickable-prototypes]]
* [[Webdesign|wiki/info/landscape/webdesign]] * [[Webdesign|wiki/info/landscape/webdesign]]
### Mobile ### Mobile
* [[Mobile Development|wiki/info/mobile/mobile-development]] * [[Mobile Development|wiki/info/mobile/mobile-development]]
* Tracking App * Tracking App
* [[Tracking App Specification|wiki/info/mobile/app-spec/app-spec]] * [[Tracking App Specification|wiki/info/mobile/app-spec/app-spec]]
* [[Event Tracking|wiki/info/mobile/event-tracking/event-tracking]] * [[Event Tracking|wiki/info/mobile/event-tracking/event-tracking]]
* [[Steps for setting up Smartphone Tracking|wiki/info/mobile/smartphone-tracking-steps]] * [[Steps for setting up Smartphone Tracking|wiki/info/mobile/smartphone-tracking-steps]]
* [[Tracking App Prototype Architecture|wiki/info/mobile/app]] * [[Tracking App Prototype Architecture|wiki/info/mobile/app]]
* Racecommittee App * Racecommittee App
* [[Racecommittee App|wiki/info/mobile/racecommittee-app]] * [[Racecommittee App|wiki/info/mobile/racecommittee-app]]
* [[Environment|wiki/info/mobile/racecommittee-app-environment]] * [[Environment|wiki/info/mobile/racecommittee-app-environment]]
* [[Administrator|wiki/info/mobile/racecommittee-app-administrator]] * [[Administrator|wiki/info/mobile/racecommittee-app-administrator]]
* [[User|wiki/info/mobile/racecommittee-app-user]] * [[User|wiki/info/mobile/racecommittee-app-user]]
* [[Android and Release Build|wiki/info/mobile/android-and-release-build]] * [[Android and Release Build|wiki/info/mobile/android-and-release-build]]
* [[Mobile Sailing Analytics|wiki/info/mobile/mobilesailinganalytics]] * [[Energy consumption of mobile apps|wiki/info/mobile/energy-consumption]]
* [[Push Notifications|wiki/info/mobile/push-notifications]] * [[Data consumption of mobile apps|wiki/info/mobile/data-consumption]]
* [[Mobile Sailing Analytics|wiki/info/mobile/mobilesailinganalytics]]
### API * [[Push Notifications|wiki/info/mobile/push-notifications]]
* [[Web Services API|wiki/info/api/sailing-webservices]] ### API
* [[API v1|wiki/info/api/api-v1]]
* [[Tracking App API|wiki/info/api/tracking-app-api]] * [[Web Services API|wiki/info/api/sailing-webservices]]
* [[Training API v1 draft|wiki/info/api/training-api-v1-draft]] * [[API v1|wiki/info/api/api-v1]]
* [[Tracking App API|wiki/info/api/tracking-app-api]]
### Security * [[Training API v1 draft|wiki/info/api/training-api-v1-draft]]
* [[Fortify Tests|wiki/info/security/fortify]]
* [[SSL / HTTPS Support|wiki/info/security/ssl-support]] ### Security
* [[Fortify Tests|wiki/info/security/fortify]]
### Miscellaneous * [[SSL / HTTPS Support|wiki/info/security/ssl-support]]
* [[Big Data - Numbers|wiki/info/misc/big-data-numbers]] ### Miscellaneous
* [[Data Quality|wiki/info/misc/data-quality]]
* [[Sailing Domain Algorithms|wiki/info/misc/sailing-domain-algorithms]] * [[Big Data - Numbers|wiki/info/misc/big-data-numbers]]
* [[Google Analytics (Web Page Tracking)|wiki/info/misc/ganalytics]] * [[Data Quality|wiki/info/misc/data-quality]]
* [[S3 Development Sample|wiki/info/misc/s3-sample]] * [[Sailing Domain Algorithms|wiki/info/misc/sailing-domain-algorithms]]
* [[Google Analytics (Web Page Tracking)|wiki/info/misc/ganalytics]]
* [[FAQ for League Operators|wiki/info/misc/league-operators-faq]] * [[S3 Development Sample|wiki/info/misc/s3-sample]]
* [[Prionet related information|wiki/info/misc/prionet-related-information]]
* [[TracTrac Workshop 2013|wiki/info/misc/tractracworkshop2013]] * [[FAQ for League Operators|wiki/info/misc/league-operators-faq]]
* [[Prionet related information|wiki/info/misc/prionet-related-information]]
## HowTo * [[TracTrac Workshop 2013|wiki/info/misc/tractracworkshop2013]]
* [[Onboarding|wiki/howto/onboarding]] ## HowTo
* [[Onboarding|wiki/howto/onboarding]]
### Development
* [[Create boat graphics for the 2D race viewer|wiki/howto/development/boatgraphicssvg]] ### Development
* [[Continuous Integration with Hudson/Jenkins|wiki/howto/development/ci]]
* [[Dispatch|wiki/howto/development/dispatch]] * [[Create boat graphics for the 2D race viewer|wiki/howto/development/boatgraphicssvg]]
* [[Working with GWT UI Binder|wiki/howto/development/gwt-ui-binder]] * [[Continuous Integration with Hudson/Jenkins|wiki/howto/development/ci]]
* [[Java De(Serialization) and Circular Dependencies|wiki/howto/development/java-de-serialization-and-circular-dependencies]] * [[Dispatch|wiki/howto/development/dispatch]]
* [[JMX Support|wiki/howto/development/jmx]] * [[Working with GWT UI Binder|wiki/howto/development/gwt-ui-binder]]
* [[Working with GWT Locally|wiki/howto/development/local-gwt]] * [[Java De(Serialization) and Circular Dependencies|wiki/howto/development/java-de-serialization-and-circular-dependencies]]
* [[Log File Analysis|wiki/howto/development/log-file-analysis]] * [[JMX Support|wiki/howto/development/jmx]]
* [[UI Tests with Selenium|wiki/howto/development/selenium-ui-tests]] * [[Working with GWT Locally|wiki/howto/development/local-gwt]]
* [[Profiling|wiki/howto/development/profiling]] * [[Log File Analysis|wiki/howto/development/log-file-analysis]]
* [[Working with GWT Super Dev Mode|wiki/howto/development/super-dev-mode]] * [[UI Tests with Selenium|wiki/howto/development/selenium-ui-tests]]
* [[Profiling|wiki/howto/development/profiling]]
### For Event Managers * [[Working with GWT Super Dev Mode|wiki/howto/development/super-dev-mode]]
* [[Set up local network with replication server|wiki/howto/eventmanagers/event-network-with-replica]] ### For Event Managers
* [[Operating Igtimi WindBots|wiki/howto/eventmanagers/windbot-operations]]
* [[Linking Race Videos|wiki/howto/eventmanagers/linking-race-videos]] * [[Set up local network with replication server|wiki/howto/eventmanagers/event-network-with-replica]]
* [[Import official results|wiki/howto/eventmanagers/results-import]] * [[Operating Igtimi WindBots|wiki/howto/eventmanagers/windbot-operations]]
* [[Linking Race Videos|wiki/howto/eventmanagers/linking-race-videos]]
### Setup * [[Import official results|wiki/howto/eventmanagers/results-import]]
* [[Configure Races on Server|wiki/howto/setup/configure-races-on-server]] ### Setup
* [[Setting up internal Jenkins on SAP Monsoon|wiki/howto/setup/setting-up-jenkins-on-sap-monsoon]]
* [[Configure Races on Server|wiki/howto/setup/configure-races-on-server]]
### Miscellaneous * [[Setting up internal Jenkins on SAP Monsoon|wiki/howto/setup/setting-up-jenkins-on-sap-monsoon]]
* [[Cook Book|wiki/howto/misc/cook-book]] ### Miscellaneous
* [[Polars|wiki/howto/misc/polars]]
* [[Server Replication|wiki/howto/misc/server-replication]] * [[Cook Book|wiki/howto/misc/cook-book]]
* [[TracTrac|wiki/howto/misc/tractrac-lifecycle]] * [[Polars|wiki/howto/misc/polars]]
* [[UI Tests|wiki/howto/misc/ui-tests-tutorial]] * [[Server Replication|wiki/howto/misc/server-replication]]
* [[Uploading Media Content|wiki/howto/misc/uploading-media-content]] * [[TracTrac|wiki/howto/misc/tractrac-lifecycle]]
* [[Monitoring Apache and RabbitMQ|wiki/misc/monitoring-apache-and-rabbitmq]] * [[UI Tests|wiki/howto/misc/ui-tests-tutorial]]
* [[Uploading Media Content|wiki/howto/misc/uploading-media-content]]
## Projects * [[Monitoring Apache and RabbitMQ|wiki/misc/monitoring-apache-and-rabbitmq]]
* [[Analytics on a stick|wiki/projects/analytics-on-a-stick]]
## Projects
## Events and Planning * [[Analytics on a stick|wiki/projects/analytics-on-a-stick]]
* [[Project Planning (bigger development)|wiki/events/planning]]
* [[General Event Planning|wiki/events/general-event-planning]] ## Events and Planning
* [[Information about Extreme Sailing Series|wiki/events/extreme-sailing-series]] * [[Project Planning (bigger development)|wiki/events/planning]]
* [[Travem&uuml;nder Woche 2014 event page|wiki/events/tw2014]] * [[General Event Planning|wiki/events/general-event-planning]]
* [[505 worlds Kiel 2014 event page|wiki/events/505-worlds-kiel-2014]] * [[Information about Extreme Sailing Series|wiki/events/extreme-sailing-series]]
* [[Kieler Woche event page|wiki/events/kieler-woche-2015]] * [[Travem&uuml;nder Woche 2014 event page|wiki/events/tw2014]]
* [[Charleston Race Week 2016|wiki/events/Charleston-Race-Week-2016]] * [[505 worlds Kiel 2014 event page|wiki/events/505-worlds-kiel-2014]]
* [[Sailing Leagues 2016|wiki/events/sailing-Leagues-2016]] * [[Kieler Woche event page|wiki/events/kieler-woche-2015]]
* [[Charleston Race Week 2016|wiki/events/Charleston-Race-Week-2016]]
## Planning * [[Sailing Leagues 2016|wiki/events/sailing-Leagues-2016]]
* [[Overview|https://wiki.sapsailing.com/pages/wiki/planning/]]
## Planning
## Internal services (not related to wiki but useful) * [[Overview|https://wiki.sapsailing.com/pages/wiki/planning/]]
* [Bugzilla Issue Tracking System](http://bugzilla.sapsailing.com/bugzilla/) ## Internal services (not related to wiki but useful)
* [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]]) * [Bugzilla Issue Tracking System](http://bugzilla.sapsailing.com/bugzilla/)
* [Main Sailing Website](http://www.sapsailing.com) * [GIT Repository (SAP)](ssh://git.wdf.sap.corp:29418/SAPSail/sapsailingcapture.git)
* [Visitor Statistics](http://analysis.sapsailing.com/) * [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/)
@@ -24,6 +24,7 @@ public enum BravoSensorDataMetadata {
"BravoNet_Node0x0A0_Voltage", "BravoNet_Node0x0A0_Temperature", "BravoNet_Node0x0A0_Current")); "BravoNet_Node0x0A0_Voltage", "BravoNet_Node0x0A0_Temperature", "BravoNet_Node0x0A0_Current"));
public final int rideHeightColumn = getColumnIndex(RIDE_HEIGHT); public final int rideHeightColumn = getColumnIndex(RIDE_HEIGHT);
public final int columnCount = columns.size();
public List<String> getColumns() { public List<String> getColumns() {
return columns; return columns;
@@ -1,5 +1,7 @@
package com.sap.sailing.domain.common.tracking.impl; package com.sap.sailing.domain.common.tracking.impl;
import java.util.Arrays;
import com.sap.sailing.domain.common.tracking.DoubleVectorFix; import com.sap.sailing.domain.common.tracking.DoubleVectorFix;
import com.sap.sse.common.TimePoint; import com.sap.sse.common.TimePoint;
@@ -34,4 +36,31 @@ public class DoubleVectorFixImpl implements DoubleVectorFix {
return fixData; 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;
}
} }
@@ -125,56 +125,53 @@ public class MongoSensorFixStoreImpl implements MongoSensorFixStore {
*/ */
@Override @Override
public <FixT extends Timed> void storeFixes(DeviceIdentifier device, Iterable<FixT> fixes) { public <FixT extends Timed> void storeFixes(DeviceIdentifier device, Iterable<FixT> fixes) {
if (Util.isEmpty(fixes)) { if (!Util.isEmpty(fixes)) {
return; try {
} final Object dbDeviceId = storeDeviceId(deviceServiceFinder, device);
try { final int nrOfTotalFixes = Util.size(fixes);
final Object dbDeviceId = storeDeviceId(deviceServiceFinder, device); final ArrayList<DBObject> dbFixes = new ArrayList<>(nrOfTotalFixes);
final int nrOfTotalFixes = Util.size(fixes);
final ArrayList<DBObject> dbFixes = new ArrayList<>(nrOfTotalFixes); TimePoint newFrom = null;
TimePoint newTo = null;
TimePoint newFrom = null; for (FixT fix : fixes) {
TimePoint newTo = null; String type = fix.getClass().getName();
for (FixT fix : fixes) { FixMongoHandler<FixT> mongoHandler = findService(type);
String type = fix.getClass().getName(); Object fixObject = mongoHandler.transformForth(fix);
FixMongoHandler<FixT> mongoHandler = findService(type); DBObject entry = new BasicDBObjectBuilder().add(FieldNames.DEVICE_ID.name(), dbDeviceId)
Object fixObject = mongoHandler.transformForth(fix); .add(FieldNames.GPSFIX_TYPE.name(), type).add(FieldNames.GPSFIX.name(), fixObject).get();
DBObject entry = new BasicDBObjectBuilder().add(FieldNames.DEVICE_ID.name(), dbDeviceId) mongoOF.storeTimed(fix, entry);
.add(FieldNames.GPSFIX_TYPE.name(), type).add(FieldNames.GPSFIX.name(), fixObject).get(); dbFixes.add(entry);
mongoOF.storeTimed(fix, entry); TimePoint fixTP = fix.getTimePoint();
dbFixes.add(entry); if (newFrom == null || newFrom.after(fixTP)) {
TimePoint fixTP = fix.getTimePoint(); newFrom = fixTP;
if (newFrom == null || newFrom.after(fixTP)) { }
newFrom = fixTP; if (newTo == null || newTo.before(fixTP)) {
newTo = fixTP;
}
} }
if (newTo == null || newTo.before(fixTP)) { fixesCollection.insert(dbFixes);
newTo = fixTP; 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); notifyListeners(device, fixes);
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);
} }
@Override @Override
@@ -71,6 +71,8 @@ public class AbstractGPSFixStoreTest {
MongoObjectFactoryImpl mongoOF = (MongoObjectFactoryImpl) service.getMongoObjectFactory(); MongoObjectFactoryImpl mongoOF = (MongoObjectFactoryImpl) service.getMongoObjectFactory();
mongoOF.getGPSFixCollection().drop(); mongoOF.getGPSFixCollection().drop();
mongoOF.getGPSFixMetadataCollection().drop(); mongoOF.getGPSFixMetadataCollection().drop();
mongoOF.getRaceLogCollection().drop();
mongoOF.getRegattaLogCollection().drop();
} }
protected void map(RegattaLog regattaLog, Competitor comp, DeviceIdentifier device, long from, long to) { protected void map(RegattaLog regattaLog, Competitor comp, DeviceIdentifier device, long from, long to) {
@@ -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.<Sideline> 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<Competitor> 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<Competitor> 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<Competitor> 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<Competitor> 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 <FixT extends Timed, TrackT extends DynamicTrack<FixT>> SensorFixMapper<FixT, TrackT, Competitor> 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<String> 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<ItemType extends WithID & Serializable> extends SensorFixTrackImpl<ItemType, TestFixImpl> {
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<TestFixImpl, DynamicSensorFixTrack<Competitor, TestFixImpl>, Competitor> {
@Override
public DynamicSensorFixTrack<Competitor, TestFixImpl> getTrack(DynamicTrackedRace race, Competitor key) {
return race.getOrCreateSensorTrack(key, TestFixTrackImpl.TRACK_NAME,
() -> new TestFixTrackImpl<Competitor>(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<Competitor>
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);
}
}
@@ -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<DoubleVectorFix> 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<DoubleVectorFix> listener1 = mockFixReceivedListener();
FixReceivedListener<DoubleVectorFix> 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<DoubleVectorFix> listener = mockFixReceivedListener();
store.addListener(listener, device);
addBravoFix(device2, FIX_TIMESTAMP, FIX_RIDE_HEIGHT);
Mockito.verifyZeroInteractions(listener);
}
@Test
public void testOnlyListenerForMatchingDeviceIsNotified() throws Exception {
FixReceivedListener<DoubleVectorFix> listener1 = mockFixReceivedListener();
FixReceivedListener<DoubleVectorFix> 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<DoubleVectorFix> 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<DoubleVectorFix> 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<DoubleVectorFix> 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<DoubleVectorFix> 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<DoubleVectorFix> 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<DoubleVectorFix> 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<DoubleVectorFix> 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<DoubleVectorFix> listener1 = mockFixReceivedListener();
FixReceivedListener<DoubleVectorFix> 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<DoubleVectorFix> listener1 = mockFixReceivedListener();
FixReceivedListener<DoubleVectorFix> 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<DoubleVectorFix> mockFixReceivedListener() {
FixReceivedListener<DoubleVectorFix> listener = Mockito.mock(FixReceivedListener.class);
return listener;
}
private void verifySingleFix(Timed expectedFix, long start, long end, DeviceIdentifier device, boolean inclusive) throws Exception {
List<Timed> 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<Timed> loadedFixes = loadFixes(start, end, device, inclusive);
assertEquals(0, loadedFixes.size());
}
private List<Timed> loadFixes(long start, long end, DeviceIdentifier device, boolean inclusive)
throws TransformationException {
List<Timed> 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);
}
}
@@ -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.CompactGPSFixImpl;
import com.sap.sailing.domain.common.tracking.impl.CompactGPSFixMovingImpl; 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.GPSFixImpl;
import com.sap.sailing.domain.common.tracking.impl.GPSFixMovingImpl; import com.sap.sailing.domain.common.tracking.impl.GPSFixMovingImpl;
import com.sap.sailing.domain.persistence.impl.DomainObjectFactoryImpl; import com.sap.sailing.domain.persistence.impl.DomainObjectFactoryImpl;
import com.sap.sailing.domain.persistence.impl.MongoObjectFactoryImpl; 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.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.GPSFixMongoHandlerImpl;
import com.sap.sailing.domain.persistence.racelog.tracking.impl.GPSFixMovingMongoHandlerImpl; import com.sap.sailing.domain.persistence.racelog.tracking.impl.GPSFixMovingMongoHandlerImpl;
import com.sap.sse.common.TypeBasedServiceFinder; import com.sap.sse.common.TypeBasedServiceFinder;
@@ -20,6 +22,7 @@ public class MockGPSFixMongoServiceFinder implements TypeBasedServiceFinder<FixM
private final DomainObjectFactoryImpl dof = new DomainObjectFactoryImpl(null, null); private final DomainObjectFactoryImpl dof = new DomainObjectFactoryImpl(null, null);
private final FixMongoHandler<?> movingHandler = new GPSFixMovingMongoHandlerImpl(mof, dof); private final FixMongoHandler<?> movingHandler = new GPSFixMovingMongoHandlerImpl(mof, dof);
private final FixMongoHandler<?> handler = new GPSFixMongoHandlerImpl(mof, dof); private final FixMongoHandler<?> handler = new GPSFixMongoHandlerImpl(mof, dof);
private final FixMongoHandler<?> doubleVectorFixHandler = new DoubleVectorFixMongoHandlerImpl(mof, dof);
@Override @Override
public FixMongoHandler<?> findService(String fixType) { public FixMongoHandler<?> findService(String fixType) {
@@ -27,6 +30,7 @@ public class MockGPSFixMongoServiceFinder implements TypeBasedServiceFinder<FixM
fixType.equals(CompactGPSFixMovingImpl.class.getName())) return movingHandler; fixType.equals(CompactGPSFixMovingImpl.class.getName())) return movingHandler;
if (fixType.equals(GPSFixImpl.class.getName()) || if (fixType.equals(GPSFixImpl.class.getName()) ||
fixType.equals(CompactGPSFixImpl.class.getName())) return handler; fixType.equals(CompactGPSFixImpl.class.getName())) return handler;
if (fixType.equals(DoubleVectorFixImpl.class.getName())) return doubleVectorFixHandler;
return null; return null;
} }
@@ -48,7 +48,6 @@ public class EndOfTrackingController extends AbstractRaceChangeListener {
if (!Util.equalsWithNull(trackedRace.getEndOfTracking(), newEndOfTracking)) { if (!Util.equalsWithNull(trackedRace.getEndOfTracking(), newEndOfTracking)) {
raceLog.add(new RaceLogEndOfTrackingEventImpl(newEndOfTracking, raceLog.add(new RaceLogEndOfTrackingEventImpl(newEndOfTracking,
raceLogEventAuthor, raceLog.getCurrentPassId())); raceLogEventAuthor, raceLog.getCurrentPassId()));
trackedRace.updateStartAndEndOfTracking(/* waitForGPSFixesToLoad */ false);
} }
} }
} }
@@ -46,7 +46,6 @@ public class StartOfTrackingController implements StartTimeChangedListener {
if (!Util.equalsWithNull(trackedRace.getStartOfTracking(), newStartOfTracking)) { if (!Util.equalsWithNull(trackedRace.getStartOfTracking(), newStartOfTracking)) {
raceLog.add(new RaceLogStartOfTrackingEventImpl(newStartOfTracking, raceLog.add(new RaceLogStartOfTrackingEventImpl(newStartOfTracking,
raceLogEventAuthor, raceLog.getCurrentPassId())); raceLogEventAuthor, raceLog.getCurrentPassId()));
trackedRace.updateStartAndEndOfTracking(/* waitForGPSFixesToLoad */ false);
} }
} }
} }
@@ -161,6 +161,9 @@ public class FixLoaderAndTracker implements TrackingDataLoader {
} }
private void loadFixes(TimeRange timeRangeToLoad, DeviceMappingWithRegattaLogEvent<? extends WithID> mapping) { private void loadFixes(TimeRange timeRangeToLoad, DeviceMappingWithRegattaLogEvent<? extends WithID> mapping) {
if(timeRangeToLoad == null) {
return;
}
if (preemptiveStopRequested.get()) { if (preemptiveStopRequested.get()) {
return; return;
} }
@@ -194,8 +197,8 @@ public class FixLoaderAndTracker implements TrackingDataLoader {
try { try {
@SuppressWarnings({ "unchecked" }) @SuppressWarnings({ "unchecked" })
DeviceMapping<Competitor> competitorMapping = (DeviceMapping<Competitor>) mapping; DeviceMapping<Competitor> competitorMapping = (DeviceMapping<Competitor>) mapping;
gpsFixStore.loadCompetitorTrack(track, competitorMapping, getStartOfTracking(), gpsFixStore.loadCompetitorTrack(track, competitorMapping, timeRangeToLoad.from(),
getEndOfTracking()); timeRangeToLoad.to());
} catch (TransformationException | NoCorrespondingServiceRegisteredException e) { } catch (TransformationException | NoCorrespondingServiceRegisteredException e) {
logger.log(Level.WARNING, "Could not load competitor track " + mapping.getMappedTo()); logger.log(Level.WARNING, "Could not load competitor track " + mapping.getMappedTo());
} }
@@ -208,12 +211,10 @@ public class FixLoaderAndTracker implements TrackingDataLoader {
try { try {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
DeviceMapping<Mark> markMapping = (DeviceMapping<Mark>) mapping; DeviceMapping<Mark> markMapping = (DeviceMapping<Mark>) mapping;
TimePoint from = getStartOfTracking(); gpsFixStore.loadMarkTrack(track, markMapping, timeRangeToLoad.from(), timeRangeToLoad.to());
TimePoint to = getEndOfTracking();
gpsFixStore.loadMarkTrack(track, markMapping, from, to);
if (track.getFirstRawFix() == null) { if (track.getFirstRawFix() == null) {
logger.fine("Loading mark positions from outside of start/end of tracking interval (" + from logger.fine("Loading mark positions from outside of start/end of tracking interval (" + timeRangeToLoad.from()
+ ".." + to + ") because no fixes were found in that interval"); + ".." + timeRangeToLoad.to() + ") because no fixes were found in that interval");
// got an empty track for the mark; try again without constraining the mapping 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 // 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 // within the device mapping interval specified
@@ -264,12 +265,10 @@ public class FixLoaderAndTracker implements TrackingDataLoader {
} }
} }
// TEMP: kommt von RaceLogSensorFixTracker
protected TimePoint getStartOfTracking() { protected TimePoint getStartOfTracking() {
return trackedRace.getStartOfTracking(); return trackedRace.getStartOfTracking();
} }
// TEMP: kommt von RaceLogSensorFixTracker
protected TimePoint getEndOfTracking() { protected TimePoint getEndOfTracking() {
return trackedRace.getEndOfTracking(); return trackedRace.getEndOfTracking();
} }
@@ -277,10 +276,7 @@ public class FixLoaderAndTracker implements TrackingDataLoader {
protected void loadFixesForExtendedTimeRange(TimePoint loadFixesFrom, TimePoint loadFixesTo) { protected void loadFixesForExtendedTimeRange(TimePoint loadFixesFrom, TimePoint loadFixesTo) {
final TimeRangeImpl extendedTimeRange = new TimeRangeImpl(loadFixesFrom, loadFixesTo); final TimeRangeImpl extendedTimeRange = new TimeRangeImpl(loadFixesFrom, loadFixesTo);
deviceMappings.forEachMapping((mapping) -> { deviceMappings.forEachMapping((mapping) -> {
TimeRange timeRangeToLoad = extendedTimeRange.intersection(mapping.getTimeRange()); loadFixes(extendedTimeRange.intersection(mapping.getTimeRange()), mapping);
if (timeRangeToLoad != null && !preemptiveStopRequested.get()) {
loadFixes(timeRangeToLoad, mapping);
}
}); });
} }
@@ -292,7 +288,6 @@ public class FixLoaderAndTracker implements TrackingDataLoader {
* @param updateCallback * @param updateCallback
* the {@link Runnable} callback used to run the update * 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) { private void updateAsyncInternal(final Runnable updateCallback) {
synchronized (FixLoaderAndTracker.this) { synchronized (FixLoaderAndTracker.this) {
activeLoaders.incrementAndGet(); activeLoaders.incrementAndGet();
@@ -301,9 +296,9 @@ public class FixLoaderAndTracker implements TrackingDataLoader {
ThreadPoolUtil.INSTANCE.getDefaultForegroundTaskThreadPoolExecutor().execute(new Runnable() { ThreadPoolUtil.INSTANCE.getDefaultForegroundTaskThreadPoolExecutor().execute(new Runnable() {
@Override @Override
public void run() { public void run() {
trackedRace.lockForSerializationRead();
try { try {
if (!preemptiveStopRequested.get()) { if (!preemptiveStopRequested.get()) {
trackedRace.lockForSerializationRead();
setStatusAndProgress(TrackedRaceStatusEnum.LOADING, 0.5); setStatusAndProgress(TrackedRaceStatusEnum.LOADING, 0.5);
synchronized (FixLoaderAndTracker.this) { synchronized (FixLoaderAndTracker.this) {
FixLoaderAndTracker.this.notifyAll(); FixLoaderAndTracker.this.notifyAll();
@@ -311,15 +306,18 @@ public class FixLoaderAndTracker implements TrackingDataLoader {
updateCallback.run(); updateCallback.run();
} }
} finally { } finally {
synchronized (FixLoaderAndTracker.this) { try {
int currentActiveLoaders = activeLoaders.decrementAndGet(); synchronized (FixLoaderAndTracker.this) {
FixLoaderAndTracker.this.notifyAll(); int currentActiveLoaders = activeLoaders.decrementAndGet();
if (currentActiveLoaders == 0) { FixLoaderAndTracker.this.notifyAll();
setStatusAndProgress(stopRequested.get() ? TrackedRaceStatusEnum.FINISHED if (currentActiveLoaders == 0) {
: TrackedRaceStatusEnum.TRACKING, 1.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 @Override
protected void mappingRemoved(DeviceMappingWithRegattaLogEvent<WithID> mapping) { protected void mappingRemoved(DeviceMappingWithRegattaLogEvent<WithID> mapping) {
final DeviceIdentifier device = mapping.getDevice(); final DeviceIdentifier device = mapping.getDevice();
if(!hasMappingForDevice(device)) { if (!hasMappingForDevice(device)) {
sensorFixStore.removeListener(listener, device); sensorFixStore.removeListener(listener, device);
} }
// TODO if tracks are always associated to only one device mapping, we could remove tracks here // 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 @Override
protected void mappingChanged(DeviceMappingWithRegattaLogEvent<WithID> oldMapping, protected void mappingChanged(DeviceMappingWithRegattaLogEvent<WithID> oldMapping,
DeviceMappingWithRegattaLogEvent<WithID> newMapping) { DeviceMappingWithRegattaLogEvent<WithID> newMapping) {
// TODO can the new time range be bigger than the old one? In this case we would need to load the final TimeRange newTimeRange = newMapping.getTimeRange();
// additional time range. 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);
}
} }
} }
} }
@@ -120,7 +120,7 @@ public class RaceLogFixTrackerManager implements TrackingDataLoader {
stopTrackerIfStillRunning(preemptive); stopTrackerIfStillRunning(preemptive);
trackedRace.removeListener(raceChangeListener); trackedRace.removeListener(raceChangeListener);
synchronized (knownRaceLogs) { synchronized (knownRaceLogs) {
for (RaceLog raceLog : trackedRace.getAttachedRaceLogs()) { for (RaceLog raceLog : knownRaceLogs) {
removeRaceLogUnlocked(raceLog); removeRaceLogUnlocked(raceLog);
} }
} }
@@ -142,10 +142,12 @@ public class RaceLogFixTrackerManager implements TrackingDataLoader {
} }
boolean isForTracking() { boolean isForTracking() {
for (RaceLog raceLog : trackedRace.getAttachedRaceLogs()) { synchronized (knownRaceLogs) {
RaceLogTrackingState raceLogTrackingState = new RaceLogTrackingStateAnalyzer(raceLog).analyze(); for (RaceLog raceLog : knownRaceLogs) {
if (raceLogTrackingState.isForTracking()) { RaceLogTrackingState raceLogTrackingState = new RaceLogTrackingStateAnalyzer(raceLog).analyze();
return true; if (raceLogTrackingState.isForTracking()) {
return true;
}
} }
} }
return false; return false;
@@ -48,7 +48,7 @@ public abstract class RegattaLogDeviceMappings<ItemT extends WithID> {
* sure to not produce a memory leak. * sure to not produce a memory leak.
*/ */
private final Set<RegattaLog> knownRegattaLogs = new HashSet<>(); private final Set<RegattaLog> knownRegattaLogs = new HashSet<>();
/** /**
* Lock object to be used when accessing {@link #knownRegattaLogs}. * Lock object to be used when accessing {@link #knownRegattaLogs}.
*/ */
@@ -127,16 +127,16 @@ public abstract class RegattaLogDeviceMappings<ItemT extends WithID> {
knownRegattaLogs.forEach((log) -> log.removeListener(regattaLogEventVisitor)); knownRegattaLogs.forEach((log) -> log.removeListener(regattaLogEventVisitor));
knownRegattaLogs.clear(); knownRegattaLogs.clear();
}); });
} }
protected void updateMappings() { protected void updateMappings() {
try { try {
updateMappings(true); updateMappingsInternal();
} catch (Exception e) { } catch (Exception e) {
logger.log(Level.WARNING, "Could not update device mappings", e); logger.log(Level.WARNING, "Could not update device mappings", e);
} }
} }
/** /**
* Calls the given callback for every known mapping that's currently known. * Calls the given callback for every known mapping that's currently known.
* *
@@ -163,11 +163,11 @@ public abstract class RegattaLogDeviceMappings<ItemT extends WithID> {
public void forEachMapping(BiConsumer<ItemT, DeviceMappingWithRegattaLogEvent<ItemT>> callback) { public void forEachMapping(BiConsumer<ItemT, DeviceMappingWithRegattaLogEvent<ItemT>> callback) {
LockUtil.executeWithReadLock(mappingsLock, () -> { LockUtil.executeWithReadLock(mappingsLock, () -> {
for (Map.Entry<ItemT, List<DeviceMappingWithRegattaLogEvent<ItemT>>> entry : mappings.entrySet()) { for (Map.Entry<ItemT, List<DeviceMappingWithRegattaLogEvent<ItemT>>> entry : mappings.entrySet()) {
ItemT item = entry.getKey(); ItemT item = entry.getKey();
for (DeviceMappingWithRegattaLogEvent<ItemT> mapping : entry.getValue()) { for (DeviceMappingWithRegattaLogEvent<ItemT> mapping : entry.getValue()) {
callback.accept(item, mapping); callback.accept(item, mapping);
}
} }
}
}); });
} }
@@ -187,13 +187,13 @@ public abstract class RegattaLogDeviceMappings<ItemT extends WithID> {
Consumer<DeviceMappingWithRegattaLogEvent<ItemT>> callback) { Consumer<DeviceMappingWithRegattaLogEvent<ItemT>> callback) {
LockUtil.executeWithReadLock(mappingsLock, () -> { LockUtil.executeWithReadLock(mappingsLock, () -> {
List<DeviceMappingWithRegattaLogEvent<ItemT>> mappingsForDevice = mappingsByDevice.get(device); List<DeviceMappingWithRegattaLogEvent<ItemT>> mappingsForDevice = mappingsByDevice.get(device);
if (mappingsForDevice != null) { if (mappingsForDevice != null) {
for (DeviceMappingWithRegattaLogEvent<ItemT> mapping : mappingsForDevice) { for (DeviceMappingWithRegattaLogEvent<ItemT> mapping : mappingsForDevice) {
if (mapping.getTimeRange().includes(timePoint)) { if (mapping.getTimeRange().includes(timePoint)) {
callback.accept(mapping); callback.accept(mapping);
}
} }
} }
}
}); });
} }
@@ -236,9 +236,7 @@ public abstract class RegattaLogDeviceMappings<ItemT extends WithID> {
* *
* @throws DoesNotHaveRegattaLogException * @throws DoesNotHaveRegattaLogException
*/ */
private final <FixT extends Timed, TrackT extends DynamicTrack<FixT>> void updateMappings(boolean loadIfNotCovered) { private final <FixT extends Timed, TrackT extends DynamicTrack<FixT>> void updateMappingsInternal() {
// TODO remove fixes, if mappings have been removed
// check if there are new time ranges not covered so far
final Map<ItemT, List<DeviceMappingWithRegattaLogEvent<ItemT>>> newMappings = calculateMappings(); final Map<ItemT, List<DeviceMappingWithRegattaLogEvent<ItemT>>> newMappings = calculateMappings();
final Map<ItemT, List<DeviceMappingWithRegattaLogEvent<ItemT>>> oldMappings = new HashMap<>(); final Map<ItemT, List<DeviceMappingWithRegattaLogEvent<ItemT>>> oldMappings = new HashMap<>();
LockUtil.lockForWrite(mappingsLock); LockUtil.lockForWrite(mappingsLock);
@@ -261,18 +259,20 @@ public abstract class RegattaLogDeviceMappings<ItemT extends WithID> {
} finally { } finally {
LockUtil.unlockAfterWrite(mappingsLock); LockUtil.unlockAfterWrite(mappingsLock);
} }
if (loadIfNotCovered) { calculateDiff(oldMappings, newMappings);
calculateDiff(oldMappings, newMappings);
}
} }
/**
* Calculates <em>and applies</em> 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<ItemT, List<DeviceMappingWithRegattaLogEvent<ItemT>>> previousMappings, private void calculateDiff(Map<ItemT, List<DeviceMappingWithRegattaLogEvent<ItemT>>> previousMappings,
Map<ItemT, List<DeviceMappingWithRegattaLogEvent<ItemT>>> newMappings) { Map<ItemT, List<DeviceMappingWithRegattaLogEvent<ItemT>>> newMappings) {
Set<ItemT> itemsToProcess = new HashSet<ItemT>(previousMappings.keySet()); Set<ItemT> itemsToProcess = new HashSet<ItemT>(previousMappings.keySet());
itemsToProcess.addAll(newMappings.keySet()); itemsToProcess.addAll(newMappings.keySet());
for(ItemT item : itemsToProcess) { for (ItemT item : itemsToProcess) {
if(!newMappings.containsKey(item)) { if (!newMappings.containsKey(item)) {
previousMappings.get(item).forEach(this::mappingRemoved); previousMappings.get(item).forEach(this::mappingRemovedInternal);
} else { } else {
final List<DeviceMappingWithRegattaLogEvent<ItemT>> oldMappings = previousMappings.containsKey(item) final List<DeviceMappingWithRegattaLogEvent<ItemT>> oldMappings = previousMappings.containsKey(item)
? previousMappings.get(item) : Collections.emptyList(); ? previousMappings.get(item) : Collections.emptyList();
@@ -281,22 +281,30 @@ public abstract class RegattaLogDeviceMappings<ItemT extends WithID> {
DeviceMappingWithRegattaLogEvent<ItemT> oldMapping = findAndRemoveMapping(newMapping, DeviceMappingWithRegattaLogEvent<ItemT> oldMapping = findAndRemoveMapping(newMapping,
oldMappings); oldMappings);
if (oldMapping == null) { if (oldMapping == null) {
mappingAdded(newMapping); mappingAddedInternal(newMapping);
} else if (newMapping.getTimeRange().equals(oldMapping.getTimeRange())) { } else if (!newMapping.getTimeRange().equals(oldMapping.getTimeRange())) {
mappingChanged(oldMapping, newMapping); mappingChangedInternal(oldMapping, newMapping);
} }
} }
oldMappings.forEach(this::mappingRemoved); oldMappings.forEach(this::mappingRemovedInternal);
} }
} }
} }
/** /**
* Called when a {@link DeviceMapping} was removed. * Called when a {@link DeviceMapping} was removed.
* *
* @param mapping the removed mapping * @param mapping the removed mapping
*/ */
protected abstract void mappingRemoved(DeviceMappingWithRegattaLogEvent<ItemT> mapping); protected abstract void mappingRemoved(DeviceMappingWithRegattaLogEvent<ItemT> mapping);
private void mappingRemovedInternal(DeviceMappingWithRegattaLogEvent<ItemT> mapping) {
try {
mappingRemoved(mapping);
} catch(Exception e) {
logger.log(Level.SEVERE, "error while removing mapping " + mapping, e);
}
}
/** /**
* Called when a {@link DeviceMapping} was added. * Called when a {@link DeviceMapping} was added.
@@ -304,6 +312,14 @@ public abstract class RegattaLogDeviceMappings<ItemT extends WithID> {
* @param mapping the new mapping * @param mapping the new mapping
*/ */
protected abstract void mappingAdded(DeviceMappingWithRegattaLogEvent<ItemT> mapping); protected abstract void mappingAdded(DeviceMappingWithRegattaLogEvent<ItemT> mapping);
private void mappingAddedInternal(DeviceMappingWithRegattaLogEvent<ItemT> 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. * Called when a {@link DeviceMapping} was changed regarding its mapped time range.
@@ -315,6 +331,15 @@ public abstract class RegattaLogDeviceMappings<ItemT extends WithID> {
protected abstract void mappingChanged(DeviceMappingWithRegattaLogEvent<ItemT> oldMapping, protected abstract void mappingChanged(DeviceMappingWithRegattaLogEvent<ItemT> oldMapping,
DeviceMappingWithRegattaLogEvent<ItemT> newMapping); DeviceMappingWithRegattaLogEvent<ItemT> newMapping);
private void mappingChangedInternal(DeviceMappingWithRegattaLogEvent<ItemT> oldMapping,
DeviceMappingWithRegattaLogEvent<ItemT> newMapping) {
try {
mappingChanged(oldMapping, newMapping);
} catch(Exception e) {
logger.log(Level.SEVERE, "error while changing mapping old: " + oldMapping + "; new: " + newMapping, e);
}
}
private DeviceMappingWithRegattaLogEvent<ItemT> findAndRemoveMapping( private DeviceMappingWithRegattaLogEvent<ItemT> findAndRemoveMapping(
DeviceMappingWithRegattaLogEvent<ItemT> mappingToFind, DeviceMappingWithRegattaLogEvent<ItemT> mappingToFind,
List<DeviceMappingWithRegattaLogEvent<ItemT>> newItemsToProcess) { List<DeviceMappingWithRegattaLogEvent<ItemT>> newItemsToProcess) {
@@ -65,7 +65,7 @@ public abstract class BaseRegattaLogDeviceMappingFinder<ItemT extends WithID>
if (!endOfFirstHalf.before(from)) { if (!endOfFirstHalf.before(from)) {
log.add(createDeviceMappingEvent(item, event.getAuthor(), from, endOfFirstHalf, event.getDevice())); 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, log.add(createDeviceMappingEvent(item, event.getAuthor(), startOfSecondHalf, to,
event.getDevice())); event.getDevice()));
} }
@@ -46,7 +46,7 @@ public class WaypointImpl implements Waypoint {
@Override @Override
public String toString() { public String toString() {
return getName(); return (getName()==null?"":getName()) + " ("+getPassingInstructions()+")";
} }
@Override @Override
@@ -107,12 +107,12 @@ public class TrackImpl<FixType extends Timed> implements Track<FixType> {
} }
/** /**
* Callers that want to iterate over the collection returned need to synchronize on <code>this</code> object to * Callers that want to iterate over the collection returned need to use {@link #lockForRead()} and
* avoid {@link ConcurrentModificationException}s. * {@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 * @return the smoothened fixes ordered by their time points; this implementation simply delegates to
* only {@link Timed} fixes we can't know how to remove outliers. Subclasses that constrain the * {@link #getInternalRawFixes()} because for only {@link Timed} fixes we can't know how to remove outliers.
* <code>FixType</code> may provide smoothening implementations. * Subclasses that constrain the <code>FixType</code> may provide smoothening implementations.
*/ */
protected NavigableSet<FixType> getInternalFixes() { protected NavigableSet<FixType> getInternalFixes() {
NavigableSet<FixType> result = getInternalRawFixes(); NavigableSet<FixType> result = getInternalRawFixes();
@@ -10,7 +10,6 @@ import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.text.ParseException; import java.text.ParseException;
import java.util.List; import java.util.List;
import java.util.concurrent.Executors;
import java.util.logging.Logger; import java.util.logging.Logger;
import org.junit.Before; 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.ReceiverType;
import com.sap.sailing.domain.tractracadapter.TracTracConnectionConstants; import com.sap.sailing.domain.tractracadapter.TracTracConnectionConstants;
import com.sap.sse.common.impl.MillisecondsTimePoint; import com.sap.sse.common.impl.MillisecondsTimePoint;
import com.sap.sse.util.ThreadPoolUtil;
import com.tractrac.model.lib.api.event.CreateModelException; import com.tractrac.model.lib.api.event.CreateModelException;
import com.tractrac.subscription.lib.api.SubscriberInitializationException; import com.tractrac.subscription.lib.api.SubscriberInitializationException;
@@ -77,7 +77,7 @@ public class ConcurrencyTest extends OnlineTracTracBasedTest {
logger.info("1 thread: "+duration+"ns"); logger.info("1 thread: "+duration+"ns");
logger.info("number of approximation points: "+approximation1.size()); logger.info("number of approximation points: "+approximation1.size());
dp = new DouglasPeucker<Competitor, GPSFixMoving>(teamAquaTrack, Executors.newFixedThreadPool(numberOfCPUs)); dp = new DouglasPeucker<Competitor, GPSFixMoving>(teamAquaTrack, ThreadPoolUtil.INSTANCE.getDefaultBackgroundTaskThreadPoolExecutor());
start = System.nanoTime(); start = System.nanoTime();
List<GPSFixMoving> approximation2 = dp.approximate(new MeterDistance(3), firstMarkPassing.getTimePoint(), lastMarkPassing.getTimePoint()); List<GPSFixMoving> approximation2 = dp.approximate(new MeterDistance(3), firstMarkPassing.getTimePoint(), lastMarkPassing.getTimePoint());
duration = System.nanoTime()-start; duration = System.nanoTime()-start;
@@ -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<RegattaLikeIdentifier, RegattaLog> 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<RegattaLog> 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));
}
}
@@ -116,7 +116,7 @@ public class BravoFixTrackSerializationTest {
} }
private void addOrReplaceBravoFixToTrack(boolean replace) { 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; fixData[BravoSensorDataMetadata.INSTANCE.rideHeightColumn] = rideHeight;
track.add(new BravoFixImpl(new DoubleVectorFixImpl(timePoint, fixData)), replace); track.add(new BravoFixImpl(new DoubleVectorFixImpl(timePoint, fixData)), replace);
} }
@@ -43,6 +43,10 @@ public abstract class AbstractRaceExecutionOrderProvider implements RaceExecutio
protected abstract Map<Fleet, Iterable<? extends RaceColumn>> getRaceColumnsOfSeries(); protected abstract Map<Fleet, Iterable<? extends RaceColumn>> getRaceColumnsOfSeries();
/**
* For all tracked races found on any of the {@link #getRaceColumnsOfSeries() race columns} produces a valid and
* potentially empty set.
*/
private Map<TrackedRace, Set<TrackedRace>> reloadAndGetPreviousRacesByRace() { private Map<TrackedRace, Set<TrackedRace>> reloadAndGetPreviousRacesByRace() {
final Map<TrackedRace, Set<TrackedRace>> previousRacesByRace = new HashMap<>(); final Map<TrackedRace, Set<TrackedRace>> previousRacesByRace = new HashMap<>();
for (Entry<Fleet, Iterable<? extends RaceColumn>> raceColumnsInSeries : getRaceColumnsOfSeries().entrySet()) { for (Entry<Fleet, Iterable<? extends RaceColumn>> raceColumnsInSeries : getRaceColumnsOfSeries().entrySet()) {
@@ -96,11 +100,13 @@ public abstract class AbstractRaceExecutionOrderProvider implements RaceExecutio
@Override @Override
public Set<TrackedRace> getPreviousRacesInExecutionOrder(TrackedRace race) { public Set<TrackedRace> getPreviousRacesInExecutionOrder(TrackedRace race) {
Set<TrackedRace> result = Collections.emptySet(); final Set<TrackedRace> result;
final Map<TrackedRace, Set<TrackedRace>> previousRacesByRace = getPreviousRacesByRace(); final Map<TrackedRace, Set<TrackedRace>> previousRacesByRace = getPreviousRacesByRace();
if (previousRacesByRace != null) { if (previousRacesByRace != null) {
result = previousRacesByRace.get(race); result = previousRacesByRace.get(race);
} } else {
result = Collections.emptySet();
}
return result; return result;
} }
} }
@@ -6,12 +6,9 @@ import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.concurrent.FutureTask; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.RunnableFuture;
import com.sap.sailing.domain.common.Bearing; import com.sap.sailing.domain.common.Bearing;
import com.sap.sailing.domain.common.Distance; import com.sap.sailing.domain.common.Distance;
@@ -31,7 +28,7 @@ import com.sap.sse.common.Util;
public class DouglasPeucker<ItemType, FixType extends GPSFix> { public class DouglasPeucker<ItemType, FixType extends GPSFix> {
private final GPSFixTrack<ItemType, FixType> track; private final GPSFixTrack<ItemType, FixType> track;
private final Executor executor; private final ScheduledExecutorService executor;
public DouglasPeucker(GPSFixTrack<ItemType, FixType> track) { public DouglasPeucker(GPSFixTrack<ItemType, FixType> track) {
this.track = track; this.track = track;
@@ -42,7 +39,7 @@ public class DouglasPeucker<ItemType, FixType extends GPSFix> {
* Allows clients to specify a non-default, optionally multi-threaded executor that will be used to perform the * Allows clients to specify a non-default, optionally multi-threaded executor that will be used to perform the
* high-effort computations. * high-effort computations.
*/ */
public DouglasPeucker(GPSFixTrack<ItemType, FixType> track, Executor executor) { public DouglasPeucker(GPSFixTrack<ItemType, FixType> track, ScheduledExecutorService executor) {
this.track = track; this.track = track;
this.executor = executor; this.executor = executor;
} }
@@ -96,15 +93,8 @@ public class DouglasPeucker<ItemType, FixType extends GPSFix> {
if (fix.getTimePoint().compareTo(to) > 0) { if (fix.getTimePoint().compareTo(to) > 0) {
break; break;
} }
RunnableFuture<Util.Pair<FixType, Distance>> crossTrackErrorFuture = new FutureTask<Util.Pair<FixType, Distance>>( Future<Util.Pair<FixType, Distance>> crossTrackErrorFuture = executor.submit(
new Callable<Util.Pair<FixType, Distance>>() { ()->new Util.Pair<FixType, Distance>(fix, fix.getPosition().absoluteCrossTrackError(fromPosition, bearing)));
@Override
public Util.Pair<FixType, Distance> call() throws Exception {
return new Util.Pair<FixType, Distance>(fix, fix.getPosition().absoluteCrossTrackError(
fromPosition, bearing));
}
});
executor.execute(crossTrackErrorFuture);
crossTrackErrorFutures.add(crossTrackErrorFuture); crossTrackErrorFutures.add(crossTrackErrorFuture);
} }
for (Future<Util.Pair<FixType, Distance>> crossTrackErrorFuture : crossTrackErrorFutures) { for (Future<Util.Pair<FixType, Distance>> crossTrackErrorFuture : crossTrackErrorFutures) {
@@ -23,6 +23,7 @@ import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util; import com.sap.sse.common.Util;
import com.sap.sse.concurrent.LockUtil; import com.sap.sse.concurrent.LockUtil;
import com.sap.sse.concurrent.NamedReentrantReadWriteLock; import com.sap.sse.concurrent.NamedReentrantReadWriteLock;
import com.sap.sse.util.impl.FutureTaskWithTracingGet;
/** /**
* Caches the expensive to compute {@link LeaderboardDTO} results of a * Caches the expensive to compute {@link LeaderboardDTO} results of a
@@ -140,15 +141,16 @@ public class LeaderboardDTOCache implements LeaderboardCache {
LockUtil.unlockAfterRead(leaderboardCacheLock); LockUtil.unlockAfterRead(leaderboardCacheLock);
} }
if (future == null) { if (future == null) {
future = new FutureTask<LeaderboardDTO>(new Callable<LeaderboardDTO>() { future = new FutureTaskWithTracingGet<LeaderboardDTO>(LeaderboardDTOCache.class.getName()+" for leaderboard "+leaderboard.getName(),
@Override new Callable<LeaderboardDTO>() {
public LeaderboardDTO call() throws Exception { @Override
LeaderboardDTO result = leaderboard.computeDTO(adjustedTimePoint, public LeaderboardDTO call() throws Exception {
namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails, LeaderboardDTO result = leaderboard.computeDTO(adjustedTimePoint,
waitForLatestAnalyses, trackedRegattaRegistry, baseDomainFactory, /* fillTotalPointsUncorrected */ false); namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails,
return result; 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 // 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 this.leaderboardCacheManager.add(leaderboard); // ensure the leaderboard is tracked for changes to invalidate
LockUtil.lockForWrite(leaderboardCacheLock); LockUtil.lockForWrite(leaderboardCacheLock);
@@ -21,7 +21,6 @@ import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.concurrent.RunnableFuture; import java.util.concurrent.RunnableFuture;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; 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.LockUtil;
import com.sap.sse.concurrent.NamedReentrantReadWriteLock; import com.sap.sse.concurrent.NamedReentrantReadWriteLock;
import com.sap.sse.util.ThreadPoolUtil; 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 * 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<Competitor>(); this.suppressedCompetitors = new HashSet<Competitor>();
this.suppressedCompetitorsLock = new NamedReentrantReadWriteLock("suppressedCompetitorsLock", /* fair */ false); this.suppressedCompetitorsLock = new NamedReentrantReadWriteLock("suppressedCompetitorsLock", /* fair */ false);
this.raceColumnListeners = new RaceColumnListeners(); this.raceColumnListeners = new RaceColumnListeners();
this.raceDetailsAtEndOfTrackingCache = new HashMap<com.sap.sse.common.Util.Pair<TrackedRace, Competitor>, RunnableFuture<RaceDetails>>(); this.raceDetailsAtEndOfTrackingCache = new HashMap<>();
initTransientFields(); initTransientFields();
} }
@@ -330,7 +330,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
} }
private void initTransientFields() { private void initTransientFields() {
this.raceDetailsAtEndOfTrackingCache = new HashMap<com.sap.sse.common.Util.Pair<TrackedRace,Competitor>, RunnableFuture<RaceDetails>>(); this.raceDetailsAtEndOfTrackingCache = new HashMap<>();
this.cacheInvalidationListeners = new HashSet<CacheInvalidationListener>(); this.cacheInvalidationListeners = new HashSet<CacheInvalidationListener>();
// When many updates are triggered in a short period of time by a single thread, ensure that the single thread // 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 // 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 // Now create the race columns and, as a future task, set their competitorsFromBestToWorst, then wait for all these
// futures to finish: // futures to finish:
Map<RaceColumn, FutureTask<List<CompetitorDTO>>> competitorsFromBestToWorstTasks = new HashMap<>(); Map<RaceColumn, Future<List<CompetitorDTO>>> competitorsFromBestToWorstTasks = new HashMap<>();
for (final RaceColumn raceColumn : this.getRaceColumns()) { for (final RaceColumn raceColumn : this.getRaceColumns()) {
boolean isMetaLeaderboardColumn = raceColumn instanceof MetaLeaderboardColumn; boolean isMetaLeaderboardColumn = raceColumn instanceof MetaLeaderboardColumn;
RaceColumnDTO raceColumnDTO = result.createEmptyRaceColumn(raceColumn.getName(), raceColumn.isMedalRace(), 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, raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getSeries().getName() : null,
fleetDTO, raceColumn.isMedalRace(), raceIdentifier, race, isMetaLeaderboardColumn); fleetDTO, raceColumn.isMedalRace(), raceIdentifier, race, isMetaLeaderboardColumn);
} }
FutureTask<List<CompetitorDTO>> task = new FutureTask<List<CompetitorDTO>>( Future<List<CompetitorDTO>> task = executor.submit(
() -> baseDomainFactory.getCompetitorDTOList(AbstractSimpleLeaderboardImpl.this.getCompetitorsFromBestToWorst(raceColumn, timePoint))); () -> baseDomainFactory.getCompetitorDTOList(AbstractSimpleLeaderboardImpl.this.getCompetitorsFromBestToWorst(raceColumn, timePoint)));
executor.execute(task);
competitorsFromBestToWorstTasks.put(raceColumn, 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 // 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<RaceColumn, FutureTask<List<CompetitorDTO>>> raceColumnAndTaskToJoin : competitorsFromBestToWorstTasks.entrySet()) { for (Map.Entry<RaceColumn, Future<List<CompetitorDTO>>> raceColumnAndTaskToJoin : competitorsFromBestToWorstTasks.entrySet()) {
try { try {
result.setCompetitorsFromBestToWorst(raceColumnAndTaskToJoin.getKey().getName(), raceColumnAndTaskToJoin.getValue().get()); result.setCompetitorsFromBestToWorst(raceColumnAndTaskToJoin.getKey().getName(), raceColumnAndTaskToJoin.getValue().get());
} catch (InterruptedException e) { } catch (InterruptedException e) {
@@ -1323,7 +1322,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
Map<String, Future<LeaderboardEntryDTO>> futuresForColumnName = new HashMap<String, Future<LeaderboardEntryDTO>>(); Map<String, Future<LeaderboardEntryDTO>> futuresForColumnName = new HashMap<String, Future<LeaderboardEntryDTO>>();
final Set<RaceColumn> discardedRaceColumns = getResultDiscardingRule().getDiscardedRaceColumns(competitor, this, getRaceColumns(), timePoint); final Set<RaceColumn> discardedRaceColumns = getResultDiscardingRule().getDiscardedRaceColumns(competitor, this, getRaceColumns(), timePoint);
for (final RaceColumn raceColumn : this.getRaceColumns()) { for (final RaceColumn raceColumn : this.getRaceColumns()) {
RunnableFuture<LeaderboardEntryDTO> future = new FutureTask<LeaderboardEntryDTO>(() -> { Future<LeaderboardEntryDTO> future = executor.submit(() -> {
Entry entry = AbstractSimpleLeaderboardImpl.this.getEntry(competitor, raceColumn, timePoint, discardedRaceColumns); Entry entry = AbstractSimpleLeaderboardImpl.this.getEntry(competitor, raceColumn, timePoint, discardedRaceColumns);
return getLeaderboardEntryDTO(entry, raceColumn, competitor, timePoint, return getLeaderboardEntryDTO(entry, raceColumn, competitor, timePoint,
namesOfRaceColumnsForWhichToLoadLegDetails != null namesOfRaceColumnsForWhichToLoadLegDetails != null
@@ -1331,7 +1330,6 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
.getName()), waitForLatestAnalyses, legRanksCache, baseDomainFactory, .getName()), waitForLatestAnalyses, legRanksCache, baseDomainFactory,
fillTotalPointsUncorrected, cache); fillTotalPointsUncorrected, cache);
}); });
executor.execute(future);
futuresForColumnName.put(raceColumn.getName(), future); futuresForColumnName.put(raceColumn.getName(), future);
} }
for (Map.Entry<String, Future<LeaderboardEntryDTO>> raceColumnNameAndFuture : futuresForColumnName.entrySet()) { for (Map.Entry<String, Future<LeaderboardEntryDTO>> raceColumnNameAndFuture : futuresForColumnName.entrySet()) {
@@ -1611,7 +1609,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
raceDetails = raceDetailsAtEndOfTrackingCache.get(key); raceDetails = raceDetailsAtEndOfTrackingCache.get(key);
if (raceDetails == null) { if (raceDetails == null) {
needToRunRaceDetails = true; needToRunRaceDetails = true;
raceDetails = new FutureTask<RaceDetails>(new Callable<RaceDetails>() { raceDetails = new FutureTaskWithTracingGet<RaceDetails>("RaceDetails for "+trackedRace, new Callable<RaceDetails>() {
@Override @Override
public RaceDetails call() throws Exception { public RaceDetails call() throws Exception {
TimePoint end = trackedRace.getEndOfRace(); TimePoint end = trackedRace.getEndOfRace();
@@ -9,7 +9,6 @@ import java.util.Map.Entry;
import java.util.Set; import java.util.Set;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@@ -173,7 +172,7 @@ public class MarkPassingCalculator {
+ " while waiting for new GPSFixes"); + " while waiting for new GPSFixes");
} }
listener.getQueue().drainTo(allNewFixInsertions); 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) { for (StorePositionUpdateStrategy fixInsertion : allNewFixInsertions) {
if (listener.isEndMarker(fixInsertion)) { if (listener.isEndMarker(fixInsertion)) {
logger.info("Stopping "+MarkPassingCalculator.this+"'s listener for race "+raceName); logger.info("Stopping "+MarkPassingCalculator.this+"'s listener for race "+raceName);
@@ -212,8 +211,7 @@ public class MarkPassingCalculator {
} }
executor.invokeAll(tasks); executor.invokeAll(tasks);
} }
updateManuallySetMarkPassings(fixedMarkPassings, removedFixedMarkPassings, suppressedMarkPassings, updateManuallySetMarkPassings(fixedMarkPassings, removedFixedMarkPassings, suppressedMarkPassings, unsuppressedMarkPassings);
unsuppressedMarkPassings);
computeMarkPasses(competitorFixes, markFixes); computeMarkPasses(competitorFixes, markFixes);
competitorFixes.clear(); competitorFixes.clear();
markFixes.clear(); markFixes.clear();
@@ -284,20 +282,35 @@ public class MarkPassingCalculator {
fixes.addAll(fixesAffectedByNewMarkFixes.getValue()); fixes.addAll(fixesAffectedByNewMarkFixes.getValue());
} }
} }
List<Callable<Object>> tasks = new ArrayList<>(); List<Callable<Void>> tasks = new ArrayList<>();
for (Entry<Competitor, Set<GPSFix>> c : combinedCompetitorFixes.entrySet()) { for (final Entry<Competitor, Set<GPSFix>> c : combinedCompetitorFixes.entrySet()) {
tasks.add(Executors.callable(new ComputeMarkPassings(c.getKey(), c.getValue()))); final Runnable runnable = new ComputeMarkPassings(c.getKey(), c.getValue());
tasks.add(new Callable<Void>() {
@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 { try {
executor.invokeAll(tasks); executor.invokeAll(tasks);
} catch (InterruptedException e) { } catch (InterruptedException e) {
e.printStackTrace(); logger.log(Level.INFO, "mark passing calculation interrupted", e);
} }
} }
private class ComputeMarkPassings implements Runnable { private class ComputeMarkPassings implements Runnable {
Competitor c; final Competitor c;
Iterable<GPSFix> fixes; final Iterable<GPSFix> fixes;
public ComputeMarkPassings(Competitor c, Iterable<GPSFix> fixes) { public ComputeMarkPassings(Competitor c, Iterable<GPSFix> fixes) {
this.c = c; this.c = c;
@@ -306,12 +319,22 @@ public class MarkPassingCalculator {
@Override @Override
public void run() { public void run() {
logger.finer("Calculating MarkPassings for race "+raceName+", competitor " + c + " (" + Util.size(fixes) + " new fixes)"); try {
Util.Pair<Iterable<Candidate>, Iterable<Candidate>> candidateDeltas = finder.getCandidateDeltas(c, fixes); if (logger.isLoggable(Level.FINER)) {
logger.finer("Received " + Util.size(candidateDeltas.getA()) + " new Candidates for race "+raceName+ logger.finer("Calculating MarkPassings for race "+raceName+", competitor " + c + " (" + Util.size(fixes) + " new fixes)");
" and competitor "+c+" and will remove " }
+ Util.size(candidateDeltas.getB()) + " old Candidates for " + c); Util.Pair<Iterable<Candidate>, Iterable<Candidate>> candidateDeltas = finder.getCandidateDeltas(c, fixes);
chooser.calculateMarkPassDeltas(c, candidateDeltas.getA(), candidateDeltas.getB()); 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;
}
} }
} }
@@ -47,6 +47,7 @@ import com.sap.sse.common.Timed;
import com.sap.sse.common.Util; import com.sap.sse.common.Util;
import com.sap.sse.common.Util.Pair; import com.sap.sse.common.Util.Pair;
import com.sap.sse.common.impl.TimeRangeImpl; import com.sap.sse.common.impl.TimeRangeImpl;
import com.sap.sse.concurrent.LockUtil;
import com.sap.sse.util.ThreadPoolUtil; import com.sap.sse.util.ThreadPoolUtil;
/** /**
@@ -383,24 +384,30 @@ public class CandidateFinderImpl implements CandidateFinder {
changedWaypoints.add(w); changedWaypoints.add(w);
} }
} }
final Set<Callable<Void>> tasks = new HashSet<>();; final Set<Callable<Void>> tasks = new HashSet<>();
final Thread executingThread = Thread.currentThread(); // most likely the MarkPassingCalculator.Listen thread
for (Competitor c : race.getRace().getCompetitors()) { for (Competitor c : race.getRace().getCompetitors()) {
tasks.add(()->{ tasks.add(()->{
List<Candidate> badCans = new ArrayList<>(); LockUtil.propagateLockSetFrom(executingThread); // "inherit" the course read lock from the "calling" thread into the thread pool executor thread
List<Candidate> newCans = new ArrayList<>(); try {
for (Waypoint w : changedWaypoints) { List<Candidate> badCans = new ArrayList<>();
Map<List<GPSFix>, Candidate> xteCans = getXteCandidates(c, w); List<Candidate> newCans = new ArrayList<>();
badCans.addAll(xteCans.values()); for (Waypoint w : changedWaypoints) {
xteCans.clear(); Map<List<GPSFix>, Candidate> xteCans = getXteCandidates(c, w);
Map<GPSFix, Candidate> distanceCans = getDistanceCandidates(c, w); badCans.addAll(xteCans.values());
badCans.addAll(distanceCans.values()); xteCans.clear();
distanceCans.clear(); Map<GPSFix, Candidate> distanceCans = getDistanceCandidates(c, w);
badCans.addAll(distanceCans.values());
distanceCans.clear();
}
Set<GPSFix> allFixes = getAllFixes(c);
newCans.addAll(checkForDistanceCandidateChanges(c, allFixes, changedWaypoints).getA());
newCans.addAll(checkForXTECandidatesChanges(c, allFixes, changedWaypoints).getA());
result.put(c, new Util.Pair<List<Candidate>, List<Candidate>>(newCans, badCans));
return null;
} finally {
LockUtil.unpropagateLockSetFrom(executingThread); // "return" the course read lock from the thread pool executor thread to the "calling" thread
} }
Set<GPSFix> allFixes = getAllFixes(c);
newCans.addAll(checkForDistanceCandidateChanges(c, allFixes, changedWaypoints).getA());
newCans.addAll(checkForXTECandidatesChanges(c, allFixes, changedWaypoints).getA());
result.put(c, new Util.Pair<List<Candidate>, List<Candidate>>(newCans, badCans));
return null;
}); });
} }
executor.invokeAll(tasks); executor.invokeAll(tasks);
@@ -513,8 +520,8 @@ public class CandidateFinderImpl implements CandidateFinder {
private Set<GPSFix> getAllFixes(Competitor c) { private Set<GPSFix> getAllFixes(Competitor c) {
Set<GPSFix> fixes = new TreeSet<GPSFix>(comp); Set<GPSFix> fixes = new TreeSet<GPSFix>(comp);
DynamicGPSFixTrack<Competitor, GPSFixMoving> track = race.getTrack(c); DynamicGPSFixTrack<Competitor, GPSFixMoving> track = race.getTrack(c);
track.lockForRead();
try { try {
track.lockForRead();
for (GPSFix fix : track.getFixes( for (GPSFix fix : track.getFixes(
timeRangeForValidCandidates.from(), /* fromInclusive */ true, timeRangeForValidCandidates.from(), /* fromInclusive */ true,
timeRangeForValidCandidates.to(), /* toInclusive */ true)) { timeRangeForValidCandidates.to(), /* toInclusive */ true)) {
@@ -542,8 +549,8 @@ public class CandidateFinderImpl implements CandidateFinder {
affectedFixes.add(fix); affectedFixes.add(fix);
GPSFix fixBefore; GPSFix fixBefore;
GPSFix fixAfter; GPSFix fixAfter;
track.lockForRead();
try { try {
track.lockForRead();
TimePoint timePoint = fix.getTimePoint(); TimePoint timePoint = fix.getTimePoint();
fixBefore = track.getLastFixBefore(timePoint); fixBefore = track.getLastFixBefore(timePoint);
fixAfter = track.getFirstFixAfter(timePoint); fixAfter = track.getFirstFixAfter(timePoint);
@@ -1206,19 +1213,24 @@ public class CandidateFinderImpl implements CandidateFinder {
* @return an average of the estimated legs before and after <code>w</code>. * @return an average of the estimated legs before and after <code>w</code>.
*/ */
private Distance getAverageLengthOfAdjacentLegs(TimePoint t, Waypoint w) { private Distance getAverageLengthOfAdjacentLegs(TimePoint t, Waypoint w) {
Course course = race.getRace().getCourse(); final Distance result;
if (w == course.getFirstWaypoint()) { final Course course = race.getRace().getCourse();
return race.getTrackedLegStartingAt(w).getGreatCircleDistance(t); if (course.getNumberOfWaypoints() < 2) {
result = null;
} else if (w == course.getFirstWaypoint()) {
result = race.getTrackedLegStartingAt(w).getGreatCircleDistance(t);
} else if (w == course.getLastWaypoint()) { } else if (w == course.getLastWaypoint()) {
return race.getTrackedLegFinishingAt(w).getGreatCircleDistance(t); result = race.getTrackedLegFinishingAt(w).getGreatCircleDistance(t);
} else { } else {
Distance before = race.getTrackedLegStartingAt(w).getGreatCircleDistance(t); Distance before = race.getTrackedLegStartingAt(w).getGreatCircleDistance(t);
Distance after = race.getTrackedLegFinishingAt(w).getGreatCircleDistance(t); Distance after = race.getTrackedLegFinishingAt(w).getGreatCircleDistance(t);
if (after != null && before != null) { 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 newNonInferredStartTime = race.getStartOfRace(/* inferred */ false);
final TimePoint newTimePointWhenToStartConsideringCandidates = getTimePointWhenToStartConsideringCandidates(newNonInferredStartTime); final TimePoint newTimePointWhenToStartConsideringCandidates = getTimePointWhenToStartConsideringCandidates(newNonInferredStartTime);
if (!Util.equalsWithNull(newTimePointWhenToStartConsideringCandidates, timeRangeForValidCandidates.from())) { if (!Util.equalsWithNull(newTimePointWhenToStartConsideringCandidates, timeRangeForValidCandidates.from())) {
final TimePoint oldTimePointWhenToStartConsideringCandidates = timeRangeForValidCandidates.from();
timeRangeForValidCandidates = new TimeRangeImpl(newTimePointWhenToStartConsideringCandidates, timeRangeForValidCandidates.to()); timeRangeForValidCandidates = new TimeRangeImpl(newTimePointWhenToStartConsideringCandidates, timeRangeForValidCandidates.to());
result = updateCandiatesAfterRaceTimeRangeChanged(newTimePointWhenToStartConsideringCandidates, timeRangeForValidCandidates.from()); result = updateCandiatesAfterRaceTimeRangeChanged(newTimePointWhenToStartConsideringCandidates, oldTimePointWhenToStartConsideringCandidates);
} else { } else {
result = Collections.emptyMap(); result = Collections.emptyMap();
} }
@@ -1427,12 +1440,10 @@ public class CandidateFinderImpl implements CandidateFinder {
TimePoint oldFinishedTime, TimePoint newFinishedTime) { TimePoint oldFinishedTime, TimePoint newFinishedTime) {
final Map<Competitor, Pair<Iterable<Candidate>, Iterable<Candidate>>> result; final Map<Competitor, Pair<Iterable<Candidate>, Iterable<Candidate>>> result;
final TimePoint newTimePointWhenToFinishConsideringCandidates = getTimePointWhenToFinishConsideringCandidates(newFinishedTime); final TimePoint newTimePointWhenToFinishConsideringCandidates = getTimePointWhenToFinishConsideringCandidates(newFinishedTime);
final TimePoint oldTimePointWhenToFinishConsideringCandidates = timeRangeForValidCandidates.to();
if (!Util.equalsWithNull(timeRangeForValidCandidates.to(), newTimePointWhenToFinishConsideringCandidates)) { if (!Util.equalsWithNull(timeRangeForValidCandidates.to(), newTimePointWhenToFinishConsideringCandidates)) {
final TimePoint oldTimePointWhenToFinishConsideringCandidates = timeRangeForValidCandidates.to();
timeRangeForValidCandidates = new TimeRangeImpl(timeRangeForValidCandidates.from(), newTimePointWhenToFinishConsideringCandidates); timeRangeForValidCandidates = new TimeRangeImpl(timeRangeForValidCandidates.from(), newTimePointWhenToFinishConsideringCandidates);
result = updateCandiatesAfterRaceTimeRangeChanged( result = updateCandiatesAfterRaceTimeRangeChanged(oldTimePointWhenToFinishConsideringCandidates, newTimePointWhenToFinishConsideringCandidates);
oldTimePointWhenToFinishConsideringCandidates,
newTimePointWhenToFinishConsideringCandidates);
} else { } else {
result = Collections.emptyMap(); result = Collections.emptyMap();
} }
@@ -37,7 +37,7 @@ public interface DoubleVectorFixImporter {
* @throws IOException * @throws IOException
* if there is a problem while reading the file * 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; throws FormatNotSupportedException, IOException;
/** /**
@@ -23,7 +23,7 @@ public abstract class TrackedRaceWithWindEssentials implements TrackedRace {
private static final Logger logger = Logger.getLogger(TrackedRaceWithWindEssentials.class.getName()); 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 * 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 * {@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 * 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() { protected NamedReentrantReadWriteLock getSerializationLock() {
return serializationLock; return serializationLock;
} }
} }
@@ -1075,8 +1075,10 @@ public class GPSFixTrackImpl<ItemType, FixType extends GPSFix> extends MappedTra
unlockAfterRead(); unlockAfterRead();
} }
} }
for (GPSTrackListener<ItemType, FixType> listener : getListeners()) { if (result) {
listener.gpsFixReceived(fix, getTrackedItem(), firstFixInTrack); for (GPSTrackListener<ItemType, FixType> listener : getListeners()) {
listener.gpsFixReceived(fix, getTrackedItem(), firstFixInTrack);
}
} }
return result; return result;
} }
@@ -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.impl.WindImpl;
import com.sap.sailing.domain.common.tracking.GPSFix; import com.sap.sailing.domain.common.tracking.GPSFix;
import com.sap.sailing.domain.common.tracking.GPSFixMoving; 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.MarkPassing;
import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.TrackedRaceStatus; import com.sap.sailing.domain.tracking.TrackedRaceStatus;
@@ -740,6 +742,16 @@ public class TrackBasedEstimationWindTrackImpl extends VirtualWindTrackImpl {
scheduleCacheRefresh(startOfInvalidation, endOfInvalidation); scheduleCacheRefresh(startOfInvalidation, endOfInvalidation);
} }
} }
@Override
public void competitorSensorFixAdded(Competitor competitor, String trackName, SensorFix fix) {
// no action required
}
@Override
public void competitorSensorTrackAdded(DynamicSensorFixTrack<Competitor, ?> track) {
// no action required
}
} }
/** /**
@@ -158,6 +158,7 @@ import com.sap.sse.util.SmartFutureCache;
import com.sap.sse.util.SmartFutureCache.AbstractCacheUpdater; import com.sap.sse.util.SmartFutureCache.AbstractCacheUpdater;
import com.sap.sse.util.SmartFutureCache.EmptyUpdateInterval; import com.sap.sse.util.SmartFutureCache.EmptyUpdateInterval;
import com.sap.sse.util.impl.ArrayListNavigableSet; import com.sap.sse.util.impl.ArrayListNavigableSet;
import com.sap.sse.util.impl.FutureTaskWithTracingGet;
import difflib.DiffUtils; import difflib.DiffUtils;
import difflib.Patch; 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. * 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: * Updates the start and end of tracking in the following precedence order:
@@ -762,10 +763,12 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
* </ol> * </ol>
*/ */
public void updateStartAndEndOfTracking(boolean waitForGPSFixesToLoad) { public void updateStartAndEndOfTracking(boolean waitForGPSFixesToLoad) {
final TimePoint oldStartOfTracking;
final TimePoint oldEndOfTracking;
synchronized (updateStartAndEndOfTrackingMonitor) { synchronized (updateStartAndEndOfTrackingMonitor) {
final Pair<TimePointSpecificationFoundInLog, TimePointSpecificationFoundInLog> trackingTimesFromRaceLog = this.getTrackingTimesFromRaceLogs(); final Pair<TimePointSpecificationFoundInLog, TimePointSpecificationFoundInLog> trackingTimesFromRaceLog = this.getTrackingTimesFromRaceLogs();
TimePoint oldStartOfTracking = getStartOfTracking(); oldStartOfTracking = getStartOfTracking();
TimePoint oldEndOfTracking = getEndOfTracking(); oldEndOfTracking = getEndOfTracking();
boolean startOfTrackingFound = false; boolean startOfTrackingFound = false;
boolean endOfTrackingFound = false; boolean endOfTrackingFound = false;
// check race log // check race log
@@ -801,9 +804,9 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
endOfTrackingFound = true; endOfTrackingFound = true;
} }
} }
startOfTrackingChanged(oldStartOfTracking, waitForGPSFixesToLoad);
endOfTrackingChanged(oldEndOfTracking, waitForGPSFixesToLoad);
} }
startOfTrackingChanged(oldStartOfTracking, waitForGPSFixesToLoad);
endOfTrackingChanged(oldEndOfTracking, waitForGPSFixesToLoad);
} }
@Override @Override
@@ -1873,7 +1876,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
synchronized (directionFromStartToNextMarkCache) { synchronized (directionFromStartToNextMarkCache) {
future = directionFromStartToNextMarkCache.get(at); future = directionFromStartToNextMarkCache.get(at);
if (future == null) { if (future == null) {
newFuture = new FutureTask<Wind>(new Callable<Wind>() { newFuture = new FutureTaskWithTracingGet<Wind>("getDirectionFromStartToNextMark for "+this, new Callable<Wind>() {
@Override @Override
public Wind call() { public Wind call() {
Wind result; Wind result;
@@ -96,4 +96,9 @@ public class ExpeditionWindTrackerFactory implements WindTrackerFactory {
windReceivers.remove(windTracker.getReceiver().getPort()); windReceivers.remove(windTracker.getReceiver().getPort());
} }
} }
@Override
public String toString() {
return "ExpeditionWindTrackerFactory [defaultPort=" + defaultPort + "]";
}
} }
@@ -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.LeaderboardWithHeaderPerspectiveLifecycle;
import com.sap.sailing.gwt.autoplay.client.shared.leaderboard.LeaderboardWithHeaderPerspectiveSettings; 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.FixedSailingAuthentication;
import com.sap.sailing.gwt.common.authentication.SAPSailingHeaderWithAuthentication;
import com.sap.sailing.gwt.common.client.SharedResources; import com.sap.sailing.gwt.common.client.SharedResources;
import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.raceboard.RaceBoardPerspectiveLifecycle; 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.PerspectiveCompositeSettings;
import com.sap.sse.gwt.client.shared.perspective.PerspectiveLifecycle; import com.sap.sse.gwt.client.shared.perspective.PerspectiveLifecycle;
import com.sap.sse.gwt.client.shared.perspective.PerspectiveLifecycleWithAllSettings; 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; import com.sap.sse.security.ui.client.UserService;
public class DesktopStartView extends Composite implements StartView { public class DesktopStartView extends Composite implements StartView {
@@ -53,7 +53,7 @@ public class DesktopStartView extends Composite implements StartView {
interface StartPageViewUiBinder extends UiBinder<Widget, DesktopStartView> { interface StartPageViewUiBinder extends UiBinder<Widget, DesktopStartView> {
} }
@UiField(provided=true) SAPHeaderWithAuthentication sapHeader; @UiField(provided=true) SAPSailingHeaderWithAuthentication sapHeader;
@UiField(provided=true) ListBox localeSelectionBox; @UiField(provided=true) ListBox localeSelectionBox;
@UiField(provided=true) ListBox eventSelectionBox; @UiField(provided=true) ListBox eventSelectionBox;
@UiField(provided=true) ListBox leaderboardSelectionBox; @UiField(provided=true) ListBox leaderboardSelectionBox;
@@ -82,8 +82,7 @@ public class DesktopStartView extends Composite implements StartView {
this.eventBus = eventBus; this.eventBus = eventBus;
this.events = new ArrayList<EventDTO>(); this.events = new ArrayList<EventDTO>();
sapHeader = new SAPHeaderWithAuthentication(StringMessages.INSTANCE.sapSailingAnalytics(), sapHeader = new SAPSailingHeaderWithAuthentication(StringMessages.INSTANCE.autoplayConfiguration());
StringMessages.INSTANCE.autoplayConfiguration());
new FixedSailingAuthentication(userService, sapHeader.getAuthenticationMenuView()); new FixedSailingAuthentication(userService, sapHeader.getAuthenticationMenuView());
eventSelectionBox = new ListBox(); eventSelectionBox = new ListBox();
@@ -4,23 +4,23 @@ import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.RepeatingCommand; import com.google.gwt.core.client.Scheduler.RepeatingCommand;
import com.google.gwt.user.client.ui.Widget; import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.gwt.common.authentication.FixedSailingAuthentication; 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.common.client.FullscreenUtil;
import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sse.gwt.client.shared.components.AbstractCompositeComponent; 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; import com.sap.sse.security.ui.client.UserService;
public class SAPHeaderComponent extends AbstractCompositeComponent<SAPHeaderComponentSettings> { public class SAPHeaderComponent extends AbstractCompositeComponent<SAPHeaderComponentSettings> {
private SAPHeaderComponentSettings settings; private SAPHeaderComponentSettings settings;
private final SAPHeaderComponentLifecycle componentLifecycle; private final SAPHeaderComponentLifecycle componentLifecycle;
private final SAPHeaderWithAuthentication sapHeader; private final SAPSailingHeaderWithAuthentication sapHeader;
public SAPHeaderComponent(SAPHeaderComponentLifecycle componentLifecycle, UserService userService, SAPHeaderComponentSettings settings, public SAPHeaderComponent(SAPHeaderComponentLifecycle componentLifecycle, UserService userService, SAPHeaderComponentSettings settings,
StringMessages stringMessages, boolean startInAutoScreenMode) { StringMessages stringMessages, boolean startInAutoScreenMode) {
this.componentLifecycle = componentLifecycle; this.componentLifecycle = componentLifecycle;
this.settings = settings; this.settings = settings;
this.sapHeader = new SAPHeaderWithAuthentication(stringMessages.sapSailingAnalytics(), settings.getTitle()); this.sapHeader = new SAPSailingHeaderWithAuthentication(settings.getTitle());
new FixedSailingAuthentication(userService, sapHeader.getAuthenticationMenuView()); new FixedSailingAuthentication(userService, sapHeader.getAuthenticationMenuView());
initWidget(sapHeader); initWidget(sapHeader);
@@ -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);
}
}
@@ -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.RootLayoutPanel;
import com.google.gwt.user.client.ui.ScrollPanel; import com.google.gwt.user.client.ui.ScrollPanel;
import com.sap.sailing.gwt.common.authentication.FixedSailingAuthentication; 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.common.client.SharedResources;
import com.sap.sailing.gwt.regattaoverview.client.RegattaRaceStatesComponent.EntryHandler; import com.sap.sailing.gwt.regattaoverview.client.RegattaRaceStatesComponent.EntryHandler;
import com.sap.sailing.gwt.settings.client.regattaoverview.RegattaOverviewBaseSettings; 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.client.AbstractSailingEntryPoint;
import com.sap.sailing.gwt.ui.shared.RegattaOverviewEntryDTO; import com.sap.sailing.gwt.ui.shared.RegattaOverviewEntryDTO;
import com.sap.sse.gwt.settings.SettingsToUrlSerializer; import com.sap.sse.gwt.settings.SettingsToUrlSerializer;
import com.sap.sse.security.ui.authentication.generic.sapheader.SAPHeaderWithAuthentication;
public class RegattaOverviewEntryPoint extends AbstractSailingEntryPoint { public class RegattaOverviewEntryPoint extends AbstractSailingEntryPoint {
@@ -33,7 +33,7 @@ public class RegattaOverviewEntryPoint extends AbstractSailingEntryPoint {
private final Label clockLabel = new Label(); private final Label clockLabel = new Label();
private final RegattaOverviewResources.LocalCss style = RegattaOverviewResources.INSTANCE.css(); private final RegattaOverviewResources.LocalCss style = RegattaOverviewResources.INSTANCE.css();
private SAPHeaderWithAuthentication siteHeader; private SAPSailingHeaderWithAuthentication siteHeader;
@Override @Override
public void doOnModuleLoad() { public void doOnModuleLoad() {
@@ -50,8 +50,7 @@ public class RegattaOverviewEntryPoint extends AbstractSailingEntryPoint {
clockLabel.addStyleName(style.clockLabel()); clockLabel.addStyleName(style.clockLabel());
siteHeader = new SAPHeaderWithAuthentication(getStringMessages() siteHeader = new SAPSailingHeaderWithAuthentication();
.sapSailingAnalytics());
new FixedSailingAuthentication(getUserService(), siteHeader.getAuthenticationMenuView()); new FixedSailingAuthentication(getUserService(), siteHeader.getAuthenticationMenuView());
siteHeader.addWidgetToRightSide(clockLabel); siteHeader.addWidgetToRightSide(clockLabel);
@@ -41,6 +41,7 @@ public abstract class AbstractFileImportWidget extends Composite {
protected final SailingServiceAsync sailingService; protected final SailingServiceAsync sailingService;
private final StringMessages stringMessages; private final StringMessages stringMessages;
private final ErrorReporter errorReporter; private final ErrorReporter errorReporter;
private boolean shouldClearListOnNewUploadComplete = true;
protected AbstractFileImportWidget(String formActionUrl, StringMessages stringMessages, protected AbstractFileImportWidget(String formActionUrl, StringMessages stringMessages,
TrackFileImportDeviceIdentifierTableWrapper table, SailingServiceAsync sailingService, TrackFileImportDeviceIdentifierTableWrapper table, SailingServiceAsync sailingService,
@@ -73,7 +74,9 @@ public abstract class AbstractFileImportWidget extends Composite {
@UiHandler("formPanelUi") @UiHandler("formPanelUi")
void onFileImportComplete(SubmitCompleteEvent event) { void onFileImportComplete(SubmitCompleteEvent event) {
table.getDataProvider().getList().clear(); if (shouldClearListOnNewUploadComplete) {
table.getDataProvider().getList().clear();
}
String[] uuids = event.getResults().split("\\n"); String[] uuids = event.getResults().split("\\n");
if (uuids.length != 1 || (uuids.length > 0 && !uuids[0].isEmpty())) { if (uuids.length != 1 || (uuids.length > 0 && !uuids[0].isEmpty())) {
sailingService.getTrackFileImportDeviceIds(Arrays.asList(uuids), sailingService.getTrackFileImportDeviceIds(Arrays.asList(uuids),
@@ -106,4 +109,7 @@ public abstract class AbstractFileImportWidget extends Composite {
protected abstract void getImporterTypes(AsyncCallback<Collection<String>> callback); protected abstract void getImporterTypes(AsyncCallback<Collection<String>> callback);
protected void setShouldClearListOnNewUploadComplete(boolean shouldClearListOnNewUploadComplete) {
this.shouldClearListOnNewUploadComplete = shouldClearListOnNewUploadComplete;
}
} }
@@ -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.Roles;
import com.sap.sailing.domain.common.security.SailingPermissionsForRoleProvider; import com.sap.sailing.domain.common.security.SailingPermissionsForRoleProvider;
import com.sap.sailing.gwt.common.authentication.FixedSailingAuthentication; 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.AbstractSailingEntryPoint;
import com.sap.sailing.gwt.ui.client.LeaderboardGroupsDisplayer; import com.sap.sailing.gwt.ui.client.LeaderboardGroupsDisplayer;
import com.sap.sailing.gwt.ui.client.LeaderboardGroupsRefresher; 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.decorator.WidgetFactory;
import com.sap.sse.security.ui.authentication.generic.GenericAuthentication; 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.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.component.UserManagementPanel;
import com.sap.sse.security.ui.client.i18n.StringMessages; import com.sap.sse.security.ui.client.i18n.StringMessages;
@@ -66,8 +66,7 @@ public class AdminConsoleEntryPoint extends AbstractSailingEntryPoint implements
private void createUI() { private void createUI() {
HeaderPanel headerPanel = new HeaderPanel(); HeaderPanel headerPanel = new HeaderPanel();
SAPHeaderWithAuthentication header = new SAPHeaderWithAuthentication(getStringMessages().sapSailingAnalytics(), SAPSailingHeaderWithAuthentication header = new SAPSailingHeaderWithAuthentication(getStringMessages().administration());
getStringMessages().administration());
GenericAuthentication genericSailingAuthentication = new FixedSailingAuthentication(getUserService(), header.getAuthenticationMenuView()); GenericAuthentication genericSailingAuthentication = new FixedSailingAuthentication(getUserService(), header.getAuthenticationMenuView());
AuthorizedContentDecorator authorizedContentDecorator = new GenericAuthorizedContentDecorator(genericSailingAuthentication); AuthorizedContentDecorator authorizedContentDecorator = new GenericAuthorizedContentDecorator(genericSailingAuthentication);
authorizedContentDecorator.setContentWidgetFactory(new WidgetFactory() { authorizedContentDecorator.setContentWidgetFactory(new WidgetFactory() {
@@ -2,9 +2,7 @@ package com.sap.sailing.gwt.ui.adminconsole;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.CaptionPanel; 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.VerticalPanel;
import com.google.gwt.user.client.ui.Widget; import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.view.client.SelectionChangeEvent; 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.dto.CompetitorDTO;
import com.sap.sailing.domain.common.racelog.tracking.MappableToDevice; import com.sap.sailing.domain.common.racelog.tracking.MappableToDevice;
import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
@@ -31,12 +28,6 @@ public class RegattaLogImportSensorDataAndAddMappingsDialog extends DataEntryDia
protected final CompetitorTableWrapper<RefreshableSingleSelectionModel<CompetitorDTO>> competitorTable; protected final CompetitorTableWrapper<RefreshableSingleSelectionModel<CompetitorDTO>> competitorTable;
private final StringMessages stringMessages; private final StringMessages stringMessages;
private final Map<TrackFileImportDeviceIdentifierDTO, CompetitorDTO> mappings = new HashMap<>();
private TrackFileImportDeviceIdentifierDTO deviceToSelect;
private CompetitorDTO compToSelect;
private boolean inInstableTransitionState = false;
public RegattaLogImportSensorDataAndAddMappingsDialog(SailingServiceAsync sailingService, public RegattaLogImportSensorDataAndAddMappingsDialog(SailingServiceAsync sailingService,
final ErrorReporter errorReporter, final StringMessages stringMessages, String leaderboardName, final ErrorReporter errorReporter, final StringMessages stringMessages, String leaderboardName,
DialogCallback<Collection<TypedDeviceMappingDTO>> callback) { DialogCallback<Collection<TypedDeviceMappingDTO>> callback) {
@@ -50,11 +41,16 @@ public class RegattaLogImportSensorDataAndAddMappingsDialog extends DataEntryDia
}, true, callback); }, true, callback);
this.stringMessages = stringMessages; this.stringMessages = stringMessages;
deviceIdTable = new TrackFileImportDeviceIdentifierTableWrapper(sailingService, stringMessages, errorReporter); deviceIdTable = new TrackFileImportDeviceIdentifierTableWrapper(sailingService, stringMessages, errorReporter);
deviceIdTable.removeTrackNameColumn();
importWidget = new SensorDataImportWidget(deviceIdTable, stringMessages, sailingService, errorReporter); importWidget = new SensorDataImportWidget(deviceIdTable, stringMessages, sailingService, errorReporter);
deviceIdTable.getSelectionModel().addSelectionChangeHandler(new SelectionChangeEvent.Handler() { deviceIdTable.getSelectionModel().addSelectionChangeHandler(new SelectionChangeEvent.Handler() {
@Override @Override
public void onSelectionChange(SelectionChangeEvent event) { 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 */ competitorTable = new CompetitorTableWrapper<>(sailingService, stringMessages, errorReporter, /* multiSelection */
@@ -63,7 +59,8 @@ public class RegattaLogImportSensorDataAndAddMappingsDialog extends DataEntryDia
competitorTable.getSelectionModel().addSelectionChangeHandler(new SelectionChangeEvent.Handler() { competitorTable.getSelectionModel().addSelectionChangeHandler(new SelectionChangeEvent.Handler() {
@Override @Override
public void onSelectionChange(SelectionChangeEvent event) { 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 <T> void selectOrClear(SingleSelectionModel<T> 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 @Override
protected Widget getAdditionalWidget() { protected Widget getAdditionalWidget() {
HorizontalPanel panel = new HorizontalPanel(); HorizontalPanel panel = new HorizontalPanel();
@@ -150,9 +103,9 @@ public class RegattaLogImportSensorDataAndAddMappingsDialog extends DataEntryDia
protected Collection<TypedDeviceMappingDTO> getResult() { protected Collection<TypedDeviceMappingDTO> getResult() {
List<TypedDeviceMappingDTO> result = new ArrayList<>(); List<TypedDeviceMappingDTO> result = new ArrayList<>();
String dataType = importWidget.getSelectedImporterType(); String dataType = importWidget.getSelectedImporterType();
for (TrackFileImportDeviceIdentifierDTO device : mappings.keySet()) { for (TrackFileImportDeviceIdentifierDTO device : deviceIdTable.getMappings().keySet()) {
DeviceIdentifierDTO deviceIdDto = new DeviceIdentifierDTO("FILE", device.uuidAsString); 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)); result.add(new TypedDeviceMappingDTO(deviceIdDto, device.from, device.to, mappedTo, null, dataType));
} }
return result; return result;
@@ -14,6 +14,7 @@ public class SensorDataImportWidget extends AbstractFileImportWidget implements
final SailingServiceAsync sailingService, final ErrorReporter errorReporter) { final SailingServiceAsync sailingService, final ErrorReporter errorReporter) {
super("/sailingserver/sensordata/import", stringMessages, table, sailingService, errorReporter); super("/sailingserver/sensordata/import", stringMessages, table, sailingService, errorReporter);
setMultipleFileUploadEnabled(true); setMultipleFileUploadEnabled(true);
setShouldClearListOnNewUploadComplete(false);
} }
@Override @Override
@@ -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();
}
}
@@ -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.CheckBox;
import com.google.gwt.user.client.ui.FlexTable; import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.FlexTable.FlexCellFormatter; 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.Grid;
import com.google.gwt.user.client.ui.HTMLTable.ColumnFormatter; import com.google.gwt.user.client.ui.HTMLTable.ColumnFormatter;
import com.google.gwt.user.client.ui.HorizontalPanel; import com.google.gwt.user.client.ui.HorizontalPanel;
@@ -85,7 +84,6 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel {
private LabeledAbstractFilterablePanel<TracTracRaceRecordDTO> racesFilterablePanel; private LabeledAbstractFilterablePanel<TracTracRaceRecordDTO> racesFilterablePanel;
private FlushableCellTable<TracTracRaceRecordDTO> racesTable; private FlushableCellTable<TracTracRaceRecordDTO> racesTable;
private static final String ZERO_AS_STRING = "0";
public TracTracEventManagementPanel(final SailingServiceAsync sailingService, ErrorReporter errorReporter, public TracTracEventManagementPanel(final SailingServiceAsync sailingService, ErrorReporter errorReporter,
RegattaRefresher regattaRefresher, StringMessages stringMessages) { RegattaRefresher regattaRefresher, StringMessages stringMessages) {
@@ -275,32 +273,7 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel {
correctWindCheckBox.setWordWrap(false); correctWindCheckBox.setWordWrap(false);
correctWindCheckBox.setValue(Boolean.TRUE); correctWindCheckBox.setValue(Boolean.TRUE);
final TextBox offsetToStartTimeOfSimulatedRaceTextBox = new TextBox(); final SimulationPanel simulationPanel = new SimulationPanel(stringMessages);
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 CheckBox ignoreTracTracMarkPassingsCheckbox = new CheckBox(stringMessages.useInternalAlgorithm()); final CheckBox ignoreTracTracMarkPassingsCheckbox = new CheckBox(stringMessages.useInternalAlgorithm());
ignoreTracTracMarkPassingsCheckbox.setWordWrap(false); ignoreTracTracMarkPassingsCheckbox.setWordWrap(false);
ignoreTracTracMarkPassingsCheckbox.setValue(Boolean.FALSE); ignoreTracTracMarkPassingsCheckbox.setValue(Boolean.FALSE);
@@ -308,8 +281,7 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel {
layoutTable.setWidget(++row, 0, trackSettingsLabel); layoutTable.setWidget(++row, 0, trackSettingsLabel);
layoutTable.setWidget(row, 1, trackWindCheckBox); layoutTable.setWidget(row, 1, trackWindCheckBox);
layoutTable.setWidget(++row, 1, correctWindCheckBox); layoutTable.setWidget(++row, 1, correctWindCheckBox);
layoutTable.setWidget(++row, 1, simulateAsLiveRacePanel); layoutTable.setWidget(++row, 1, simulationPanel);
layoutTable.setWidget(++row, 1, simulateWithOffsetPanel);
layoutTable.setWidget(++row, 1, ignoreTracTracMarkPassingsCheckbox); layoutTable.setWidget(++row, 1, ignoreTracTracMarkPassingsCheckbox);
// Filter // Filter
@@ -413,8 +385,8 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel {
@Override @Override
public void onClick(ClickEvent event) { public void onClick(ClickEvent event) {
Duration offsetToStartTimeOfSimulatedRace = null; Duration offsetToStartTimeOfSimulatedRace = null;
if(simulateWithStartTimeNowCheckBox.getValue().booleanValue()) { if (simulationPanel.isSimulate()) {
offsetToStartTimeOfSimulatedRace = getMillisecondsDurationFromMinutesAsString(offsetToStartTimeOfSimulatedRaceTextBox.getValue()); offsetToStartTimeOfSimulatedRace = getMillisecondsDurationFromMinutesAsString(simulationPanel.getOffsetToStartTimeInMinutes());
} }
trackSelectedRaces(trackWindCheckBox.getValue(), correctWindCheckBox.getValue(), trackSelectedRaces(trackWindCheckBox.getValue(), correctWindCheckBox.getValue(),
offsetToStartTimeOfSimulatedRace, ignoreTracTracMarkPassingsCheckbox.getValue()); offsetToStartTimeOfSimulatedRace, ignoreTracTracMarkPassingsCheckbox.getValue());
@@ -1,6 +1,18 @@
package com.sap.sailing.gwt.ui.adminconsole; 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.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.SailingServiceAsync;
import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.common.client.DateAndTimeFormatterUtil; 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.EntityIdentityComparator;
import com.sap.sse.gwt.client.celltable.RefreshableSingleSelectionModel; import com.sap.sse.gwt.client.celltable.RefreshableSingleSelectionModel;
public class TrackFileImportDeviceIdentifierTableWrapper extends TableWrapper<TrackFileImportDeviceIdentifierDTO, public class TrackFileImportDeviceIdentifierTableWrapper extends
RefreshableSingleSelectionModel<TrackFileImportDeviceIdentifierDTO>> { TableWrapper<TrackFileImportDeviceIdentifierDTO, RefreshableSingleSelectionModel<TrackFileImportDeviceIdentifierDTO>> {
private TextColumn<TrackFileImportDeviceIdentifierDTO> trackNameColumn;
public TrackFileImportDeviceIdentifierTableWrapper(SailingServiceAsync sailingService, StringMessages stringMessages, private final Map<TrackFileImportDeviceIdentifierDTO, CompetitorDTO> mappings = new HashMap<>();
ErrorReporter errorReporter) {
public TrackFileImportDeviceIdentifierTableWrapper(SailingServiceAsync sailingService,
StringMessages stringMessages, ErrorReporter errorReporter) {
super(sailingService, stringMessages, errorReporter, /* multiSelection */ false, /* enablePager */ true, super(sailingService, stringMessages, errorReporter, /* multiSelection */ false, /* enablePager */ true,
new EntityIdentityComparator<TrackFileImportDeviceIdentifierDTO>() { new EntityIdentityComparator<TrackFileImportDeviceIdentifierDTO>() {
@Override @Override
@@ -21,54 +35,49 @@ RefreshableSingleSelectionModel<TrackFileImportDeviceIdentifierDTO>> {
TrackFileImportDeviceIdentifierDTO dto2) { TrackFileImportDeviceIdentifierDTO dto2) {
return dto1.uuidAsString.equals(dto2.uuidAsString); return dto1.uuidAsString.equals(dto2.uuidAsString);
} }
@Override @Override
public int hashCode(TrackFileImportDeviceIdentifierDTO t) { public int hashCode(TrackFileImportDeviceIdentifierDTO t) {
return t.uuidAsString.hashCode(); return t.uuidAsString.hashCode();
} }
}); });
TextColumn<TrackFileImportDeviceIdentifierDTO> uuidColumn = new TextColumn<TrackFileImportDeviceIdentifierDTO>() { TextColumn<TrackFileImportDeviceIdentifierDTO> uuidColumn = new TextColumn<TrackFileImportDeviceIdentifierDTO>() {
@Override @Override
public String getValue(TrackFileImportDeviceIdentifierDTO object) { public String getValue(TrackFileImportDeviceIdentifierDTO object) {
return object.uuidAsString; return object.uuidAsString;
} }
}; };
TextColumn<TrackFileImportDeviceIdentifierDTO> fileNameColumn = new TextColumn<TrackFileImportDeviceIdentifierDTO>() { TextColumn<TrackFileImportDeviceIdentifierDTO> fileNameColumn = new TextColumn<TrackFileImportDeviceIdentifierDTO>() {
@Override @Override
public String getValue(TrackFileImportDeviceIdentifierDTO object) { public String getValue(TrackFileImportDeviceIdentifierDTO object) {
return object.fileName; return object.fileName;
} }
}; };
trackNameColumn = new TextColumn<TrackFileImportDeviceIdentifierDTO>() {
TextColumn<TrackFileImportDeviceIdentifierDTO> trackNameColumn = new TextColumn<TrackFileImportDeviceIdentifierDTO>() {
@Override @Override
public String getValue(TrackFileImportDeviceIdentifierDTO object) { public String getValue(TrackFileImportDeviceIdentifierDTO object) {
return object.trackName; return object.trackName;
} }
}; };
TextColumn<TrackFileImportDeviceIdentifierDTO> fromColumn = new TextColumn<TrackFileImportDeviceIdentifierDTO>() { TextColumn<TrackFileImportDeviceIdentifierDTO> fromColumn = new TextColumn<TrackFileImportDeviceIdentifierDTO>() {
@Override @Override
public String getValue(TrackFileImportDeviceIdentifierDTO object) { public String getValue(TrackFileImportDeviceIdentifierDTO object) {
return DateAndTimeFormatterUtil.formatDateAndTime(object.from); return DateAndTimeFormatterUtil.formatDateAndTime(object.from);
} }
}; };
TextColumn<TrackFileImportDeviceIdentifierDTO> toColumn = new TextColumn<TrackFileImportDeviceIdentifierDTO>() { TextColumn<TrackFileImportDeviceIdentifierDTO> toColumn = new TextColumn<TrackFileImportDeviceIdentifierDTO>() {
@Override @Override
public String getValue(TrackFileImportDeviceIdentifierDTO object) { public String getValue(TrackFileImportDeviceIdentifierDTO object) {
return DateAndTimeFormatterUtil.formatDateAndTime(object.to); return DateAndTimeFormatterUtil.formatDateAndTime(object.to);
} }
}; };
TextColumn<TrackFileImportDeviceIdentifierDTO> numberOfFixesColumn = new TextColumn<TrackFileImportDeviceIdentifierDTO>() { TextColumn<TrackFileImportDeviceIdentifierDTO> numberOfFixesColumn = new TextColumn<TrackFileImportDeviceIdentifierDTO>() {
@Override @Override
public String getValue(TrackFileImportDeviceIdentifierDTO object) { public String getValue(TrackFileImportDeviceIdentifierDTO object) {
return "" + object.numFixes; return "" + object.numFixes;
} }
}; };
table.setTableBuilder(new MappingTableBuilder(table));
table.addColumn(uuidColumn, "UUID"); table.addColumn(uuidColumn, "UUID");
table.addColumn(fileNameColumn, "Filename"); table.addColumn(fileNameColumn, "Filename");
table.addColumn(trackNameColumn, "Trackname"); table.addColumn(trackNameColumn, "Trackname");
@@ -77,4 +86,87 @@ RefreshableSingleSelectionModel<TrackFileImportDeviceIdentifierDTO>> {
table.addColumn(numberOfFixesColumn, "# fixes"); table.addColumn(numberOfFixesColumn, "# fixes");
} }
public void removeTrackNameColumn() {
table.removeColumn(trackNameColumn);
}
class MappingTableBuilder extends DefaultCellTableBuilder<TrackFileImportDeviceIdentifierDTO> {
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<TrackFileImportDeviceIdentifierDTO> cellTable;
public MappingTableBuilder(AbstractCellTable<TrackFileImportDeviceIdentifierDTO> 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<TrackFileImportDeviceIdentifierDTO> 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<TrackFileImportDeviceIdentifierDTO, CompetitorDTO> getMappings() {
return mappings;
}
} }
@@ -1,71 +1,71 @@
package com.sap.sailing.gwt.ui.leaderboardedit; package com.sap.sailing.gwt.ui.leaderboardedit;
import java.util.List; import java.util.List;
import com.google.gwt.dom.client.Style.Position; import com.google.gwt.dom.client.Style.Position;
import com.google.gwt.dom.client.Style.Unit; import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.user.client.Window; import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.RootPanel; import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.Widget; import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.domain.common.security.Permission; import com.sap.sailing.domain.common.security.Permission;
import com.sap.sailing.domain.common.security.SailingPermissionsForRoleProvider; import com.sap.sailing.domain.common.security.SailingPermissionsForRoleProvider;
import com.sap.sailing.gwt.common.authentication.FixedSailingAuthentication; import com.sap.sailing.gwt.common.authentication.FixedSailingAuthentication;
import com.sap.sailing.gwt.ui.client.AbstractSailingEntryPoint; import com.sap.sailing.gwt.common.authentication.SAPSailingHeaderWithAuthentication;
import com.sap.sse.gwt.client.async.AsyncActionsExecutor; import com.sap.sailing.gwt.ui.client.AbstractSailingEntryPoint;
import com.sap.sse.gwt.client.async.MarkedAsyncCallback; import com.sap.sse.gwt.client.async.AsyncActionsExecutor;
import com.sap.sse.security.ui.authentication.decorator.AuthorizedContentDecorator; import com.sap.sse.gwt.client.async.MarkedAsyncCallback;
import com.sap.sse.security.ui.authentication.decorator.WidgetFactory; import com.sap.sse.security.ui.authentication.decorator.AuthorizedContentDecorator;
import com.sap.sse.security.ui.authentication.generic.GenericAuthentication; import com.sap.sse.security.ui.authentication.decorator.WidgetFactory;
import com.sap.sse.security.ui.authentication.generic.GenericAuthorizedContentDecorator; import com.sap.sse.security.ui.authentication.generic.GenericAuthentication;
import com.sap.sse.security.ui.authentication.generic.sapheader.SAPHeaderWithAuthentication; 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 public class LeaderboardEditPage extends AbstractSailingEntryPoint {
protected void doOnModuleLoad() { @Override
super.doOnModuleLoad(); protected void doOnModuleLoad() {
super.doOnModuleLoad();
sailingService.getLeaderboardNames(new MarkedAsyncCallback<List<String>>(
new AsyncCallback<List<String>>() { sailingService.getLeaderboardNames(new MarkedAsyncCallback<List<String>>(
@Override new AsyncCallback<List<String>>() {
public void onSuccess(List<String> leaderboardNames) { @Override
final String leaderboardName = Window.Location.getParameter("name"); public void onSuccess(List<String> leaderboardNames) {
if (leaderboardNames.contains(leaderboardName)) { final String leaderboardName = Window.Location.getParameter("name");
if (leaderboardNames.contains(leaderboardName)) {
SAPHeaderWithAuthentication header = initHeader();
GenericAuthentication genericSailingAuthentication = new FixedSailingAuthentication(getUserService(), header.getAuthenticationMenuView()); SAPHeaderWithAuthentication header = initHeader();
AuthorizedContentDecorator authorizedContentDecorator = new GenericAuthorizedContentDecorator(genericSailingAuthentication); GenericAuthentication genericSailingAuthentication = new FixedSailingAuthentication(getUserService(), header.getAuthenticationMenuView());
authorizedContentDecorator.setPermissionToCheck(Permission.MANAGE_LEADERBOARD_RESULTS, SailingPermissionsForRoleProvider.INSTANCE); AuthorizedContentDecorator authorizedContentDecorator = new GenericAuthorizedContentDecorator(genericSailingAuthentication);
authorizedContentDecorator.setContentWidgetFactory(new WidgetFactory() { authorizedContentDecorator.setPermissionToCheck(Permission.MANAGE_LEADERBOARD_RESULTS, SailingPermissionsForRoleProvider.INSTANCE);
@Override authorizedContentDecorator.setContentWidgetFactory(new WidgetFactory() {
public Widget get() { @Override
EditableLeaderboardPanel leaderboardPanel = new EditableLeaderboardPanel(sailingService, new AsyncActionsExecutor(), leaderboardName, null, public Widget get() {
LeaderboardEditPage.this, getStringMessages(), userAgent); EditableLeaderboardPanel leaderboardPanel = new EditableLeaderboardPanel(sailingService, new AsyncActionsExecutor(), leaderboardName, null,
leaderboardPanel.ensureDebugId("EditableLeaderboardPanel"); LeaderboardEditPage.this, getStringMessages(), userAgent);
return leaderboardPanel; leaderboardPanel.ensureDebugId("EditableLeaderboardPanel");
} return leaderboardPanel;
}); }
});
RootPanel.get().add(authorizedContentDecorator);
RootPanel.get().add(header); RootPanel.get().add(authorizedContentDecorator);
} else { RootPanel.get().add(header);
RootPanel.get().add(new Label(getStringMessages().noSuchLeaderboard())); } else {
} RootPanel.get().add(new Label(getStringMessages().noSuchLeaderboard()));
} }
@Override }
public void onFailure(Throwable t) { @Override
reportError("Error trying to obtain list of leaderboard names: "+t.getMessage()); 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(), private SAPHeaderWithAuthentication initHeader() {
getStringMessages().editScores()); SAPSailingHeaderWithAuthentication header = new SAPSailingHeaderWithAuthentication(getStringMessages().editScores());
header.getElement().getStyle().setPosition(Position.FIXED); header.getElement().getStyle().setPosition(Position.FIXED);
header.getElement().getStyle().setTop(0, Unit.PX); header.getElement().getStyle().setTop(0, Unit.PX);
header.getElement().getStyle().setWidth(100, Unit.PCT); header.getElement().getStyle().setWidth(100, Unit.PCT);
return header; return header;
} }
} }
@@ -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 * can be obtained within this timeout, wind for the race will be tracked; otherwise, the method returns without
* taking any effect. * 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(); Regatta regatta = raceHandle.getRegatta();
if (regatta != null) { if (regatta != null) {
RaceDefinition race = raceHandle.getRace(timeoutInMilliseconds); RaceDefinition race = raceHandle.getRace(timeoutInMilliseconds);
@@ -2173,7 +2173,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
} else { } else {
actualTimePoint = timePoint; actualTimePoint = timePoint;
} }
RaceDefinition race = trackedRace.getRace(); final RaceDefinition race = trackedRace.getRace();
int rank = 1; int rank = 1;
final List<Competitor> competitorsFromBestToWorst = trackedRace.getCompetitorsFromBestToWorst(actualTimePoint); final List<Competitor> competitorsFromBestToWorst = trackedRace.getCompetitorsFromBestToWorst(actualTimePoint);
for (Competitor competitor : competitorsFromBestToWorst) { for (Competitor competitor : competitorsFromBestToWorst) {
@@ -10,10 +10,7 @@ import java.util.Map.Entry;
import java.util.NavigableMap; import java.util.NavigableMap;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger; import java.util.logging.Logger;
import com.google.gwt.user.server.rpc.RemoteServiceServlet; 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.TimePoint;
import com.sap.sse.common.Util; import com.sap.sse.common.Util;
import com.sap.sse.common.impl.MillisecondsTimePoint; 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 { public class SimulatorServiceImpl extends RemoteServiceServlet implements SimulatorService {
@@ -107,12 +104,7 @@ public class SimulatorServiceImpl extends RemoteServiceServlet implements Simula
private SpeedWithBearing averageWind = null; private SpeedWithBearing averageWind = null;
public SimulatorServiceImpl() { public SimulatorServiceImpl() {
final int THREAD_POOL_SIZE = Math.max(Runtime.getRuntime().availableProcessors()/2, 3); final ScheduledExecutorService simulatorExecutor = ThreadPoolUtil.INSTANCE.getDefaultForegroundTaskThreadPoolExecutor();
Executor simulatorExecutor = new ThreadPoolExecutor(/* corePoolSize */THREAD_POOL_SIZE,
/* maximumPoolSize */THREAD_POOL_SIZE,
/* keepAliveTime */60, TimeUnit.SECONDS,
/* workQueue */new LinkedBlockingQueue<Runnable>(),
new ThreadFactoryWithPriority(Thread.NORM_PRIORITY, /* daemon */ true));
// TODO: initialize smart-future-cache for simulation-results and add to simulation-service // TODO: initialize smart-future-cache for simulation-results and add to simulation-service
simulationService = SimulationServiceFactory.INSTANCE.getService(simulatorExecutor, null); simulationService = SimulationServiceFactory.INSTANCE.getService(simulatorExecutor, null);
} }
@@ -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.RootLayoutPanel;
import com.google.gwt.user.client.ui.Widget; import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.gwt.common.authentication.FixedSailingAuthentication; 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.AbstractSailingEntryPoint;
import com.sap.sailing.gwt.ui.client.RemoteServiceMappingConstants; import com.sap.sailing.gwt.ui.client.RemoteServiceMappingConstants;
import com.sap.sailing.gwt.ui.client.SimulatorService; 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.decorator.WidgetFactory;
import com.sap.sse.security.ui.authentication.generic.GenericAuthentication; 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.GenericAuthorizedContentDecorator;
import com.sap.sse.security.ui.authentication.generic.sapheader.SAPHeaderWithAuthentication;
public class SimulatorEntryPoint extends AbstractSailingEntryPoint { public class SimulatorEntryPoint extends AbstractSailingEntryPoint {
@@ -187,8 +187,7 @@ public class SimulatorEntryPoint extends AbstractSailingEntryPoint {
} }
private void createSimulatorPanel() { private void createSimulatorPanel() {
SAPHeaderWithAuthentication header = new SAPHeaderWithAuthentication(getStringMessages() SAPSailingHeaderWithAuthentication header = new SAPSailingHeaderWithAuthentication(getStringMessages().strategySimulatorTitle());
.sapSailingAnalytics(), getStringMessages().strategySimulatorTitle());
GenericAuthentication genericSailingAuthentication = new FixedSailingAuthentication(getUserService(), header.getAuthenticationMenuView()); GenericAuthentication genericSailingAuthentication = new FixedSailingAuthentication(getUserService(), header.getAuthenticationMenuView());
AuthorizedContentDecorator authorizedContentDecorator = new GenericAuthorizedContentDecorator(genericSailingAuthentication); AuthorizedContentDecorator authorizedContentDecorator = new GenericAuthorizedContentDecorator(genericSailingAuthentication);
@@ -218,7 +218,7 @@
<RaceResult RaceID="e18498ce-3885-4a4a-8ad5-be92893cdb55" TeamID="faddda24-6fab-4db4-8657-cdb471d12cc1" RacePoints="29" Discard="true" /> <RaceResult RaceID="e18498ce-3885-4a4a-8ad5-be92893cdb55" TeamID="faddda24-6fab-4db4-8657-cdb471d12cc1" RacePoints="29" Discard="true" />
<SeriesResult TeamID="faddda24-6fab-4db4-8657-cdb471d12cc1" TotalPoints="151" NetPoints="93" Rank="50" /> <SeriesResult TeamID="faddda24-6fab-4db4-8657-cdb471d12cc1" TotalPoints="151" NetPoints="93" Rank="50" />
<RaceResult RaceID="b68b5758-e172-417a-ba4f-bfd7b441940e" TeamID="01723ea5-2910-4e7c-a8dc-c69ca8535cbb" RacePoints="20" /> <RaceResult RaceID="b68b5758-e172-417a-ba4f-bfd7b441940e" TeamID="01723ea5-2910-4e7c-a8dc-c69ca8535cbb" RacePoints="20" />
<RaceResult RaceID="3c4563fe-b12b-4dec-8b0f-222ee079c7d5" TeamID="01723ea5-2910-4e7c-a8dc-c69ca8535cbb" RacePoints="29" Discard="true" /> <RaceResult RaceID="3c4563fe-b12b-4dec-8b0f-222ee079c7d5" TeamID="01723ea5-2910-4e7c-a8dc-c69ca8535cbb" RacePoints="29" Discard="true" ScoreCode="UFD" />
<RaceResult RaceID="857d4587-9e52-46e1-ab3a-8892a6aac167" TeamID="01723ea5-2910-4e7c-a8dc-c69ca8535cbb" RacePoints="21" /> <RaceResult RaceID="857d4587-9e52-46e1-ab3a-8892a6aac167" TeamID="01723ea5-2910-4e7c-a8dc-c69ca8535cbb" RacePoints="21" />
<RaceResult RaceID="87dfaa87-1d6c-4e92-820e-dd8e96ae3690" TeamID="01723ea5-2910-4e7c-a8dc-c69ca8535cbb" RacePoints="12" /> <RaceResult RaceID="87dfaa87-1d6c-4e92-820e-dd8e96ae3690" TeamID="01723ea5-2910-4e7c-a8dc-c69ca8535cbb" RacePoints="12" />
<RaceResult RaceID="62ed6be3-281a-44a3-885e-f0edbbf85409" TeamID="01723ea5-2910-4e7c-a8dc-c69ca8535cbb" RacePoints="35" Discard="true" ScoreCode="BFD" /> <RaceResult RaceID="62ed6be3-281a-44a3-885e-f0edbbf85409" TeamID="01723ea5-2910-4e7c-a8dc-c69ca8535cbb" RacePoints="35" Discard="true" ScoreCode="BFD" />
@@ -2,6 +2,7 @@ package com.sap.sailing.manage2sail.resultimport.test;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertSame;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import java.io.File; import java.io.File;
@@ -10,7 +11,9 @@ import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date; import java.util.Date;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@@ -22,7 +25,9 @@ import javax.xml.parsers.ParserConfigurationException;
import org.junit.Test; import org.junit.Test;
import org.xml.sax.SAXException; 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;
import com.sap.sailing.domain.common.RegattaScoreCorrections.ScoreCorrectionForCompetitorInRace;
import com.sap.sailing.domain.common.RegattaScoreCorrections.ScoreCorrectionsForRace; import com.sap.sailing.domain.common.RegattaScoreCorrections.ScoreCorrectionsForRace;
import com.sap.sailing.domain.persistence.DomainObjectFactory; import com.sap.sailing.domain.persistence.DomainObjectFactory;
import com.sap.sailing.domain.persistence.MongoObjectFactory; import com.sap.sailing.domain.persistence.MongoObjectFactory;
@@ -128,11 +133,52 @@ public class ParserTest {
} }
assertNotNull(resultFor29er); 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()); RegattaScoreCorrections _29erResult = scoreCorrectionProvider.getScoreCorrections(YES_EVENT_NAME, ISAF_ID_29ER , resultFor29er.getB());
assertNotNull(_29erResult); assertNotNull(_29erResult);
Iterable<ScoreCorrectionsForRace> scoreCorrectionsForRaces = _29erResult.getScoreCorrectionsForRaces(); Iterable<ScoreCorrectionsForRace> scoreCorrectionsForRaces = _29erResult.getScoreCorrectionsForRaces();
assertNotNull(scoreCorrectionsForRaces); assertNotNull(scoreCorrectionsForRaces);
assertEquals(8, Util.size(scoreCorrectionsForRaces)); assertEquals(numberOfRaceColumnsWithScores, Util.size(scoreCorrectionsForRaces));
Set<String> raceNamesOrNumbers = new HashSet<>();
for (ScoreCorrectionsForRace scoreCorrectionForRace : scoreCorrectionsForRaces) {
raceNamesOrNumbers.add(scoreCorrectionForRace.getRaceNameOrNumber());
}
assertEquals(new HashSet<String>(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());
}
} }
/**
<Race RaceID="3c4563fe-b12b-4dec-8b0f-222ee079c7d5" RaceName="R2 Yellow" RaceNumber="2" RaceStartDate="2013-05-18" RaceStartTime="12:00:00" RaceStatus="finished" />
<Race RaceID="b68b5758-e172-417a-ba4f-bfd7b441940e" RaceName="R1 Yellow" RaceNumber="1" RaceStartDate="2013-05-18" RaceStartTime="12:00:00" RaceStatus="finished" />
<Race RaceID="5f78543c-e17c-4130-83ac-bfc15c2a5f50" RaceName="R1 Blue" RaceNumber="1" RaceStartDate="2013-05-18" RaceStartTime="12:00:00" RaceStatus="finished" />
<Race RaceID="753d9dd7-7fdf-40cb-8243-5d70e3771fc1" RaceName="R3 Yellow" RaceNumber="3" RaceStartDate="2013-05-18" RaceStartTime="12:00:00" RaceStatus="finished" />
<Race RaceID="857d4587-9e52-46e1-ab3a-8892a6aac167" RaceName="R3 Blue" RaceNumber="3" RaceStartDate="2013-05-18" RaceStartTime="12:00:00" RaceStatus="finished" />
<Race RaceID="1f4d9046-1067-4d2e-bc78-083a072d63e1" RaceName="R4 Yellow" RaceNumber="4" RaceStartDate="2013-05-19" RaceStartTime="12:00:00" RaceStatus="finished" />
<Race RaceID="87dfaa87-1d6c-4e92-820e-dd8e96ae3690" RaceName="R4 Blue" RaceNumber="4" RaceStartDate="2013-05-19" RaceStartTime="12:00:00" RaceStatus="finished" />
<Race RaceID="345e28a8-efa4-4ec5-a7a3-410cb251c88c" RaceName="R5 Yellow" RaceNumber="5" RaceStartDate="2013-05-19" RaceStartTime="12:00:00" RaceStatus="finished" />
<Race RaceID="62ed6be3-281a-44a3-885e-f0edbbf85409" RaceName="R5 Blue" RaceNumber="5" RaceStartDate="2013-05-19" RaceStartTime="12:00:00" RaceStatus="finished" />
<Race RaceID="8422bca3-bbfd-4761-ba6c-21e25083c38b" RaceName="R6 Yellow" RaceNumber="6" RaceStartDate="2013-05-19" RaceStartTime="12:00:00" RaceStatus="finished" />
<Race RaceID="52d92322-d4d6-4f52-a66e-92fdde72767e" RaceName="R6 Blue" RaceNumber="6" RaceStartDate="2013-05-19" RaceStartTime="12:00:00" RaceStatus="finished" />
<Race RaceID="ec270635-04b2-4a12-afed-2018be075f7e" RaceName="R9 Gold" RaceNumber="9" RaceStartDate="2013-05-20" RaceStartTime="12:00:00" RaceStatus="planned" />
<Race RaceID="7e6ddb42-8db8-4a3e-9358-d7cb40f28a77" RaceName="R9 Silver" RaceNumber="9" RaceStartDate="2013-05-20" RaceStartTime="12:00:00" RaceStatus="planned" />
<Race RaceID="e18498ce-3885-4a4a-8ad5-be92893cdb55" RaceName="R7 Silver" RaceNumber="7" RaceStartDate="2013-05-20" RaceStartTime="12:00:00" RaceStatus="finished" />
<Race RaceID="086b0bfc-5712-44b1-91de-c1c7f74f2218" RaceName="R7 Gold" RaceNumber="7" RaceStartDate="2013-05-20" RaceStartTime="12:00:00" RaceStatus="finished" />
<Race RaceID="7cdec8a2-5175-4736-9296-823f7e1325f7" RaceName="R8 Gold" RaceNumber="8" RaceStartDate="2013-05-20" RaceStartTime="12:00:00" RaceStatus="finished" />
<Race RaceID="3025aa09-0427-42ab-8f03-6b019b8d4ddd" RaceName="R8 Silver" RaceNumber="8" RaceStartDate="2013-05-20" RaceStartTime="12:00:00" RaceStatus="inprogress" />
<Race RaceID="37c2ae0f-4889-46ff-aad9-54d964dd193a" RaceName="R2 Blue" RaceNumber="2" RaceStartDate="2013-05-18" RaceStartTime="12:00:00" RaceStatus="finished" />
*/
} }
@@ -17,8 +17,12 @@ public class FlatGPSFixJsonSerializer implements JsonSerializer<GPSFix> {
result.put(FIELD_LON_DEG, object.getPosition().getLngDeg()); result.put(FIELD_LON_DEG, object.getPosition().getLngDeg());
result.put(FIELD_LAT_DEG, object.getPosition().getLatDeg()); result.put(FIELD_LAT_DEG, object.getPosition().getLatDeg());
result.put(FIELD_TIME_MILLIS, object.getTimePoint().asMillis()); result.put(FIELD_TIME_MILLIS, object.getTimePoint().asMillis());
// TODO the default accuracy is 1.0; when fixes ever have an accuracy, report it here // TODO Insert Accuracy if someday available from the server
result.put(FIELD_ACCURACY, 1.0); // 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; return result;
} }
} }
@@ -4,11 +4,15 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import org.apache.commons.fileupload.FileItem;
import org.junit.Test; import org.junit.Test;
import com.sap.sailing.domain.common.tracking.GPSFix; import com.sap.sailing.domain.common.tracking.GPSFix;
@@ -30,32 +34,33 @@ public class GPSFixImportTest {
@Test @Test
public void testReReadingEOFFromBufferedInputStream() throws IOException { public void testReReadingEOFFromBufferedInputStream() throws IOException {
InputStream in = getClass().getResourceAsStream("/Cardiff Race17 - COMPETITORS.gpx"); try (InputStream in = getClass().getResourceAsStream("/Cardiff Race17 - COMPETITORS.gpx");
BufferedInputStreamWithPublicBuffer bis = new BufferedInputStreamWithPublicBuffer(in); 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 bis.mark(10 * 1024 * 1024); // the file has ~2MB, so 10MB is much more than twice the size, so should never
int size = 0; // be required
while (bis.read() != -1) { int size = 0;
size++; 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 @Test
@@ -73,8 +78,81 @@ public class GPSFixImportTest {
public void storeFix(GPSFix fix, DeviceIdentifier deviceIdentifier) { public void storeFix(GPSFix fix, DeviceIdentifier deviceIdentifier) {
} }
}; };
InputStream in = getClass().getResourceAsStream("/Cardiff Race17 - COMPETITORS.gpx"); FileItem fi = new FileItem() {
servlet.importFiles(Arrays.asList(new Pair<>("test.gpx", in)), new AlwaysFailingGPSFixImporter(-1)); 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 // getting to here without errors is good enough
} }
} }
@@ -52,9 +52,7 @@ public class GPSFixesResource extends AbstractSailingServerResource {
List<GPSFixMoving> fixes = data.getB(); List<GPSFixMoving> fixes = data.getB();
try { try {
for (GPSFixMoving fix : fixes) { getService().getSensorFixStore().storeFixes(device, fixes);
getService().getSensorFixStore().storeFix(device, fix);
}
logger.log(Level.INFO, "Added " + fixes.size() + " fixes for device " + device.toString() + " to store"); logger.log(Level.INFO, "Added " + fixes.size() + " fixes for device " + device.toString() + " to store");
} catch (NoCorrespondingServiceRegisteredException e) { } catch (NoCorrespondingServiceRegisteredException e) {
logger.log(Level.WARNING, "Could not store fix for device " + device); logger.log(Level.WARNING, "Could not store fix for device " + device);
@@ -62,5 +60,4 @@ public class GPSFixesResource extends AbstractSailingServerResource {
return Response.ok().build(); return Response.ok().build();
} }
} }
@@ -2,7 +2,6 @@ package com.sap.sailing.server.gateway.trackfiles.impl;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
@@ -32,13 +31,12 @@ import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util.Pair; 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 * Import servlet for sensor data files. Importers are located through the OSGi service registry and matched against the
* name provided by the upload formular. * name provided by the upload form.
*/ */
public class SensorDataImportServlet extends AbstractFileUploadServlet { public class SensorDataImportServlet extends AbstractFileUploadServlet {
private static final long serialVersionUID = 1120226743039934620L; private static final long serialVersionUID = 1120226743039934620L;
private static final Logger logger = Logger.getLogger(SensorDataImportServlet.class.getName()); private static final Logger logger = Logger.getLogger(SensorDataImportServlet.class.getName());
private static final int READ_BUFFER_SIZE = 1024 * 1024 * 1024;
public void storeFixes(Iterable<DoubleVectorFix> fixes, DeviceIdentifier deviceIdentifier) { public void storeFixes(Iterable<DoubleVectorFix> fixes, DeviceIdentifier deviceIdentifier) {
try { try {
@@ -54,21 +52,16 @@ public class SensorDataImportServlet extends AbstractFileUploadServlet {
* *
* @throws IOException * @throws IOException
*/ */
private Iterable<TrackFileImportDeviceIdentifier> importFiles(Iterable<Pair<String, InputStream>> files) private Iterable<TrackFileImportDeviceIdentifier> importFiles(Iterable<Pair<String, FileItem>> files)
throws IOException { throws IOException {
final Set<TrackFileImportDeviceIdentifier> deviceIds = new HashSet<>(); final Set<TrackFileImportDeviceIdentifier> deviceIds = new HashSet<>();
final Map<DeviceIdentifier, TimePoint> from = new HashMap<>(); final Map<DeviceIdentifier, TimePoint> from = new HashMap<>();
final Map<DeviceIdentifier, TimePoint> to = new HashMap<>(); final Map<DeviceIdentifier, TimePoint> to = new HashMap<>();
final Collection<DoubleVectorFixImporter> availableImporters = new LinkedHashSet<>(); final Collection<DoubleVectorFixImporter> availableImporters = new LinkedHashSet<>();
availableImporters.addAll(getOSGiRegisteredImporters()); availableImporters.addAll(getOSGiRegisteredImporters());
for (Pair<String, InputStream> file : files) { for (Pair<String, FileItem> file : files) {
final String requestedImporterName = file.getA(); final String requestedImporterName = file.getA();
BufferedInputStream in = new BufferedInputStream(file.getB()) { final FileItem fi = file.getB();
@Override
public void close() throws IOException {
// prevent importers from closing this stream
}
};
DoubleVectorFixImporter importerToUse = null; DoubleVectorFixImporter importerToUse = null;
for (DoubleVectorFixImporter candidate : availableImporters) { for (DoubleVectorFixImporter candidate : availableImporters) {
if (candidate.getType().equals(requestedImporterName)) { if (candidate.getType().equals(requestedImporterName)) {
@@ -77,43 +70,37 @@ public class SensorDataImportServlet extends AbstractFileUploadServlet {
} }
} }
if (importerToUse == null) { if (importerToUse == null) {
in.close();
throw new RuntimeException("Sensor importer not found"); throw new RuntimeException("Sensor importer not found");
} }
in.mark(READ_BUFFER_SIZE);
try {
in.reset();
} catch (IOException e1) {
logger.log(Level.SEVERE, "Could not reset stream", e1);
}
logger.log(Level.INFO, logger.log(Level.INFO,
"Going to import sensor data file with importer " + importerToUse.getClass().getSimpleName()); "Going to import sensor data file with importer " + importerToUse.getClass().getSimpleName());
try { try (BufferedInputStream in = new BufferedInputStream(fi.getInputStream())) {
importerToUse.importFixes(in, new DoubleVectorFixImporter.Callback() { try {
@Override importerToUse.importFixes(in, new DoubleVectorFixImporter.Callback() {
public void addFixes(Iterable<DoubleVectorFix> fixes, TrackFileImportDeviceIdentifier device) { @Override
deviceIds.add(device); public void addFixes(Iterable<DoubleVectorFix> fixes, TrackFileImportDeviceIdentifier device) {
storeFixes(fixes, device); deviceIds.add(device);
TimePoint earliestFixSoFarFromCurrentDevice = from.get(device); storeFixes(fixes, device);
TimePoint latestFixSoFarFromCurrentDevice = to.get(device); TimePoint earliestFixSoFarFromCurrentDevice = from.get(device);
for (DoubleVectorFix fix : fixes) { TimePoint latestFixSoFarFromCurrentDevice = to.get(device);
if (earliestFixSoFarFromCurrentDevice == null for (DoubleVectorFix fix : fixes) {
|| earliestFixSoFarFromCurrentDevice.after(fix.getTimePoint())) { if (earliestFixSoFarFromCurrentDevice == null
earliestFixSoFarFromCurrentDevice = fix.getTimePoint(); || earliestFixSoFarFromCurrentDevice.after(fix.getTimePoint())) {
from.put(device, earliestFixSoFarFromCurrentDevice); earliestFixSoFarFromCurrentDevice = fix.getTimePoint();
} from.put(device, earliestFixSoFarFromCurrentDevice);
if (latestFixSoFarFromCurrentDevice == null }
|| latestFixSoFarFromCurrentDevice.before(fix.getTimePoint())) { if (latestFixSoFarFromCurrentDevice == null
latestFixSoFarFromCurrentDevice = fix.getTimePoint(); || latestFixSoFarFromCurrentDevice.before(fix.getTimePoint())) {
to.put(device, latestFixSoFarFromCurrentDevice); latestFixSoFarFromCurrentDevice = fix.getTimePoint();
to.put(device, latestFixSoFarFromCurrentDevice);
}
} }
} }
} }, fi.getName(), requestedImporterName);
}, requestedImporterName); logger.log(Level.INFO, "Successfully imported file " + requestedImporterName);
logger.log(Level.INFO, "Successfully imported file " + requestedImporterName); } catch (FormatNotSupportedException e) {
} catch (FormatNotSupportedException e) { logger.log(Level.INFO, "Failed to import file " + requestedImporterName);
logger.log(Level.INFO, "Failed to import file " + requestedImporterName); }
} }
} }
return deviceIds; return deviceIds;
@@ -125,7 +112,6 @@ public class SensorDataImportServlet extends AbstractFileUploadServlet {
@Override @Override
protected void process(List<FileItem> fileItems, HttpServletRequest req, HttpServletResponse resp) protected void process(List<FileItem> fileItems, HttpServletRequest req, HttpServletResponse resp)
throws IOException { throws IOException {
List<Pair<String, InputStream>> files = new ArrayList<>();
String importerName = null; String importerName = null;
searchForPrefferedImporter: for (FileItem fi : fileItems) { searchForPrefferedImporter: for (FileItem fi : fileItems) {
if ("preferredImporter".equalsIgnoreCase(fi.getFieldName())) { if ("preferredImporter".equalsIgnoreCase(fi.getFieldName())) {
@@ -134,11 +120,12 @@ public class SensorDataImportServlet extends AbstractFileUploadServlet {
} }
} }
if (importerName == null) { if (importerName == null) {
importerName = "BRAVO"; throw new RuntimeException("Missing preferred importer");
} }
List<Pair<String, FileItem>> files = new ArrayList<>();
for (FileItem fi : fileItems) { for (FileItem fi : fileItems) {
if ("file".equalsIgnoreCase(fi.getFieldName())) { if ("file".equalsIgnoreCase(fi.getFieldName())) {
files.add(new Pair<>(importerName, fi.getInputStream())); files.add(new Pair<>(importerName, fi));
} }
} }
final Iterable<TrackFileImportDeviceIdentifier> mappingList = importFiles(files); final Iterable<TrackFileImportDeviceIdentifier> mappingList = importFiles(files);
@@ -2,18 +2,17 @@ package com.sap.sailing.server.gateway.trackfiles.impl;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
@@ -42,7 +41,8 @@ import com.sap.sse.common.Util.Pair;
* The available importers are tried one by one in the following order, until the first one is found that does not fail * The available importers are tried one by one in the following order, until the first one is found that does not fail
* with an {@link FormatNotSupportedException}: * with an {@link FormatNotSupportedException}:
* <ul> * <ul>
* <li>If the type of a {@link #PREFERRED_IMPORTER preferred importer} is transmitted, this is the first that is used.</li> * <li>If the type of a {@link #PREFERRED_IMPORTER preferred importer} is transmitted, this is the first that is used.
* </li>
* <li>Then the importers registered for a matching {@link GPSFixImporter#FILE_EXTENSION_PROPERTY file extension} are * <li>Then the importers registered for a matching {@link GPSFixImporter#FILE_EXTENSION_PROPERTY file extension} are
* used.</li> * used.</li>
* <li>If all this fails, all other available importers are used.</li> * <li>If all this fails, all other available importers are used.</li>
@@ -55,8 +55,6 @@ public class TrackFilesImportServlet extends AbstractFileUploadServlet {
public static final String PREFERRED_IMPORTER = "preferredImporter"; public static final String PREFERRED_IMPORTER = "preferredImporter";
private static final long serialVersionUID = 1120226743039934620L; private static final long serialVersionUID = 1120226743039934620L;
private static final Logger logger = Logger.getLogger(TrackFilesImportServlet.class.getName()); private static final Logger logger = Logger.getLogger(TrackFilesImportServlet.class.getName());
private static final int READ_BUFFER_SIZE = 1024 * 1024 * 1024;
public void storeFix(GPSFix fix, DeviceIdentifier deviceIdentifier) { public void storeFix(GPSFix fix, DeviceIdentifier deviceIdentifier) {
try { try {
@@ -84,69 +82,67 @@ public class TrackFilesImportServlet extends AbstractFileUploadServlet {
} }
return result; return result;
} }
Iterable<TrackFileImportDeviceIdentifier> importFiles(Iterable<Pair<String, InputStream>> files, GPSFixImporter preferredImporter) protected Iterable<TrackFileImportDeviceIdentifier> importFiles(Iterable<Pair<String, FileItem>> files,
throws IOException { GPSFixImporter preferredImporter) throws IOException {
final Set<TrackFileImportDeviceIdentifier> deviceIds = new HashSet<>(); final Set<TrackFileImportDeviceIdentifier> deviceIds = new HashSet<>();
final Map<DeviceIdentifier, TimePoint> from = new HashMap<>(); final Map<DeviceIdentifier, TimePoint> from = new HashMap<>();
final Map<DeviceIdentifier, TimePoint> to = new HashMap<>(); final Map<DeviceIdentifier, TimePoint> to = new HashMap<>();
for (Pair<String, FileItem> pair : files) {
for (Pair<String, InputStream> file : files) { final String fileName = pair.getA();
final String fileName = file.getA(); final FileItem fileItem = pair.getB();
String fileExt = null; String fileExt = null;
if (fileName.contains(".")) { if (fileName.contains(".")) {
fileExt = fileName.substring(fileName.lastIndexOf(".") + 1); fileExt = fileName.substring(fileName.lastIndexOf(".") + 1);
} }
Collection<GPSFixImporter> importersToTry = new LinkedHashSet<>(); Set<GPSFixImporter> importersToTry = new LinkedHashSet<>();
if (preferredImporter != null) { if (preferredImporter != null) {
importersToTry.add(preferredImporter); importersToTry.add(preferredImporter);
} }
importersToTry.addAll(getGPSFixImporters(fileExt)); importersToTry.addAll(getGPSFixImporters(fileExt));
importersToTry.addAll(getGPSFixImporters(null)); importersToTry.addAll(getGPSFixImporters(null));
BufferedInputStream in = new BufferedInputStream(file.getB()) {
@Override
public void close() throws IOException {
//prevent importers from closing this stream
}
};
in.mark(READ_BUFFER_SIZE);
boolean done = false;
Iterator<GPSFixImporter> iter = importersToTry.iterator();
while (iter.hasNext() && ! done) {
try {
in.reset();
} catch (IOException e1) {
e1.printStackTrace();
}
boolean failed = false;
GPSFixImporter importer = iter.next();
logger.log(Level.INFO, "Trying to import file " + fileName + " with importer " + importer.getType());
try {
importer.importFixes(in, new Callback() {
@Override
public void addFix(GPSFix fix, TrackFileImportDeviceIdentifier device) {
deviceIds.add(device);
storeFix(fix, device);
TimePoint earliestFixSoFarFromCurrentDevice = from.get(device);
if (earliestFixSoFarFromCurrentDevice == null || earliestFixSoFarFromCurrentDevice.after(fix.getTimePoint())) {
earliestFixSoFarFromCurrentDevice = fix.getTimePoint();
from.put(device, earliestFixSoFarFromCurrentDevice);
}
TimePoint latestFixSoFarFromCurrentDevice = to.get(device);
if (latestFixSoFarFromCurrentDevice == null || latestFixSoFarFromCurrentDevice.before(fix.getTimePoint())) {
latestFixSoFarFromCurrentDevice = fix.getTimePoint();
to.put(device, latestFixSoFarFromCurrentDevice);
}
}
}, true, fileName); logger.log(Level.INFO,
} catch (FormatNotSupportedException e) { "System knows " +
failed = true; importersToTry.size() +
" importers: "+
importersToTry.stream().map(i -> i.getType()).collect(Collectors.joining(", "))
);
parsersLoop: for (GPSFixImporter importer : importersToTry) {
boolean succeeded = false;
logger.log(Level.INFO, "Trying to import file " + fileName + " with importer " + importer.getType());
try (BufferedInputStream in = new BufferedInputStream(fileItem.getInputStream())) {
try {
importer.importFixes(in, new Callback() {
@Override
public void addFix(GPSFix fix, TrackFileImportDeviceIdentifier device) {
deviceIds.add(device);
storeFix(fix, device);
TimePoint earliestFixSoFarFromCurrentDevice = from.get(device);
if (earliestFixSoFarFromCurrentDevice == null
|| earliestFixSoFarFromCurrentDevice.after(fix.getTimePoint())) {
earliestFixSoFarFromCurrentDevice = fix.getTimePoint();
from.put(device, earliestFixSoFarFromCurrentDevice);
}
TimePoint latestFixSoFarFromCurrentDevice = to.get(device);
if (latestFixSoFarFromCurrentDevice == null
|| latestFixSoFarFromCurrentDevice.before(fix.getTimePoint())) {
latestFixSoFarFromCurrentDevice = fix.getTimePoint();
to.put(device, latestFixSoFarFromCurrentDevice);
}
}
}, true, fileName);
succeeded = true;
} catch (Exception e) {
logger.log(Level.INFO, "Failed with " + e.getClass().getSimpleName()
+ " while importing file using " + importer.getType());
}
} }
if (! failed) { if (succeeded) {
done = true; logger.log(Level.INFO, "Successfully imported file " + fileName + " using " + importer.getType());
logger.log(Level.INFO, "Successfully imported file " + fileName); break parsersLoop;
} }
} }
} }
@@ -156,10 +152,10 @@ public class TrackFilesImportServlet extends AbstractFileUploadServlet {
@Override @Override
protected void process(List<FileItem> fileItems, HttpServletRequest req, HttpServletResponse resp) throws IOException { protected void process(List<FileItem> fileItems, HttpServletRequest req, HttpServletResponse resp) throws IOException {
String prefImporterType = null; String prefImporterType = null;
List<Pair<String, InputStream>> files = new ArrayList<>(); List<Pair<String, FileItem>> files = new ArrayList<>();
for (FileItem item : fileItems) { for (FileItem item : fileItems) {
if (!item.isFormField()) if (!item.isFormField())
files.add(new Pair<String, InputStream>(item.getName(), item.getInputStream())); files.add(new Pair<String, FileItem>(item.getName(), item));
else { else {
if (item.getFieldName() != null && item.getFieldName().equals(PREFERRED_IMPORTER)) { if (item.getFieldName() != null && item.getFieldName().equals(PREFERRED_IMPORTER)) {
prefImporterType = item.getString(); prefImporterType = item.getString();
@@ -167,18 +163,15 @@ public class TrackFilesImportServlet extends AbstractFileUploadServlet {
} }
} }
GPSFixImporter preferredImporter = null; GPSFixImporter preferredImporter = null;
if (prefImporterType != null && ! prefImporterType.isEmpty()) { if (prefImporterType != null && !prefImporterType.isEmpty()) {
preferredImporter = getServiceFinderFactory().createServiceFinder(GPSFixImporter.class). preferredImporter = getServiceFinderFactory().createServiceFinder(GPSFixImporter.class)
findService(prefImporterType); .findService(prefImporterType);
} }
final Iterable<TrackFileImportDeviceIdentifier> mappingList = importFiles(files, preferredImporter); final Iterable<TrackFileImportDeviceIdentifier> mappingList = importFiles(files, preferredImporter);
// setJsonResponseHeader(resp);
//setJsonResponseHeader(resp); // DO NOT set a JSON response header. This causes the browser to wrap the response in a
//DO NOT set a JSON response header. This causes the browser to wrap the response in a // <pre> tag when uploading from GWT, as this is an AJAX-request inside an iFrame.
//<pre> tag when uploading from GWT, as this is an AJAX-request inside an iFrame.
resp.setContentType("text/html"); resp.setContentType("text/html");
for (TrackFileImportDeviceIdentifier mapping : mappingList) { for (TrackFileImportDeviceIdentifier mapping : mappingList) {
String stringRep = mapping.getId().toString(); String stringRep = mapping.getId().toString();
resp.getWriter().println(stringRep); resp.getWriter().println(stringRep);
@@ -47,7 +47,9 @@ import com.sap.sailing.domain.abstractlog.race.impl.RaceLogFinishPositioningConf
import com.sap.sailing.domain.abstractlog.race.impl.RaceLogStartTimeEventImpl; import com.sap.sailing.domain.abstractlog.race.impl.RaceLogStartTimeEventImpl;
import com.sap.sailing.domain.abstractlog.race.impl.RaceLogWindFixEventImpl; import com.sap.sailing.domain.abstractlog.race.impl.RaceLogWindFixEventImpl;
import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceCompetitorMappingEvent; import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceCompetitorMappingEvent;
import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceMappingEvent;
import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogRegisterCompetitorEvent; import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogRegisterCompetitorEvent;
import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogDeviceCompetitorBravoMappingEventImpl;
import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogDeviceCompetitorMappingEventImpl; import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogDeviceCompetitorMappingEventImpl;
import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogRegisterCompetitorEventImpl; import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogRegisterCompetitorEventImpl;
import com.sap.sailing.domain.base.BoatClass; import com.sap.sailing.domain.base.BoatClass;
@@ -94,7 +96,9 @@ import com.sap.sailing.domain.common.impl.WindImpl;
import com.sap.sailing.domain.common.impl.WindSourceWithAdditionalID; import com.sap.sailing.domain.common.impl.WindSourceWithAdditionalID;
import com.sap.sailing.domain.common.media.MediaTrack; import com.sap.sailing.domain.common.media.MediaTrack;
import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus;
import com.sap.sailing.domain.common.sensordata.BravoSensorDataMetadata;
import com.sap.sailing.domain.common.tracking.GPSFix; import com.sap.sailing.domain.common.tracking.GPSFix;
import com.sap.sailing.domain.common.tracking.impl.DoubleVectorFixImpl;
import com.sap.sailing.domain.common.tracking.impl.GPSFixMovingImpl; import com.sap.sailing.domain.common.tracking.impl.GPSFixMovingImpl;
import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard; import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard;
import com.sap.sailing.domain.leaderboard.Leaderboard; import com.sap.sailing.domain.leaderboard.Leaderboard;
@@ -108,11 +112,12 @@ import com.sap.sailing.domain.leaderboard.meta.LeaderboardGroupMetaLeaderboard;
import com.sap.sailing.domain.persistence.PersistenceFactory; import com.sap.sailing.domain.persistence.PersistenceFactory;
import com.sap.sailing.domain.persistence.media.MediaDBFactory; import com.sap.sailing.domain.persistence.media.MediaDBFactory;
import com.sap.sailing.domain.racelog.tracking.EmptySensorFixStore; import com.sap.sailing.domain.racelog.tracking.EmptySensorFixStore;
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.MockSmartphoneImeiServiceFinderFactory;
import com.sap.sailing.domain.racelog.tracking.test.mock.SmartphoneImeiIdentifier; import com.sap.sailing.domain.racelog.tracking.test.mock.SmartphoneImeiIdentifier;
import com.sap.sailing.domain.racelogtracking.DeviceIdentifier; import com.sap.sailing.domain.racelogtracking.DeviceIdentifier;
import com.sap.sailing.domain.ranking.OneDesignRankingMetric; import com.sap.sailing.domain.ranking.OneDesignRankingMetric;
import com.sap.sailing.domain.test.TrackBasedTest; import com.sap.sailing.domain.test.TrackBasedTest;
import com.sap.sailing.domain.tracking.DynamicTrackedRegatta; import com.sap.sailing.domain.tracking.DynamicTrackedRegatta;
import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.WindTrack; import com.sap.sailing.domain.tracking.WindTrack;
@@ -123,9 +128,10 @@ import com.sap.sailing.server.gateway.jaxrs.spi.MasterDataResource;
import com.sap.sailing.server.impl.RacingEventServiceImpl; import com.sap.sailing.server.impl.RacingEventServiceImpl;
import com.sap.sailing.server.masterdata.DummyTrackedRace; import com.sap.sailing.server.masterdata.DummyTrackedRace;
import com.sap.sailing.server.masterdata.MasterDataImporter; import com.sap.sailing.server.masterdata.MasterDataImporter;
import com.sap.sailing.server.testsupport.RacingEventServiceImplMock; import com.sap.sailing.server.testsupport.RacingEventServiceImplMock;
import com.sap.sse.common.Color; import com.sap.sse.common.Color;
import com.sap.sse.common.TimePoint; import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Timed;
import com.sap.sse.common.Util; import com.sap.sse.common.Util;
import com.sap.sse.common.impl.MillisecondsDurationImpl; import com.sap.sse.common.impl.MillisecondsDurationImpl;
import com.sap.sse.common.impl.MillisecondsTimePoint; import com.sap.sse.common.impl.MillisecondsTimePoint;
@@ -289,6 +295,18 @@ public class MasterDataImportTest {
GPSFix gpsFix = new GPSFixMovingImpl(new DegreePosition(54.333, 10.133), logTimePoint2, GPSFix gpsFix = new GPSFixMovingImpl(new DegreePosition(54.333, 10.133), logTimePoint2,
new KnotSpeedWithBearingImpl(10, new DegreeBearingImpl(90))); new KnotSpeedWithBearingImpl(10, new DegreeBearingImpl(90)));
sourceService.getSensorFixStore().storeFix(deviceIdentifier, gpsFix); sourceService.getSensorFixStore().storeFix(deviceIdentifier, gpsFix);
// Add sensor fix
DeviceIdentifier deviceIdentifier2 = new SmartphoneImeiIdentifier("b");
RegattaLogDeviceMappingEvent<Competitor> bravoMappingEvent = new RegattaLogDeviceCompetitorBravoMappingEventImpl(
logTimePoint4, author, competitor, deviceIdentifier2, logTimePoint,
logTimePoint3);
regatta.getRegattaLog().add(bravoMappingEvent);
double[] fixData = new double[BravoSensorDataMetadata.INSTANCE.columnCount];
double rideHeightValue = 1337.0;
fixData[BravoSensorDataMetadata.INSTANCE.rideHeightColumn] = rideHeightValue;
DoubleVectorFixImpl doubleVectorFix = new DoubleVectorFixImpl(logTimePoint2, fixData);
sourceService.getSensorFixStore().storeFix(deviceIdentifier2, doubleVectorFix);
// Set score correction // Set score correction
double scoreCorrection = 12.0; double scoreCorrection = 12.0;
@@ -427,7 +445,13 @@ public class MasterDataImportTest {
Assert.assertEquals(competitor.getId(), registerEventOnTarget.getCompetitor().getId()); Assert.assertEquals(competitor.getId(), registerEventOnTarget.getCompetitor().getId());
// Check for import of racelogtracking fix // Check for import of racelogtracking fix
Assert.assertTrue(destService.getSensorFixStore().getNumberOfFixes(deviceIdentifier) == 1); SensorFixStore sensorFixStore = destService.getSensorFixStore();
Assert.assertEquals(1, sensorFixStore.getNumberOfFixes(deviceIdentifier));
verifyFix(gpsFix, sensorFixStore, logTimePoint, logTimePoint3, deviceIdentifier);
// Check for import of sensor fix
Assert.assertEquals(1, sensorFixStore.getNumberOfFixes(deviceIdentifier2));
verifyFix(doubleVectorFix, sensorFixStore, logTimePoint, logTimePoint3, deviceIdentifier2);
// Check for persisting of race log events: // Check for persisting of race log events:
RacingEventService dest2 = new RacingEventServiceImplMock(){}; RacingEventService dest2 = new RacingEventServiceImplMock(){};
@@ -446,6 +470,17 @@ public class MasterDataImportTest {
Assert.assertNotNull(registerEventOnTarget2); Assert.assertNotNull(registerEventOnTarget2);
Assert.assertEquals(registerEvent.getId(), registerEventOnTarget2.getId()); Assert.assertEquals(registerEvent.getId(), registerEventOnTarget2.getId());
} }
private void verifyFix(Timed expectedFix, SensorFixStore store, TimePoint start, TimePoint end, DeviceIdentifier deviceIdentifier) {
List<Timed> fixes = new ArrayList<>(1);
try {
store.loadFixes(fixes::add, deviceIdentifier, start, end, true);
assertEquals(1, fixes.size());
assertEquals(expectedFix, fixes.get(0));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Test @Test
public void testMasterDataImportForScoreCorrections() throws MalformedURLException, IOException, public void testMasterDataImportForScoreCorrections() throws MalformedURLException, IOException,
@@ -53,7 +53,7 @@ public class BravoDataImportTest {
callbackCallCount++; callbackCallCount++;
sumRideHeight += new BravoFixImpl(fix).getRideHeight(); sumRideHeight += new BravoFixImpl(fix).getRideHeight();
} }
}, "source"); }, "filename", "source");
Assert.assertEquals(importData.expectedFixesCount, callbackCallCount); Assert.assertEquals(importData.expectedFixesCount, callbackCallCount);
Assert.assertEquals(importData.expectedAverageRideHeight, Assert.assertEquals(importData.expectedAverageRideHeight,
callbackCallCount == 0 ? sumRideHeight : sumRideHeight / callbackCallCount, 0.00001); callbackCallCount == 0 ? sumRideHeight : sumRideHeight / callbackCallCount, 0.00001);
@@ -13,46 +13,43 @@ import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer; import org.mockito.stubbing.Answer;
import org.w3c.dom.Element; import org.w3c.dom.Element;
import slash.navigation.base.BaseNavigationPosition;
import slash.navigation.gpx.binding11.WptType;
import com.sap.sailing.domain.common.tracking.GPSFix; import com.sap.sailing.domain.common.tracking.GPSFix;
import com.sap.sailing.domain.trackfiles.TrackFileImportDeviceIdentifier; import com.sap.sailing.domain.trackfiles.TrackFileImportDeviceIdentifier;
import com.sap.sailing.server.trackfiles.impl.BaseRouteConverterGPSFixImporterImpl; import com.sap.sailing.server.trackfiles.impl.BaseRouteConverterGPSFixImporterImpl;
import com.sap.sailing.server.trackfiles.impl.RouteConverterGPSFixImporterImpl; import com.sap.sailing.server.trackfiles.impl.RouteConverterGPSFixImporterImpl;
import slash.navigation.base.BaseNavigationPosition;
import slash.navigation.gpx.binding11.WptType;
public class RouteConverterGpx11ExtensionsTest { public class RouteConverterGpx11ExtensionsTest {
private boolean extensionsFound = false; private boolean extensionsFound = false;
@Test @Test
public void areExtensionsRead() throws Exception { public void areExtensionsRead() throws Exception {
String testFile = "/equine-extensions.gpx"; String testFile = "/equine-extensions.gpx";
InputStream in = getClass().getResourceAsStream(testFile); try (InputStream in = getClass().getResourceAsStream(testFile)) {
assert in != null : new Exception("Input file not found"); assert in != null : new Exception("Input file not found");
BaseRouteConverterGPSFixImporterImpl importer = spy(new RouteConverterGPSFixImporterImpl());
BaseRouteConverterGPSFixImporterImpl importer = spy(new RouteConverterGPSFixImporterImpl()); doAnswer(new Answer<GPSFix>() {
doAnswer(new Answer<GPSFix>() { @Override
@Override public GPSFix answer(InvocationOnMock invocation) throws Throwable {
public GPSFix answer(InvocationOnMock invocation) throws Throwable { BaseNavigationPosition p = (BaseNavigationPosition) invocation.getArguments()[0];
BaseNavigationPosition p = (BaseNavigationPosition) invocation.getArguments()[0]; WptType waypoint = p.asGpxPosition().getOrigin(WptType.class);
WptType waypoint = p.asGpxPosition().getOrigin(WptType.class); final List<Object> extensions = waypoint.getExtensions().getAny();
final List<Object> extensions = waypoint.getExtensions().getAny(); for (Object o : extensions) {
for (Object o : extensions) { Element el = (Element) o;
Element el = (Element) o; System.out.println(el.getLocalName() + " " + el.getTextContent());
System.out.println(el.getLocalName() + " " + el.getTextContent()); extensionsFound = true;
extensionsFound = true; }
return (GPSFix) invocation.callRealMethod();
} }
}).when(importer).convertToGPSFix(any(BaseNavigationPosition.class));
return (GPSFix) invocation.callRealMethod(); importer.importFixes(in, new com.sap.sailing.domain.trackimport.GPSFixImporter.Callback() {
} @Override
}).when(importer).convertToGPSFix(any(BaseNavigationPosition.class)); public void addFix(GPSFix fix, TrackFileImportDeviceIdentifier device) {
}
importer.importFixes(in, new com.sap.sailing.domain.trackimport.GPSFixImporter.Callback() { }, false, "source");
@Override assertTrue(extensionsFound);
public void addFix(GPSFix fix, TrackFileImportDeviceIdentifier device) { }
}
}, false, "source");
assertTrue(extensionsFound);
} }
} }
@@ -17,37 +17,39 @@ import com.sap.sailing.server.trackfiles.impl.RouteConverterGPSFixImporterImpl;
public class TrackFileImportTest { public class TrackFileImportTest {
private boolean callbackCalled = false; private boolean callbackCalled = false;
@Before @Before
public void setup() { public void setup() {
callbackCalled = false; callbackCalled = false;
} }
@Test @Test
public void testGpx() throws IOException, FormatNotSupportedException { public void testGpx() throws IOException, FormatNotSupportedException {
InputStream in = getClass().getResourceAsStream("/Cardiff Race17 - COMPETITORS.gpx"); try (InputStream in = getClass().getResourceAsStream("/Cardiff Race17 - COMPETITORS.gpx")) {
new RouteConverterGPSFixImporterImpl().importFixes(in, new Callback() { new RouteConverterGPSFixImporterImpl().importFixes(in, new Callback() {
@Override @Override
public void addFix(GPSFix fix, TrackFileImportDeviceIdentifier device) { public void addFix(GPSFix fix, TrackFileImportDeviceIdentifier device) {
if (fix instanceof GPSFixMoving) { if (fix instanceof GPSFixMoving) {
callbackCalled = true; callbackCalled = true;
}
} }
} }, false, "source");
}, false, "source"); assertTrue(callbackCalled);
assertTrue(callbackCalled); }
} }
@Test @Test
public void testKmlOnlyLatLong() throws IOException, FormatNotSupportedException { public void testKmlOnlyLatLong() throws IOException, FormatNotSupportedException {
InputStream in = getClass().getResourceAsStream("/Cardiff Race22 - COMPETITORS.kml"); try (InputStream in = getClass().getResourceAsStream("/Cardiff Race22 - COMPETITORS.kml")) {
new RouteConverterGPSFixImporterImpl().importFixes(in, new Callback() { new RouteConverterGPSFixImporterImpl().importFixes(in, new Callback() {
@Override @Override
public void addFix(GPSFix fix, TrackFileImportDeviceIdentifier device) { public void addFix(GPSFix fix, TrackFileImportDeviceIdentifier device) {
if (fix instanceof GPSFix) { if (fix instanceof GPSFix) {
callbackCalled = true; callbackCalled = true;
}
} }
} }, false, "source");
}, false, "source"); assertTrue(callbackCalled);
assertTrue(callbackCalled); }
} }
} }
@@ -26,7 +26,6 @@ import com.tractrac.model.lib.api.event.CreateModelException;
import com.tractrac.subscription.lib.api.SubscriberInitializationException; import com.tractrac.subscription.lib.api.SubscriberInitializationException;
public class TrackedRacesExportTest extends OnlineTracTracBasedTest { public class TrackedRacesExportTest extends OnlineTracTracBasedTest {
public TrackedRacesExportTest() throws MalformedURLException, URISyntaxException { public TrackedRacesExportTest() throws MalformedURLException, URISyntaxException {
super(); super();
} }
@@ -37,35 +36,34 @@ public class TrackedRacesExportTest extends OnlineTracTracBasedTest {
} }
@Before @Before
public void setUp() throws URISyntaxException, IOException, InterruptedException, SubscriberInitializationException, CreateModelException { public void setUp() throws URISyntaxException, IOException, InterruptedException, SubscriberInitializationException,
URI storedUri = new URI("file:///"+new File("resources/event_20120905_erEuropean-Gold_fleet_-_race_1.mtb").getCanonicalPath().replace('\\', '/')); CreateModelException {
super.setUp(new URL("file:///"+new File("resources/event_20120905_erEuropean-Gold_fleet_-_race_1.txt").getCanonicalPath()), URI storedUri = new URI("file:///" + new File("resources/event_20120905_erEuropean-Gold_fleet_-_race_1.mtb")
.getCanonicalPath().replace('\\', '/'));
super.setUp(
new URL("file:///"
+ new File("resources/event_20120905_erEuropean-Gold_fleet_-_race_1.txt").getCanonicalPath()),
/* liveUri */ null, /* storedUri */ storedUri, /* liveUri */ null, /* storedUri */ storedUri,
new ReceiverType[] { ReceiverType.RACECOURSE, ReceiverType.RAWPOSITIONS, ReceiverType.MARKPASSINGS }); new ReceiverType[] { ReceiverType.RACECOURSE, ReceiverType.RAWPOSITIONS, ReceiverType.MARKPASSINGS });
} }
private byte[] getBytes(TrackFilesDataSource data, TrackFilesFormat format, TrackedRace race, private byte[] getBytes(TrackFilesDataSource data, TrackFilesFormat format, TrackedRace race,
boolean dataBeforeAfter, boolean rawFixes) throws IOException { boolean dataBeforeAfter, boolean rawFixes) throws IOException {
try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
ByteArrayOutputStream out = new ByteArrayOutputStream(); ZipOutputStream zip = new ZipOutputStream(out);
ZipOutputStream zip = new ZipOutputStream(out); TrackFileExporterImpl.INSTANCE.writeAllData(Collections.singletonList(data), format,
TrackFileExporterImpl.INSTANCE.writeAllData(Collections.singletonList(data), format, Collections.singletonList(race), Collections.singletonList(race), true, true, zip);
true, true, zip); zip.flush();
byte[] result = out.toByteArray();
zip.flush(); return result;
byte[] result = out.toByteArray(); }
out.close();
return result;
} }
@Test @Test
public void doAllFormatsWork() throws IOException, FormatNotSupportedException { public void doAllFormatsWork() throws IOException, FormatNotSupportedException {
TrackedRace race = getTrackedRace(); TrackedRace race = getTrackedRace();
for (TrackFilesFormat format : TrackFilesFormat.values()) { for (TrackFilesFormat format : TrackFilesFormat.values()) {
byte[] data = getBytes(TrackFilesDataSource.COMPETITORS, format, race, true, true); byte[] data = getBytes(TrackFilesDataSource.COMPETITORS, format, race, true, true);
assertTrue(data.length > 4000); assertTrue(data.length > 4000);
} }
} }
@@ -73,10 +71,8 @@ public class TrackedRacesExportTest extends OnlineTracTracBasedTest {
@Test @Test
public void doAllDataSourcesWork() throws IOException, FormatNotSupportedException { public void doAllDataSourcesWork() throws IOException, FormatNotSupportedException {
TrackedRace race = getTrackedRace(); TrackedRace race = getTrackedRace();
for (TrackFilesDataSource source : TrackFilesDataSource.values()) { for (TrackFilesDataSource source : TrackFilesDataSource.values()) {
byte[] data = getBytes(source, TrackFilesFormat.Gpx11, race, true, true); byte[] data = getBytes(source, TrackFilesFormat.Gpx11, race, true, true);
assertTrue(data.length > 40); assertTrue(data.length > 40);
} }
} }
@@ -46,15 +46,15 @@ public class BravoDataImporterImpl implements DoubleVectorFixImporter {
private final String BOF = "jjlDATE\tjjlTIME\tEpoch"; private final String BOF = "jjlDATE\tjjlTIME\tEpoch";
private static final int BATCH_SIZE = 5000; private static final int BATCH_SIZE = 5000;
public void importFixes(InputStream inputStream, Callback callback, String sourceName) public void importFixes(InputStream inputStream, Callback callback, String filename, String sourceName)
throws FormatNotSupportedException, IOException { throws FormatNotSupportedException, IOException {
final TrackFileImportDeviceIdentifier trackIdentifier = new TrackFileImportDeviceIdentifierImpl( final TrackFileImportDeviceIdentifier trackIdentifier = new TrackFileImportDeviceIdentifierImpl(
UUID.randomUUID(), sourceName, sourceName + "_Imu", MillisecondsTimePoint.now()); UUID.randomUUID(), filename, sourceName, MillisecondsTimePoint.now());
try { try {
LOG.fine("Import CSV from " + sourceName); LOG.fine("Import CSV from " + filename);
final InputStreamReader isr; final InputStreamReader isr;
if (sourceName.endsWith("gz")) { if (sourceName.endsWith("gz")) {
LOG.fine("Using gzip stream reader " + sourceName); LOG.fine("Using gzip stream reader " + filename);
isr = new InputStreamReader(new GZIPInputStream(inputStream)); isr = new InputStreamReader(new GZIPInputStream(inputStream));
} else { } else {
isr = new InputStreamReader(inputStream); isr = new InputStreamReader(inputStream);
@@ -63,10 +63,10 @@ public class BravoDataImporterImpl implements DoubleVectorFixImporter {
try (BufferedReader buffer = new BufferedReader(isr)) { try (BufferedReader buffer = new BufferedReader(isr)) {
String headerLine = null; String headerLine = null;
headerSearch: while (headerLine == null) { headerSearch: while (headerLine == null) {
LOG.fine("Searching for header in imu file"); LOG.fine("Searching for header in bravo file");
String headerCandidate = buffer.readLine(); String headerCandidate = buffer.readLine();
if (headerCandidate == null) { if (headerCandidate == null) {
throw new RuntimeException("Missing required header in file " + sourceName); throw new RuntimeException("Missing required header in file " + filename);
} }
if (headerCandidate.startsWith(BOF)) { if (headerCandidate.startsWith(BOF)) {
LOG.fine("Found header"); LOG.fine("Found header");
@@ -91,7 +91,7 @@ public class BravoDataImporterImpl implements DoubleVectorFixImporter {
buffer.close(); buffer.close();
} }
} catch (Exception e) { } catch (Exception e) {
LOG.log(Level.SEVERE, "Exception parsing CSV file " + sourceName, e); LOG.log(Level.SEVERE, "Exception parsing bravo CSV file " + filename, e);
} }
} }
@@ -125,7 +125,7 @@ public class BravoDataImporterImpl implements DoubleVectorFixImporter {
// long r16TP = ZonedDateTime.of(LocalDateTime.of(2016, 3, 19, 11, 55), ZoneId.of("Europe/Berlin")) // long r16TP = ZonedDateTime.of(LocalDateTime.of(2016, 3, 19, 11, 55), ZoneId.of("Europe/Berlin"))
// .toEpochSecond() * 1000; // .toEpochSecond() * 1000;
// fixTp = fixTp.plus(r16TP - 1461828459589l); // fixTp = fixTp.plus(r16TP - 1461828459589l);
double[] fixData = new double[metadata.getColumns().size()]; double[] fixData = new double[metadata.columnCount];
for (int columnIndexInFix = 0; columnIndexInFix < fixData.length; columnIndexInFix++) { for (int columnIndexInFix = 0; columnIndexInFix < fixData.length; columnIndexInFix++) {
String columnName = metadata.getColumns().get(columnIndexInFix); String columnName = metadata.getColumns().get(columnIndexInFix);
Integer columnIndexInFile = colIndices.get(columnName); Integer columnIndexInFile = colIndices.get(columnName);
@@ -6,7 +6,6 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@@ -18,7 +17,7 @@ import com.sap.sailing.domain.common.trackfiles.TrackFilesFormat;
import com.sap.sailing.domain.trackimport.FormatNotSupportedException; import com.sap.sailing.domain.trackimport.FormatNotSupportedException;
import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.server.trackfiles.TrackFileExporter; import com.sap.sailing.server.trackfiles.TrackFileExporter;
import com.sap.sse.util.impl.ThreadFactoryWithPriority; import com.sap.sse.util.ThreadPoolUtil;
/** /**
* Export data to well-known formats such as GPX, KML etc. Internally, the RouteConverter Library is used. * Export data to well-known formats such as GPX, KML etc. Internally, the RouteConverter Library is used.
@@ -56,7 +55,7 @@ public class TrackFileExporterImpl implements TrackFileExporter {
}; };
List<WriteRaceDataCallable> callables = new ArrayList<>(); List<WriteRaceDataCallable> callables = new ArrayList<>();
ExecutorService executor = Executors.newCachedThreadPool(new ThreadFactoryWithPriority(Thread.NORM_PRIORITY, /* daemon */ true)); ExecutorService executor = ThreadPoolUtil.INSTANCE.getDefaultForegroundTaskThreadPoolExecutor();
List<String> errors = new ArrayList<>(); List<String> errors = new ArrayList<>();
for (TrackedRace race : races) { for (TrackedRace race : races) {
for (TrackFilesDataSource d : data) { for (TrackFilesDataSource d : data) {
@@ -189,7 +189,7 @@ public interface RacingEventService extends TrackedRegattaRegistry, RegattaFetch
* @throws SocketException * @throws SocketException
* thrown, e.g., in case there is already another listener on the port requested * thrown, e.g., in case there is already another listener on the port requested
*/ */
void startTrackingWind(Regatta regatta, RaceDefinition race, boolean correctByDeclination) throws Exception; void startTrackingWind(Regatta regatta, RaceDefinition race, boolean correctByDeclination);
void stopTrackingWind(Regatta regatta, RaceDefinition race) throws SocketException, IOException; void stopTrackingWind(Regatta regatta, RaceDefinition race) throws SocketException, IOException;
@@ -28,7 +28,6 @@ import java.util.Map.Entry;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@@ -605,7 +604,7 @@ public class RacingEventServiceImpl implements RacingEventService, ClearStateTes
operationExecutionListeners = new ConcurrentHashMap<>(); operationExecutionListeners = new ConcurrentHashMap<>();
courseListeners = new ConcurrentHashMap<>(); courseListeners = new ConcurrentHashMap<>();
persistentRegattasForRaceIDs = new ConcurrentHashMap<>(); persistentRegattasForRaceIDs = new ConcurrentHashMap<>();
Executor simulatorExecutor = ThreadPoolUtil.INSTANCE.getDefaultBackgroundTaskThreadPoolExecutor(); final ScheduledExecutorService simulatorExecutor = ThreadPoolUtil.INSTANCE.getDefaultBackgroundTaskThreadPoolExecutor();
// TODO: initialize smart-future-cache for simulation-results and add to simulation-service // TODO: initialize smart-future-cache for simulation-results and add to simulation-service
simulationService = SimulationServiceFactory.INSTANCE.getService(simulatorExecutor, this); simulationService = SimulationServiceFactory.INSTANCE.getService(simulatorExecutor, this);
this.raceLogReplicator = new RaceLogReplicatorAndNotifier(this); this.raceLogReplicator = new RaceLogReplicatorAndNotifier(this);
@@ -2291,9 +2290,13 @@ public class RacingEventServiceImpl implements RacingEventService, ClearStateTes
} }
@Override @Override
public void startTrackingWind(Regatta regatta, RaceDefinition race, boolean correctByDeclination) throws Exception { public void startTrackingWind(Regatta regatta, RaceDefinition race, boolean correctByDeclination) {
for (WindTrackerFactory windTrackerFactory : getWindTrackerFactories()) { for (WindTrackerFactory windTrackerFactory : getWindTrackerFactories()) {
windTrackerFactory.createWindTracker(getOrCreateTrackedRegatta(regatta), race, correctByDeclination); try {
windTrackerFactory.createWindTracker(getOrCreateTrackedRegatta(regatta), race, correctByDeclination);
} catch (Exception e) {
logger.log(Level.SEVERE, "Error trying to track wind using wind tracker factory "+windTrackerFactory, e);
}
} }
} }
@@ -1,6 +1,6 @@
package com.sap.sailing.server.simulation; package com.sap.sailing.server.simulation;
import java.util.concurrent.Executor; import java.util.concurrent.ScheduledExecutorService;
import com.sap.sailing.server.RacingEventService; import com.sap.sailing.server.RacingEventService;
@@ -8,6 +8,6 @@ public interface SimulationServiceFactory {
static SimulationServiceFactory INSTANCE = new SimulationServiceFactoryImpl(); static SimulationServiceFactory INSTANCE = new SimulationServiceFactoryImpl();
SimulationService getService(Executor executor, RacingEventService racingEventService); SimulationService getService(ScheduledExecutorService executor, RacingEventService racingEventService);
} }
@@ -1,12 +1,12 @@
package com.sap.sailing.server.simulation; package com.sap.sailing.server.simulation;
import java.util.concurrent.Executor; import java.util.concurrent.ScheduledExecutorService;
import com.sap.sailing.server.RacingEventService; import com.sap.sailing.server.RacingEventService;
public class SimulationServiceFactoryImpl implements SimulationServiceFactory { public class SimulationServiceFactoryImpl implements SimulationServiceFactory {
public SimulationService getService(Executor executor, RacingEventService racingEventService) { public SimulationService getService(ScheduledExecutorService executor, RacingEventService racingEventService) {
return new SimulationServiceImpl(executor, racingEventService); return new SimulationServiceImpl(executor, racingEventService);
} }
@@ -8,9 +8,7 @@ import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.TreeMap; import java.util.TreeMap;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor; import java.util.concurrent.Future;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.logging.Logger; import java.util.logging.Logger;
@@ -59,13 +57,12 @@ import com.sap.sse.common.TimePoint;
import com.sap.sse.common.impl.MillisecondsDurationImpl; import com.sap.sse.common.impl.MillisecondsDurationImpl;
import com.sap.sse.common.impl.MillisecondsTimePoint; import com.sap.sse.common.impl.MillisecondsTimePoint;
import com.sap.sse.util.SmartFutureCache; import com.sap.sse.util.SmartFutureCache;
import com.sap.sse.util.impl.ThreadFactoryWithPriority;
public class SimulationServiceImpl implements SimulationService { public class SimulationServiceImpl implements SimulationService {
private static final Logger logger = Logger.getLogger(SimulationService.class.getName()); private static final Logger logger = Logger.getLogger(SimulationService.class.getName());
final private Executor executor; final private ScheduledExecutorService executor;
final private SmartFutureCache<LegIdentifier, SimulationResults, SmartFutureCache.EmptyUpdateInterval> cache; final private SmartFutureCache<LegIdentifier, SimulationResults, SmartFutureCache.EmptyUpdateInterval> cache;
final private RacingEventService racingEventService; final private RacingEventService racingEventService;
final private ScheduledExecutorService scheduler; final private ScheduledExecutorService scheduler;
@@ -73,9 +70,9 @@ public class SimulationServiceImpl implements SimulationService {
final private HashMap<RaceIdentifier, LegChangeListener> legListeners; final private HashMap<RaceIdentifier, LegChangeListener> legListeners;
final private long WAIT_MILLIS = 20000; // milliseconds to wait until earliest cache-update for simulation final private long WAIT_MILLIS = 20000; // milliseconds to wait until earliest cache-update for simulation
public SimulationServiceImpl(Executor executor, RacingEventService racingEventService) { public SimulationServiceImpl(ScheduledExecutorService executor, RacingEventService racingEventService) {
this.executor = executor; this.executor = executor;
this.scheduler = Executors.newScheduledThreadPool(1, new ThreadFactoryWithPriority(Thread.NORM_PRIORITY, /* daemon */ true)); this.scheduler = executor;
this.racingEventService = racingEventService; this.racingEventService = racingEventService;
if (racingEventService != null) { if (racingEventService != null) {
this.raceListeners = new HashMap<String, SimulationRaceListener>(); this.raceListeners = new HashMap<String, SimulationRaceListener>();
@@ -437,31 +434,26 @@ public class SimulationServiceImpl implements SimulationService {
Simulator simulator = new SimulatorImpl(simulationParameters); Simulator simulator = new SimulatorImpl(simulationParameters);
Map<PathType, Path> result = new HashMap<PathType, Path>(); Map<PathType, Path> result = new HashMap<PathType, Path>();
FutureTask<Path> taskOmniscient = null; Future<Path> taskOmniscient = null;
if (simulationParameters.showOmniscient()) { if (simulationParameters.showOmniscient()) {
// schedule omniscient task // schedule omniscient task
taskOmniscient = new FutureTask<Path>(() -> simulator.getPath(PathType.OMNISCIENT)); taskOmniscient = executor.submit(() -> simulator.getPath(PathType.OMNISCIENT));
executor.execute(taskOmniscient);
} }
FutureTask<Path> task1TurnerLeft = null; Future<Path> task1TurnerLeft = null;
FutureTask<Path> task1TurnerRight = null; Future<Path> task1TurnerRight = null;
if (simulationParameters.getLegType() != LegType.REACHING) { if (simulationParameters.getLegType() != LegType.REACHING) {
// schedule 1-turner tasks // schedule 1-turner tasks
task1TurnerLeft = new FutureTask<Path>(() -> simulator.getPath(PathType.ONE_TURNER_LEFT)); task1TurnerLeft = executor.submit(() -> simulator.getPath(PathType.ONE_TURNER_LEFT));
task1TurnerRight = new FutureTask<Path>(() -> simulator.getPath(PathType.ONE_TURNER_RIGHT)); task1TurnerRight = executor.submit(() -> simulator.getPath(PathType.ONE_TURNER_RIGHT));
executor.execute(task1TurnerLeft);
executor.execute(task1TurnerRight);
} }
FutureTask<Path> taskOpportunistLeft = null; Future<Path> taskOpportunistLeft = null;
FutureTask<Path> taskOpportunistRight = null; Future<Path> taskOpportunistRight = null;
if (simulationParameters.showOpportunist()) { if (simulationParameters.showOpportunist()) {
// schedule opportunist tasks (which depend on 1-turner results) // schedule opportunist tasks (which depend on 1-turner results)
taskOpportunistLeft = new FutureTask<Path>(() -> simulator.getPath(PathType.OPPORTUNIST_LEFT)); taskOpportunistLeft = executor.submit(() -> simulator.getPath(PathType.OPPORTUNIST_LEFT));
taskOpportunistRight = new FutureTask<Path>(() -> simulator.getPath(PathType.OPPORTUNIST_RIGHT)); taskOpportunistRight = executor.submit(() -> simulator.getPath(PathType.OPPORTUNIST_RIGHT));
executor.execute(taskOpportunistLeft);
executor.execute(taskOpportunistRight);
} }
Path path1TurnerLeft = null; Path path1TurnerLeft = null;
@@ -37,6 +37,9 @@ import javax.xml.bind.annotation.XmlType;
* &lt;enumeration value="SCP"/> * &lt;enumeration value="SCP"/>
* &lt;enumeration value="ZFP"/> * &lt;enumeration value="ZFP"/>
* &lt;enumeration value="RTD"/> * &lt;enumeration value="RTD"/>
* &lt;enumeration value="UFD"/>
* &lt;enumeration value="TLE"/>
* &lt;enumeration value="STP"/>
* &lt;/restriction> * &lt;/restriction>
* &lt;/simpleType> * &lt;/simpleType>
* </pre> * </pre>
@@ -147,8 +150,21 @@ public enum IFBoatStatus {
* Retired * Retired
* *
*/ */
RTD; RTD,
/**
* Uniform Flag Disqualification
*/
UFD,
/**
* Time limit Expired
*/
TLE,
/**
* Standard Penalty by Race Committee; gives a certain number of penalty points on top of rank-inferred score
*/
STP;
public String value() { public String value() {
return name(); return name();
} }
@@ -1,73 +1,81 @@
.siteheader { .siteheader {
font-size: 1rem; font-size: 1rem;
line-height: 1.333; line-height: 1.333;
background: #333; background: #333;
border-bottom: 4px solid #f0ab00; border-bottom: 4px solid #f0ab00;
height: 75px; height: 75px;
width: 100%; width: 100%;
overflow: hidden; overflow: hidden;
vertical-align: top; vertical-align: top;
position: relative; position: relative;
} }
.siteHeaderTitle { .siteHeaderTitle {
color: #fff; color: #fff;
vertical-align: top; vertical-align: top;
font-size: 20px; font-size: 20px;
font-weight: 700; font-weight: 700;
} }
.siteHeaderSubTitle { .siteHeaderSubTitle {
color: #fff; color: #fff;
font-size: 18px; font-size: 18px;
vertical-align: top; vertical-align: top;
} }
.siteheaderLeft { .siteheaderLeft {
position: relative; top:10px;left:10px; position: relative; top:10px;left:10px;
display: inline-block; display: inline-block;
text-align: left; text-align: left;
height: 50px; height: 50px;
vertical-align: top; vertical-align: top;
} }
.siteheader2ndCol { .siteheader2ndCol {
position: absolute; top:10px;left:235px; position: absolute; top:10px;left:235px;
display: inline-block; display: inline-block;
text-align: left; text-align: left;
height:50px; height:50px;
vertical-align: top; vertical-align: top;
} }
.siteheaderRight { .siteheaderRight {
position: absolute; top:10px;right:10px; position: absolute; top:10px;right:10px;
text-align: right; text-align: right;
display: inline-block; display: inline-block;
height:50px; height:50px;
vertical-align: top; vertical-align: top;
} }
.logo { .logo, .logo:hover, .logo:visited {
margin: 0px; margin: 0px;
display: inline-block; display: inline-block;
} gwt-sprite: "logo";
background-size: cover;
.logotitle { width: 80px;
font-size: 1rem; height: 40px;
display: inline-block; border: none;
color: #f6f9fc; text-decoration: none;
width: 150px; }
height: 100%;
padding: 17px 15px 0 0; .logotitle, .logotitle:hover, .logotitle:visited {
vertical-align: top; font-size: 1rem;
text-align: right; display: inline-block;
border-right: 1px solid rgb(180,180,180); color: #fff;
margin: 0px; width: 150px;
margin-left: -25px; height: 100%;
box-sizing: border-box; padding: 17px 15px 0 0;
} vertical-align: top;
.pagetitle { text-align: right;
color: #ffffff; border: none;
font-size: 1.1rem; border-right: 1px solid rgb(180,180,180);
font-weight: bold; margin: 0px;
} margin-left: -25px;
box-sizing: border-box;
text-decoration: none;
}
.pagetitle {
color: #ffffff;
font-size: 1.1rem;
font-weight: bold;
}
@@ -1,65 +1,75 @@
package com.sap.sse.gwt.client.sapheader; package com.sap.sse.gwt.client.sapheader;
import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.DivElement; import com.google.gwt.dom.client.AnchorElement;
import com.google.gwt.dom.client.Style.Display; import com.google.gwt.dom.client.DivElement;
import com.google.gwt.dom.client.Style.Unit; import com.google.gwt.dom.client.Style.Display;
import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.uibinder.client.UiField; import com.google.gwt.i18n.client.LocaleInfo;
import com.google.gwt.user.client.ui.Composite; import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.user.client.ui.SimplePanel; import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.client.ui.Widget; import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.SimplePanel;
/** import com.google.gwt.user.client.ui.Widget;
* Generic header widget that is designed with a SAP logo and SAP color scheme.
* /**
* The header has the following elements: * Generic header widget that is designed with a SAP logo and SAP color scheme.
* <ul> *
* <li>SAP Logo with an application title (like "Sailing Analytics")</li> * The header has the following elements:
* <li>Page title</li> * <ul>
* <li>Optional Subtitle</li> * <li>SAP Logo with an application title (like "Sailing Analytics")</li>
* <li>Optional widget to show on the right side of the header</li> * <li>Page title</li>
* </ul> * <li>Optional Subtitle</li>
*/ * <li>Optional widget to show on the right side of the header</li>
public class SAPHeader extends Composite { * </ul>
private static SAPHeaderUiBinder uiBinder = GWT.create(SAPHeaderUiBinder.class); */
public class SAPHeader extends Composite {
interface SAPHeaderUiBinder extends UiBinder<Widget, SAPHeader> {
} private static final String LOGO_URL = "https://sapsponsorships.com/";
@UiField private static SAPHeaderUiBinder uiBinder = GWT.create(SAPHeaderUiBinder.class);
DivElement applicationNameUi;
@UiField interface SAPHeaderUiBinder extends UiBinder<Widget, SAPHeader> {
DivElement pageTitleUi; }
@UiField
DivElement titleUi; @UiField
@UiField AnchorElement applicationNameAnchor;
DivElement subTitleUi; @UiField
@UiField DivElement pageTitleUi;
SimplePanel rightSideUi; @UiField
DivElement titleUi;
public SAPHeader(String applicationName) { @UiField
SAPHeaderResources.INSTANCE.css().ensureInjected(); DivElement subTitleUi;
initWidget(uiBinder.createAndBindUi(this)); @UiField
applicationNameUi.setInnerText(applicationName != null ? applicationName : "&nbsp;"); SimplePanel rightSideUi;
} @UiField
AnchorElement logoAnchor;
public void setHeaderTitle(String title) {
titleUi.setInnerText(title); public SAPHeader(String applicationName, String applicationBaseUrl) {
} SAPHeaderResources.INSTANCE.css().ensureInjected();
initWidget(uiBinder.createAndBindUi(this));
public void setHeaderSubTitle(String subtitle) { applicationNameAnchor.setInnerText(applicationName != null ? applicationName : "&nbsp;");
if (subtitle == null || subtitle.isEmpty()) { logoAnchor.setHref(LOGO_URL);
subTitleUi.getStyle().setDisplay(Display.NONE); String sapSailingUrl = applicationBaseUrl + "?locale=" + LocaleInfo.getCurrentLocale().getLocaleName();
titleUi.getStyle().setMarginTop(14, Unit.PX); applicationNameAnchor.setHref(sapSailingUrl);
} else { }
subTitleUi.setInnerText(subtitle);
subTitleUi.getStyle().clearDisplay(); public void setHeaderTitle(String title) {
titleUi.getStyle().clearMarginTop(); titleUi.setInnerText(title);
} }
}
public void setHeaderSubTitle(String subtitle) {
public void addWidgetToRightSide(Widget widget) { if (subtitle == null || subtitle.isEmpty()) {
rightSideUi.add(widget); subTitleUi.getStyle().setDisplay(Display.NONE);
} titleUi.getStyle().setMarginTop(14, Unit.PX);
} } else {
subTitleUi.setInnerText(subtitle);
subTitleUi.getStyle().clearDisplay();
titleUi.getStyle().clearMarginTop();
}
}
public void addWidgetToRightSide(Widget widget) {
rightSideUi.add(widget);
}
}
@@ -1,24 +1,20 @@
<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent"> <!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder" <ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder"
xmlns:g="urn:import:com.google.gwt.user.client.ui"> xmlns:g="urn:import:com.google.gwt.user.client.ui">
<ui:with field="local_res" <ui:with field="local_res"
type="com.sap.sse.gwt.client.sapheader.SAPHeaderResources" /> type="com.sap.sse.gwt.client.sapheader.SAPHeaderResources" />
<g:HTMLPanel> <g:HTMLPanel>
<header class="{local_res.css.siteheader}"> <header class="{local_res.css.siteheader}">
<div class="{local_res.css.siteheaderLeft}"> <div class="{local_res.css.siteheaderLeft}">
<g:Image resource="{local_res.logo}" pixelSize="80, 40" altText="SAP Sailing Analytics" styleName="{local_res.css.logo}"/> <a class="{local_res.css.logo}" ui:field="logoAnchor" target="_blank"></a>
<div ui:field="applicationNameUi" class="{local_res.css.logotitle}"></div> <a class="{local_res.css.logotitle}" target="_blank" ui:field="applicationNameAnchor"></a>
</div> </div>
<div <div ui:field="pageTitleUi" class="{local_res.css.siteheader2ndCol}">
ui:field="pageTitleUi" <div ui:field="titleUi" style="margin-top:14px;" class="{local_res.css.siteHeaderTitle}"></div>
class="{local_res.css.siteheader2ndCol}" <div ui:field="subTitleUi" style="display:none;" class="{local_res.css.siteHeaderSubTitle}"></div>
> </div>
<div ui:field="titleUi" style="margin-top:14px;" class="{local_res.css.siteHeaderTitle}"></div> <g:SimplePanel ui:field="rightSideUi" styleName="{local_res.css.siteheaderRight}" />
<div ui:field="subTitleUi" style="display:none;" class="{local_res.css.siteHeaderSubTitle}"></div> </header>
</div> </g:HTMLPanel>
<g:SimplePanel ui:field="rightSideUi" styleName="{local_res.css.siteheaderRight}"> </ui:UiBinder>
</g:SimplePanel>
</header>
</g:HTMLPanel>
</ui:UiBinder>
@@ -1,28 +1,28 @@
package com.sap.sse.gwt.client.sapheader; package com.sap.sse.gwt.client.sapheader;
import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.GWT;
import com.google.gwt.resources.client.ClientBundle; import com.google.gwt.resources.client.ClientBundle;
import com.google.gwt.resources.client.CssResource; import com.google.gwt.resources.client.CssResource;
import com.google.gwt.resources.client.ImageResource; import com.google.gwt.resources.client.ImageResource;
public interface SAPHeaderResources extends ClientBundle { public interface SAPHeaderResources extends ClientBundle {
public static final SAPHeaderResources INSTANCE = GWT.create(SAPHeaderResources.class); public static final SAPHeaderResources INSTANCE = GWT.create(SAPHeaderResources.class);
@Source("SAPHeader.gss") @Source("SAPHeader.gss")
LocalCss css(); LocalCss css();
public interface LocalCss extends CssResource { public interface LocalCss extends CssResource {
String siteheader(); String siteheader();
String siteheaderLeft(); String siteheaderLeft();
String siteHeaderTitle(); String siteHeaderTitle();
String siteHeaderSubTitle(); String siteHeaderSubTitle();
String siteheader2ndCol(); String siteheader2ndCol();
String siteheaderRight(); String siteheaderRight();
String logo(); String logo();
String logotitle(); String logotitle();
String pagetitle(); String pagetitle();
} }
@Source("logo-small@2x.png") @Source("logo-small@2x.png")
ImageResource logo(); ImageResource logo();
} }
@@ -1,58 +1,58 @@
package com.sap.sse.security.ui.authentication.generic.sapheader; package com.sap.sse.security.ui.authentication.generic.sapheader;
import com.google.gwt.user.client.ui.Anchor; import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.SimplePanel; import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.Widget; import com.google.gwt.user.client.ui.Widget;
import com.sap.sse.gwt.client.sapheader.SAPHeader; import com.sap.sse.gwt.client.sapheader.SAPHeader;
import com.sap.sse.security.ui.authentication.generic.GenericAuthentication; import com.sap.sse.security.ui.authentication.generic.GenericAuthentication;
import com.sap.sse.security.ui.authentication.view.AuthenticationMenuView; import com.sap.sse.security.ui.authentication.view.AuthenticationMenuView;
import com.sap.sse.security.ui.authentication.view.AuthenticationMenuViewImpl; import com.sap.sse.security.ui.authentication.view.AuthenticationMenuViewImpl;
/** /**
* {@link SAPHeader} which is decorated by a authentication control on the right side. This is typically used to * {@link SAPHeader} which is decorated by a authentication control on the right side. This is typically used to
* integrate with {@link GenericAuthentication}. * integrate with {@link GenericAuthentication}.
*/ */
public class SAPHeaderWithAuthentication extends SAPHeader { public class SAPHeaderWithAuthentication extends SAPHeader {
private static final SAPHeaderWithAuthenticationResources res = SAPHeaderWithAuthenticationResources.INSTANCE; private static final SAPHeaderWithAuthenticationResources res = SAPHeaderWithAuthenticationResources.INSTANCE;
private final SimplePanel rightWrapper; private final SimplePanel rightWrapper;
private AuthenticationMenuView authenticationMenuView; private AuthenticationMenuView authenticationMenuView;
public SAPHeaderWithAuthentication(String applicationName, String headerTitle) { public SAPHeaderWithAuthentication(String applicationName, String applicationBaseUrl, String headerTitle) {
this(applicationName); this(applicationName, applicationBaseUrl);
setHeaderTitle(headerTitle); setHeaderTitle(headerTitle);
} }
public SAPHeaderWithAuthentication(String applicationName) { public SAPHeaderWithAuthentication(String applicationName, String applicationBaseUrl) {
super(applicationName); super(applicationName, applicationBaseUrl);
res.css().ensureInjected(); res.css().ensureInjected();
FlowPanel rightWithAuthentication = new FlowPanel(); FlowPanel rightWithAuthentication = new FlowPanel();
rightWithAuthentication.addStyleName(res.css().header_right_wrapper()); rightWithAuthentication.addStyleName(res.css().header_right_wrapper());
Anchor authenticationMenu = new Anchor(); Anchor authenticationMenu = new Anchor();
authenticationMenu.addStyleName(res.css().usermanagement_icon()); authenticationMenu.addStyleName(res.css().usermanagement_icon());
rightWithAuthentication.add(authenticationMenu); rightWithAuthentication.add(authenticationMenu);
authenticationMenuView = new AuthenticationMenuViewImpl(authenticationMenu, res.css().usermanagement_loggedin(), res.css().usermanagement_open()); authenticationMenuView = new AuthenticationMenuViewImpl(authenticationMenu, res.css().usermanagement_loggedin(), res.css().usermanagement_open());
rightWrapper = new SimplePanel(); rightWrapper = new SimplePanel();
rightWrapper.addStyleName(res.css().header_right_extension()); rightWrapper.addStyleName(res.css().header_right_extension());
rightWithAuthentication.add(rightWrapper); rightWithAuthentication.add(rightWrapper);
super.addWidgetToRightSide(rightWithAuthentication); super.addWidgetToRightSide(rightWithAuthentication);
} }
@Override @Override
public void addWidgetToRightSide(Widget widget) { public void addWidgetToRightSide(Widget widget) {
rightWrapper.add(widget); rightWrapper.add(widget);
} }
/** /**
* @return the {@link AuthenticationMenuView} associated with the authentication control on the right side of the header. * @return the {@link AuthenticationMenuView} associated with the authentication control on the right side of the header.
*/ */
public AuthenticationMenuView getAuthenticationMenuView() { public AuthenticationMenuView getAuthenticationMenuView() {
return authenticationMenuView; return authenticationMenuView;
} }
} }
@@ -13,6 +13,7 @@ import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.concurrent.FutureTask; import java.util.concurrent.FutureTask;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock;
@@ -23,6 +24,11 @@ import com.sap.sse.common.Util.Pair;
import com.sap.sse.concurrent.LockUtil; import com.sap.sse.concurrent.LockUtil;
import com.sap.sse.concurrent.NamedReentrantReadWriteLock; import com.sap.sse.concurrent.NamedReentrantReadWriteLock;
import com.sap.sse.util.SmartFutureCache.UpdateInterval; import com.sap.sse.util.SmartFutureCache.UpdateInterval;
import com.sap.sse.util.impl.HasTracingGet;
import com.sap.sse.util.impl.HasTracingGetImpl;
import com.sap.sse.util.impl.KnowsExecutor;
import com.sap.sse.util.impl.KnowsExecutorAndTracingGet;
import com.sap.sse.util.impl.KnowsExecutorAndTracingGetImpl;
/** /**
* A cache for which a background update can be triggered. Readers can decide whether they want to wait for any ongoing * A cache for which a background update can be triggered. Readers can decide whether they want to wait for any ongoing
@@ -188,6 +194,10 @@ public class SmartFutureCache<K, V, U extends UpdateInterval<U>> {
return callable.call(); return callable.call();
} }
@Override
public String toString() {
return callable==null?"null":callable.toString();
}
} }
/** /**
@@ -201,7 +211,9 @@ public class SmartFutureCache<K, V, U extends UpdateInterval<U>> {
* @author Axel Uhl (D043530) * @author Axel Uhl (D043530)
* *
*/ */
private class FutureTaskWithCancelBlocking extends FutureTask<V> implements Callable<V> { private class FutureTaskWithCancelBlocking extends FutureTask<V> implements KnowsExecutor, Callable<V> {
private final KnowsExecutorAndTracingGet<V> tracingGetHelper;
private boolean runningAndReadUpdateInterval; private boolean runningAndReadUpdateInterval;
private final K key; private final K key;
@@ -228,6 +240,7 @@ public class SmartFutureCache<K, V, U extends UpdateInterval<U>> {
public FutureTaskWithCancelBlocking(SettableCallable<V> callable, final K key, final U updateInterval, public FutureTaskWithCancelBlocking(SettableCallable<V> callable, final K key, final U updateInterval,
final boolean callerWaitsSynchronouslyForResult, final Thread callerThread) { final boolean callerWaitsSynchronouslyForResult, final Thread callerThread) {
super(callable); super(callable);
tracingGetHelper = new KnowsExecutorAndTracingGetImpl<>();
callable.setCallable(this); callable.setCallable(this);
this.gettingThreads = new HashSet<>(); this.gettingThreads = new HashSet<>();
this.key = key; this.key = key;
@@ -274,7 +287,7 @@ public class SmartFutureCache<K, V, U extends UpdateInterval<U>> {
} }
} }
} }
return super.get(); return tracingGetHelper.callGetAndTraceAfterEachTimeout(this);
} }
@Override @Override
@@ -363,6 +376,16 @@ public class SmartFutureCache<K, V, U extends UpdateInterval<U>> {
public U getUpdateInterval() { public U getUpdateInterval() {
return updateInterval; return updateInterval;
} }
@Override
public void setExecutorThisTaskIsScheduledFor(ThreadPoolExecutor executorThisTaskIsScheduledFor) {
tracingGetHelper.setExecutorThisTaskIsScheduledFor(executorThisTaskIsScheduledFor);
}
@Override
public String toString() {
return getClass().getName()+" [key="+key+", updateInterval="+updateInterval+", cacheUpdateComputer="+cacheUpdateComputer+"]";
}
} }
public SmartFutureCache(CacheUpdater<K, V, U> cacheUpdateComputer, String nameForLocks) { public SmartFutureCache(CacheUpdater<K, V, U> cacheUpdateComputer, String nameForLocks) {
@@ -507,14 +530,33 @@ public class SmartFutureCache<K, V, U extends UpdateInterval<U>> {
notifyAll(); notifyAll();
} }
} }
public String toString() {
final String result;
synchronized (this) {
if (isSet) {
result = value==null?"null":value.toString();
} else {
result = "unset";
}
}
return result;
}
} }
private static class TransitiveFuture<T> implements Future<T> { private static class TransitiveFuture<T> implements Future<T> {
private final SettableFuture<Future<T>> future; private final SettableFuture<Future<T>> future;
private final HasTracingGet<T> tracingGetHelper;
protected TransitiveFuture(SettableFuture<Future<T>> future) { protected TransitiveFuture(SettableFuture<Future<T>> future) {
super(); super();
this.future = future; this.future = future;
this.tracingGetHelper = new HasTracingGetImpl<T>() {
@Override
protected String getAdditionalTraceInfo() {
return "transitive future "+TransitiveFuture.this.future.toString();
}
};
} }
@Override @Override
@@ -546,7 +588,7 @@ public class SmartFutureCache<K, V, U extends UpdateInterval<U>> {
@Override @Override
public T get() throws InterruptedException, ExecutionException { public T get() throws InterruptedException, ExecutionException {
return future.get().get(); return tracingGetHelper.callGetAndTraceAfterEachTimeout(this);
} }
@Override @Override
@@ -52,7 +52,7 @@ public interface ThreadPoolUtil {
* *
* The same configuration as for {@link #getDefaultBackgroundTaskThreadPoolExecutor()} is used. * The same configuration as for {@link #getDefaultBackgroundTaskThreadPoolExecutor()} is used.
*/ */
ScheduledExecutorService createBackgroundTaskThreadPoolExecutor(); ScheduledExecutorService createBackgroundTaskThreadPoolExecutor(String name);
/** /**
* In case an application module really requires its own thread pool instead of * In case an application module really requires its own thread pool instead of
@@ -74,5 +74,7 @@ public interface ThreadPoolUtil {
* *
* The same configuration as for {@link #getDefaultForegroundTaskThreadPoolExecutor()} is used. * The same configuration as for {@link #getDefaultForegroundTaskThreadPoolExecutor()} is used.
*/ */
ScheduledExecutorService createForegroundTaskThreadPoolExecutor(); ScheduledExecutorService createForegroundTaskThreadPoolExecutor(String name);
ScheduledExecutorService createForegroundTaskThreadPoolExecutor(int size, String name);
} }
@@ -0,0 +1,37 @@
package com.sap.sse.util.impl;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class FutureTaskWithTracingGet<V> extends FutureTask<V> {
private final HasTracingGet<V> hasTracingGet;
private final String traceInfo;
public FutureTaskWithTracingGet(String traceInfo, Callable<V> callable) {
super(callable);
this.traceInfo = traceInfo;
hasTracingGet = createHasTracingGet();
}
public FutureTaskWithTracingGet(String traceInfo, Runnable runnable, V result) {
super(runnable, result);
this.traceInfo = traceInfo;
hasTracingGet = createHasTracingGet();
}
@Override
public V get() throws InterruptedException, ExecutionException {
return hasTracingGet.callGetAndTraceAfterEachTimeout(this);
}
private HasTracingGet<V> createHasTracingGet() {
return new HasTracingGetImpl<V>() {
@Override
protected String getAdditionalTraceInfo() {
return traceInfo;
}
};
}
}
@@ -0,0 +1,16 @@
package com.sap.sse.util.impl;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeoutException;
public interface HasTracingGet<V> {
/**
* Keeps calling {@link Future#get(long, java.util.concurrent.TimeUnit)} with a timeout until a result is available
* or an exception other than a {@link TimeoutException} is thrown. When a timeout occurs, some trace output is
* written to the log. The method is guaranteed not to call {@link Future#get()} and therefore can be used in an
* implementation of a {@link Future#get()} method without causing an endless recursion as long as
* {@link Future#get(long, java.util.concurrent.TimeUnit)} is not calling this method.
*/
V callGetAndTraceAfterEachTimeout(Future<V> future) throws InterruptedException, ExecutionException;
}
@@ -0,0 +1,37 @@
package com.sap.sse.util.impl;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sap.sse.common.Duration;
/**
* Can be used with a {@link Future} task and outputs trace messages to the log after {@link #MILLIS_AFTER_WHICH_TO_TRACE_NON_RETURNING_GET} milliseconds
* of a {@link #get()} call not returning. The {@link #getAdditionalTraceInfo()} method must be implemented by subclasses to provide some meaningful
* hint as to which object is currently timing out.
*
* @author Axel Uhl (d043530)
*
* @param <V>
*/
public abstract class HasTracingGetImpl<V> implements HasTracingGet<V> {
private static final Logger logger = Logger.getLogger(HasTracingGetImpl.class.getName());
private final long MILLIS_AFTER_WHICH_TO_TRACE_NON_RETURNING_GET = Duration.ONE_SECOND.times(5).asMillis();
public V callGetAndTraceAfterEachTimeout(Future<V> future) throws InterruptedException, ExecutionException {
while (true) {
try {
return future.get(MILLIS_AFTER_WHICH_TO_TRACE_NON_RETURNING_GET, TimeUnit.MILLISECONDS);
} catch (TimeoutException e) {
logger.log(Level.INFO, "Timeout waiting for future task "+future+
" (retrying); "+ getAdditionalTraceInfo(), e);
}
}
}
protected abstract String getAdditionalTraceInfo();
}
@@ -0,0 +1,14 @@
package com.sap.sse.util.impl;
import java.util.concurrent.ThreadPoolExecutor;
/**
* A {@link ThreadPoolExecutor} can be set on this object. This is used, in particular, by the
* {@link NamedTracingScheduledThreadPoolExecutor} whenever a task of this type is enqueued.
*
* @author Axel Uhl (d043530)
*
*/
public interface KnowsExecutor {
void setExecutorThisTaskIsScheduledFor(ThreadPoolExecutor executorThisTaskIsScheduledFor);
}
@@ -0,0 +1,17 @@
package com.sap.sse.util.impl;
import java.util.concurrent.Future;
import com.sap.sse.util.ThreadPoolUtil;
/**
* Can be used with a {@link Future} task and outputs trace messages to the log after {@link #MILLIS_AFTER_WHICH_TO_TRACE_NON_RETURNING_GET} milliseconds
* of a {@link #get()} call not returning. When used in conjunction with a {@link NamedTracingScheduledThreadPoolExecutor} such as
* the ones returned by {@link ThreadPoolUtil}, the task will be
*
* @author Axel Uhl (d043530)
*
* @param <V>
*/
public interface KnowsExecutorAndTracingGet<V> extends KnowsExecutor, HasTracingGet<V> {
}
@@ -0,0 +1,31 @@
package com.sap.sse.util.impl;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor;
import com.sap.sse.util.ThreadPoolUtil;
/**
* Can be used with a {@link Future} task and outputs trace messages to the log after
* {@link #MILLIS_AFTER_WHICH_TO_TRACE_NON_RETURNING_GET} milliseconds of a {@link #get()} call not returning. When used
* in conjunction with a {@link NamedTracingScheduledThreadPoolExecutor} such as the ones returned by
* {@link ThreadPoolUtil}, the task will be
*
* @author Axel Uhl (d043530)
*
* @param <V>
*/
public class KnowsExecutorAndTracingGetImpl<V> extends HasTracingGetImpl<V> implements KnowsExecutorAndTracingGet<V> {
private ThreadPoolExecutor executorThisTaskIsScheduledFor;
@Override
public void setExecutorThisTaskIsScheduledFor(ThreadPoolExecutor executorThisTaskIsScheduledFor) {
this.executorThisTaskIsScheduledFor = executorThisTaskIsScheduledFor;
}
@Override
protected String getAdditionalTraceInfo() {
return executorThisTaskIsScheduledFor == null ? "not scheduled"
: ("scheduled with executor " + executorThisTaskIsScheduledFor);
}
}
@@ -0,0 +1,164 @@
package com.sap.sse.util.impl;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Future;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.RunnableFuture;
import java.util.concurrent.RunnableScheduledFuture;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sap.sse.common.Duration;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.impl.MillisecondsTimePoint;
import com.sap.sse.concurrent.ConcurrentWeakHashMap;
/**
* A scheduled thread pool executor with a name that produces {@link Future} tasks that write a trace message
* to the log after their {@link Future#get()} method hasn't produced a result for some time, then keep trying.
* The tasks are linked to this executor, also when a {@link KnowsExecutor} task is submitted to the {@link #execute(Runnable)}
* method, so that when they need to trace they
*
* @author Axel Uhl (d043530)
*
*/
public class NamedTracingScheduledThreadPoolExecutor extends ScheduledThreadPoolExecutor {
private static final Logger logger = Logger.getLogger(NamedTracingScheduledThreadPoolExecutor.class.getName());
private final String name;
/**
* If logging level is at least {@link Level#FINE}, each runnable's start time point is recorded in the
* {@link #beforeExecute(Thread, Runnable)} method. If at this logging level, the
* {@link #afterExecute(Runnable, Throwable)} method calculates the runnable's duration, and if above the
* {@link #DURATION_LOGGING_THRESHOLD}, writes a log entry.
*/
private final ConcurrentHashMap<Runnable, TimePoint> startTimePoints = new ConcurrentHashMap<>();
/**
* When logging {@link Level#FINE}, the {@link #schedule(Runnable, long, TimeUnit)} variants produce wrappers
* around the runnables whose content is nulled when {@link #afterExecute(Runnable, Throwable)} is called. In order
* to be able to log long-running runnables correctly, this map stores the connection between the wrapping
* future and the runnable wrapped by it.
*/
private final ConcurrentWeakHashMap<ScheduledFuture<?>, Runnable> wrappedRunnables = new ConcurrentWeakHashMap<>();
private final Duration DURATION_LOGGING_THRESHOLD = Duration.ONE_SECOND.times(5);
public NamedTracingScheduledThreadPoolExecutor(String name, int corePoolSize, RejectedExecutionHandler handler) {
super(corePoolSize, handler);
this.name = name;
}
public NamedTracingScheduledThreadPoolExecutor(String name, int corePoolSize, ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
super(corePoolSize, threadFactory, handler);
this.name = name;
}
public NamedTracingScheduledThreadPoolExecutor(String name, int corePoolSize, ThreadFactory threadFactory) {
super(corePoolSize, threadFactory);
this.name = name;
}
public NamedTracingScheduledThreadPoolExecutor(String name, int corePoolSize) {
super(corePoolSize);
this.name = name;
}
@Override
protected <V> RunnableScheduledFuture<V> decorateTask(Runnable runnable, RunnableScheduledFuture<V> task) {
return new ThreadPoolAwareRunnableScheduledFutureDelegate<>(this, task);
}
@Override
protected <V> RunnableScheduledFuture<V> decorateTask(Callable<V> callable, RunnableScheduledFuture<V> task) {
return new ThreadPoolAwareRunnableScheduledFutureDelegate<>(this, task);
}
@Override
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
return new ThreadPoolAwareFutureTask<T>(this, runnable, value);
}
@Override
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
return new ThreadPoolAwareFutureTask<T>(this, callable);
}
@Override
public void execute(Runnable command) {
if (command instanceof KnowsExecutor) {
((KnowsExecutor) command).setExecutorThisTaskIsScheduledFor(this);
}
super.execute(command);
}
@Override
protected void beforeExecute(Thread t, Runnable r) {
super.beforeExecute(t, r);
if (logger.isLoggable(Level.FINE)) {
startTimePoints.put(r, MillisecondsTimePoint.now());
}
}
@Override
protected void afterExecute(Runnable r, Throwable t) {
if (logger.isLoggable(Level.FINE)) {
final TimePoint end = MillisecondsTimePoint.now();
final TimePoint startTime = startTimePoints.remove(r);
Runnable theRunnableToLog = wrappedRunnables.get(r);
if (theRunnableToLog == null) {
theRunnableToLog = r;
}
if (startTime != null) {
final Duration duration = startTime.until(end);
if (duration.compareTo(DURATION_LOGGING_THRESHOLD) > 0) {
logger.fine("Runnable "+theRunnableToLog+" took "+duration+" to complete in executor "+this+(t==null?"":("It failed with exception "+t)));
}
} else {
logger.fine("Strange internal problem: had expected to find start time for "+r);
}
}
super.afterExecute(r, t);
}
@Override
public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) {
final ScheduledFuture<?> result = super.schedule(command, delay, unit);
if (logger.isLoggable(Level.FINE)) {
// remember which runnable was wrapped by the future because it will have been nulled out when afterExecute is called
wrappedRunnables.put(result, command);
}
return result;
}
@Override
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) {
final ScheduledFuture<?> result = super.scheduleAtFixedRate(command, initialDelay, period, unit);
if (logger.isLoggable(Level.FINE)) {
// remember which runnable was wrapped by the future because it will have been nulled out when afterExecute is called
wrappedRunnables.put(result, command);
}
return result;
}
@Override
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) {
final ScheduledFuture<?> result = super.scheduleWithFixedDelay(command, initialDelay, delay, unit);
if (logger.isLoggable(Level.FINE)) {
// remember which runnable was wrapped by the future because it will have been nulled out when afterExecute is called
wrappedRunnables.put(result, command);
}
return result;
}
@Override
public String toString() {
return super.toString() + "[name=" + name + "]";
}
}
@@ -0,0 +1,62 @@
package com.sap.sse.util.impl;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sap.sse.util.ThreadPoolUtil;
/**
* A future task that outputs trace messages to the log after {@link #MILLIS_AFTER_WHICH_TO_TRACE_NON_RETURNING_GET} milliseconds
* of a {@link #get()} call not returning. When used in conjunction with a {@link NamedTracingScheduledThreadPoolExecutor} such as
* the ones returned by {@link ThreadPoolUtil}, the task will be updated by the executor when added to the queue. When the {@link #get()}
* method doesn't respond after the timeout period, the executor that scheduled this tasks, its name as well as its queue size
* are traced into the log.
*
* @author Axel Uhl (d043530)
*
* @param <V>
*/
public class ThreadPoolAwareFutureTask<V> extends FutureTask<V> implements KnowsExecutor {
private static final Logger logger = Logger.getLogger(ThreadPoolAwareFutureTask.class.getName());
private final KnowsExecutorAndTracingGet<V> getHelper = new KnowsExecutorAndTracingGetImpl<V>();
private final Object callableOrRunnableIfLoggingFine;
public ThreadPoolAwareFutureTask(ThreadPoolExecutor executor, Callable<V> callable) {
super(callable);
if (logger.isLoggable(Level.FINE)) {
callableOrRunnableIfLoggingFine = callable;
} else {
callableOrRunnableIfLoggingFine = null;
}
getHelper.setExecutorThisTaskIsScheduledFor(executor);
}
public ThreadPoolAwareFutureTask(ThreadPoolExecutor executor, Runnable runnable, V result) {
super(runnable, result);
if (logger.isLoggable(Level.FINE)) {
callableOrRunnableIfLoggingFine = runnable;
} else {
callableOrRunnableIfLoggingFine = null;
}
getHelper.setExecutorThisTaskIsScheduledFor(executor);
}
@Override
public void setExecutorThisTaskIsScheduledFor(ThreadPoolExecutor executorThisTaskIsScheduledFor) {
this.getHelper.setExecutorThisTaskIsScheduledFor(executorThisTaskIsScheduledFor);
}
@Override
public V get() throws InterruptedException, ExecutionException {
return getHelper.callGetAndTraceAfterEachTimeout(this);
}
@Override
public String toString() {
return super.toString()+(callableOrRunnableIfLoggingFine==null?"":(" "+callableOrRunnableIfLoggingFine.toString()));
}
}
@@ -0,0 +1,53 @@
package com.sap.sse.util.impl;
import java.util.concurrent.Delayed;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.RunnableScheduledFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
public class ThreadPoolAwareRunnableScheduledFutureDelegate<V> extends KnowsExecutorAndTracingGetImpl<V> implements KnowsExecutorAndTracingGet<V>, RunnableScheduledFuture<V> {
private final RunnableScheduledFuture<V> future;
public ThreadPoolAwareRunnableScheduledFutureDelegate(ThreadPoolExecutor executor, RunnableScheduledFuture<V> future) {
this.future = future;
setExecutorThisTaskIsScheduledFor(executor);
}
public long getDelay(TimeUnit unit) {
return future.getDelay(unit);
}
public void run() {
future.run();
}
public boolean isPeriodic() {
return future.isPeriodic();
}
public boolean cancel(boolean mayInterruptIfRunning) {
return future.cancel(mayInterruptIfRunning);
}
public int compareTo(Delayed o) {
return future.compareTo(o);
}
public boolean isCancelled() {
return future.isCancelled();
}
public boolean isDone() {
return future.isDone();
}
public V get() throws InterruptedException, ExecutionException {
return callGetAndTraceAfterEachTimeout(future);
}
public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
return future.get(timeout, unit);
}
}
@@ -1,21 +1,18 @@
package com.sap.sse.util.impl; package com.sap.sse.util.impl;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import com.sap.sse.util.ThreadPoolUtil; import com.sap.sse.util.ThreadPoolUtil;
public class ThreadPoolUtilImpl implements ThreadPoolUtil { public class ThreadPoolUtilImpl implements ThreadPoolUtil {
private static final int REASONABLE_THREAD_POOL_SIZE = Math.max(Runtime.getRuntime().availableProcessors()/2, 3); private static final int REASONABLE_THREAD_POOL_SIZE = Math.max(Runtime.getRuntime().availableProcessors()-1, 3);
private final ScheduledExecutorService defaultBackgroundTaskThreadPoolExecutor; private final ScheduledExecutorService defaultBackgroundTaskThreadPoolExecutor;
private final ScheduledExecutorService defaultForegroundTaskThreadPoolExecutor; private final ScheduledExecutorService defaultForegroundTaskThreadPoolExecutor;
public ThreadPoolUtilImpl() { public ThreadPoolUtilImpl() {
defaultBackgroundTaskThreadPoolExecutor = Executors.newScheduledThreadPool(/* corePoolSize */ REASONABLE_THREAD_POOL_SIZE, defaultBackgroundTaskThreadPoolExecutor = createBackgroundTaskThreadPoolExecutor("Default background executor");
new ThreadFactoryWithPriority(Thread.NORM_PRIORITY-1, /* daemon */ true)); defaultForegroundTaskThreadPoolExecutor = createForegroundTaskThreadPoolExecutor(2*REASONABLE_THREAD_POOL_SIZE, "Default foreground executor");
defaultForegroundTaskThreadPoolExecutor = Executors.newScheduledThreadPool(/* corePoolSize */ Math.max(Runtime.getRuntime().availableProcessors()-1, 6),
new ThreadFactoryWithPriority(Thread.NORM_PRIORITY, /* daemon */ true));
} }
@Override @Override
@@ -29,18 +26,26 @@ public class ThreadPoolUtilImpl implements ThreadPoolUtil {
} }
@Override @Override
public ScheduledExecutorService createBackgroundTaskThreadPoolExecutor() { public ScheduledExecutorService createBackgroundTaskThreadPoolExecutor(String name) {
return createThreadPoolExecutor(Thread.NORM_PRIORITY-1); return createThreadPoolExecutor(name, Thread.NORM_PRIORITY-1);
} }
@Override @Override
public ScheduledExecutorService createForegroundTaskThreadPoolExecutor() { public ScheduledExecutorService createForegroundTaskThreadPoolExecutor(String name) {
return createThreadPoolExecutor(Thread.NORM_PRIORITY); return createThreadPoolExecutor(name, Thread.NORM_PRIORITY);
} }
private ScheduledExecutorService createThreadPoolExecutor(final int priority) { @Override
return Executors.newScheduledThreadPool(/* corePoolSize */ REASONABLE_THREAD_POOL_SIZE, public ScheduledExecutorService createForegroundTaskThreadPoolExecutor(int size, String name) {
new ThreadFactoryWithPriority(priority, /* daemon */ true)); return createThreadPoolExecutor(name, Thread.NORM_PRIORITY, size);
}
private ScheduledExecutorService createThreadPoolExecutor(String name, final int priority) {
return createThreadPoolExecutor(name, priority, /* corePoolSize */ REASONABLE_THREAD_POOL_SIZE);
}
private ScheduledExecutorService createThreadPoolExecutor(String name, final int priority, final int size) {
return new NamedTracingScheduledThreadPoolExecutor(name, /* corePoolSize */ size, new ThreadFactoryWithPriority(priority, /* daemon */ true));
} }
@Override @Override
@@ -1,11 +1,21 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="settings_category_energy_saving">Batterie Sparen</string> <string name="settings_category_energy_saving">Batterie Sparen</string>
<string name="energy_saving_enabled">Batteriesparmodus</string>
<string name="preference_energy_saving_is_enabled">Ein</string> <string name="preference_energy_saving_sending_interval">Sendeintervall für GPS-Updates auswählen</string>
<string name="preference_energy_saving_is_disabled">Aus</string> <string name="preference_energy_saving_sending_interval_summary">Wählen Sie den größtmöglichen Wert, der dennoch eine dem Anwendungsfall entsprechend hohe Aktualisierungsrate ermöglicht.</string>
<string name="preference_energy_saving_summary_is_enabled">GPS Updates werden alle 30 Sekunden verschickt.</string> <string name="preference_energy_saving_enabled_automatically">Der Energiesparmodus wurde automatisch aktiviert, GPS Updates werden alle 20 Sekunden an den Server gesendet. Diese Einstellung wird überschrieben, wenn Sie oben ein anderes Intervall auswählen.</string>
<string name="preference_energy_saving_summary_is_disabled">GPS Updates werden jede Sekunde verschickt.</string>
<string name="preference_energy_saving_sending_interval_message">GPS Updates werden%1$s %2$s an den Server gesendet.</string>
<string name="preference_energy_saving_sending_interval_message_every">alle</string>
<string-array name="preference_energy_saving_sending_interval_entries">
<item>sofort</item>
<item>5 Sekunden</item>
<item>10 Sekunden</item>
<item>20 Sekunden</item>
<item>60 Sekunden</item>
</string-array>
<string name="preference_heading_with_declination_subtracted_is_enabled">Ein</string> <string name="preference_heading_with_declination_subtracted_is_enabled">Ein</string>
<string name="preference_heading_with_declination_subtracted_is_disabled">Aus</string> <string name="preference_heading_with_declination_subtracted_is_disabled">Aus</string>
<string name="preference_heading_with_declination_subtracted_summary_is_enabled">Anzeige: Missweisend</string> <string name="preference_heading_with_declination_subtracted_summary_is_enabled">Anzeige: Missweisend</string>
@@ -78,5 +78,6 @@
<string name="upload_in_progress">Wird hochgeladen&#8230;</string> <string name="upload_in_progress">Wird hochgeladen&#8230;</string>
<string name="upload_io_error">Fehler beim Hochladen: </string> <string name="upload_io_error">Fehler beim Hochladen: </string>
<string name="upload_retry">Fehler beim Hochladen.\nZum Wiederholen hier tippen.</string> <string name="upload_retry">Fehler beim Hochladen.\nZum Wiederholen hier tippen.</string>
<string name="tracking_status_gps_found">GPS Signal gefunden!</string>
</resources> </resources>
@@ -7,14 +7,34 @@
<string name="app_folder" translatable="false">tracking</string> <string name="app_folder" translatable="false">tracking</string>
<string name="preference_energy_saving_enabled_key" translatable="false">preferenceEnergySavingEnabled</string> <string name="preference_energy_saving_enabled_key" translatable="false">preferenceEnergySavingEnabled</string>
<string name="preference_energy_saving_override_key" translatable="false">preferenceEnergySavingOverride</string>
<string name="preference_heading_with_declination_subtracted_key" translatable="false">preferenceHeadingWithDeclinationSubtracted</string> <string name="preference_heading_with_declination_subtracted_key" translatable="false">preferenceHeadingWithDeclinationSubtracted</string>
<string name="settings_category_energy_saving">Battery Saving</string> <string name="settings_category_energy_saving">Battery Saving</string>
<string name="energy_saving_enabled">Save Energy</string>
<string name="preference_energy_saving_is_enabled">On</string> <string name="preference_energy_saving_sending_interval_key" translatable="false">msgSndInt</string>
<string name="preference_energy_saving_is_disabled">Off</string> <string name="preference_energy_saving_sending_interval_default" translatable="false">0</string>
<string name="preference_energy_saving_summary_is_enabled">GPS updates are being sent to server every 30 seconds.</string> <string name="preference_energy_saving_sending_interval">choose GPS update sending interval</string>
<string name="preference_energy_saving_summary_is_disabled">GPS updates are being sent to server every second.</string> <string name="preference_energy_saving_sending_interval_summary">Choose a value as high as possible to preserve battery life while still maintaining a reasonable live experience, depending on your use case.</string>
<string name="preference_energy_saving_enabled_automatically">Energy saving mode has been enabled automatically, GPS updates are being sent to the server every 20 seconds. You can change that by selecting an interval above.</string>
<string name="preference_energy_saving_sending_interval_message">GPS updates will be sent to the server%1$s %2$s.</string>
<string name="preference_energy_saving_sending_interval_message_key">msgSndIntMessage</string>
<string name="preference_energy_saving_sending_interval_message_every">every</string>
<string-array name="preference_energy_saving_sending_interval_entries">
<item>immediately</item>
<item>5 seconds</item>
<item>10 seconds</item>
<item>20 seconds</item>
<item>60 seconds</item>
</string-array>
<string-array name="preference_energy_saving_sending_interval_values" translatable="false">
<item>0</item>
<item>5</item>
<item>10</item>
<item>20</item>
<item>60</item>
</string-array>
<string name="preference_gps_fix_interval_ms_key" translatable="false">gpsFixInterval</string> <string name="preference_gps_fix_interval_ms_key" translatable="false">gpsFixInterval</string>
<string name="preference_gps_fix_interval_ms_default" translatable="false">500</string> <string name="preference_gps_fix_interval_ms_default" translatable="false">500</string>
@@ -19,4 +19,5 @@
<string name="tracking_status_no_gps_signal">No GPS Signal!</string> <string name="tracking_status_no_gps_signal">No GPS Signal!</string>
<string name="desc_regatta_user_image">Regatta User Image</string> <string name="desc_regatta_user_image">Regatta User Image</string>
<string name="desc_add_photo_button">Add Photo Button</string> <string name="desc_add_photo_button">Add Photo Button</string>
<string name="tracking_status_gps_found">GPS signal found!</string>
</resources> </resources>
@@ -3,14 +3,17 @@
<!-- Energy Saving --> <!-- Energy Saving -->
<PreferenceCategory android:title="@string/settings_category_energy_saving"> <PreferenceCategory android:title="@string/settings_category_energy_saving">
<SwitchPreference <ListPreference
android:defaultValue="false" android:defaultValue="@string/preference_energy_saving_sending_interval_default"
android:key="@string/preference_energy_saving_enabled_key" android:key="@string/preference_energy_saving_sending_interval_key"
android:switchTextOn="@string/preference_energy_saving_is_enabled" android:entryValues="@array/preference_energy_saving_sending_interval_values"
android:switchTextOff="@string/preference_energy_saving_is_disabled" android:entries="@array/preference_energy_saving_sending_interval_entries"
android:summaryOn="@string/preference_energy_saving_summary_is_enabled" android:title="@string/preference_energy_saving_sending_interval"
android:summaryOff="@string/preference_energy_saving_summary_is_disabled" android:summary="@string/preference_energy_saving_sending_interval_summary"/>
android:title="@string/energy_saving_enabled"/>
<EditTextPreference
android:key="@string/preference_energy_saving_sending_interval_message_key"
android:selectable="false"/>
</PreferenceCategory> </PreferenceCategory>
<!-- Connection --> <!-- Connection -->
@@ -68,7 +68,7 @@ public class TrackingService extends Service implements GoogleApiClient.Connecti
private int NOTIFICATION_ID = R.string.tracker_started; private int NOTIFICATION_ID = R.string.tracker_started;
public final static int UPDATE_INTERVAL_IN_MILLIS_DEFAULT = 1000; public final static int UPDATE_INTERVAL_IN_MILLIS_DEFAULT = 1000;
private final static int UPDATE_INTERVAL_IN_MILLIS_POWERSAVE_MODE = 30000; private final static int UPDATE_INTERVAL_IN_MILLIS_POWERSAVE_MODE = 20000;
private final static float BATTERY_POWER_SAVE_THRESHOLD = 0.2f; private final static float BATTERY_POWER_SAVE_THRESHOLD = 0.2f;
private boolean initialLocation; private boolean initialLocation;
@@ -163,6 +163,7 @@ public class TrackingService extends Service implements GoogleApiClient.Connecti
prefs.setTrackerIsTracking(true); prefs.setTrackerIsTracking(true);
prefs.setTrackerIsTrackingCheckinDigest(checkinDigest); prefs.setTrackerIsTrackingCheckinDigest(checkinDigest);
prefs.setEnergySavingOverride(false);
} }
private void stopTracking() { private void stopTracking() {
@@ -348,17 +349,18 @@ public class TrackingService extends Service implements GoogleApiClient.Connecti
} }
/** /**
* Update whether message sending service should retry every 3 seconds or every 30. * Update the interval in which message sending service should retry - possibly enable power saving
*/ */
private void updateResendIntervalSetting() { private void updateResendIntervalSetting() {
float batteryPct = getBatteryPercentage(); float batteryPct = getBatteryPercentage();
boolean batteryIsCharging = prefs.getBatteryIsCharging(); boolean batteryIsCharging = prefs.getBatteryIsCharging();
int updateInterval = UPDATE_INTERVAL_IN_MILLIS_DEFAULT; int updateInterval = prefs.getMessageSendingIntervalInMillis();
if (prefs.getEnergySavingEnabledByUser() || (batteryPct < BATTERY_POWER_SAVE_THRESHOLD && !batteryIsCharging)) { if (batteryPct < BATTERY_POWER_SAVE_THRESHOLD && !batteryIsCharging && !prefs.getEnergySavingOverride()) {
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
ExLog.i(this, "POWER-LEVELS", "in power saving mode"); ExLog.i(this, "POWER-LEVELS", "in power saving mode");
} }
updateInterval = UPDATE_INTERVAL_IN_MILLIS_POWERSAVE_MODE; updateInterval = UPDATE_INTERVAL_IN_MILLIS_POWERSAVE_MODE;
prefs.setEnergySavingEnabledAutomatically(true);
} else { } else {
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
ExLog.i(this, "POWER-LEVELS", "in default power mode"); ExLog.i(this, "POWER-LEVELS", "in default power mode");
@@ -7,11 +7,11 @@ import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.provider.Settings; import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException; import android.provider.Settings.SettingNotFoundException;
import android.text.TextUtils;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast;
import com.sap.sailing.android.shared.logging.ExLog; import com.sap.sailing.android.shared.logging.ExLog;
import com.sap.sailing.android.shared.services.sending.MessageSendingService.APIConnectivity; import com.sap.sailing.android.shared.services.sending.MessageSendingService.APIConnectivity;
@@ -32,6 +32,8 @@ public class TrackingFragment extends BaseFragment {
private AppPreferences prefs; private AppPreferences prefs;
private long lastGPSQualityUpdate; private long lastGPSQualityUpdate;
private Toast gpsToast;
private boolean gpsFound = true;
private String TAG = TrackingFragment.class.getName(); private String TAG = TrackingFragment.class.getName();
@@ -69,6 +71,9 @@ public class TrackingFragment extends BaseFragment {
qualityIndicator.setSignalQuality(savedInstanceState.getInt(SIS_GPS_QUALITY)); qualityIndicator.setSignalQuality(savedInstanceState.getInt(SIS_GPS_QUALITY));
accuracyText.setText(savedInstanceState.getString(SIS_GPS_ACCURACY)); accuracyText.setText(savedInstanceState.getString(SIS_GPS_ACCURACY));
unsentFixesText.setText(savedInstanceState.getString(SIS_GPS_UNSENT_FIXES)); unsentFixesText.setText(savedInstanceState.getString(SIS_GPS_UNSENT_FIXES));
} else {
//initially set quality to "No GPS" on start tracking
updateTrackingStatus(GPSQuality.noSignal);
} }
} }
@@ -115,9 +120,22 @@ public class TrackingFragment extends BaseFragment {
if (quality == GPSQuality.noSignal) { if (quality == GPSQuality.noSignal) {
textView.setText(getString(R.string.tracking_status_no_gps_signal)); textView.setText(getString(R.string.tracking_status_no_gps_signal));
textView.setTextColor(getResources().getColor(R.color.sap_red)); textView.setTextColor(getResources().getColor(R.color.sap_red));
//either GPS has been found before or tracking just started (but is not available now) --> show noGPS Toast
if (gpsFound || gpsToast == null) {
gpsToast = Toast.makeText(getActivity(),getString(R.string.tracking_status_no_gps_signal), Toast.LENGTH_LONG);
gpsToast.show();
gpsFound = false;
}
} else { } else {
textView.setText(getString(R.string.tracking_status_tracking)); textView.setText(getString(R.string.tracking_status_tracking));
textView.setTextColor(getResources().getColor(R.color.fiori_text_color)); textView.setTextColor(getResources().getColor(R.color.fiori_text_color));
gpsToast.cancel();
//GPS was lost before but is available again now --> show gpsFound toast
if (!gpsFound) {
gpsToast = Toast.makeText(getActivity(),getString(R.string.tracking_status_gps_found), Toast.LENGTH_SHORT);
gpsToast.show();
gpsFound = true;
}
} }
} }
} }
@@ -134,9 +152,9 @@ public class TrackingFragment extends BaseFragment {
public void run() { public void run() {
TextView textView = (TextView) getActivity().findViewById(R.id.mode); TextView textView = (TextView) getActivity().findViewById(R.id.mode);
if (apiConnectivity == APIConnectivity.transmissionSuccess) { if (apiConnectivity == APIConnectivity.transmissionSuccess) {
if (prefs.getEnergySavingEnabledByUser()) { if (prefs.getEnergySavingEnabledAutomatically() && !prefs.getEnergySavingOverride()) {
textView.setText(getString(R.string.tracking_mode_battery_saving)); textView.setText(getString(R.string.tracking_mode_battery_saving));
textView.setTextColor(getResources().getColor(R.color.fiori_text_color)); textView.setTextColor(getResources().getColor(R.color.sap_red));
} else { } else {
textView.setText(getString(R.string.tracking_mode_live)); textView.setText(getString(R.string.tracking_mode_live));
textView.setTextColor(getResources().getColor(R.color.fiori_text_color)); textView.setTextColor(getResources().getColor(R.color.fiori_text_color));
@@ -183,7 +201,7 @@ public class TrackingFragment extends BaseFragment {
} else { } else {
locationProviders = Settings.Secure.getString(context.getContentResolver(), locationProviders = Settings.Secure.getString(context.getContentResolver(),
Settings.Secure.LOCATION_PROVIDERS_ALLOWED); Settings.Secure.LOCATION_PROVIDERS_ALLOWED);
return !TextUtils.isEmpty(locationProviders); return locationProviders.contains("gps");
} }
} else { } else {
return false; return false;
@@ -1,7 +1,9 @@
package com.sap.sailing.android.tracking.app.ui.fragments.preference; package com.sap.sailing.android.tracking.app.ui.fragments.preference;
import android.os.Bundle; import android.os.Bundle;
import android.preference.ListPreference;
import android.preference.Preference; import android.preference.Preference;
import android.preference.Preference.OnPreferenceChangeListener;
import android.preference.SwitchPreference; import android.preference.SwitchPreference;
import com.sap.sailing.android.shared.ui.fragments.preference.BasePreferenceFragment; import com.sap.sailing.android.shared.ui.fragments.preference.BasePreferenceFragment;
@@ -9,8 +11,7 @@ import com.sap.sailing.android.shared.util.UniqueDeviceUuid;
import com.sap.sailing.android.tracking.app.R; import com.sap.sailing.android.tracking.app.R;
import com.sap.sailing.android.tracking.app.utils.AppPreferences; import com.sap.sailing.android.tracking.app.utils.AppPreferences;
public class GeneralPreferenceFragment extends BasePreferenceFragment { public class GeneralPreferenceFragment extends BasePreferenceFragment implements OnPreferenceChangeListener {
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
@@ -24,23 +25,83 @@ public class GeneralPreferenceFragment extends BasePreferenceFragment {
if (currentDeviceId != null && currentDeviceId.length() > 0) { if (currentDeviceId != null && currentDeviceId.length() > 0) {
deviceIdPreference.setSummary(currentDeviceId); deviceIdPreference.setSummary(currentDeviceId);
} else { } else {
// this would, in most caes, be set elsewhere, but since we don't want to show an empty summary, // this would, in most cases, be set elsewhere, but since we don't want to show an empty summary,
// we can just set it here. (UniqueDeviceUuid.getUniqueId sets it in prefs.) // we can just set it here. (UniqueDeviceUuid.getUniqueId sets it in prefs.)
String newDeviceId = UniqueDeviceUuid.getUniqueId(getActivity()); String newDeviceId = UniqueDeviceUuid.getUniqueId(getActivity());
deviceIdPreference.setSummary(newDeviceId); deviceIdPreference.setSummary(newDeviceId);
} }
ListPreference prefSendingInterval = findPreference(R.string.preference_energy_saving_sending_interval_key);
prefSendingInterval.setDefaultValue(R.string.preference_energy_saving_sending_interval_default);
prefSendingInterval.setOnPreferenceChangeListener(this);
updateEnergySavingMessage(null);
} }
@Override @Override
public void onPause() { public void onPause() {
super.onPause(); super.onPause();
SwitchPreference prefEnergy = findPreference(R.string.preference_energy_saving_enabled_key); //SwitchPreference prefEnergy = findPreference(R.string.preference_energy_saving_enabled_key);
SwitchPreference prefDeclination = findPreference(R.string.preference_heading_with_declination_subtracted_key); SwitchPreference prefDeclination = findPreference(R.string.preference_heading_with_declination_subtracted_key);
AppPreferences prefs = new AppPreferences(getActivity()); AppPreferences prefs = new AppPreferences(getActivity());
prefs.setEnergySavingEnabledByUser(prefEnergy.isChecked()); //prefs.setEnergySavingEnabledByUser(prefEnergy.isChecked());
prefs.setDisplayHeadingWithSubtractedDeclination(prefDeclination.isChecked()); prefs.setDisplayHeadingWithSubtractedDeclination(prefDeclination.isChecked());
} }
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
AppPreferences prefs = new AppPreferences(getActivity());
//GPS message sending interval settings
if(preference.getKey().equals(getString(R.string.preference_energy_saving_sending_interval_key))) {
int msgSndInt = Integer.parseInt((String)newValue);
switch (msgSndInt){
case 0:
//send every second
prefs.setMessageResendIntervalInMillis(1000);
break;
default:
prefs.setMessageResendIntervalInMillis(msgSndInt * 1000);
break;
}
if(prefs.getEnergySavingEnabledAutomatically())
prefs.setEnergySavingOverride(true);
updateEnergySavingMessage(newValue.toString());
}
return true;
}
private void updateEnergySavingMessage(String value){
Preference msgSndIntMessage = findPreference(getString(R.string.preference_energy_saving_sending_interval_message_key));
ListPreference msgSndIntPreference = (ListPreference) findPreference(getString(R.string.preference_energy_saving_sending_interval_key));
//when value == null, (meaning the settings screen just launched) - current entry will be selected
//otherwise, we need to get the index of the newly selected entry as the one resulting from getEntry() will be the previous one
String entry;
if (value == null){
entry = msgSndIntPreference.getEntry().toString();
} else {
int indexOfEntry = msgSndIntPreference.findIndexOfValue(value);
entry = msgSndIntPreference.getEntries()[indexOfEntry].toString();
}
//possibly fill in the word "every"
String fillEvery = (entry.equals(msgSndIntPreference.getEntries()[0])) ? "" : " " + getString(R.string.preference_energy_saving_sending_interval_message_every);
//set Text
AppPreferences prefs = new AppPreferences(getActivity());
if(prefs.getEnergySavingEnabledAutomatically() && !prefs.getEnergySavingOverride()){
msgSndIntMessage.setSummary(getString(R.string.preference_energy_saving_enabled_automatically));
} else {
msgSndIntMessage.setSummary(String.format(
getString(R.string.preference_energy_saving_sending_interval_message),
fillEvery,
entry)
);
}
}
} }
@@ -112,15 +112,24 @@ public class AppPreferences extends BaseAppPreferences {
preferences.edit().putString(context.getString(R.string.preference_competitor_key), id).commit(); preferences.edit().putString(context.getString(R.string.preference_competitor_key), id).commit();
} }
public void setEnergySavingEnabledByUser(boolean newValue) { public void setEnergySavingEnabledAutomatically(boolean newValue) {
preferences.edit().putBoolean(context.getString(R.string.preference_energy_saving_enabled_key), newValue) preferences.edit().putBoolean(context.getString(R.string.preference_energy_saving_enabled_key), newValue)
.commit(); .commit();
} }
public boolean getEnergySavingEnabledByUser() { public boolean getEnergySavingEnabledAutomatically() {
return preferences.getBoolean(context.getString(R.string.preference_energy_saving_enabled_key), false); return preferences.getBoolean(context.getString(R.string.preference_energy_saving_enabled_key), false);
} }
public void setEnergySavingOverride(boolean newValue) {
preferences.edit().putBoolean(context.getString(R.string.preference_energy_saving_override_key), newValue)
.commit();
}
public boolean getEnergySavingOverride() {
return preferences.getBoolean(context.getString(R.string.preference_energy_saving_override_key), false);
}
public void setDisplayHeadingWithSubtractedDeclination(boolean newValue) { public void setDisplayHeadingWithSubtractedDeclination(boolean newValue) {
preferences.edit() preferences.edit()
.putBoolean(context.getString(R.string.preference_heading_with_declination_subtracted_key), newValue) .putBoolean(context.getString(R.string.preference_heading_with_declination_subtracted_key), newValue)
@@ -186,4 +195,5 @@ public class AppPreferences extends BaseAppPreferences {
public void removeFailedUpload(String key) { public void removeFailedUpload(String key) {
pref.edit().remove(key).commit(); pref.edit().remove(key).commit();
} }
} }
@@ -9,6 +9,7 @@
<string name="reset_position">Position zurücksetzen</string> <string name="reset_position">Position zurücksetzen</string>
<string name="getting_marks">Empfangen der Markierungen&#8230;</string> <string name="getting_marks">Empfangen der Markierungen&#8230;</string>
<string name="set">Gesetzt</string> <string name="set">Gesetzt</string>
<string name="unknown">Unbekannt</string>
<string name="not_set">Nicht gesetzt</string> <string name="not_set">Nicht gesetzt</string>
<string name="mark_list_accuracy">(Genauigkeit ca. %1$sm)</string> <string name="mark_list_accuracy">(Genauigkeit ca. %1$sm)</string>
<string name="accuracy">GPS Genauigkeit</string> <string name="accuracy">GPS Genauigkeit</string>
@@ -12,6 +12,7 @@
<string name="reset_position">Reset Position</string> <string name="reset_position">Reset Position</string>
<string name="getting_marks">Getting race marks&#8230;</string> <string name="getting_marks">Getting race marks&#8230;</string>
<string name="set">set</string> <string name="set">set</string>
<string name="unknown">n/a</string>
<string name="not_set">not set</string> <string name="not_set">not set</string>
<string name="mark_list_accuracy">(Accurary ca. %1$sm)</string> <string name="mark_list_accuracy">(Accurary ca. %1$sm)</string>
<string name="buoy_detail_accuracy_ca" translatable="false">ca. %1$.02fm</string> <string name="buoy_detail_accuracy_ca" translatable="false">ca. %1$.02fm</string>
@@ -33,8 +33,16 @@ public class MarkAdapter extends ResourceCursorAdapter {
} else { } else {
final DecimalFormat df = new DecimalFormat("#.##"); final DecimalFormat df = new DecimalFormat("#.##");
final double accuracy = markPings.get(0).getAccuracy(); final double accuracy = markPings.get(0).getAccuracy();
final String accuracyString = String.format(context.getString(R.string.mark_list_accuracy), df.format(accuracy)); String accuracyString= "";
setText = context.getString(R.string.set) + " " + accuracyString; //accuracy-values that are stored as -1 will be displayed as "n/a"
if(accuracy == MarkPingInfo.NOT_SET_BY_USER){
accuracyString = context.getString(R.string.unknown);
}
else {
accuracyString = context.getString(R.string.set) + " " +
String.format(context.getString(R.string.mark_list_accuracy), df.format(accuracy));
}
setText = accuracyString;
} }
markName.setText(name); markName.setText(name);
markSet.setText(setText); markSet.setText(setText);
@@ -55,7 +55,6 @@ public class BuoyFragment extends BaseFragment
private static final String TAG = BuoyFragment.class.getName(); private static final String TAG = BuoyFragment.class.getName();
private static final int GPS_MIN_TIME = 1000; private static final int GPS_MIN_TIME = 1000;
private static final String N_A = "n/a";
private static final int REQUEST_CHECK_SETTINGS = 1 << 16; private static final int REQUEST_CHECK_SETTINGS = 1 << 16;
private TextView accuracyTextView; private TextView accuracyTextView;
@@ -230,8 +229,8 @@ public class BuoyFragment extends BaseFragment
} }
private void setUpTextUI(Location location) { private void setUpTextUI(Location location) {
String accuracyText = N_A; String accuracyText = this.getString(R.string.unknown);
String distanceText = N_A; String distanceText = this.getString(R.string.unknown);
if (location != null) { if (location != null) {
accuracyText = getString(R.string.buoy_detail_accuracy_ca, location.getAccuracy()); accuracyText = getString(R.string.buoy_detail_accuracy_ca, location.getAccuracy());
MarkPingInfo markPing = positioningActivity.getMarkPing(); MarkPingInfo markPing = positioningActivity.getMarkPing();
@@ -189,8 +189,17 @@ public class CheckinManager {
org.json.simple.JSONObject simplePosition; org.json.simple.JSONObject simplePosition;
simplePosition = JsonHelper.convertToSimple(positionJson); simplePosition = JsonHelper.convertToSimple(positionJson);
GPSFix gpsFix = deserializer.deserialize(simplePosition); GPSFix gpsFix = deserializer.deserialize(simplePosition);
MarkPingInfo ping = new MarkPingInfo(mark.getId(), gpsFix, positionJson.getDouble(FlatGPSFixJsonSerializer.FIELD_ACCURACY)); //accepts JSON messages without accuracy and with, without will be displayed as "n/a"
MarkPingInfo ping;
if (positionJson.has(FlatGPSFixJsonSerializer.FIELD_ACCURACY)){
ping = new MarkPingInfo(mark.getId(), gpsFix, positionJson.getDouble(FlatGPSFixJsonSerializer.FIELD_ACCURACY));
}
else {
ping = new MarkPingInfo(mark.getId(), gpsFix, MarkPingInfo.NOT_SET_BY_USER);
}
if (ping != null){
pings.add(ping); pings.add(ping);
}
} }
} }
marks.add(mark); marks.add(mark);
@@ -8,6 +8,8 @@ public class MarkPingInfo {
private final Serializable markId; private final Serializable markId;
private final GPSFix gpsFix; private final GPSFix gpsFix;
private final double accuracy; private final double accuracy;
public static final int NOT_SET_BY_USER = -1; // this is used for accuracies that are displayed as "n/a"
public MarkPingInfo(Serializable markId, GPSFix gpsFix, double accuracy) { public MarkPingInfo(Serializable markId, GPSFix gpsFix, double accuracy) {
super(); super();
+6
View File
@@ -397,6 +397,12 @@ cp -rf ~/servers/server/logs/* /var/log/old/<event-name>/<instance-public-ipv4>/
- Or you manually force a logrotate run with `logrotate --force /etc/logrotate.d/httpd`, which copies `/var/log/httpd/` towards `/var/log/old/<event-name>/<instance-public-ipv4>` - Or you manually force a logrotate run with `logrotate --force /etc/logrotate.d/httpd`, which copies `/var/log/httpd/` towards `/var/log/old/<event-name>/<instance-public-ipv4>`
- Once all this is done, you can go ahead and terminate the instance via AWS - Once all this is done, you can go ahead and terminate the instance via AWS
### Comparing Server Content after Master Data Import
The script ``java/target/compareServers`` helps comparing server content after master data import. Run with two server URLs you want to compare, ideally in an empty directory where file downloads can be stored. Run initially with the ``-elv`` option to get verbose output. Make sure you have your ``http_proxy`` and ``https_proxy`` environment variables set or unset, depending on your network environment. Should the initial comparison fail, analyze the differences and continue by using ``-cel`` as command line arguments, telling the script to continue where it left off, exiting when hitting a difference and listing the leaderboard groups currently being compared. Repeat until done.
Should you want to compare servers of which you know they have different sets of leaderboard groups, start with ``compareServers -elv`` and then manually adjust the ``leaderboardgroups.new.sed`` and ``leaderboardgroups.old.sed`` files according to your needs, then continue with the ``-cel`` switches to restrict comparisons to what you left in the ``leaderboardgroups.*.sed`` files.
## Migration Checklist ## Migration Checklist
### Before switching sapsailing.com to the EC2 webserver ### Before switching sapsailing.com to the EC2 webserver
Loaded 100 of 105 files, more files were not shown because too many files have changed in this diff. Show more