Merge remote-tracking branch 'origin/master' into maneuver_refactoring

This commit is contained in:
Vladislav Chumak committed 2018-08-16 22:18:11 +02:00
commit 1334e2fbb9
99 files changed
+1225 -628

No files matched your search

+122
View File
@@ -0,0 +1,122 @@
#!/bin/sh
# This is a template for creating backup of data and persist them
# to a central backup server. Make sure to adapt this script to your needs.
#
# Please do not change the name of the bakup (bup save -n) because that can
# have strange effects. If you change it you absolutely need to make sure that
# only the same root directories are associated to that name. It is NOT possible
# to backup different root directories to the same backup name.
#
# Maintainer: simon.marcel.pamies@sap.com, axel.uhl@sap.com
# Directories to backup
BACKUP_DIRECTORIES="/etc /var/log/mongodb"
# Prefix for backup - set that to the hostname of your server
# Make sure to change this!
PREFIX="database"
# Directory for temporary files
TARGET_DIR=/var/lib/mysql/backup
# Configuration for MongoDB backup
MONGO_PORTS="10202 10201"
MONGO_INSTALLATION=/opt/mongodb-linux-x86_64-2.6.7
MONGO_CMD=$MONGO_INSTALLATION/bin/mongo
MONGOEXPORT_CMD=$MONGO_INSTALLATION/bin/mongoexport
MONGODB_COLLECTION_WIND="WIND_TRACKS"
MONGODB_COLLECTION_LOCAL="local"
# Configuration for MySQL
MYSQL_DATABASES="bugs mysql"
MYSQLEXPORT_CMD="mysqldump -u root --password=sailaway"
# Configuration for external backup server - needs a ssh key
BACKUP_SERVER="backup@172.31.25.136"
# Set date for this backup - this makes it possible to restore
# files from different branches (named backups)
BACKUP_DATE=`date +%s`
# Aliases
BUP_CMD="/opt/bup/bup"
BUP_ADDITIONAL="-r $BACKUP_SERVER:/home/backup/$PREFIX"
BUP_IGNORES='--exclude-rx=/war/$'
BUP_CMD_INDEX="$BUP_CMD index $BUP_IGNORES"
BUP_CMD_SAVE="$BUP_CMD save --date=$BACKUP_DATE $BUP_ADDITIONAL"
PARAM=$@
# Make sure to install the right libraries
yum install python-devel fuse-python pyxattr pylibacl perl-Time-HiRes
# We need to set HOME in case it isn't set right - assuming root is calling
export HOME=/root
# Make sure to init local and remote repository
# It is perfectly safe to reinitialize a repository w/o overwriting data
$BUP_CMD init
ssh $BACKUP_SERVER "/opt/bup/bup init -r /home/backup/$PREFIX"
# Backup general directories
for OTHER_DIR in $BACKUP_DIRECTORIES; do
NORMALIZED_DIR_NAME=${OTHER_DIR//\//-}
echo "Backing up $OTHER_DIR"
$BUP_CMD_INDEX $OTHER_DIR
$BUP_CMD_SAVE -n dir$NORMALIZED_DIR_NAME $OTHER_DIR
# no need to remove any backup files here
done
# **************************************
# Start of database specific backup
# **************************************
# Export MongoDB instances with wind upon request (weekly parameter provided)
for MONGO_PORT in $MONGO_PORTS; do
echo Backing up MongoDB running on port $MONGO_PORT
MONGODB_DATABASES=`echo "show dbs" | $MONGO_CMD --port $MONGO_PORT | tail -n +3 | grep -v "^bye$" | awk '{print $1;}'`
for i in $MONGODB_DATABASES; do
echo " Backing up database $i in MongoDB running on port $MONGO_PORT"
MONGODB_COLLECTIONS=`echo "use $i
show collections" | $MONGO_CMD --port $MONGO_PORT | tail -n +4 | grep -v "^system.indexes$" | grep -v "^bye$"`
echo "Collections in MongoDB $i running on port $MONGO_PORT: $MONGODB_COLLECTIONS"
for j in $MONGODB_COLLECTIONS; do
if [ "$j" = "$MONGODB_COLLECTION_WIND" ]; then
WIND_SUFFIX="-wind"
else
WIND_SUFFIX=
fi
if [ "$i" != "winddb" ]; then
DB_DIR=$TARGET_DIR/mongodb-$MONGO_PORT-$i$WIND_SUFFIX
else
DB_DIR=$TARGET_DIR/mongodb-$MONGO_PORT$WIND_SUFFIX
fi
if [ "$i" != "$MONGODB_COLLECTION_LOCAL" -a \( "$i" != "$MONGODB_COLLECTION_WIND" -o "$PARAM" = "weekly" \) ]; then
echo " Backing up collection $j in MongoDB $i running on port $MONGO_PORT"
echo "$MONGOEXPORT_CMD --port $MONGO_PORT -d $i -c $j > $DB_DIR/$j.json"
mkdir -p $DB_DIR
$MONGOEXPORT_CMD --port $MONGO_PORT -d $i -c $j > $DB_DIR/$j.json
fi
done
done
done
# Export MySQL dump for selected databases
for MYSQL_DB in $MYSQL_DATABASES; do
mkdir $TARGET_DIR/mysql
echo "$MYSQLEXPORT_CMD $MYSQL_DB > $TARGET_DIR/mysql/$MYSQL_DB.sql"
$MYSQLEXPORT_CMD $MYSQL_DB > $TARGET_DIR/mysql/$MYSQL_DB.sql
done
# Backup the whole thing now and push to external server
$BUP_CMD_INDEX $TARGET_DIR
$BUP_CMD_SAVE -n databases $TARGET_DIR/
# clean up
find $TARGET_DIR -name *.json | xargs rm -f
rm -f $TARGET_DIR/mysql/*.sql
# **************************************
# End of database specific backup
# **************************************
-149
View File
@@ -1,149 +0,0 @@
#!/bin/sh
# This is a template for creating backup of data and persist them
# to a central backup server. Make sure to adapt this script to your needs.
#
# Please do not change the name of the bakup (bup save -n) because that can
# have strange effects. If you change it you absolutely need to make sure that
# only the same root directories are associated to that name. It is NOT possible
# to backup different root directories to the same backup name.
#
# Maintainer: simon.marcel.pamies@sap.com
#
# ######################################################################
# CHANGELOG
#
# 2016-09-13 (Steffen Wagner):
# - extracting all databases from a mongo connection instead of defining
# the exportable databases manually
# - made databases excludeable (by regex and name)
#
# Directories to backup
BACKUP_DIRECTORIES="/etc /var/log/mongodb"
# Prefix for backup - set that to the hostname of your server
# Make sure to change this!
PREFIX="database"
# Directory for temporary files
TARGET_DIR=/var/lib/mysql/backup
# Configuration for MongoDB backup
MONGO_CONNECTIONS="localhost:10201 localhost:10202"
MONGOEXPORT_CMD="/opt/mongodb-linux-x86_64-1.8.1/bin/mongoexport"
MONGODB_COLLECTIONS_NO_WIND="ActiveFileStorageService COMMAND_MESSAGES COMPETITORS CONFIGURATIONS EVENTS FileStorageServiceProperties GPS_FIXES GPS_FIXES_METADATA IGTIMI_ACCESS_TOKENS LAST_MESSAGE_COUNT LEADERBOARDS LEADERBOARD_GROUPS LEADERBOARD_GROUP_LINKS_FOR_EVENTS PREFERENCES RACES_MESSAGES RACE_LOGS REGATTAS REGATTA_FOR_RACE_ID REGATTA_LOGS RESULT_URLS SAILING_SERVERS SETTINGS SWISSTIMING_ARCHIVE_CONFIGURATIONS SWISSTIMING_CONFIGURATIONS TRACTRAC_CONFIGURATIONS USERS VIDEOS"
MONGODB_COLLECTIONS_WIND="WIND_TRACKS"
MONGODB_EXCLUDEDB="10201:LOAD-TEST 10201:winddb 10201:live 10201:local 10201:admin 10202:DNSTEST2 10202:imagetest 10202:live 10202:replication-test-master 10202:load-test 10202:admin 10202:dev 10202:dev-2010 10202:BYC-2010 10202:sapsailinganalytics-dev 10202:test"
# Configuration for MySQL
MYSQL_DATABASES="bugs mysql"
MYSQLEXPORT_CMD="mysqldump -u root --password=sailaway"
# Configuration for external backup server - needs a ssh key
BACKUP_SERVER="backup@172.31.25.136"
# Set date for this backup - this makes it possible to restore
# files from different branches (named backups)
BACKUP_DATE=`date +%s`
# Aliases
BUP_CMD="/opt/bup/bup"
BUP_ADDITIONAL="-r $BACKUP_SERVER:/home/backup/$PREFIX"
BUP_IGNORES='--exclude-rx=/war/$'
BUP_CMD_INDEX="$BUP_CMD index $BUP_IGNORES"
BUP_CMD_SAVE="$BUP_CMD save --date=$BACKUP_DATE $BUP_ADDITIONAL"
PARAM=$@
# Make sure to install the right libraries
yum install python-devel fuse-python pyxattr pylibacl perl-Time-HiRes
# We need to set HOME in case it isn't set right - assuming root is calling
export HOME=/root
# Make sure to init local and remote repository
# It is perfectly safe to reinitialize a repository w/o overwriting data
$BUP_CMD init
ssh $BACKUP_SERVER "/opt/bup/bup init -r /home/backup/$PREFIX"
# Backup general directories
for OTHER_DIR in $BACKUP_DIRECTORIES; do
NORMALIZED_DIR_NAME=${OTHER_DIR//\//-}
echo "Backing up $OTHER_DIR"
$BUP_CMD_INDEX $OTHER_DIR
$BUP_CMD_SAVE -n dir$NORMALIZED_DIR_NAME $OTHER_DIR
# no need to remove any backup files here
done
# **************************************
# Start of database backups
# **************************************
# Backup all databases from all mongo connections
for MONGO_DB in $MONGO_CONNECTIONS; do
CONNECTION=(${MONGO_DB//:/ })
DATABASES_UNFORMATTED=($(mongo --host ${CONNECTION[0]} --port ${CONNECTION[1]} --quiet --eval "JSON.stringify(db.adminCommand('listDatabases'))"))
DATABASES_FORMATTED=`echo ${DATABASES_UNFORMATTED} | jq -r --arg port ${CONNECTION[1]} '.databases| map($port + ":" + .name)|@csv' | tr -d '"' | tr ',' ' '`
# Remove all explicit excluded databases
LIST_DATABASES_FORMATTED=( $DATABASES_FORMATTED )
LIST_MONGODB_EXCLUDEDB=( $MONGODB_EXCLUDEDB )
DATABASES_EXCLUDED=`echo ${LIST_DATABASES_FORMATTED[@]} ${LIST_MONGODB_EXCLUDEDB[@]} | tr ' ' '\n' | sort | uniq -u`
# Remove all regex excluded databases
DATABASES_FINAL=`echo ${DATABASES_EXCLUDED} | sed -r 's/[0-9]{5}:replica[0-9]+//gi'`
# Export MongoDB instances with wind upon request (weekly parameter provided)
if [ "$PARAM" = "weekly" ]; then
for MONGO_CONFIG in $DATABASES_FINAL; do
PARTS=(${MONGO_CONFIG//:/ })
if [ "${PARTS[1]}" != "winddb" ]; then
mkdir $TARGET_DIR/mongodb-${PARTS[0]}-${PARTS[1]}-wind
DB_SUFFIX=${PARTS[0]}-${PARTS[1]}-wind
else
mkdir $TARGET_DIR/mongodb-${PARTS[0]}-wind
DB_SUFFIX=${PARTS[0]}-wind
fi
for COLLECTION in $MONGODB_COLLECTIONS_WIND; do
echo "$MONGOEXPORT_CMD --port ${PARTS[0]} -d ${PARTS[1]} -c $COLLECTION > $TARGET_DIR/mongodb-$DB_SUFFIX/$COLLECTION.json"
$MONGOEXPORT_CMD --port ${PARTS[0]} -d ${PARTS[1]} -c $COLLECTION > $TARGET_DIR/mongodb-$DB_SUFFIX/$COLLECTION.json
done
done
fi
# Export MongoDB instances without wind
for MONGO_CONFIG in $DATABASES_FINAL; do
PARTS=(${MONGO_CONFIG//:/ })
if [ "${PARTS[1]}" != "winddb" ]; then
mkdir $TARGET_DIR/mongodb-${PARTS[0]}-${PARTS[1]}
DB_SUFFIX=${PARTS[0]}-${PARTS[1]}
else
mkdir $TARGET_DIR/mongodb-${PARTS[0]}
DB_SUFFIX=${PARTS[0]}
fi
for COLLECTION in $MONGODB_COLLECTIONS_NO_WIND; do
echo "$MONGOEXPORT_CMD --port ${PARTS[0]} -d ${PARTS[1]} -c $COLLECTION > $TARGET_DIR/mongodb-$DB_SUFFIX/$COLLECTION.json"
$MONGOEXPORT_CMD --port ${PARTS[0]} -d ${PARTS[1]} -c $COLLECTION > $TARGET_DIR/mongodb-$DB_SUFFIX/$COLLECTION.json
done
done
done
# Export MySQL dump for selected databases
for MYSQL_DB in $MYSQL_DATABASES; do
mkdir $TARGET_DIR/mysql
echo "$MYSQLEXPORT_CMD $MYSQL_DB > $TARGET_DIR/mysql/$MYSQL_DB.sql"
$MYSQLEXPORT_CMD $MYSQL_DB > $TARGET_DIR/mysql/$MYSQL_DB.sql
done
# Backup the whole thing now and push to external server
$BUP_CMD_INDEX $TARGET_DIR
$BUP_CMD_SAVE -n databases $TARGET_DIR/
# clean up
find $TARGET_DIR -name *.json | xargs rm -f
rm -f $TARGET_DIR/mysql/*.sql
# **************************************
# End of database specific backup
# **************************************
+3
View File
@@ -634,6 +634,9 @@ if [[ "$@" == "build" ]] || [[ "$@" == "all" ]]; then
SDK_MANAGER="$ANDROID_HOME/tools/bin/sdkmanager.bat"
fi
# Uncomment the following line for testing an artifact stages in the SAP-central Nexus system:
# mobile_extra="-P -with-not-android-relevant -P with-mobile -P use-staged-third-party-artifacts -Dmaven.repo.local=${TMP}/temp_maven_repo"
# Use the following line for regular builds with no staged Nexus artifacts:
mobile_extra="-P -with-not-android-relevant -P with-mobile"
if [ $testing -eq 0 ]; then
+1 -1
View File
@@ -3,7 +3,7 @@
# Executes the following script on the DB server:
##!/bin/sh
#useremail=$1
#for i in `echo "show dbs" | /opt/mongodb-linux-x86_64-2.6.7/bin/mongo --port 10202 | grep -v "^bye$" | awk '{print $1;'}`; do
#for i in `echo "show dbs" | /opt/mongodb-linux-x86_64-2.6.7/bin/mongo --port 10202 | tail -n +3 | grep -v "^bye$" | awk '{print $1;}'`; do
# match=`echo "db.USERS.find({EMAIL: '$useremail'}); db.COMPETITORS.find({email: '$useremail'})" | /opt/mongodb-linux-x86_64-2.6.7/bin/mongo --quiet --port 10202 $i`
# if [ "$match" != "" ]; then
# echo ${i}: $match
View File
File mode changed.
+16
View File
@@ -33,6 +33,22 @@
</parameters.integration-tests>
</properties>
</profile>
<profile>
<id>use-staged-third-party-artifacts</id>
<pluginRepositories>
<pluginRepository>
<id>staged-third-party-artifacts</id>
<url>http://nexus.wdf.sap.corp:8081/nexus/content/repositories/3rd-party.releases.manual-uploads.all-closed-stages/</url>
</pluginRepository>
</pluginRepositories>
<repositories>
<repository>
<id>staged-third-party-artifacts</id>
<url>http://nexus.wdf.sap.corp:8081/nexus/content/repositories/3rd-party.releases.manual-uploads.all-closed-stages/</url>
</repository>
</repositories>
</profile>
</profiles>
</settings>
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry exported="true" kind="lib" path="lib/gwt-servlet.jar" sourcepath="C:/data/java/gwt-2.8.2/gwt-user.jar"/>
<classpathentry exported="true" kind="lib" path="lib/gwt-servlet.jar" sourcepath="/com.google.gwt.user/lib/gwt-user.jar"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7"/>
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
<classpathentry kind="lib" path="lib/gwt-servlet-deps.jar"/>
@@ -1,47 +1,49 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<launchConfiguration type="com.gwtplugins.gdt.eclipse.suite.webapp">
<booleanAttribute key="com.gwtplugins.gdt.eclipse.core.RUN_SERVER" value="false"/>
<stringAttribute key="com.gwtplugins.gdt.eclipse.suiteMainTypeProcessor.PREVIOUSLY_SET_MAIN_TYPE_NAME" value="com.google.gwt.dev.DevMode"/>
<booleanAttribute key="com.gwtplugins.gdt.eclipse.suiteWarArgumentProcessor.IS_WAR_FROM_PROJECT_PROPERTIES" value="true"/>
<stringAttribute key="com.gwtplugins.gwt.eclipse.core.CLASSIC_DEVMODE_CODE_SERVER_PORT" value="9875"/>
<listAttribute key="com.gwtplugins.gwt.eclipse.core.ENTRY_POINT_MODULES">
<listEntry value="com.sap.sailing.dashboards.gwt.RibDashboard"/>
</listAttribute>
<stringAttribute key="com.gwtplugins.gwt.eclipse.core.URL" value="/security/ui/Login.html"/>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
<listEntry value="/com.sap.sailing.dashboards.gwt"/>
</listAttribute>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
<listEntry value="4"/>
</listAttribute>
<listAttribute key="org.eclipse.debug.ui.favoriteGroups">
<listEntry value="org.eclipse.debug.ui.launchGroup.debug"/>
<listEntry value="org.eclipse.debug.ui.launchGroup.run"/>
</listAttribute>
<booleanAttribute key="org.eclipse.jdt.debug.ui.CONSIDER_INHERITED_MAIN" value="true"/>
<booleanAttribute key="org.eclipse.jdt.debug.ui.INCLUDE_EXTERNAL_JARS" value="true"/>
<booleanAttribute key="org.eclipse.jdt.launching.ATTR_USE_START_ON_FIRST_THREAD" value="false"/>
<listAttribute key="org.eclipse.jdt.launching.CLASSPATH">
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry containerPath=&quot;org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8&quot; javaProject=&quot;com.sap.sailing.gwt.ui&quot; path=&quot;1&quot; type=&quot;4&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.gwt.ui/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.gwt.ui/src/main/java&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.dashboards.gwt/src/main/java&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.server/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.gwt/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.common/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry id=&quot;org.eclipse.jdt.launching.classpathentry.defaultClasspath&quot;&gt;&#13;&#10; &lt;memento exportedEntriesOnly=&quot;false&quot; project=&quot;com.sap.sailing.dashboards.gwt&quot;/&gt;&#13;&#10;&lt;/runtimeClasspathEntry&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.common/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/org.moxieapps.gwt.highcharts/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.gwt.adminconsole/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.expeditionconnector/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.expeditionconnector.common/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
</listAttribute>
<stringAttribute key="org.eclipse.jdt.launching.CLASSPATH_PROVIDER" value="com.gwtplugins.gwt.eclipse.core.moduleClasspathProvider"/>
<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/>
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.google.gwt.dev.DevMode"/>
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-nosuperDevMode -startupUrl /security/ui/Login.html -logLevel INFO -noserver -incremental -war &quot;${project_loc:com.sap.sailing.dashboards.gwt}&quot; -remoteUI &quot;${gwt_remote_ui_server_port}:${unique_id}&quot; -codeServerPort 9875 -startupUrl /dashboards/RibDashboard.html com.sap.sailing.dashboards.gwt.RibDashboard"/>
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="com.sap.sailing.dashboards.gwt"/>
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-XX:+UseG1GC -XX:+UseStringDeduplication -Dgwt.watchFileChanges=false -Xmx2048m -Dgwt-usearchives=false -Dgwt.persistentunitcache=false"/>
<stringAttribute key="org.eclipse.jdt.launching.WORKING_DIRECTORY" value="${project_loc:com.sap.sailing.gwt.ui}/.tmp/gwt-work"/>
<booleanAttribute key="com.gwtplugins.gdt.eclipse.core.RUN_SERVER" value="false"/>
<stringAttribute key="com.gwtplugins.gdt.eclipse.suiteMainTypeProcessor.PREVIOUSLY_SET_MAIN_TYPE_NAME" value="com.google.gwt.dev.DevMode"/>
<booleanAttribute key="com.gwtplugins.gdt.eclipse.suiteWarArgumentProcessor.IS_WAR_FROM_PROJECT_PROPERTIES" value="true"/>
<stringAttribute key="com.gwtplugins.gwt.eclipse.core.CLASSIC_DEVMODE_CODE_SERVER_PORT" value="9875"/>
<listAttribute key="com.gwtplugins.gwt.eclipse.core.ENTRY_POINT_MODULES">
<listEntry value="com.sap.sailing.dashboards.gwt.RibDashboard"/>
</listAttribute>
<stringAttribute key="com.gwtplugins.gwt.eclipse.core.URL" value="/dashboards/RibDashboard.html"/>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
<listEntry value="/com.sap.sailing.dashboards.gwt"/>
</listAttribute>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
<listEntry value="4"/>
</listAttribute>
<listAttribute key="org.eclipse.debug.ui.favoriteGroups">
<listEntry value="org.eclipse.debug.ui.launchGroup.debug"/>
<listEntry value="org.eclipse.debug.ui.launchGroup.run"/>
</listAttribute>
<booleanAttribute key="org.eclipse.jdt.debug.ui.CONSIDER_INHERITED_MAIN" value="true"/>
<booleanAttribute key="org.eclipse.jdt.debug.ui.INCLUDE_EXTERNAL_JARS" value="true"/>
<booleanAttribute key="org.eclipse.jdt.launching.ATTR_USE_START_ON_FIRST_THREAD" value="false"/>
<listAttribute key="org.eclipse.jdt.launching.CLASSPATH">
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry containerPath=&quot;org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8&quot; javaProject=&quot;com.sap.sailing.gwt.ui&quot; path=&quot;1&quot; type=&quot;4&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry id=&quot;org.eclipse.jdt.launching.classpathentry.defaultClasspath&quot;&gt;&#13;&#10; &lt;memento exportedEntriesOnly=&quot;false&quot; project=&quot;com.sap.sailing.dashboards.gwt&quot;/&gt;&#13;&#10;&lt;/runtimeClasspathEntry&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.dashboards.gwt/src/main/java&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.gwt.ui/src/main/java&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.gwt.ui/src/main/resources&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.server/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.gwt/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.gwt/resources&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.common/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.common/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/org.moxieapps.gwt.highcharts/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.gwt.adminconsole/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.shared.android/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.expeditionconnector.common/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
</listAttribute>
<stringAttribute key="org.eclipse.jdt.launching.CLASSPATH_PROVIDER" value="com.gwtplugins.gwt.eclipse.core.moduleClasspathProvider"/>
<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/>
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.google.gwt.dev.DevMode"/>
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-nosuperDevMode -logLevel INFO -noserver -incremental -war &quot;${project_loc:com.sap.sailing.dashboards.gwt}&quot; -remoteUI &quot;${gwt_remote_ui_server_port}:${unique_id}&quot; -codeServerPort 9875 -startupUrl /dashboards/RibDashboard.html com.sap.sailing.dashboards.gwt.RibDashboard"/>
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="com.sap.sailing.dashboards.gwt"/>
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-XX:+UseG1GC -XX:+UseStringDeduplication -Dgwt.watchFileChanges=false -Xmx2048m -Dgwt-usearchives=false -Dgwt.persistentunitcache=false"/>
<stringAttribute key="org.eclipse.jdt.launching.WORKING_DIRECTORY" value="${project_loc:com.sap.sailing.gwt.ui}/.tmp/gwt-work"/>
</launchConfiguration>
@@ -1,48 +1,50 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<launchConfiguration type="com.gwtplugins.gdt.eclipse.suite.webapp">
<booleanAttribute key="com.gwtplugins.gdt.eclipse.core.RUN_SERVER" value="false"/>
<stringAttribute key="com.gwtplugins.gdt.eclipse.suiteMainTypeProcessor.PREVIOUSLY_SET_MAIN_TYPE_NAME" value="com.google.gwt.dev.DevMode"/>
<booleanAttribute key="com.gwtplugins.gdt.eclipse.suiteWarArgumentProcessor.IS_WAR_FROM_PROJECT_PROPERTIES" value="true"/>
<stringAttribute key="com.gwtplugins.gwt.eclipse.core.CLASSIC_DEVMODE_CODE_SERVER_PORT" value="9875"/>
<listAttribute key="com.gwtplugins.gwt.eclipse.core.ENTRY_POINT_MODULES">
<listEntry value="com.sap.sailing.dashboards.gwt.RibDashboard"/>
</listAttribute>
<booleanAttribute key="com.gwtplugins.gwt.eclipse.core.SUPERDEVMODE_ENABLED" value="true"/>
<stringAttribute key="com.gwtplugins.gwt.eclipse.core.URL" value="/security/ui/Login.html"/>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
<listEntry value="/com.sap.sailing.dashboards.gwt"/>
</listAttribute>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
<listEntry value="4"/>
</listAttribute>
<listAttribute key="org.eclipse.debug.ui.favoriteGroups">
<listEntry value="org.eclipse.debug.ui.launchGroup.debug"/>
<listEntry value="org.eclipse.debug.ui.launchGroup.run"/>
</listAttribute>
<booleanAttribute key="org.eclipse.jdt.debug.ui.CONSIDER_INHERITED_MAIN" value="true"/>
<booleanAttribute key="org.eclipse.jdt.debug.ui.INCLUDE_EXTERNAL_JARS" value="true"/>
<booleanAttribute key="org.eclipse.jdt.launching.ATTR_USE_START_ON_FIRST_THREAD" value="false"/>
<listAttribute key="org.eclipse.jdt.launching.CLASSPATH">
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry containerPath=&quot;org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8&quot; javaProject=&quot;com.sap.sailing.gwt.ui&quot; path=&quot;1&quot; type=&quot;4&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.gwt.ui/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.gwt.ui/src/main/java&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.dashboards.gwt/src/main/java&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.server/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.gwt/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.common/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry id=&quot;org.eclipse.jdt.launching.classpathentry.defaultClasspath&quot;&gt;&#13;&#10; &lt;memento exportedEntriesOnly=&quot;false&quot; project=&quot;com.sap.sailing.dashboards.gwt&quot;/&gt;&#13;&#10;&lt;/runtimeClasspathEntry&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.common/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/org.moxieapps.gwt.highcharts/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.gwt.adminconsole/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.expeditionconnector/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.expeditionconnector.common/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
</listAttribute>
<stringAttribute key="org.eclipse.jdt.launching.CLASSPATH_PROVIDER" value="com.gwtplugins.gwt.eclipse.core.moduleClasspathProvider"/>
<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/>
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.google.gwt.dev.DevMode"/>
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-startupUrl /security/ui/Login.html -logLevel INFO -noserver -incremental -war &quot;${project_loc:com.sap.sailing.dashboards.gwt}&quot; -remoteUI &quot;${gwt_remote_ui_server_port}:${unique_id}&quot; -codeServerPort 9875 -startupUrl /dashboards/RibDashboard.html com.sap.sailing.dashboards.gwt.RibDashboard"/>
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="com.sap.sailing.dashboards.gwt"/>
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-XX:+UseG1GC -XX:+UseStringDeduplication -Dgwt.watchFileChanges=false -Xmx2048m -Dgwt-usearchives=false -Dgwt.persistentunitcache=false"/>
<stringAttribute key="org.eclipse.jdt.launching.WORKING_DIRECTORY" value="${project_loc:com.sap.sailing.gwt.ui}/.tmp/gwt-work"/>
<booleanAttribute key="com.gwtplugins.gdt.eclipse.core.RUN_SERVER" value="false"/>
<stringAttribute key="com.gwtplugins.gdt.eclipse.suiteMainTypeProcessor.PREVIOUSLY_SET_MAIN_TYPE_NAME" value="com.google.gwt.dev.DevMode"/>
<booleanAttribute key="com.gwtplugins.gdt.eclipse.suiteWarArgumentProcessor.IS_WAR_FROM_PROJECT_PROPERTIES" value="true"/>
<stringAttribute key="com.gwtplugins.gwt.eclipse.core.CLASSIC_DEVMODE_CODE_SERVER_PORT" value="9875"/>
<listAttribute key="com.gwtplugins.gwt.eclipse.core.ENTRY_POINT_MODULES">
<listEntry value="com.sap.sailing.dashboards.gwt.RibDashboard"/>
</listAttribute>
<booleanAttribute key="com.gwtplugins.gwt.eclipse.core.SUPERDEVMODE_ENABLED" value="true"/>
<stringAttribute key="com.gwtplugins.gwt.eclipse.core.URL" value="/dashboards/RibDashboard.html"/>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
<listEntry value="/com.sap.sailing.dashboards.gwt"/>
</listAttribute>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
<listEntry value="4"/>
</listAttribute>
<listAttribute key="org.eclipse.debug.ui.favoriteGroups">
<listEntry value="org.eclipse.debug.ui.launchGroup.debug"/>
<listEntry value="org.eclipse.debug.ui.launchGroup.run"/>
</listAttribute>
<booleanAttribute key="org.eclipse.jdt.debug.ui.CONSIDER_INHERITED_MAIN" value="true"/>
<booleanAttribute key="org.eclipse.jdt.debug.ui.INCLUDE_EXTERNAL_JARS" value="true"/>
<booleanAttribute key="org.eclipse.jdt.launching.ATTR_USE_START_ON_FIRST_THREAD" value="false"/>
<listAttribute key="org.eclipse.jdt.launching.CLASSPATH">
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry containerPath=&quot;org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8&quot; javaProject=&quot;com.sap.sailing.gwt.ui&quot; path=&quot;1&quot; type=&quot;4&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry id=&quot;org.eclipse.jdt.launching.classpathentry.defaultClasspath&quot;&gt;&#13;&#10; &lt;memento exportedEntriesOnly=&quot;false&quot; project=&quot;com.sap.sailing.dashboards.gwt&quot;/&gt;&#13;&#10;&lt;/runtimeClasspathEntry&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.dashboards.gwt/src/main/java&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.gwt.ui/src/main/java&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.gwt.ui/src/main/resources&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.server/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.gwt/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.gwt/resources&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.common/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.common/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/org.moxieapps.gwt.highcharts/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.gwt.adminconsole/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.shared.android/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.expeditionconnector.common/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
</listAttribute>
<stringAttribute key="org.eclipse.jdt.launching.CLASSPATH_PROVIDER" value="com.gwtplugins.gwt.eclipse.core.moduleClasspathProvider"/>
<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/>
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.google.gwt.dev.DevMode"/>
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-logLevel INFO -noserver -incremental -war &quot;${project_loc:com.sap.sailing.dashboards.gwt}&quot; -remoteUI &quot;${gwt_remote_ui_server_port}:${unique_id}&quot; -codeServerPort 9875 -startupUrl /dashboards/RibDashboard.html com.sap.sailing.dashboards.gwt.RibDashboard"/>
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="com.sap.sailing.dashboards.gwt"/>
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-XX:+UseG1GC -XX:+UseStringDeduplication -Dgwt.watchFileChanges=false -Xmx2048m -Dgwt-usearchives=false -Dgwt.persistentunitcache=false"/>
<stringAttribute key="org.eclipse.jdt.launching.WORKING_DIRECTORY" value="${project_loc:com.sap.sailing.gwt.ui}/.tmp/gwt-work"/>
</launchConfiguration>
@@ -17,19 +17,14 @@ Require-Bundle: com.sap.sailing.domain,
com.sap.sailing.gwt.ui,
org.moxieapps.gwt.highcharts;bundle-version="1.6.0";resolution:=optional,
com.sap.sse.gwt,
com.sap.sailing.domain.igtimiadapter,
com.sap.sailing.expeditionconnector,
com.sap.sailing.server.gateway,
com.sap.sailing.declination,
org.json.simple;bundle-version="1.1.0",
com.sap.sailing.udpconnector,
com.sap.sailing.expeditionconnector.common,
com.googlecode.mgwt;bundle-version="2.0.0";resolution:=optional,
com.sap.sse.gwt.adminconsole,
org.apache.commons.math;bundle-version="2.1.0",
com.sap.sse.security.ui
com.sap.sse.security.ui,
com.sap.sse
Bundle-ActivationPolicy: lazy
Import-Package: com.sap.sse.util,
javax.servlet;version="3.1.0",
Import-Package: javax.servlet;version="3.1.0",
javax.servlet.descriptor;version="3.1.0",
javax.servlet.http;version="3.1.0",
org.apache.commons.io,
@@ -21,7 +21,7 @@ public class DashboardClientFactoryImpl implements DashboardClientFactory{
public DashboardClientFactoryImpl() {
dispatch = new DashboardDispatchSystemImpl();
sailingService = SailingServiceHelper.createSailingServiceInstance(false);
sailingService = SailingServiceHelper.createSailingServiceInstance(true);
dashboardFiveSecondsTimer = new Timer(PlayModes.Live);
dashboardFiveSecondsTimer.setRefreshInterval(DASHBOARD_RERFRESH_INTERVAL);
@@ -21,7 +21,7 @@ public class RibDashboardEntryPoint extends AbstractEntryPoint<StringMessages> {
public void doOnModuleLoad() {
applyMGWTSettings();
DashboardClientFactory dashboardClientFactory = new DashboardClientFactoryImpl();
DashboardPanel rootPanel = new DashboardPanel(dashboardClientFactory);
DashboardPanel rootPanel = new DashboardPanel(dashboardClientFactory, this);
RootLayoutPanel.get().add(rootPanel);
WindBotDataRetriever windBotDataRetriever = new WindBotDataRetriever(dashboardClientFactory);
windBotDataRetriever.addNumberOfWindBotsChangeListeners(rootPanel);
@@ -36,6 +36,7 @@ import com.sap.sailing.dashboards.gwt.shared.dto.RaceIdDTO;
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.shared.EventDTO;
import com.sap.sse.gwt.client.ErrorReporter;
import com.sap.sse.gwt.client.media.ImageDTO;
/**
@@ -88,12 +89,12 @@ public class DashboardPanel extends Composite implements NumberOfWindBotsChangeL
DashboardClientFactory dashboardClientFactory;
private static final Logger logger = Logger.getLogger(DashboardPanel.class.getName());
public DashboardPanel(DashboardClientFactory dashboardClientFactory) {
public DashboardPanel(DashboardClientFactory dashboardClientFactory, ErrorReporter errorReporter) {
DashboardPanelResources.INSTANCE.style().ensureInjected();
this.dashboardClientFactory = dashboardClientFactory;
leftWindBotWidget = new WindBotWidget(dashboardClientFactory);
rightWindBotWidget = new WindBotWidget(dashboardClientFactory);
startanalysisComponent = new StartAnalysisWidget(this.dashboardClientFactory);
startanalysisComponent = new StartAnalysisWidget(this.dashboardClientFactory, errorReporter);
startlineAdvantagesByWindComponent = new StartlineAdvantagesByWindWidget(this.dashboardClientFactory);
startlineAdvantageByGeometryWidget = new StartlineAdvantageByGeometryWidget(this.dashboardClientFactory);
stringConstants = StringMessages.INSTANCE;
@@ -43,6 +43,7 @@ import com.sap.sailing.dashboards.gwt.shared.dto.StartAnalysisDTO;
import com.sap.sailing.domain.common.dto.CompetitorDTO;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMapResources;
import com.sap.sse.gwt.client.ErrorReporter;
/**
* The class contains an collection of {@link StartAnalysisCard}s that are displayed in horizontal aligned pages. It
@@ -112,11 +113,12 @@ public class StartAnalysisWidget extends Composite implements HasWidgets, PollsL
* */
private int numberOfStartAnalysisCards = 0;
private boolean displaysCards;
private List<StartAnalysisDTO> starts;
private List<StartAnalysisCard> pageChangeListener;
private final List<StartAnalysisDTO> starts;
private final List<StartAnalysisCard> pageChangeListener;
private BottomNotification bottomNotification;
private CompetitorSelectionPopup competitorSelectionPopup;
private DashboardClientFactory dashboardClientFactory;
private final DashboardClientFactory dashboardClientFactory;
private final ErrorReporter errorReporter;
private final String cookieKeyForSelectedCompetitorInLeaderboard;
@@ -131,8 +133,9 @@ public class StartAnalysisWidget extends Composite implements HasWidgets, PollsL
/**
* Component that contains handles, displays and loads startanalysis cards.
* */
public StartAnalysisWidget(DashboardClientFactory dashboardClientFactory) {
public StartAnalysisWidget(DashboardClientFactory dashboardClientFactory, ErrorReporter errorReporter) {
StartAnalysisWidgetResources.INSTANCE.gss().ensureInjected();
this.errorReporter = errorReporter;
dashboardWidgetHeaderAndNoDataMessage = new DashboardWidgetHeaderAndNoDataMessage();
selectedCompetitorWithSettingsButton = new SettingsButtonWithSelectionIndicationLabel();
selectedCompetitorWithSettingsButton.disable();
@@ -378,7 +381,7 @@ public class StartAnalysisWidget extends Composite implements HasWidgets, PollsL
}
final StartAnalysisCard startlineAnalysisCard = new StartAnalysisCard(numberOfStartAnalysisCards
* SCROLL_OFFSET_STARTANALYSIS_CARDS + MARGIN_LEFT_STARTANALYSIS_CARD, numberOfStartAnalysisCards,
startAnalysisDTO, dashboardClientFactory.getSailingService(), raceMapResources);
startAnalysisDTO, dashboardClientFactory.getSailingService(), errorReporter, raceMapResources);
startanalysis_card_container.add(startlineAnalysisCard);
startlineAnalysisCard.startAnalysisComponentPageChangedToIndexAndStartAnalysis(page, startAnalysisDTO);
registerPageChangeListener(startlineAnalysisCard);
@@ -4,6 +4,8 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.stream.Collectors;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.Scheduler;
@@ -23,6 +25,8 @@ import com.sap.sailing.dashboards.gwt.client.widgets.startanalysis.StartAnalysis
import com.sap.sailing.dashboards.gwt.client.widgets.startanalysis.rankingtable.StartAnalysisStartRankTable;
import com.sap.sailing.dashboards.gwt.shared.StartlineAdvantageType;
import com.sap.sailing.dashboards.gwt.shared.dto.StartAnalysisDTO;
import com.sap.sailing.domain.common.dto.BoatDTO;
import com.sap.sailing.domain.common.dto.CompetitorDTO;
import com.sap.sailing.domain.common.racelog.RacingProcedureType;
import com.sap.sailing.gwt.ui.client.RaceCompetitorSelectionModel;
import com.sap.sailing.gwt.ui.client.RaceTimesInfoProvider;
@@ -38,6 +42,7 @@ import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMapResources;
import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMapSettings;
import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMapZoomSettings;
import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMapZoomSettings.ZoomTypes;
import com.sap.sse.gwt.client.ErrorReporter;
import com.sap.sse.gwt.client.async.AsyncActionsExecutor;
import com.sap.sse.gwt.client.player.Timer.PlayModes;
@@ -70,8 +75,9 @@ public class StartAnalysisCard extends Composite implements HasWidgets, StartAna
private StartAnalysisDTO startAnalysisDTO;
private RaceMap raceMap;
private SailingServiceAsync sailingServiceAsync;
private StringMessages stringMessages;
private final SailingServiceAsync sailingServiceAsync;
private final ErrorReporter errorReporter;
private final StringMessages stringMessages;
private final RaceCompetitorSelectionModel competitorSelectionModel;
private final double WIND_LINE_ADVANTAGE_DIV_WIDTH_IN_PT = 185;
@@ -80,11 +86,15 @@ public class StartAnalysisCard extends Composite implements HasWidgets, StartAna
private RaceMapResources raceMapResources;
public StartAnalysisCard(double leftCSSProperty, int cardId, StartAnalysisDTO startAnalysisDTO, SailingServiceAsync sailingServiceAsync, RaceMapResources raceMapResources) {
public StartAnalysisCard(double leftCSSProperty, int cardId, StartAnalysisDTO startAnalysisDTO,
SailingServiceAsync sailingServiceAsync, ErrorReporter errorReporter, RaceMapResources raceMapResources) {
stringMessages = StringMessages.INSTANCE;
this.sailingServiceAsync = sailingServiceAsync;
this.errorReporter = errorReporter;
this.raceMapResources = raceMapResources;
competitorSelectionModel = new RaceCompetitorSelectionModel(/* hasMultiSelection */true);
final Map<CompetitorDTO, BoatDTO> competitorsToBoats = startAnalysisDTO.startAnalysisCompetitorDTOs.stream()
.collect(Collectors.toMap(c -> c.competitorDTO, c -> c.boatDTO));
competitorSelectionModel = new RaceCompetitorSelectionModel(/* hasMultiSelection */true, competitorsToBoats);
competitorSelectionModel.setCompetitors(startAnalysisDTO.getCompetitorDTOsFromStartAnaylsisCompetitorDTOs(), raceMap);
initWidget(uiBinder.createAndBindUi(this));
startanalysis_card.getElement().getStyle().setLeft(leftCSSProperty, Unit.PCT);
@@ -167,10 +177,11 @@ public class StartAnalysisCard extends Composite implements HasWidgets, StartAna
RaceTimesInfoProvider raceTimesInfoProvider = new RaceTimesInfoProvider(sailingServiceAsync,
asyncActionsExecutor, null, Collections.singletonList(startAnalysisDTO.regattaAndRaceIdentifier), 5000l /* requestInterval */);
asyncActionsExecutor, errorReporter,
Collections.singletonList(startAnalysisDTO.regattaAndRaceIdentifier), 5000l /* requestInterval */);
raceMap = new RaceMap(null, null, new RaceMapLifecycle(StringMessages.INSTANCE), raceMapSettings,
sailingServiceAsync,
asyncActionsExecutor, null, timer, competitorSelectionModel,
asyncActionsExecutor, errorReporter, timer, competitorSelectionModel,
new RaceCompetitorSet(competitorSelectionModel), StringMessages.INSTANCE,
startAnalysisDTO.regattaAndRaceIdentifier, raceMapResources, /* showHeaderPanel */ true, new DefaultQuickRanksDTOProvider());
raceTimesInfoProvider.addRaceTimesInfoProviderListener(raceMap);
@@ -193,6 +193,8 @@ public final class StartAnalysisDTOFactory extends AbstractStartAnalysisCreation
StartAnalysisCompetitorDTO startAnalysisCompetitorDTOsForRace = new StartAnalysisCompetitorDTO();
startAnalysisCompetitorDTOsForRace.competitorDTO = dashboardDispatchContext.getRacingEventService().getBaseDomainFactory().getCompetitorAndBoatStore()
.convertToCompetitorDTO(competitor);
startAnalysisCompetitorDTOsForRace.boatDTO = dashboardDispatchContext.getRacingEventService().getBaseDomainFactory().getCompetitorAndBoatStore()
.convertToBoatDTO(trackedRace.getBoatOfCompetitor(competitor));
startAnalysisCompetitorDTOsForRace.rankingTableEntryDTO = createRankTableEntry(trackedRace, rank, competitor);
return startAnalysisCompetitorDTOsForRace;
}
@@ -2,6 +2,7 @@ package com.sap.sailing.dashboards.gwt.shared.dto;
import java.io.Serializable;
import com.sap.sailing.domain.common.dto.BoatDTO;
import com.sap.sailing.domain.common.dto.CompetitorDTO;
public class StartAnalysisCompetitorDTO implements Serializable{
@@ -9,5 +10,6 @@ public class StartAnalysisCompetitorDTO implements Serializable{
private static final long serialVersionUID = -4236444628127858568L;
public CompetitorDTO competitorDTO;
public BoatDTO boatDTO;
public StartAnalysisRankingTableEntryDTO rankingTableEntryDTO;
}
@@ -188,7 +188,7 @@ AbsRelativeBearingToNextMarkAfterManeuver=Rumbo relativo absoluto a la siguiente
RatioBetweenDistanceSailedWithAndWithoutManeuver=Ratio entre distancia navegada con y sin maniobra
RatioBetweenDistanceSailedTowardMiddleAngleProjectionWithAndWithoutManeuver=Ratio entre distancia navegada hacia la proyección del ángulo medio con y sin maniobra
DurationLostByManeuver=Tiempo perdido por maniobra
DurationLostByManeuverTowardMiddleAngleProjection=tiempo perdido por maniobra hacia la proyección del ángulo medio
DurationLostByManeuverTowardMiddleAngleProjection=Tiempo perdido por maniobra hacia la proyección del ángulo medio
JibingCount=Recuento de trasluchadas
TackingCount=Recuento de viradas
AbsTwaAtMaxTurningRate=TWA absoluto en velocidad máxima de giro
@@ -199,3 +199,4 @@ PercentageOfMainCurveProgressUntilHighestSpeed=Porcentaje de progreso de curva p
PercentageOfMainCurveProgressUntilMaxTurningRate=Porcentaje de progreso de curva principal hasta velocidad máxima de giro
AbsTwaAtManeuverMiddle=TWA absoluto en mitad de maniobra
GpsSamplingRate=Velocidad de muestreo de GPS
RaceDuration=Duración de carrera
@@ -199,3 +199,4 @@ PercentageOfMainCurveProgressUntilHighestSpeed=Pourcentage de progression de la
PercentageOfMainCurveProgressUntilMaxTurningRate=Pourcentage de progression de la courbe principale jusqu''à la vitesse angulaire max.
AbsTwaAtManeuverMiddle=TWA absolu au milieu de la manœuvre
GpsSamplingRate=Fréquence d''échantillonage GPS
RaceDuration=Durée de la course
@@ -199,3 +199,4 @@ PercentageOfMainCurveProgressUntilHighestSpeed=最高速度までの主曲線進
PercentageOfMainCurveProgressUntilMaxTurningRate=回転率最大速度までの主曲線進行の比率 (%)
AbsTwaAtManeuverMiddle=マニューバー中間での絶対 TWA
GpsSamplingRate=GPS サンプリング率
RaceDuration=レース持続時間
@@ -188,7 +188,7 @@ AbsRelativeBearingToNextMarkAfterManeuver=Rumo relativo absoluto até a marca se
RatioBetweenDistanceSailedWithAndWithoutManeuver=Proporção entre distância navegada com e sem manobra
RatioBetweenDistanceSailedTowardMiddleAngleProjectionWithAndWithoutManeuver=Proporção entre distância navegada em direção à projeção do ângulo médio com e sem manobra
DurationLostByManeuver=Duração perdida por manobra
DurationLostByManeuverTowardMiddleAngleProjection=duração perdida por manobra em direção à projeção do ângulo médio
DurationLostByManeuverTowardMiddleAngleProjection=Duração perdida por manobra em direção à projeção do ângulo médio
JibingCount=Contagem de jaibe
TackingCount=Contagem de cambada
AbsTwaAtMaxTurningRate=TWA absoluto na taxa de viragem máxima
@@ -199,3 +199,4 @@ PercentageOfMainCurveProgressUntilHighestSpeed=Porcentagem de progresso da curva
PercentageOfMainCurveProgressUntilMaxTurningRate=Porcentagem de progresso da curva principal até velocidade de taxa de viragem máxima
AbsTwaAtManeuverMiddle=TWA absoluto no meio da manobra
GpsSamplingRate=Taxa de amostra de GPS
RaceDuration=Duração da corrida
@@ -188,7 +188,7 @@ AbsRelativeBearingToNextMarkAfterManeuver=Абс. относительное н
RatioBetweenDistanceSailedWithAndWithoutManeuver=Соотношение пройденного расстояния с маневром и без
RatioBetweenDistanceSailedTowardMiddleAngleProjectionWithAndWithoutManeuver=Соотношение пройденного расстояния на срединную угловую проекцию с маневром и без
DurationLostByManeuver=Потеря длительности за счет маневра
DurationLostByManeuverTowardMiddleAngleProjection=потеря длительности за счет маневра на срединную угловую проекцию
DurationLostByManeuverTowardMiddleAngleProjection=Потеря длительности за счет маневра на срединную угловую проекцию
JibingCount=Поворотов фордевинд
TackingCount=Поворотов оверштаг
AbsTwaAtMaxTurningRate=Абс. TWA при макс. скорости поворота
@@ -199,3 +199,4 @@ PercentageOfMainCurveProgressUntilHighestSpeed=Процент прохожден
PercentageOfMainCurveProgressUntilMaxTurningRate=Процент прохождения основной кривой до макс. скорости поворота
AbsTwaAtManeuverMiddle=Абс. TWA в середине маневра
GpsSamplingRate=Частота дискретизации GPS
RaceDuration=Длительность гонки
@@ -199,3 +199,4 @@ PercentageOfMainCurveProgressUntilHighestSpeed=最高速度前主要曲线进度
PercentageOfMainCurveProgressUntilMaxTurningRate=最大回转率速度前主要曲线进度的百分比
AbsTwaAtManeuverMiddle=操作中间的绝对实际风向角
GpsSamplingRate=GPS 采样率
RaceDuration=比赛轮次持续时间
@@ -17,7 +17,7 @@ public enum BoatClassMasterdata {
_49ER ("49er", true, 4.88, 1.93, BoatHullType.MONOHULL, true),
_49ERFX ("49erFX", true, 4.88, 1.93, BoatHullType.MONOHULL, true, "49FX"),
_420 ("420", true, 4.20, 1.65, BoatHullType.MONOHULL, true, "420er", "420M", "420W"),
_470 ("470", true, 4.70, 1.68, BoatHullType.MONOHULL, true, "470er", "470M", "470W", "470 - M", "470 - W"),
_470 ("470", true, 4.70, 1.68, BoatHullType.MONOHULL, true, "470er", "470M", "470W", "470 - M", "470 - W", "470 Men", "470 Women"),
_5O5 ("5O5", true, 5.03, 1.88, BoatHullType.MONOHULL, true, "505", "5o5", "505er", "5o5er"),
_5_5MR ("5.5mR", true, 9.50, 1.92, BoatHullType.MONOHULL, true, "5.5 Meter", "5.5 Metre", "5.5", "5.5M", "5.5-metre", "5.5 metre"),
_6MR ("6mR", true, 11, 2.2, BoatHullType.MONOHULL, true, "6 Meter", "6 Metre", "6", "6M", "6-metre", "6 metre"),
@@ -68,8 +68,6 @@ public enum CollectionNames {
*/
REGATTA_FOR_RACE_ID,
/**
* Legacy store for competitors before implementation of bug2822
* Contains the old competitors with contained boats.
@@ -67,10 +67,8 @@ public abstract class PassAwareRaceLogAnalyzerTest<AnalyzerType extends RaceLogA
RaceLogEvent minorEvent = createEvent(RaceLogEvent.class, 0, 0, UUID.randomUUID(), author);
TargetPair pair = getTargetEventsAndResultForPassAwareTests(1, author);
ResultType expectedResult = pair.getB();
pair.addAll(raceLog);
raceLog.add(minorEvent);
assertEquals(expectedResult, analyzer.analyze());
}
@@ -30,7 +30,8 @@ public abstract class RaceLogAnalyzerTest<AnalyzerType extends RaceLogAnalyzer<?
protected abstract AnalyzerType createAnalyzer(RaceLog raceLog);
protected static <T extends RaceLogEvent> T createEvent(Class<T> type, long milliseconds) {
return createEvent(/* priority */ 0, type, milliseconds);
final T result = createEvent(/* priority */ 0, type, milliseconds);
return result;
}
protected static <T extends RaceLogEvent> T createEvent(int priority, Class<T> type, long milliseconds) {
@@ -7,6 +7,7 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.Arrays;
import java.util.Collections;
import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
@@ -18,21 +19,36 @@ import com.sap.sailing.domain.abstractlog.race.RaceLog;
import com.sap.sailing.domain.abstractlog.race.RaceLogEvent;
import com.sap.sailing.domain.abstractlog.race.RaceLogEventVisitor;
import com.sap.sailing.domain.abstractlog.race.RaceLogRaceStatusEvent;
import com.sap.sailing.domain.abstractlog.race.RaceLogStartProcedureChangedEvent;
import com.sap.sailing.domain.abstractlog.race.RaceLogStartTimeEvent;
import com.sap.sailing.domain.abstractlog.race.analyzing.impl.RaceLogResolver;
import com.sap.sailing.domain.abstractlog.race.analyzing.impl.RaceStatusAnalyzer;
import com.sap.sailing.domain.abstractlog.race.analyzing.impl.RaceStatusAnalyzer.Clock;
import com.sap.sailing.domain.abstractlog.race.impl.RaceLogRaceStatusEventImpl;
import com.sap.sailing.domain.abstractlog.race.impl.RaceLogStartProcedureChangedEventImpl;
import com.sap.sailing.domain.abstractlog.race.impl.RaceLogStartTimeEventImpl;
import com.sap.sailing.domain.abstractlog.race.impl.SimpleRaceLogIdentifierImpl;
import com.sap.sailing.domain.abstractlog.race.state.ReadonlyRaceState;
import com.sap.sailing.domain.abstractlog.race.state.impl.ReadonlyRaceStateImpl;
import com.sap.sailing.domain.abstractlog.race.state.racingprocedure.RacingProcedure;
import com.sap.sailing.domain.abstractlog.race.state.racingprocedure.impl.ReadonlyRacingProcedureFactory;
import com.sap.sailing.domain.base.configuration.impl.EmptyRegattaConfiguration;
import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus;
import com.sap.sailing.domain.common.racelog.RacingProcedureType;
import com.sap.sse.common.Duration;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util.Pair;
import com.sap.sse.common.impl.MillisecondsTimePoint;
public class RaceStatusAnalyzerTest extends PassAwareRaceLogAnalyzerTest<RaceStatusAnalyzer, RaceLogRaceStatus> {
public class RaceStatusAnalyzerTest extends PassAwareRaceLogAnalyzerTest<RaceStatusAnalyzer, Pair<RaceLogRaceStatus, TimePoint>> {
private RacingProcedure racingProcedure = mock(RacingProcedure.class);
private TimePoint now = MillisecondsTimePoint.now();
private Clock constantTimeClock = ()->now;
@Override
protected RaceStatusAnalyzer createAnalyzer(RaceLog raceLog) {
return new RaceStatusAnalyzer(mock(RaceLogResolver.class), raceLog, racingProcedure);
return new RaceStatusAnalyzer(mock(RaceLogResolver.class), raceLog, constantTimeClock, racingProcedure);
}
@Override
@@ -40,7 +56,7 @@ public class RaceStatusAnalyzerTest extends PassAwareRaceLogAnalyzerTest<RaceSta
RaceLogRaceStatusEvent event = createEvent(RaceLogRaceStatusEvent.class, 1, passId, author);
when(event.getNextStatus()).thenReturn(RaceLogRaceStatus.RUNNING);
doAnswer(new StatusVisitorAnswer()).when(event).accept(any(RaceLogEventVisitor.class));
return new TargetPair(Arrays.asList(event), event.getNextStatus());
return new TargetPair(Arrays.asList(event), new Pair<>(event.getNextStatus(), now));
}
@Override
@@ -49,38 +65,46 @@ public class RaceStatusAnalyzerTest extends PassAwareRaceLogAnalyzerTest<RaceSta
RaceLogRaceStatusEvent event = createEvent(RaceLogRaceStatusEvent.class, 1, passId, author);
when(event.getNextStatus()).thenReturn(RaceLogRaceStatus.FINISHING);
doAnswer(new StatusVisitorAnswer()).when(event).accept(any(RaceLogEventVisitor.class));
return new TargetPair(Arrays.asList(event), event.getNextStatus());
return new TargetPair(Arrays.asList(event), new Pair<>(event.getNextStatus(), now));
}
@Test
public void testUnscheduldedForNone() {
RaceLogEvent event1 = createEvent(RaceLogEvent.class, 1);
raceLog.add(event1);
assertEquals(RaceLogRaceStatus.UNSCHEDULED, analyzer.analyze());
assertEquals(RaceLogRaceStatus.UNSCHEDULED, analyzer.analyze().getA());
}
@Test
public void testMostRecent() {
RaceLogRaceStatusEvent event1 = createEvent(RaceLogRaceStatusEvent.class, 1);
when(event1.getNextStatus()).thenReturn(RaceLogRaceStatus.FINISHING);
RaceLogRaceStatusEvent event2 = createEvent(RaceLogRaceStatusEvent.class, 2);
when(event2.getNextStatus()).thenReturn(RaceLogRaceStatus.FINISHED);
doAnswer(new StatusVisitorAnswer()).when(event2).accept(any(RaceLogEventVisitor.class));
LogEventAuthorImpl author = new LogEventAuthorImpl("author", 0);
RaceLogRaceStatusEvent event1 = new RaceLogRaceStatusEventImpl(
new MillisecondsTimePoint(1), author, /* passId */ 1, RaceLogRaceStatus.FINISHING);
RaceLogRaceStatusEvent event2 = new RaceLogRaceStatusEventImpl(
new MillisecondsTimePoint(2), author, /* passId */ 1, RaceLogRaceStatus.FINISHED);
raceLog.add(event1);
raceLog.add(event2);
assertEquals(event2.getNextStatus(), analyzer.analyze());
assertEquals(event2.getNextStatus(), analyzer.analyze().getA());
}
@Test
public void testFinishingWithFinishedInFuture() {
now = new MillisecondsTimePoint(2);
LogEventAuthorImpl author = new LogEventAuthorImpl("author", 0);
RaceLogRaceStatusEvent event1 = new RaceLogRaceStatusEventImpl(
new MillisecondsTimePoint(1), author, /* passId */ 1, RaceLogRaceStatus.FINISHING);
RaceLogRaceStatusEvent event2 = new RaceLogRaceStatusEventImpl(
new MillisecondsTimePoint(3), author, /* passId */ 1, RaceLogRaceStatus.FINISHED);
raceLog.add(event1);
raceLog.add(event2);
assertEquals(event1.getNextStatus(), analyzer.analyze().getA());
}
@Test
public void testStartphaseNotYetActive() {
when(racingProcedure.isStartphaseActive(any(TimePoint.class), any(TimePoint.class))).thenReturn(false);
RaceLogStartTimeEvent event = createStartTimeEvent(MillisecondsTimePoint.now().plus(20000).asMillis(), true);
raceLog.add(event);
assertEquals(RaceLogRaceStatus.SCHEDULED, analyzer.analyze());
assertEquals(RaceLogRaceStatus.SCHEDULED, analyzer.analyze().getA());
}
@Test
@@ -90,7 +114,7 @@ public class RaceStatusAnalyzerTest extends PassAwareRaceLogAnalyzerTest<RaceSta
RaceLogStartTimeEvent event = createStartTimeEvent(MillisecondsTimePoint.now().plus(20000).asMillis(), true);
raceLog.add(event);
assertEquals(RaceLogRaceStatus.STARTPHASE, analyzer.analyze());
assertEquals(RaceLogRaceStatus.STARTPHASE, analyzer.analyze().getA());
}
@Test
@@ -100,7 +124,7 @@ public class RaceStatusAnalyzerTest extends PassAwareRaceLogAnalyzerTest<RaceSta
RaceLogStartTimeEvent event = createStartTimeEvent(0, false);
raceLog.add(event);
assertEquals(RaceLogRaceStatus.RUNNING, analyzer.analyze());
assertEquals(RaceLogRaceStatus.RUNNING, analyzer.analyze().getA());
}
@Test
@@ -112,13 +136,47 @@ public class RaceStatusAnalyzerTest extends PassAwareRaceLogAnalyzerTest<RaceSta
return startTime.minus(1);
}
}, racingProcedure);
when(racingProcedure.isStartphaseActive(any(TimePoint.class), any(TimePoint.class))).thenReturn(false);
RaceLogStartTimeEvent event = createStartTimeEvent(startTime);
raceLog.add(event);
assertEquals(RaceLogRaceStatus.SCHEDULED, analyzer.analyze());
assertEquals(RaceLogRaceStatus.SCHEDULED, analyzer.analyze().getA());
}
@Test
public void testRaceStateStatusUpdateWithoutNewRaceLogEvent() throws InterruptedException {
final TimePoint[] timeForClock = { MillisecondsTimePoint.now() };
final RaceStatusAnalyzer.Clock clock = new RaceStatusAnalyzer.Clock() {
@Override
public TimePoint now() {
return timeForClock[0];
}
};
final int analyzeAndUpdateCachedRaceStatusCalls[] = new int[1];
ReadonlyRaceState raceState = new ReadonlyRaceStateImpl(mock(RaceLogResolver.class), raceLog,
new SimpleRaceLogIdentifierImpl("regatta", "column", "fleet"), clock, new ReadonlyRacingProcedureFactory(
new EmptyRegattaConfiguration()), Collections.emptyMap()) {
@Override
protected void analyzeAndUpdateCachedRaceStatus() {
analyzeAndUpdateCachedRaceStatusCalls[0]++;
super.analyzeAndUpdateCachedRaceStatus();
}
};
RaceLogStartProcedureChangedEvent startProcedureEvent = new RaceLogStartProcedureChangedEventImpl(
timeForClock[0], mock(AbstractLogEventAuthor.class), /* passId */ 0, RacingProcedureType.RRS26);
raceLog.add(startProcedureEvent);
final TimePoint startTime = timeForClock[0].plus(Duration.ONE_MINUTE.times(10));
RaceLogStartTimeEvent event = createStartTimeEvent(startTime);
raceLog.add(event);
assertEquals(startTime, raceState.getStartTime());
assertEquals(RaceLogRaceStatus.SCHEDULED, raceState.getStatus());
timeForClock[0] = timeForClock[0].plus(Duration.ONE_MINUTE.times(9)); // one minute before start; we should be in STARTPHASE
assertEquals(RaceLogRaceStatus.STARTPHASE, raceState.getStatus());
timeForClock[0] = timeForClock[0].plus(Duration.ONE_MINUTE.times(2)); // one minute after start; we should be RUNNING
assertEquals(RaceLogRaceStatus.RUNNING, raceState.getStatus());
timeForClock[0] = timeForClock[0].plus(Duration.ONE_MINUTE.times(2)); // three minutes after start; we should still be RUNNING, without recalculating
final int analyzeAndUpdateCachedRaceStatusCallsBefore = analyzeAndUpdateCachedRaceStatusCalls[0];
assertEquals(RaceLogRaceStatus.RUNNING, raceState.getStatus());
assertEquals(analyzeAndUpdateCachedRaceStatusCalls[0], analyzeAndUpdateCachedRaceStatusCallsBefore);
}
@Test
@@ -141,7 +199,7 @@ public class RaceStatusAnalyzerTest extends PassAwareRaceLogAnalyzerTest<RaceSta
raceLog.add(event1);
raceLog.add(event2);
assertEquals(event2.getNextStatus(), analyzer.analyze());
assertEquals(event2.getNextStatus(), analyzer.analyze().getA());
}
private static RaceLogStartTimeEvent createStartTimeEvent(long startTimeAsMillis, boolean isGreater) {
@@ -155,10 +213,7 @@ public class RaceStatusAnalyzerTest extends PassAwareRaceLogAnalyzerTest<RaceSta
}
private static RaceLogStartTimeEvent createStartTimeEvent(TimePoint startTime) {
RaceLogStartTimeEvent event = createEvent(RaceLogStartTimeEvent.class, 1);
when(event.getStartTime()).thenReturn(startTime);
doAnswer(new StartTimeVisitorAnswer()).when(event).accept(any(RaceLogEventVisitor.class));
return event;
return new RaceLogStartTimeEventImpl(startTime, new LogEventAuthorImpl("Author", /* priority */ 0), /* pass */ 0, startTime);
}
private static class StartTimeVisitorAnswer implements Answer<Void> {
@@ -140,33 +140,27 @@ public class RaceStateTest {
}
@Test
public void testAdvancePass() {
public void testAdvancePass() throws InterruptedException {
state.addChangedListener(listener);
int oldPassId = raceLog.getCurrentPassId();
state.setAdvancePass(mock(TimePoint.class));
assertEquals(oldPassId + 1, raceLog.getCurrentPassId());
verify(listener).onAdvancePass(state);
verifyNoMoreInteractions(listener);
}
@Test
public void testAbort() {
public void testAbort() throws InterruptedException {
state.addChangedListener(listener);
state.setAborted(mock(TimePoint.class), false, Flags.NONE);
// TODO: change test when interface is complete
verifyNoMoreInteractions(listener);
}
@Test
public void testGeneralRecall() {
public void testGeneralRecall() throws InterruptedException {
state.addChangedListener(listener);
state.setGeneralRecall(mock(TimePoint.class));
// TODO: change test when interface is complete
verifyNoMoreInteractions(listener);
}
@@ -174,14 +168,11 @@ public class RaceStateTest {
@Test
public void testInvalidateAfterAdvancePass() throws InterruptedException {
state.addChangedListener(listener);
state.forceNewStartTime(nowMock, new MillisecondsTimePoint(1));
Thread.sleep(100);
state.setFinishedTime(new MillisecondsTimePoint(10));
state.setCourseDesign(nowMock, mock(CourseBase.class), CourseDesignerMode.ADMIN_CONSOLE);
state.setAdvancePass(mock(TimePoint.class));
assertNull(state.getStartTime());
assertEquals(RaceLogRaceStatus.UNSCHEDULED, state.getStatus());
verify(listener, times(1)).onAdvancePass(state);
@@ -1,5 +1,9 @@
package com.sap.sailing.domain.abstractlog.race.analyzing.impl;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import com.sap.sailing.domain.abstractlog.race.RaceLog;
import com.sap.sailing.domain.abstractlog.race.RaceLogDependentStartTimeEvent;
import com.sap.sailing.domain.abstractlog.race.RaceLogEvent;
@@ -10,11 +14,19 @@ import com.sap.sailing.domain.abstractlog.race.impl.RaceLogRaceStatusEventCompar
import com.sap.sailing.domain.abstractlog.race.state.racingprocedure.ReadonlyRacingProcedure;
import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util.Pair;
import com.sap.sse.common.impl.MillisecondsTimePoint;
import com.sap.sse.util.impl.ArrayListNavigableSet;
public class RaceStatusAnalyzer extends RaceLogAnalyzer<RaceLogRaceStatus> {
/**
* Returns the status and the clock {@link TimePoint} at which it was calculated. That time point is obtained
* from the {@link Clock} passed to this analyzer at construction time. It is queried at the time the analysis
* takes place ({@link #performAnalysis()}.
*
* @author Axel Uhl (d043530)
*
*/
public class RaceStatusAnalyzer extends RaceLogAnalyzer<Pair<RaceLogRaceStatus, TimePoint>> {
public interface Clock {
TimePoint now();
}
@@ -24,26 +36,27 @@ public class RaceStatusAnalyzer extends RaceLogAnalyzer<RaceLogRaceStatus> {
public TimePoint now() {
return MillisecondsTimePoint.now();
}
}
private final EventDispatcher eventDispatcher;
private RaceLogResolver resolver;
private final RaceLogResolver resolver;
private final Clock clock;
private final ReadonlyRacingProcedure racingProcedure;
public RaceStatusAnalyzer(RaceLogResolver resolver, RaceLog raceLog, ReadonlyRacingProcedure racingProcedure) {
super(raceLog);
this.eventDispatcher = new EventDispatcher(new StandardClock(), racingProcedure);
this.resolver = resolver;
this(resolver, raceLog, new StandardClock(), racingProcedure);
}
public RaceStatusAnalyzer(RaceLogResolver resolver, RaceLog raceLog, Clock clock, ReadonlyRacingProcedure racingProcedure) {
super(raceLog);
this.eventDispatcher = new EventDispatcher(clock, racingProcedure);
this.resolver = resolver;
this.clock = clock;
this.racingProcedure = racingProcedure;
}
private static final Set<RaceLogRaceStatus> statusTypesToIgnoreIfEventNotValidYet = new HashSet<>(Arrays.asList(
new RaceLogRaceStatus[] { RaceLogRaceStatus.FINISHED, RaceLogRaceStatus.FINISHING }));
@Override
protected RaceLogRaceStatus performAnalysis() {
protected Pair<RaceLogRaceStatus, TimePoint> performAnalysis() {
ArrayListNavigableSet<RaceLogRaceStatusEvent> statusEvents = new ArrayListNavigableSet<>(
RaceLogRaceStatusEventComparator.INSTANCE);
for (RaceLogEvent event : getPassEvents()) {
@@ -51,21 +64,42 @@ public class RaceStatusAnalyzer extends RaceLogAnalyzer<RaceLogRaceStatus> {
statusEvents.add((RaceLogRaceStatusEvent) event);
}
}
final TimePoint now = clock.now();
final EventDispatcher eventDispatcher = new EventDispatcher(now, racingProcedure);
Set<RaceLogRaceStatus> statusesToIgnore = new HashSet<>();
RaceLogRaceStatus result = RaceLogRaceStatus.UNSCHEDULED;
for (RaceLogRaceStatusEvent event : statusEvents.descendingSet()) {
event.accept(eventDispatcher);
return eventDispatcher.nextStatus;
// We need to read across a not yet valid FINISHED event and record that other FINISHED
// events need to be ignored; if we find a not yet valid FINISHING event, ignore further FINISHING
// events; but dispatch all other events (particularly start-related events) to the dispatcher:
final boolean dispatch;
if (statusTypesToIgnoreIfEventNotValidYet.contains(event.getNextStatus())) {
if (event.getLogicalTimePoint().after(now)) { // event not valid yet
statusesToIgnore.add(event.getNextStatus()); // but ignore events of same status with lesser relevance
dispatch = false;
} else {
dispatch = true;
}
} else {
dispatch = true;
}
if (dispatch) {
event.accept(eventDispatcher);
result = eventDispatcher.nextStatus;
break;
}
}
return RaceLogRaceStatus.UNSCHEDULED;
return new Pair<>(result, now);
}
private class EventDispatcher extends BaseRaceLogEventVisitor {
private final Clock clock;
private final TimePoint now;
private final ReadonlyRacingProcedure racingProcedure;
public RaceLogRaceStatus nextStatus;
public EventDispatcher(Clock clock, ReadonlyRacingProcedure racingProcedure) {
this.clock = clock;
public EventDispatcher(TimePoint now, ReadonlyRacingProcedure racingProcedure) {
this.now = now;
this.racingProcedure = racingProcedure;
this.nextStatus = RaceLogRaceStatus.UNKNOWN;
}
@@ -80,7 +114,6 @@ public class RaceStatusAnalyzer extends RaceLogAnalyzer<RaceLogRaceStatus> {
if (startTime == null) {
nextStatus = RaceLogRaceStatus.PRESCHEDULED;
} else {
TimePoint now = clock.now();
if (racingProcedure.isStartphaseActive(startTime, now)) {
nextStatus = RaceLogRaceStatus.STARTPHASE;
} else if (now.before(startTime)) {
@@ -1,12 +1,10 @@
package com.sap.sailing.domain.abstractlog.race.state;
import java.util.Collection;
import com.sap.sailing.domain.abstractlog.race.state.impl.RaceStateEvents;
public interface RaceStateEventScheduler {
void scheduleStateEvents(Collection<RaceStateEvent> stateEvents);
void scheduleStateEvents(Iterable<RaceStateEvent> stateEvents);
void unscheduleStateEvent(RaceStateEvents stateEventName);
@@ -78,7 +78,7 @@ public class RaceStateImpl extends ReadonlyRaceStateImpl implements RaceState {
private RaceStateImpl(RaceLogResolver raceLogResolver, RaceLog raceLog, AbstractLogEventAuthor author, RaceStatusAnalyzer.Clock analyzersClock,
RacingProcedureFactory procedureFactory) {
super(raceLogResolver, raceLog, /* forRaceLogIdentifier */ null, analyzersClock, procedureFactory,
Collections.<SimpleRaceLogIdentifier, ReadonlyRaceState>emptyMap(), /* update */ true);
Collections.<SimpleRaceLogIdentifier, ReadonlyRaceState>emptyMap());
this.author = author;
}
@@ -2,6 +2,8 @@ package com.sap.sailing.domain.abstractlog.race.state.impl;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import com.sap.sailing.domain.abstractlog.race.CompetitorResults;
@@ -44,6 +46,8 @@ import com.sap.sailing.domain.common.racelog.RacingProcedureType;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.TimeRange;
import com.sap.sse.common.Util;
import com.sap.sse.common.Util.Pair;
import com.sap.sse.common.impl.TimeRangeImpl;
import com.sap.sse.util.WeakIdentityHashMap;
/**
@@ -67,6 +71,13 @@ public class ReadonlyRaceStateImpl implements ReadonlyRaceState, RaceLogChangedL
* See also bug4704.
*/
public static ReadonlyRaceState getOrCreate(RaceLogResolver raceLogResolver, RaceLog raceLog) {
return getOrCreate(raceLogResolver, raceLog, /* forRaceLogIdentifier */null,
Collections.<SimpleRaceLogIdentifier, ReadonlyRaceState> emptyMap());
}
public static ReadonlyRaceState getOrCreate(RaceLogResolver raceLogResolver, RaceLog raceLog,
SimpleRaceLogIdentifier forRaceLogIdentifier,
Map<SimpleRaceLogIdentifier, ReadonlyRaceState> dependentRaceStates) {
WeakIdentityHashMap<RaceLogResolver, ReadonlyRaceState> raceStatesForRaceLog;
ReadonlyRaceState result = null;
synchronized (raceStateCache) {
@@ -78,7 +89,7 @@ public class ReadonlyRaceStateImpl implements ReadonlyRaceState, RaceLogChangedL
result = raceStatesForRaceLog.get(raceLogResolver);
}
if (result == null) {
result = createInternal(raceLogResolver, raceLog);
result = createInternal(raceLogResolver, raceLog, forRaceLogIdentifier, dependentRaceStates);
raceStatesForRaceLog.put(raceLogResolver, result);
}
}
@@ -86,9 +97,10 @@ public class ReadonlyRaceStateImpl implements ReadonlyRaceState, RaceLogChangedL
}
private static final ReadonlyRaceState createInternal(RaceLogResolver raceLogResolver, RaceLog raceLog) {
return create(raceLogResolver, raceLog, /* forRaceLogIdentifier */null,
Collections.<SimpleRaceLogIdentifier, ReadonlyRaceState> emptyMap());
private static final ReadonlyRaceState createInternal(RaceLogResolver raceLogResolver, RaceLog raceLog,
SimpleRaceLogIdentifier forRaceLogIdentifier,
Map<SimpleRaceLogIdentifier, ReadonlyRaceState> dependentRaceStates) {
return create(raceLogResolver, raceLog, forRaceLogIdentifier, dependentRaceStates);
}
/**
@@ -139,7 +151,8 @@ public class ReadonlyRaceStateImpl implements ReadonlyRaceState, RaceLogChangedL
private RaceStateEventScheduler scheduler;
/**
* We need one for each change in {@link RacingProcedureType}.
* We need one for each change in {@link RacingProcedureType}. The analyzer uses the {@link #statusAnalyzerClock}
* as its time source.
*/
private RaceStatusAnalyzer statusAnalyzer;
private final RacingProcedureTypeAnalyzer racingProcedureAnalyzer;
@@ -168,6 +181,22 @@ public class ReadonlyRaceStateImpl implements ReadonlyRaceState, RaceLogChangedL
private RacingProcedureType cachedRacingProcedureTypeNoFallback;
private RaceLogRaceStatus cachedRaceStatus;
/**
* The time taken from {@link #statusAnalyzerClock} when the race status was last updated. Set to a valid
* value whenever {@link #cachedRaceStatus} is set.
*/
private TimePoint clockTimePointWhenCachedRaceStatusWasSet;
/**
* Time points determined during {@link #updateCachedRaceStatusAndValidity(RaceLogRaceStatus)} at which
* the race status may change. If the time range between {@link #clockTimePointWhenCachedRaceStatusWasSet} and
* the current {@link #statusAnalyzerClock clock} time point spans any of these, the {@link #cachedRaceStatus}
* is to be considered invalid and needs to be re-calculated. Set to a valid but possibly empty iterable
* whenever {@link #cachedRaceStatus} is set.
*/
private Iterable<TimePoint> timePointsWhenRaceStatusMayChange;
private int cachedPassId;
private StartTimeFinderResult cachedStartTimeFinderResult;
private TimePoint cachedFinishingTime;
@@ -187,7 +216,7 @@ public class ReadonlyRaceStateImpl implements ReadonlyRaceState, RaceLogChangedL
SimpleRaceLogIdentifier forRaceLogIdentifier, RacingProcedureFactory procedureFactory,
Map<SimpleRaceLogIdentifier, ReadonlyRaceState> dependentRaceStates) {
this(raceLogResolver, raceLog, forRaceLogIdentifier, new RaceStatusAnalyzer.StandardClock(),
procedureFactory, dependentRaceStates, /* update */true);
procedureFactory, dependentRaceStates);
}
/**
@@ -196,14 +225,11 @@ public class ReadonlyRaceStateImpl implements ReadonlyRaceState, RaceLogChangedL
* new race state. This can help avoid endless recursions because this race state can be passed along
* together with the identifier so that when again along a dependent call chain a race state is required
* for that race log identifier, this one can be used.
* @param update
* if <code>true</code>, the race state will be updated whenever the underlying <code>raceLog</code>
* changes.
*/
protected ReadonlyRaceStateImpl(RaceLogResolver raceLogResolver, RaceLog raceLog,
SimpleRaceLogIdentifier forRaceLogIdentifier, Clock analyzersClock,
RacingProcedureFactory procedureFactory,
final Map<SimpleRaceLogIdentifier, ReadonlyRaceState> dependentRaceStates, boolean update) {
final Map<SimpleRaceLogIdentifier, ReadonlyRaceState> dependentRaceStates) {
this.raceLog = raceLog;
this.raceLogResolver = raceLogResolver;
this.procedureFactory = procedureFactory;
@@ -325,9 +351,30 @@ public class ReadonlyRaceStateImpl implements ReadonlyRaceState, RaceLogChangedL
@Override
public RaceLogRaceStatus getStatus() {
final TimePoint timePointForAnalysis = statusAnalyzerClock.now();
final TimeRange timeRangeSinceLastStatusUpdate = new TimeRangeImpl(clockTimePointWhenCachedRaceStatusWasSet, timePointForAnalysis);
if (overlapsTimePointForPotentialStatusChange(timeRangeSinceLastStatusUpdate)) {
analyzeAndUpdateCachedRaceStatus();
}
return cachedRaceStatus;
}
/**
* Finds out whether any of the time points in {@link #timePointsWhenRaceStatusMayChange} falls into the time range
* provided. In this case we assume that a race status update may have happened between the time point when the
* {@link #cachedRaceStatus} was updated last and the current request time point, forming the time range provided
* as parameter here.
*/
private boolean overlapsTimePointForPotentialStatusChange(TimeRange timeRangeSinceLastStatusUpdate) {
for (final TimePoint e : timePointsWhenRaceStatusMayChange) {
if (timeRangeSinceLastStatusUpdate.includes(e)) {
return true;
}
}
return false;
}
@Override
public TimePoint getStartTime() {
return cachedStartTimeFinderResult.getStartTime();
@@ -434,7 +481,7 @@ public class ReadonlyRaceStateImpl implements ReadonlyRaceState, RaceLogChangedL
} else {
RaceLog resolvedRaceLog = raceLogResolver.resolve(dependentOnRaceIdentifier);
if (resolvedRaceLog != null) {
raceStateToObserve = ReadonlyRaceStateImpl.create(raceLogResolver, resolvedRaceLog,
raceStateToObserve = ReadonlyRaceStateImpl.getOrCreate(raceLogResolver, resolvedRaceLog,
dependentOnRaceIdentifier, dependentRaceStates);
raceStateToObserve.addChangedListener(raceStateToObserveListener);
}
@@ -463,13 +510,6 @@ public class ReadonlyRaceStateImpl implements ReadonlyRaceState, RaceLogChangedL
recreateRacingProcedure(dependentRaceStates);
changedListeners.onRacingProcedureChanged(this);
}
RaceLogRaceStatus status = statusAnalyzer.analyze();
if (!Util.equalsWithNull(cachedRaceStatus, status)) {
cachedRaceStatus = status;
changedListeners.onStatusChanged(this);
}
int passId = raceLog.getCurrentPassId();
if (cachedPassId != passId) {
cachedPassId = passId;
@@ -478,38 +518,33 @@ public class ReadonlyRaceStateImpl implements ReadonlyRaceState, RaceLogChangedL
cachedRacingProcedureType = null;
cachedRacingProcedureTypeNoFallback = null;
}
StartTimeFinderResult startTimeFinderResult = startTimeAnalyzer.analyze();
if (!Util.equalsWithNull(cachedStartTimeFinderResult, startTimeFinderResult)) {
cachedStartTimeFinderResult = startTimeFinderResult;
changedListeners.onStartTimeChanged(this);
}
adjustObserverForRelativeStartTime(startTimeFinderResult, dependentRaceStates);
TimePoint finishingTime = finishingTimeAnalyzer.analyze();
if (!Util.equalsWithNull(cachedFinishingTime, finishingTime)) {
cachedFinishingTime = finishingTime;
changedListeners.onFinishingTimeChanged(this);
}
TimePoint finishedTime = finishedTimeAnalyzer.analyze();
if (!Util.equalsWithNull(cachedFinishedTime, finishedTime)) {
cachedFinishedTime = finishedTime;
changedListeners.onFinishedTimeChanged(this);
}
analyzeAndUpdateCachedRaceStatus(); // do this after start and finishing times have been updated
TimeRange protest = protestTimeAnalyzer.analyze();
if (!Util.equalsWithNull(cachedProtest, protest)) {
cachedProtest = protest;
changedListeners.onProtestTimeChanged(this);
}
CompetitorResults positionedCompetitors = finishPositioningListAnalyzer.analyze();
if (!Util.equalsWithNull(cachedPositionedCompetitors, positionedCompetitors)) {
cachedPositionedCompetitors = positionedCompetitors;
changedListeners.onFinishingPositioningsChanged(this);
}
CompetitorResults confirmedPositionedCompetitors = confirmedFinishPositioningListAnalyzer.analyze();
if (!Util.equalsWithNull(cachedConfirmedPositionedCompetitors, confirmedPositionedCompetitors)) {
cachedConfirmedPositionedCompetitors = confirmedPositionedCompetitors;
@@ -517,13 +552,11 @@ public class ReadonlyRaceStateImpl implements ReadonlyRaceState, RaceLogChangedL
changedListeners.onFinishingPositionsConfirmed(this);
}
}
CourseBase courseDesign = courseDesignerAnalyzer.analyze();
if (!Util.equalsWithNull(cachedCourseDesign, courseDesign)) {
cachedCourseDesign = courseDesign;
changedListeners.onCourseDesignChanged(this);
}
Wind windFix = lastWindFixAnalyzer.analyze();
if (!Util.equalsWithNull(cachedWindFix, windFix)) {
cachedWindFix = windFix;
@@ -531,6 +564,46 @@ public class ReadonlyRaceStateImpl implements ReadonlyRaceState, RaceLogChangedL
}
}
/**
* This method has protected visibility (and not private) to allow instrumenting by test cases
*/
protected void analyzeAndUpdateCachedRaceStatus() {
Pair<RaceLogRaceStatus, TimePoint> statusAndTimePoint = statusAnalyzer.analyze();
if (!Util.equalsWithNull(cachedRaceStatus, statusAndTimePoint)) {
updateCachedRaceStatusAndValidity(statusAndTimePoint);
}
}
/**
* Updates the {@link #cachedRaceStatus} and calculates and remembers its validity, based on
* {@link ReadonlyRacingProcedure#createStartStateEvents(TimePoint)} and which of those have already
* passed at the current clock time and which ones are still lying ahead.
*/
private void updateCachedRaceStatusAndValidity(Pair<RaceLogRaceStatus, TimePoint> statusAndTimePoint) {
final RaceLogRaceStatus oldRaceStatus = cachedRaceStatus;
cachedRaceStatus = statusAndTimePoint.getA();
clockTimePointWhenCachedRaceStatusWasSet = statusAndTimePoint.getB();
if (getStartTimeFinderResult() == null || getStartTime() == null) {
timePointsWhenRaceStatusMayChange = Collections.emptyList();
} else {
final TimePoint startTime = getStartTime();
List<TimePoint> newTimePointsWhenRaceStatusMayChange = new LinkedList<>();
for (final RaceStateEvent e : racingProcedure.createStartStateEvents(startTime)) {
newTimePointsWhenRaceStatusMayChange.add(e.getTimePoint());
}
if (getFinishingTime() != null) {
newTimePointsWhenRaceStatusMayChange.add(getFinishingTime());
}
if (getFinishedTime() != null) {
newTimePointsWhenRaceStatusMayChange.add(getFinishedTime());
}
timePointsWhenRaceStatusMayChange = newTimePointsWhenRaceStatusMayChange;
}
if (!Util.equalsWithNull(oldRaceStatus, cachedRaceStatus)) {
changedListeners.onStatusChanged(this);
}
}
private void recreateRacingProcedure(Map<SimpleRaceLogIdentifier, ReadonlyRaceState> dependentRaceStates) {
if (racingProcedure != null) {
removeChangedListener(racingProcedure);
@@ -539,10 +612,8 @@ public class ReadonlyRaceStateImpl implements ReadonlyRaceState, RaceLogChangedL
racingProcedure = procedureFactory.createRacingProcedure(cachedRacingProcedureType, raceLog, raceLogResolver);
racingProcedure.setStateEventScheduler(scheduler);
addChangedListener(racingProcedure);
statusAnalyzer = new RaceStatusAnalyzer(raceLogResolver, raceLog, statusAnalyzerClock, racingProcedure);
// let's do an update because status might have changed with new procedure
update(dependentRaceStates);
}
}
@@ -4,6 +4,7 @@ import com.sap.sailing.domain.abstractlog.race.RaceLog;
import com.sap.sailing.domain.abstractlog.race.analyzing.impl.RaceLogAnalyzer;
import com.sap.sailing.domain.abstractlog.race.state.RaceState;
import com.sap.sailing.domain.abstractlog.race.state.RaceStateChangedListener;
import com.sap.sailing.domain.abstractlog.race.state.RaceStateEvent;
import com.sap.sailing.domain.abstractlog.race.state.RaceStateEventProcessor;
import com.sap.sailing.domain.abstractlog.race.state.RaceStateEventScheduler;
import com.sap.sailing.domain.abstractlog.race.state.ReadonlyRaceState;
@@ -122,6 +123,13 @@ public interface ReadonlyRacingProcedure extends RaceStateEventProcessor, RaceSt
RacingProcedurePrerequisite checkPrerequisitesForStart(TimePoint now, TimePoint startTime,
RacingProcedurePrerequisite.FulfillmentFunction function);
/**
* Delivers the time points and types of the events around the start, each optionally
* leading to a change in race status.
*/
Iterable<RaceStateEvent> createStartStateEvents(TimePoint startTime);
/**
* This method is called by the framework. You don't need to call it.
*/
@@ -1,7 +1,6 @@
package com.sap.sailing.domain.abstractlog.race.state.racingprocedure.basic.impl;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor;
@@ -66,7 +65,7 @@ public class BasicRacingProcedureImpl extends BaseRacingProcedure {
}
@Override
protected Collection<RaceStateEvent> createStartStateEvents(TimePoint startTime) {
public Iterable<RaceStateEvent> createStartStateEvents(TimePoint startTime) {
return Arrays.<RaceStateEvent>asList(new RaceStateEventImpl(startTime, RaceStateEvents.START));
}
@@ -1,7 +1,6 @@
package com.sap.sailing.domain.abstractlog.race.state.racingprocedure.ess.impl;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor;
@@ -64,7 +63,7 @@ public class ESSRacingProcedureImpl extends BaseRacingProcedure implements ESSRa
}
@Override
protected Collection<RaceStateEvent> createStartStateEvents(TimePoint startTime) {
public Iterable<RaceStateEvent> createStartStateEvents(TimePoint startTime) {
return Arrays.<RaceStateEvent> asList(new RaceStateEventImpl(startTime.minus(startPhaseAPDownInterval),
RaceStateEvents.ESS_AP_DOWN), new RaceStateEventImpl(startTime.minus(startPhaseESSThreeUpInterval),
RaceStateEvents.ESS_THREE_UP), new RaceStateEventImpl(startTime.minus(startPhaseESSTwoUpInterval),
@@ -1,7 +1,6 @@
package com.sap.sailing.domain.abstractlog.race.state.racingprocedure.gate.impl;
import java.util.Arrays;
import java.util.Collection;
import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor;
import com.sap.sailing.domain.abstractlog.race.RaceLog;
@@ -119,7 +118,7 @@ public class GateStartRacingProcedureImpl extends BaseRacingProcedure implements
}
@Override
protected Collection<RaceStateEvent> createStartStateEvents(TimePoint startTime) {
public Iterable<RaceStateEvent> createStartStateEvents(TimePoint startTime) {
return Arrays.<RaceStateEvent> asList(
new RaceStateEventImpl(startTime.minus(startPhaseClassOverGolfUpInterval),
RaceStateEvents.GATE_CLASS_OVER_GOLF_UP),
@@ -276,8 +276,6 @@ public abstract class BaseRacingProcedure extends BaseRaceStateChangedListener i
return configuration;
}
protected abstract Collection<RaceStateEvent> createStartStateEvents(TimePoint startTime);
@Override
public void onStartTimeChanged(ReadonlyRaceState state) {
getChangedListeners().onActiveFlagsChanged(this);
@@ -1,7 +1,6 @@
package com.sap.sailing.domain.abstractlog.race.state.racingprocedure.league.impl;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor;
@@ -81,7 +80,7 @@ public class LeagueRacingProcedureImpl extends BaseRacingProcedure implements Le
}
@Override
protected Collection<RaceStateEvent> createStartStateEvents(TimePoint startTime) {
public Iterable<RaceStateEvent> createStartStateEvents(TimePoint startTime) {
// Remark: We are reusing here CLASS_UP, STARTMODE_UP and STARTMODE_DOWN from the RRS26 procedure
// The question is why there are no generic CLASS_UP, STARTMODE_UP and STARTMODE_DOWN events
return Arrays.<RaceStateEvent> asList(
@@ -28,17 +28,14 @@ public abstract class ConfigurableStartModeFlagRacingProcedureImpl extends BaseR
public ConfigurableStartModeFlagRacingProcedureImpl(RaceLog raceLog, AbstractLogEventAuthor author,
ConfigurableStartModeFlagRacingProcedureConfiguration configuration, RaceLogResolver raceLogResolver) {
super(raceLog, author, configuration, raceLogResolver);
RacingProcedureTypeAnalyzer procedureAnalyzer = new RacingProcedureTypeAnalyzer(raceLog);
if (configuration.getStartModeFlags() != null) {
this.startModeFlagAnalyzer = new StartModeFlagFinder(procedureAnalyzer, raceLog, configuration.getStartModeFlags());
} else {
this.startModeFlagAnalyzer = new StartModeFlagFinder(procedureAnalyzer, raceLog, getDefaultStartModeFlags());
}
this.cachedStartmodeFlag = getDefaultStartMode();
this.startmodeFlagHasBeenSet = false;
update();
}
@@ -1,7 +1,6 @@
package com.sap.sailing.domain.abstractlog.race.state.racingprocedure.line.impl;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
@@ -64,7 +63,7 @@ public class RRS26RacingProcedureImpl extends ConfigurableStartModeFlagRacingPro
}
@Override
protected Collection<RaceStateEvent> createStartStateEvents(TimePoint startTime) {
public Iterable<RaceStateEvent> createStartStateEvents(TimePoint startTime) {
return Arrays.<RaceStateEvent> asList(
new RaceStateEventImpl(startTime.minus(startPhaseClassUpInterval), RaceStateEvents.RRS26_CLASS_UP),
new RaceStateEventImpl(startTime.minus(startPhaseStartModeUpInterval), RaceStateEvents.RRS26_STARTMODE_UP),
@@ -1,7 +1,6 @@
package com.sap.sailing.domain.abstractlog.race.state.racingprocedure.line.impl;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
@@ -69,7 +68,7 @@ public class SWCRacingProcedureImpl extends ConfigurableStartModeFlagRacingProce
}
@Override
protected Collection<RaceStateEvent> createStartStateEvents(TimePoint startTime) {
public Iterable<RaceStateEvent> createStartStateEvents(TimePoint startTime) {
return Arrays.<RaceStateEvent> asList(
new RaceStateEventImpl(startTime.minus(CLASS_AND_STARTMODE_UP_INTERVAL), RaceStateEvents.SWC_CLASS_AND_STARTMODE_UP),
new RaceStateEventImpl(startTime.minus(FIVE_MINUTES_FLAG_UP_INTERVAL), RaceStateEvents.SWC_FIVE_UP),
@@ -1,6 +1,7 @@
package com.sap.sailing.domain.swisstimingadapter;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.UnknownHostException;
import java.text.ParseException;
@@ -53,11 +54,11 @@ public interface SwissTimingFactory {
SwissTimingRaceTracker createRaceTracker(RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, WindStore windStore,
DomainFactory domainFactory, TrackedRegattaRegistry trackedRegattaRegistry, RaceLogResolver raceLogResolver, SwissTimingTrackingConnectivityParameters connectivityParams)
throws InterruptedException, UnknownHostException, IOException, ParseException;
throws InterruptedException, UnknownHostException, IOException, ParseException, URISyntaxException;
RaceTracker createRaceTracker(Regatta regatta, WindStore windStore, DomainFactory domainFactory, TrackedRegattaRegistry trackedRegattaRegistry,
RaceLogResolver raceLogResolver, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, SwissTimingTrackingConnectivityParameters connectivityParams)
throws UnknownHostException, InterruptedException, IOException, ParseException;
throws UnknownHostException, InterruptedException, IOException, ParseException, URISyntaxException;
Race createRace(String raceId, String raceName, String description, BoatClass boatClass);
@@ -1,6 +1,7 @@
package com.sap.sailing.domain.swisstimingadapter.impl;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.UnknownHostException;
import java.text.ParseException;
import java.util.HashMap;
@@ -91,7 +92,7 @@ public class SwissTimingFactoryImpl implements SwissTimingFactory {
public SwissTimingRaceTracker createRaceTracker(RaceLogStore raceLogStore, RegattaLogStore regattaLogStore,
WindStore windStore, DomainFactory domainFactory, TrackedRegattaRegistry trackedRegattaRegistry,
RaceLogResolver raceLogResolver, SwissTimingTrackingConnectivityParameters connectivityParams)
throws InterruptedException, UnknownHostException, IOException, ParseException {
throws InterruptedException, UnknownHostException, IOException, ParseException, URISyntaxException {
return new SwissTimingRaceTrackerImpl(raceLogStore, regattaLogStore, windStore, domainFactory, this,
trackedRegattaRegistry, raceLogResolver, connectivityParams);
}
@@ -100,7 +101,7 @@ public class SwissTimingFactoryImpl implements SwissTimingFactory {
public RaceTracker createRaceTracker(Regatta regatta, WindStore windStore, DomainFactory domainFactory,
TrackedRegattaRegistry trackedRegattaRegistry, RaceLogResolver raceLogResolver, RaceLogStore raceLogStore,
RegattaLogStore regattaLogStore, SwissTimingTrackingConnectivityParameters connectivityParams)
throws UnknownHostException, InterruptedException, IOException, ParseException {
throws UnknownHostException, InterruptedException, IOException, ParseException, URISyntaxException {
return new SwissTimingRaceTrackerImpl(regatta, windStore, domainFactory, this, trackedRegattaRegistry,
raceLogStore, regattaLogStore, raceLogResolver, connectivityParams);
}
@@ -128,7 +128,7 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl
WindStore windStore, DomainFactory domainFactory, SwissTimingFactory factory,
TrackedRegattaRegistry trackedRegattaRegistry, RaceLogResolver raceLogResolver,
SwissTimingTrackingConnectivityParameters connectivityParams)
throws InterruptedException, UnknownHostException, IOException, ParseException {
throws InterruptedException, UnknownHostException, IOException, ParseException, URISyntaxException {
this(/* regatta */ null, windStore, domainFactory, factory, trackedRegattaRegistry, raceLogStore, regattaLogStore, raceLogResolver, connectivityParams);
}
@@ -136,7 +136,7 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl
SwissTimingFactory factory, TrackedRegattaRegistry trackedRegattaRegistry, RaceLogStore raceLogStore,
RegattaLogStore regattaLogStore, RaceLogResolver raceLogResolver,
SwissTimingTrackingConnectivityParameters connectivityParams)
throws InterruptedException, UnknownHostException, IOException, ParseException {
throws InterruptedException, UnknownHostException, IOException, ParseException, URISyntaxException {
super(connectivityParams);
this.raceLogResolver = raceLogResolver;
this.tmdMessageQueue = new TMDMessageQueue(this);
@@ -155,6 +155,8 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl
connectivityParams.getRaceID(), connectivityParams.getRaceName(), connectivityParams.getRaceDescription(), connectivityParams.getBoatClass());
this.domainFactory = domainFactory;
this.raceID = connectivityParams.getRaceID();
// start out with an empty course, so we don't depend on receiving the CCG message before the timeout
this.course = new CourseImpl(this.raceID, /* start with empty marks list */ Collections.emptyList());
this.raceName = connectivityParams.getRaceName();
this.startList = connectivityParams.getStartList();
this.startListFromManage2Sail = connectivityParams.getStartList() != null;
@@ -170,6 +172,9 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl
this.updateURL = connectivityParams.getUpdateURL();
this.updateUsername = connectivityParams.getUpdateUsername();
this.updatePassword = connectivityParams.getUpdatePassword();
if (connectivityParams.getStartList() != null) {
createRaceDefinition(course);
}
}
@Override
@@ -12,16 +12,20 @@ import com.google.gwt.i18n.client.NumberFormat;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.AcceptsOneWidget;
import com.sap.sailing.domain.common.DetailType;
import com.sap.sailing.domain.common.RaceIdentifier;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.WindSource;
import com.sap.sailing.domain.common.dto.CompetitorDTO;
import com.sap.sailing.domain.common.dto.LeaderboardDTO;
import com.sap.sailing.domain.common.dto.LeaderboardRowDTO;
import com.sap.sailing.domain.common.dto.RaceColumnDTO;
import com.sap.sailing.gwt.autoplay.client.app.AnimationPanel;
import com.sap.sailing.gwt.autoplay.client.app.AutoPlayClientFactory;
import com.sap.sailing.gwt.autoplay.client.app.AutoPlayPresenterConfigured;
import com.sap.sailing.gwt.autoplay.client.utils.AutoplayHelper;
import com.sap.sailing.gwt.settings.client.leaderboard.SingleRaceLeaderboardSettings;
import com.sap.sailing.gwt.ui.client.FlagImageResolverImpl;
import com.sap.sailing.gwt.ui.client.LeaderboardUpdateListener;
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.client.shared.racemap.RaceCompetitorSet;
@@ -152,12 +156,7 @@ public class LiveRaceWithRacemapAndLeaderBoardPresenterImpl
view.showErrorNoLive(this, panel, new IllegalStateException("No race is live"));
return;
}
final SingleRaceLeaderboardSettings leaderboardSettings = new SingleRaceLeaderboardSettings(
/* maneuverDetailsToShow */ null, /* legDetailsToShow */ null, /* raceDetailsToShow */ null,
/* overallDetailsToShow */ null, /* delayBetweenAutoAdvancesInMilliseconds */ null,
/* showAddedScores */ false, /* showCompetitorShortNameColumn */ true,
/* showCompetitorFullNameColumn */ false, /* isCompetitorNationalityColumnVisible */ false,
/* showCompetitorBoatInfoColumn */ false, /* showRaceRankColumn */ true);
final SingleRaceLeaderboardSettings leaderboardSettings = getLeaderboardSettings(false);
timer = new com.sap.sse.gwt.client.player.Timer(
// perform the first request as "live" but don't by default auto-play
PlayModes.Live, PlayStates.Playing,
@@ -167,12 +166,42 @@ public class LiveRaceWithRacemapAndLeaderBoardPresenterImpl
getSlideCtx().getContextDefinition().getLeaderboardName(), errorReporter, StringMessages.INSTANCE,
false, null, false, null, false, true, false, false, false, new SixtyInchLeaderboardStyle(true),
FlagImageResolverImpl.get(), Arrays.asList(DetailType.values()));
leaderboardPanel.addLeaderboardUpdateListener(new LeaderboardUpdateListener() {
@Override
public void updatedLeaderboard(LeaderboardDTO leaderboard) {
// if this is a changing boat race, enable the boat column
if (leaderboardPanel.getLeaderboard() != null) {
LeaderboardDTO leaderboardDTO = leaderboardPanel.getLeaderboard();
boolean boatsRequired = leaderboardDTO.canBoatsOfCompetitorsChangePerRace;
if (leaderboardPanel.getSettings().isShowCompetitorBoatInfoColumn() != boatsRequired) {
leaderboardPanel.updateSettings(getLeaderboardSettings(boatsRequired));
}
}
}
@Override
public void currentRaceSelected(RaceIdentifier raceIdentifier, RaceColumnDTO raceColumn) {
}
});
getPlace().getRaceMap().setQuickRanksDTOProvider(new QuickRanksDTOFromLeaderboardDTOProvider(
new RaceCompetitorSet(getPlace().getRaceMapSelectionProvider()), liveRace));
view.startingWith(this, panel, getPlace().getRaceMap(), leaderboardPanel);
selectionTimer.schedule(SWITCH_COMPETITOR_DELAY + AnimationPanel.ANIMATION_DURATION + AnimationPanel.DELAY);
}
private SingleRaceLeaderboardSettings getLeaderboardSettings(boolean showBoatColumn) {
final SingleRaceLeaderboardSettings leaderboardSettings = new SingleRaceLeaderboardSettings(
/* maneuverDetailsToShow */ null, /* legDetailsToShow */ null, /* raceDetailsToShow */ null,
/* overallDetailsToShow */ null, /* delayBetweenAutoAdvancesInMilliseconds */ null,
/* showAddedScores */ false, /* showCompetitorShortNameColumn */ true,
/* showCompetitorFullNameColumn */ false, showBoatColumn,
/* showCompetitorBoatInfoColumn */ false, /* showRaceRankColumn */ true);
return leaderboardSettings;
}
@Override
public void onStop() {
if (timer != null) {
@@ -48,10 +48,11 @@ public class MultiRegattaListSteps extends Composite {
double parts = Math.pow(seriesProgress.getMaxRacesPerFleet(), 0.4);
sumParts += parts;
}
final boolean showSeriesName = regattaProgress.getSeries().size() > 1;
for(RegattaProgressSeriesDTO seriesProgress : regattaProgress.getSeries()) {
double parts = Math.pow(seriesProgress.getMaxRacesPerFleet(), 0.4);
double percentage = 100.0 * parts / sumParts;
MultiRegattaListStepsBody stepsBody = new MultiRegattaListStepsBody(seriesProgress);
MultiRegattaListStepsBody stepsBody = new MultiRegattaListStepsBody(seriesProgress, showSeriesName);
allMultiregattaSteps.add(stepsBody);
stepsBody.getElement().getStyle().setWidth(percentage, Unit.PCT);
stepsContainerUi.appendChild(stepsBody.getElement());
@@ -1,7 +1,5 @@
package com.sap.sailing.gwt.home.desktop.partials.multiregattalist;
import static com.sap.sailing.domain.common.LeaderboardNameConstants.DEFAULT_SERIES_NAME;
import java.util.Map;
import java.util.Map.Entry;
@@ -42,17 +40,17 @@ public class MultiRegattaListStepsBody extends UIObject implements RequiresResiz
private final String seriesName, seriesNameMedium, seriesNameShort;
private int seriesNameLength, seriesNameMediumLength;
public MultiRegattaListStepsBody(RegattaProgressSeriesDTO seriesProgress) {
MultiRegattaListStepsBody(RegattaProgressSeriesDTO seriesProgress, boolean showName) {
setElement(uiBinder.createAndBindUi(this));
textContainerUi.getStyle().setVisibility(Visibility.HIDDEN);
longNameDummyUi.setInnerText(seriesName = caculateSeriesName(seriesProgress));
longNameDummyUi.setInnerText(seriesName = caculateSeriesName(seriesProgress, showName));
mediumNameDummyUi.setInnerText(seriesNameMedium = caculateSeriesNameMedium());
seriesNameShort = caculateSeriesNameShort();
if (seriesProgress.isCompleted()) {
progressUi.setInnerText(String.valueOf(seriesProgress.getTotalRaceCount()));
progressUi.setInnerText(I18N.racesCount(seriesProgress.getTotalRaceCount()));
} else {
checkUi.getStyle().setDisplay(Display.NONE);
progressUi.setInnerText(I18N.currentOfTotal(
progressUi.setInnerText(I18N.currentOfTotalRaces(
seriesProgress.getProgressRaceCount(), seriesProgress.getTotalRaceCount()));
}
addFleetProgresses(seriesProgress.getFleetState(), seriesProgress.getMaxRacesPerFleet());
@@ -93,9 +91,9 @@ public class MultiRegattaListStepsBody extends UIObject implements RequiresResiz
}
}
private String caculateSeriesName(RegattaProgressSeriesDTO seriesProgress) {
if (seriesProgress.getName() == null || seriesProgress.getName().isEmpty()
|| DEFAULT_SERIES_NAME.equals(seriesProgress.getName())) {
private String caculateSeriesName(RegattaProgressSeriesDTO seriesProgress, boolean showName) {
if (!showName || seriesProgress.getName() == null || seriesProgress.getName().isEmpty()) {
nameUi.getStyle().setDisplay(Display.NONE);
return I18N.races();
} else {
return seriesProgress.getName();
@@ -1,5 +1,7 @@
package com.sap.sailing.gwt.home.desktop.places.event.regatta.racestab;
import java.util.Optional;
import com.google.gwt.place.shared.Prefix;
import com.sap.sailing.gwt.home.desktop.places.event.regatta.AbstractEventRegattaPlace;
import com.sap.sailing.gwt.home.shared.app.HasMobileVersion;
@@ -8,24 +10,37 @@ import com.sap.sailing.gwt.home.shared.places.event.AbstractEventPlace;
import com.sap.sailing.gwt.home.shared.places.event.EventContext;
public class RegattaRacesPlace extends AbstractEventRegattaPlace implements HasMobileVersion {
private final String preferredSeriesName;
public RegattaRacesPlace(String id, String leaderboardName) {
super(id, leaderboardName);
this.preferredSeriesName = null;
}
public RegattaRacesPlace(EventContext context) {
this(context, null);
}
public RegattaRacesPlace(EventContext context, String preferredSeriesName) {
super(context);
this.preferredSeriesName = preferredSeriesName;
}
public final Optional<String> getPreferredSeriesName() {
return Optional.ofNullable(preferredSeriesName);
}
@Override
public AbstractEventRegattaPlace newInstanceWithContext(EventContext ctx) {
return new RegattaRacesPlace(ctx);
return new RegattaRacesPlace(ctx, null);
}
@Prefix(PlaceTokenPrefixes.RegattaRaces)
public static class Tokenizer extends AbstractEventPlace.Tokenizer<RegattaRacesPlace> {
@Override
protected RegattaRacesPlace getRealPlace(EventContext context) {
return new RegattaRacesPlace(context);
return new RegattaRacesPlace(context, null);
}
}
}
@@ -34,6 +34,7 @@ import com.sap.sailing.gwt.home.shared.places.user.confirmation.ConfirmationPlac
import com.sap.sailing.gwt.home.shared.places.user.passwordreset.PasswordResetActivityProxy;
import com.sap.sailing.gwt.home.shared.places.user.passwordreset.PasswordResetPlace;
import com.sap.sailing.gwt.home.shared.places.user.profile.AbstractUserProfilePlace;
import com.sap.sse.gwt.client.DOMUtils;
public class MobileActivityMapper implements ActivityMapper {
private final MobileApplicationClientFactory clientFactory;
@@ -52,6 +53,7 @@ public class MobileActivityMapper implements ActivityMapper {
SwitchingEntryPoint.reloadApp();
return null;
}
DOMUtils.scrollToTop(clientFactory.getRoot());
if (place instanceof ErrorPlace) {
return new ErrorActivityProxy((ErrorPlace) place, clientFactory);
} else if (place instanceof StartPlace) {
@@ -21,6 +21,9 @@
.eventsteps_phases_phaseinactive {
color: #999;
}
.eventsteps_phases_phase_link {
color: #000;
}
/* .eventsteps_phases_phase:nth-child(2n-1) { */
/* padding-right: 0.333333333333333em; */
/* } */
@@ -35,6 +38,7 @@
/* padding: .666666666666667em 0; */
display: inline;
vertical-align: middle;
margin-right: 0.333333333333333rem;
}
.eventsteps_phases_phase_progressbar {
height: 1.266666666666667em;
@@ -58,7 +62,7 @@ img.eventsteps_phases_phase_progress_icon {
vertical-align: middle;
height: 1.133333333333333em;
width: 1.133333333333333em;
margin: 0 0.333333333333333em;
margin-right: 0.333333333333333em;
}
.eventsteps_phases_phase_progress_text {
font-size: 0.866666666666667em;
@@ -1,5 +1,7 @@
package com.sap.sailing.gwt.home.mobile.partials.eventsteps;
import java.util.function.Function;
import com.google.gwt.core.client.GWT;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.uibinder.client.UiBinder;
@@ -11,6 +13,7 @@ import com.sap.sailing.gwt.home.communication.regatta.RegattaProgressSeriesDTO;
import com.sap.sailing.gwt.home.communication.regatta.RegattaWithProgressDTO;
import com.sap.sailing.gwt.home.mobile.partials.section.MobileSection;
import com.sap.sailing.gwt.home.mobile.partials.sectionHeader.SectionHeaderContent;
import com.sap.sailing.gwt.home.shared.app.PlaceNavigation;
import com.sap.sailing.gwt.home.shared.refresh.RefreshableWidget;
public class EventSteps extends Composite implements RefreshableWidget<RegattaWithProgressDTO> {
@@ -22,8 +25,10 @@ public class EventSteps extends Composite implements RefreshableWidget<RegattaWi
@UiField SectionHeaderContent sectionHeaderUi;
private final MobileSection regattaProgessUi;
private final Function<String, PlaceNavigation<?>> racesNavigationFactory;
public EventSteps(RegattaMetadataDTO regatta) {
public EventSteps(RegattaMetadataDTO regatta, Function<String, PlaceNavigation<?>> racesNavigationFactory) {
this.racesNavigationFactory = racesNavigationFactory;
EventStepsResources.INSTANCE.css().ensureInjected();
initWidget(regattaProgessUi = uiBinder.createAndBindUi(this));
ImageResource boatClassIcon = BoatClassImageResolver.getBoatClassIconResource(regatta.getBoatClass());
@@ -36,8 +41,10 @@ public class EventSteps extends Composite implements RefreshableWidget<RegattaWi
@Override
public void setData(RegattaWithProgressDTO data) {
regattaProgessUi.clearContent();
final boolean showSeriesName = data.getProgress().getSeries().size() > 1;
for (RegattaProgressSeriesDTO seriesProgress : data.getProgress().getSeries()) {
regattaProgessUi.addContent(new EventStepsPhase(seriesProgress));
final PlaceNavigation<?> navigation = racesNavigationFactory.apply(seriesProgress.getName());
regattaProgessUi.addContent(new EventStepsPhase(seriesProgress, navigation, showSeriesName));
}
}
@@ -1,11 +1,10 @@
package com.sap.sailing.gwt.home.mobile.partials.eventsteps;
import static com.sap.sailing.domain.common.LeaderboardNameConstants.DEFAULT_SERIES_NAME;
import java.util.Map;
import java.util.Map.Entry;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.AnchorElement;
import com.google.gwt.dom.client.DivElement;
import com.google.gwt.dom.client.ImageElement;
import com.google.gwt.dom.client.Style.Display;
@@ -16,7 +15,9 @@ import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.gwt.home.communication.race.FleetMetadataDTO;
import com.sap.sailing.gwt.home.communication.regatta.RegattaProgressFleetDTO;
import com.sap.sailing.gwt.home.communication.regatta.RegattaProgressSeriesDTO;
import com.sap.sailing.gwt.home.shared.app.PlaceNavigation;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sse.gwt.client.LinkUtil;
public class EventStepsPhase extends Composite {
@@ -26,20 +27,26 @@ public class EventStepsPhase extends Composite {
interface EventStepsPhaseUiBinder extends UiBinder<Widget, EventStepsPhase> {
}
@UiField AnchorElement anchorUi;
@UiField DivElement nameUi;
@UiField ImageElement checkUi;
@UiField DivElement progressUi;
@UiField DivElement fleetsContainerUi;
public EventStepsPhase(RegattaProgressSeriesDTO seriesProgress) {
EventStepsPhase(RegattaProgressSeriesDTO seriesProgress, PlaceNavigation<?> placeNavigation, boolean showName) {
initWidget(uiBinder.createAndBindUi(this));
nameUi.setInnerText(DEFAULT_SERIES_NAME.equals(seriesProgress.getName()) ? I18N.races() : seriesProgress.getName());
LinkUtil.configureForAction(anchorUi, () -> placeNavigation.goToPlace());
if (showName) {
nameUi.setInnerText(seriesProgress.getName());
} else {
nameUi.removeFromParent();
}
if (seriesProgress.isCompleted()) {
progressUi.setInnerText(String.valueOf(seriesProgress.getTotalRaceCount()));
progressUi.setInnerText(I18N.racesCount(seriesProgress.getTotalRaceCount()));
} else {
checkUi.getStyle().setDisplay(Display.NONE);
int current = seriesProgress.getProgressRaceCount(), total = seriesProgress.getTotalRaceCount();
progressUi.setInnerText(I18N.currentOfTotal(current, total));
progressUi.setInnerText(I18N.currentOfTotalRaces(current, total));
}
if (seriesProgress.getProgressRaceCount() == 0) {
addStyleName(EventStepsResources.INSTANCE.css().eventsteps_phases_phaseinactive());
@@ -6,11 +6,13 @@
<ui:with field="local_res" type="com.sap.sailing.gwt.home.mobile.partials.eventsteps.EventStepsResources" />
<ui:with field="shr" type="com.sap.sailing.gwt.home.shared.resources.SharedHomeResources" />
<g:HTMLPanel addStyleNames="{local_res.css.eventsteps_phases_phase}">
<div class="{local_res.css.eventsteps_phases_phase_progress}">
<div class="{local_res.css.eventsteps_phases_phase_name}" ui:field="nameUi"></div>
<img src="{shr.greenCheck.getSafeUri}" class="{local_res.css.eventsteps_phases_phase_progress_icon}" ui:field="checkUi"></img>
<div class="{local_res.css.eventsteps_phases_phase_progress_text}" ui:field="progressUi"></div>
</div>
<div class="{local_res.css.eventsteps_phases_phase_progressbar}" ui:field="fleetsContainerUi"></div>
<a class="{local_res.css.eventsteps_phases_phase_link}" ui:field="anchorUi">
<div class="{local_res.css.eventsteps_phases_phase_progress}">
<div class="{local_res.css.eventsteps_phases_phase_name}" ui:field="nameUi"></div>
<img src="{shr.greenCheck.getSafeUri}" class="{local_res.css.eventsteps_phases_phase_progress_icon}" ui:field="checkUi"></img>
<div class="{local_res.css.eventsteps_phases_phase_progress_text}" ui:field="progressUi"></div>
</div>
<div class="{local_res.css.eventsteps_phases_phase_progressbar}" ui:field="fleetsContainerUi"></div>
</a>
</g:HTMLPanel>
</ui:UiBinder>
@@ -16,6 +16,7 @@ public interface EventStepsResources extends ClientBundle {
String eventsteps_phases();
String eventsteps_phases_phase();
String eventsteps_phases_phaseinactive();
String eventsteps_phases_phase_link();
String eventsteps_phases_phase_name();
String eventsteps_phases_phase_progressbar();
String eventsteps_phases_phase_progressbar_fleet();
@@ -1,5 +1,8 @@
package com.sap.sailing.gwt.home.mobile.partials.regattacompetition;
import java.util.HashMap;
import java.util.Map;
import com.google.gwt.core.client.GWT;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
@@ -8,6 +11,7 @@ import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.gwt.home.communication.event.RaceCompetitionFormatSeriesDTO;
import com.sap.sailing.gwt.home.shared.partials.regattacompetition.RegattaCompetitionView;
import com.sap.sse.gwt.client.DOMUtils;
public class RegattaCompetition extends Composite implements RegattaCompetitionView {
@@ -17,6 +21,7 @@ public class RegattaCompetition extends Composite implements RegattaCompetitionV
}
@UiField FlowPanel regattaSeriesContainerUi;
private final Map<String, RegattaCompetitionSeries> seriesNameToUiComponent = new HashMap<>();
public RegattaCompetition() {
RegattaCompetitionResources.INSTANCE.css().ensureInjected();
@@ -31,8 +36,12 @@ public class RegattaCompetition extends Composite implements RegattaCompetitionV
@Override
public RegattaCompetitionSeriesView addSeriesView(RaceCompetitionFormatSeriesDTO series) {
RegattaCompetitionSeries seriesView = new RegattaCompetitionSeries(series);
seriesNameToUiComponent.put(series.getSeriesName(), seriesView);
regattaSeriesContainerUi.add(seriesView);
return seriesView;
}
public void scrollToSeries(final String seriesName) {
DOMUtils.scrollToTop(seriesNameToUiComponent.get(seriesName));
}
}
@@ -5,4 +5,6 @@ import com.sap.sailing.gwt.home.shared.app.PlaceNavigation;
public interface RegattaRacesNavigationProvider {
PlaceNavigation<?> getRegattaRacesNavigation(String regattaId);
PlaceNavigation<?> getRegattaRacesNavigation(String regattaId, String seriesName);
}
@@ -247,6 +247,12 @@ public abstract class AbstractEventActivity<PLACE extends AbstractEventPlace> ex
EventContext ctx = new EventContext(getCtx()).withRegattaId(regattaId).withRegattaAnalyticsManager(null);
return clientFactory.getNavigator().getEventNavigation(new RegattaRacesPlace(ctx), null, false);
}
@Override
public PlaceNavigation<?> getRegattaRacesNavigation(String regattaId, String prefSeriesName) {
EventContext ctx = new EventContext(getCtx()).withRegattaId(regattaId).withRegattaAnalyticsManager(null);
return clientFactory.getNavigator().getEventNavigation(new RegattaRacesPlace(ctx, prefSeriesName), null, false);
}
public PlaceNavigation<?> getLatesNewsNavigation() {
EventContext ctx = new EventContext(getCtx()).withRegattaId(null).withRegattaAnalyticsManager(null);
@@ -3,6 +3,7 @@ package com.sap.sailing.gwt.home.mobile.places.event.overview.regatta;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Panel;
@@ -19,6 +20,7 @@ import com.sap.sailing.gwt.home.mobile.partials.quickfinder.Quickfinder;
import com.sap.sailing.gwt.home.mobile.places.QuickfinderPresenter;
import com.sap.sailing.gwt.home.mobile.places.event.EventViewBase;
import com.sap.sailing.gwt.home.mobile.places.event.overview.AbstractEventOverview;
import com.sap.sailing.gwt.home.shared.app.PlaceNavigation;
import com.sap.sailing.gwt.ui.client.FlagImageResolver;
import com.sap.sailing.gwt.ui.client.StringMessages;
@@ -54,7 +56,9 @@ public class RegattaOverviewImpl extends AbstractEventOverview {
}
private void setupProgress(Panel container) {
eventStepsUi = new EventSteps(currentPresenter.getRegatta());
final Function<String, PlaceNavigation<?>> racesNavigationFactory = prefSeriesName -> currentPresenter
.getRegattaRacesNavigation(getRegattaId(), prefSeriesName);
eventStepsUi = new EventSteps(currentPresenter.getRegatta(), racesNavigationFactory);
refreshManager.add(eventStepsUi, new GetRegattaWithProgressAction(getEventId(), getRegattaId()));
container.add(eventStepsUi);
}
@@ -1,6 +1,7 @@
package com.sap.sailing.gwt.home.mobile.places.event.races;
import java.util.List;
import java.util.Optional;
import com.sap.sailing.gwt.home.communication.eventview.EventViewDTO;
import com.sap.sailing.gwt.home.desktop.places.event.regatta.racestab.RegattaRacesPlace;
@@ -32,4 +33,9 @@ public class RacesActivity extends AbstractEventActivity<RegattaRacesPlace> impl
initQuickfinder(view, true);
return view;
}
@Override
public Optional<String> getPreferredSeriesName() {
return getPlace().getPreferredSeriesName();
}
}
@@ -1,10 +1,14 @@
package com.sap.sailing.gwt.home.mobile.places.event.races;
import java.util.Optional;
import com.sap.sailing.gwt.home.mobile.places.event.EventViewBase;
public interface RacesView extends EventViewBase {
public interface Presenter extends EventViewBase.Presenter {
Optional<String> getPreferredSeriesName();
}
}
@@ -5,13 +5,16 @@ import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.gwt.home.communication.event.EventReferenceWithStateDTO;
import com.sap.sailing.gwt.home.communication.event.GetCompetitionFormatRacesAction;
import com.sap.sailing.gwt.home.communication.event.RaceCompetitionFormatSeriesDTO;
import com.sap.sailing.gwt.home.communication.event.SimpleCompetitorDTO;
import com.sap.sailing.gwt.home.communication.eventview.RegattaMetadataDTO;
import com.sap.sailing.gwt.home.communication.race.SimpleRaceMetadataDTO;
@@ -27,6 +30,7 @@ import com.sap.sailing.gwt.home.shared.partials.filter.RacesByCompetitorTextBoxF
import com.sap.sailing.gwt.home.shared.partials.placeholder.InfoPlaceholder;
import com.sap.sailing.gwt.home.shared.partials.regattacompetition.RegattaCompetitionPresenter;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sse.gwt.dispatch.shared.commands.ListResult;
public class RacesViewImpl extends AbstractEventView<RacesView.Presenter> implements RacesView {
@@ -71,6 +75,13 @@ public class RacesViewImpl extends AbstractEventView<RacesView.Presenter> implem
protected String getRaceViewerURL(SimpleRaceMetadataDTO raceMetadata, String mode) {
return currentPresenter.getRaceViewerURL(raceMetadata, mode);
}
@Override
public void setData(ListResult<RaceCompetitionFormatSeriesDTO> data) {
super.setData(data);
final Consumer<? super String> scrollCommand = regattaCompetitionUi::scrollToSeries;
Scheduler.get().scheduleDeferred(() -> currentPresenter.getPreferredSeriesName().ifPresent(scrollCommand));
}
}
private class RacesViewImplFilterPresenter extends FilterPresenter<SimpleRaceMetadataDTO, SimpleCompetitorDTO> {
@@ -12,7 +12,7 @@ public abstract class LeaderboardPanelLifecycle<T extends LeaderboardSettings>
protected final Iterable<DetailType> availableDetailTypes;
public LeaderboardPanelLifecycle(StringMessages stringMessages, Iterable<DetailType> availableDetailTypes) {
protected LeaderboardPanelLifecycle(StringMessages stringMessages, Iterable<DetailType> availableDetailTypes) {
this.stringMessages = stringMessages;
this.availableDetailTypes = availableDetailTypes;
}
@@ -66,7 +66,8 @@ public abstract class LeaderboardSettings extends AbstractGenericSerializableSet
showCompetitorBoatInfoColumn = new BooleanSetting("showCompetitorBoatInfoColumn", this, false);
}
public LeaderboardSettings() {
public LeaderboardSettings(boolean showCompetitorBoatInfoColumnDefault) {
showCompetitorBoatInfoColumn.setDefaultValue(showCompetitorBoatInfoColumnDefault);
}
/**
@@ -46,7 +46,8 @@ public abstract class LeaderboardSettingsDialogComponent<T extends LeaderboardSe
protected T initialSettings;
protected Iterable<DetailType> availableDetailTypes;
public LeaderboardSettingsDialogComponent(T initialSettings, StringMessages stringMessages, Iterable<DetailType> availableDetailTypes, boolean canBoatInfoBeShown) {
protected LeaderboardSettingsDialogComponent(T initialSettings, StringMessages stringMessages,
Iterable<DetailType> availableDetailTypes, boolean canBoatInfoBeShown) {
this.initialSettings = initialSettings;
this.stringMessages = stringMessages;
this.canBoatInfoBeShown = canBoatInfoBeShown;
@@ -44,7 +44,7 @@ public class LeaderboardSettingsFactory {
}
public SingleRaceLeaderboardSettings createNewSettingsWithCustomRaceDetails(List<DetailType> raceDetailsToShow) {
SingleRaceLeaderboardSettings defaultSettings = new SingleRaceLeaderboardSettings();
SingleRaceLeaderboardSettings defaultSettings = new SingleRaceLeaderboardSettings(false);
return new SingleRaceLeaderboardSettings(
defaultSettings.getManeuverDetailsToShow(),
defaultSettings.getLegDetailsToShow(),
@@ -11,11 +11,13 @@ import com.sap.sse.common.settings.generic.support.SettingsUtil;
public class MultiRaceLeaderboardPanelLifecycle extends LeaderboardPanelLifecycle<MultiRaceLeaderboardSettings> {
protected final List<String> namesOfRaceColumns;
private final boolean canBoatInfoBeShown;
public MultiRaceLeaderboardPanelLifecycle(AbstractLeaderboardDTO leaderboard, StringMessages stringMessages,
Iterable<DetailType> availableDetailTypes) {
super(stringMessages, availableDetailTypes);
this.namesOfRaceColumns = leaderboard != null ? leaderboard.getNamesOfRaceColumns() : new ArrayList<String>();
this.canBoatInfoBeShown = leaderboard != null ? !leaderboard.canBoatsOfCompetitorsChangePerRace : false;
}
@Override
public MultiRaceLeaderboardSettings extractUserSettings(MultiRaceLeaderboardSettings currentLeaderboardSettings) {
@@ -55,7 +57,8 @@ public class MultiRaceLeaderboardPanelLifecycle extends LeaderboardPanelLifecycl
@Override
public MultiRaceLeaderboardSettingsDialogComponent getSettingsDialogComponent(MultiRaceLeaderboardSettings settings) {
return new MultiRaceLeaderboardSettingsDialogComponent(settings, namesOfRaceColumns, stringMessages, availableDetailTypes, false);
return new MultiRaceLeaderboardSettingsDialogComponent(settings, namesOfRaceColumns, stringMessages,
availableDetailTypes, canBoatInfoBeShown);
}
@Override
@@ -28,7 +28,7 @@ public class MultiRaceLeaderboardSettings extends LeaderboardSettings {
public MultiRaceLeaderboardSettings() {
super();
super(false);
}
public MultiRaceLeaderboardSettings(Collection<DetailType> maneuverDetailsToShow,
@@ -48,6 +48,7 @@ public class MultiRaceLeaderboardSettings extends LeaderboardSettings {
}
public MultiRaceLeaderboardSettings(Iterable<String> namesOfRaceColumnsToShow) {
super(false);
this.namesOfRaceColumnsToShow.setValues(namesOfRaceColumnsToShow);
}
@@ -9,8 +9,10 @@ import com.sap.sse.gwt.client.dialog.DataEntryDialog;
public class OverallLeaderboardSettingsDialogComponent extends MultiRaceLeaderboardSettingsDialogComponent {
public OverallLeaderboardSettingsDialogComponent(MultiRaceLeaderboardSettings initialSettings, List<String> allRaceColumnNames, StringMessages stringMessages, Iterable<DetailType> availableDetailTypes) {
super(initialSettings, allRaceColumnNames, stringMessages, availableDetailTypes, false);
public OverallLeaderboardSettingsDialogComponent(MultiRaceLeaderboardSettings initialSettings,
List<String> allRaceColumnNames, StringMessages stringMessages, Iterable<DetailType> availableDetailTypes) {
super(initialSettings, allRaceColumnNames, stringMessages, availableDetailTypes,
/* canBoatInfoBeShown */ false);
}
@Override
@@ -10,15 +10,16 @@ public class SingleRaceLeaderboardSettings extends LeaderboardSettings {
private BooleanSetting showRaceRankColumn;
public SingleRaceLeaderboardSettings() {
super();
public SingleRaceLeaderboardSettings(boolean showCompetitorBoatInfoColumnDefault) {
super(showCompetitorBoatInfoColumnDefault);
}
public SingleRaceLeaderboardSettings(Collection<DetailType> maneuverDetailsToShow,
Collection<DetailType> legDetailsToShow, Collection<DetailType> raceDetailsToShow,
Collection<DetailType> overallDetailsToShow, Long delayBetweenAutoAdvancesInMilliseconds,
boolean showAddedScores, boolean showCompetitorShortNameColumn,
boolean showCompetitorFullNameColumn, boolean showCompetitorBoatInfoColumn, boolean isCompetitorNationalityColumnVisible,boolean showRaceRankColumn) {
boolean showAddedScores, boolean showCompetitorShortNameColumn, boolean showCompetitorFullNameColumn,
boolean showCompetitorBoatInfoColumn, boolean isCompetitorNationalityColumnVisible,
boolean showRaceRankColumn) {
super(maneuverDetailsToShow, legDetailsToShow, raceDetailsToShow, overallDetailsToShow,
delayBetweenAutoAdvancesInMilliseconds, showAddedScores, showCompetitorShortNameColumn,
showCompetitorFullNameColumn, showCompetitorBoatInfoColumn, isCompetitorNationalityColumnVisible);
@@ -13,11 +13,11 @@ import com.sap.sse.gwt.client.dialog.DataEntryDialog.Validator;
public class SingleRaceLeaderboardSettingsDialogComponent
extends LeaderboardSettingsDialogComponent<SingleRaceLeaderboardSettings> {
protected CheckBox showRaceRankColumn;
private CheckBox showRaceRankColumn;
public SingleRaceLeaderboardSettingsDialogComponent(SingleRaceLeaderboardSettings initialSettings,
StringMessages stringMessages, Iterable<DetailType> availableDetailTypes) {
super(initialSettings, stringMessages, availableDetailTypes, true /*canBoatInfoBeShown*/);
super(initialSettings, stringMessages, availableDetailTypes, /* canBoatInfoBeShown */ true);
}
@Override
@@ -33,7 +33,8 @@ public class SingleRaceLeaderboardSettingsDialogComponent
1000l * (delayBetweenAutoAdvancesValue == null ? 0l : delayBetweenAutoAdvancesValue.longValue()),
/* showAddedScores */ showAddedScoresCheckBox.getValue().booleanValue(),
showCompetitorShortNameColumnCheckBox.getValue(), showCompetitorFullNameColumnCheckBox.getValue(),
showCompetitorBoatInfoColumnCheckBox.getValue(), isCompetitorNationalityColumnVisible.getValue(), showRaceRankColumn.getValue());
showCompetitorBoatInfoColumnCheckBox.getValue(), isCompetitorNationalityColumnVisible.getValue(),
showRaceRankColumn.getValue());
return newSettings;
}
@@ -16,6 +16,12 @@ public class RaceCompetitorSelectionModel extends CompetitorSelectionModel imple
boatsOfCompetitors = new HashMap<>();
}
public RaceCompetitorSelectionModel(boolean hasMultiSelection, Map<CompetitorDTO, BoatDTO> boatsOfCompetitors) {
super(hasMultiSelection);
this.boatsOfCompetitors = boatsOfCompetitors;
addAll(boatsOfCompetitors.keySet());
}
public RaceCompetitorSelectionModel(boolean hasMultiSelection, CompetitorColorProvider competitorColorProvider, Map<CompetitorDTO, BoatDTO> boatsOfCompetitors) {
super(hasMultiSelection, competitorColorProvider);
this.boatsOfCompetitors = boatsOfCompetitors;
@@ -29,11 +35,11 @@ public class RaceCompetitorSelectionModel extends CompetitorSelectionModel imple
@Override
public void setBoat(CompetitorDTO competitor, BoatDTO boat) {
boatsOfCompetitors.put(competitor, boat);
boatsOfCompetitors.put(competitor, boat);
}
@Override
public BoatDTO getBoat(CompetitorDTO competitor) {
return boatsOfCompetitors.get(competitor);
return boatsOfCompetitors.get(competitor);
}
}
@@ -2,14 +2,13 @@ package com.sap.sailing.gwt.ui.client;
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.rpc.ServiceDefTarget;
import com.sap.sse.gwt.client.EntryPointHelper;
import com.sap.sse.gwt.client.ServiceRoutingProvider;
/**
* Set of convenience factory methods that create a {@link SailingServiceAsync} instance.
*/
public abstract class SailingServiceHelper {
private final static String moduleBaseURL = GWT.getModuleBaseURL();
private SailingServiceHelper() {
}
@@ -41,20 +40,20 @@ public abstract class SailingServiceHelper {
* @param sameBundle {@code false} if using {@link SailingService} from a different OSGi bundle (e.g. dashboards), {@code true} otherwise
*/
public static SailingServiceAsync createSailingServiceInstance(boolean sameBundle, ServiceRoutingProvider routingProvider) {
SailingServiceAsync service = GWT.create(SailingService.class);
ServiceDefTarget serviceToRegister = (ServiceDefTarget) service;
StringBuilder baseURL = new StringBuilder();
baseURL.append(moduleBaseURL.substring(0, moduleBaseURL.lastIndexOf('/', moduleBaseURL.length() - 2) + 1));
if (!sameBundle) {
baseURL.append(RemoteServiceMappingConstants.WEB_CONTEXT_PATH).append("/");
}
baseURL.append(RemoteServiceMappingConstants.sailingServiceRemotePath);
final SailingServiceAsync service = GWT.create(SailingService.class);
final ServiceDefTarget serviceToRegister = (ServiceDefTarget) service;
final StringBuilder servicePath = new StringBuilder(RemoteServiceMappingConstants.sailingServiceRemotePath);
if (routingProvider != null) {
baseURL.append(routingProvider.routingSuffixPath());
servicePath.append(routingProvider.routingSuffixPath());
}
final String servicePathWithRoutingSuffix = servicePath.toString();
if (sameBundle) {
EntryPointHelper.registerASyncService(serviceToRegister, servicePathWithRoutingSuffix);
} else {
EntryPointHelper.registerASyncService(serviceToRegister, RemoteServiceMappingConstants.WEB_CONTEXT_PATH, servicePathWithRoutingSuffix);
}
serviceToRegister.setServiceEntryPoint(baseURL.toString());
return service;
}
}
@@ -1441,7 +1441,7 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages,
String courseGeometry();
String degreesToWind(String diff);
String miniLeeaderboardLegendText();
String currentOfTotal(int current, int total);
String currentOfTotalRaces(int current, @PluralCount(DefaultRule_1_0n.class) int total);
String noFinishedRaces();
String racesOverview();
String listFormatLabel();
@@ -1428,7 +1428,8 @@ numberOfRacesShort=# Races
courseGeometry=Course Geometry
degreesToWind={0}° to wind
miniLeeaderboardLegendText=Shows the points along with the number of races per competitor
currentOfTotal={0,number}/{1,number}
currentOfTotalRaces={0,number}/{1,number} races
currentOfTotalRaces[one]={0,number}/{1,number} race
noFinishedRaces=There are no finished races yet
racesOverview=Races overview
listFormatLabel=List format
@@ -1411,7 +1411,8 @@ numberOfRacesShort=# Rennen
courseGeometry=Kursgeometrie
degreesToWind={0}° zum Wind
miniLeeaderboardLegendText=Es werden die Punkte und die Anzahl Rennen je Team angezeigt
currentOfTotal={0,number}/{1,number}
currentOfTotalRaces={0,number}/{1,number} Rennen
currentOfTotalRaces[one]={0,number}/{1,number} Rennen
noFinishedRaces=Es wurden noch keine Wettfahrten beendet.
racesOverview=Wettfahrtübersicht
listFormatLabel=Listenansicht
@@ -1424,7 +1424,6 @@ numberOfRacesShort=Nº regatas
courseGeometry=Geometría de la regata
degreesToWind={0}° respecto al viento
miniLeeaderboardLegendText=Muestra los puntos y el número de carreras por competidor
currentOfTotal={0,number}/{1,number}
noFinishedRaces=Todavía no ha finalizado ninguna carrera
racesOverview=Resumen de carreras
listFormatLabel=Formato de lista
@@ -1424,7 +1424,6 @@ numberOfRacesShort=Nombre de courses
courseGeometry=Géométrie du parcours
degreesToWind={0}° au vent
miniLeeaderboardLegendText=Affiche les points associés au nombre de courses par concurrent
currentOfTotal={0,number}/{1,number}
noFinishedRaces=Aucune course terminée pour l''instant.
racesOverview=Synthèse des courses
listFormatLabel=Format de liste
@@ -1424,7 +1424,6 @@ numberOfRacesShort=# レース
courseGeometry=コース配置
degreesToWind=風と {0}°
miniLeeaderboardLegendText=競技者別にレース数とともに得点を表示
currentOfTotal={0,number}/{1,number}
noFinishedRaces=フィニッシュしたレースがまだありません。
racesOverview=レース概要
listFormatLabel=レース一覧
@@ -1424,7 +1424,6 @@ numberOfRacesShort=Nº de corridas
courseGeometry=Geometria do percurso
degreesToWind={0}° para vento
miniLeeaderboardLegendText=Exibe os pontos juntamente com o número de corridas por competidor
currentOfTotal={0,number}/{1,number}
noFinishedRaces=Ainda não existem corridas concluídas.
racesOverview=Síntese de corridas
listFormatLabel=Formato de lista
@@ -1424,7 +1424,6 @@ numberOfRacesShort=Гонки
courseGeometry=Геометрия курса
degreesToWind={0}° к ветру
miniLeeaderboardLegendText=Показывает баллы и число гонок каждого участника
currentOfTotal={0,number}/{1,number}
noFinishedRaces=Завершенных гонок еще нет
racesOverview=Обзор гонок
listFormatLabel=Формат списка
@@ -1424,7 +1424,6 @@ numberOfRacesShort=比赛轮次数
courseGeometry=场地几何结构
degreesToWind={0}°(相对于风)
miniLeeaderboardLegendText=显示分数以及各参赛队的比赛轮次数
currentOfTotal={0,number}/{1,number}
noFinishedRaces=没有结束的比赛轮次
racesOverview=比赛轮次总览
listFormatLabel=清单格式
@@ -885,6 +885,7 @@ public class RaceMap extends AbstractCompositeComponent<RaceMapSettings> impleme
@Override
public void timeChanged(final Date newTime, final Date oldTime) {
boolean isRedraw = oldTime == null;
if (newTime != null && isMapInitialized) {
if (raceIdentifier != null) {
RegattaAndRaceIdentifier race = raceIdentifier;
@@ -894,7 +895,8 @@ public class RaceMap extends AbstractCompositeComponent<RaceMapSettings> impleme
final com.sap.sse.common.Util.Triple<Map<CompetitorDTO, Date>, Map<CompetitorDTO, Date>, Map<CompetitorDTO, Boolean>> fromAndToAndOverlap =
fixesAndTails.computeFromAndTo(newTime, competitorsToShow, settings.getEffectiveTailLengthInMilliseconds());
// Request map data update, possibly in two calls; see method details
callGetRaceMapDataForAllOverlappingAndTipsOfNonOverlappingAndGetBoatPositionsForAllOthers(fromAndToAndOverlap, race, newTime, transitionTimeInMillis, competitorsToShow);
callGetRaceMapDataForAllOverlappingAndTipsOfNonOverlappingAndGetBoatPositionsForAllOthers(
fromAndToAndOverlap, race, newTime, transitionTimeInMillis, competitorsToShow, isRedraw);
// draw the wind into the map, get the combined wind
List<String> windSourceTypeNames = new ArrayList<String>();
windSourceTypeNames.add(WindSourceType.EXPEDITION.name());
@@ -950,6 +952,7 @@ public class RaceMap extends AbstractCompositeComponent<RaceMapSettings> impleme
public WindInfoForRaceDTO getLastCombinedWindTrackInfoDTO() {
return lastCombinedWindTrackInfoDTO;
}
/**
* Requests updates for map data and, when received, updates the map structures accordingly.
* <p>
@@ -970,12 +973,14 @@ public class RaceMap extends AbstractCompositeComponent<RaceMapSettings> impleme
* current tail length setting the new tail segment does not overlap with the old one, requiring a full load of the
* entire tail data for that competitor. For these non-overlapping requests, this method creates a
* {@link GetBoatPositionsAction} request loading the entire tail required, but not quick ranks, sidelines and mark
* positions. Updating the results of this call is done in {@link #updateBoatPositions(Date, long, Map, Iterable, Map, boolean)}.
* positions. Updating the results of this call is done in
* {@link #updateBoatPositions(Date, long, Map, Iterable, Map, boolean)}.
* <p>
*/
private void callGetRaceMapDataForAllOverlappingAndTipsOfNonOverlappingAndGetBoatPositionsForAllOthers(
final Triple<Map<CompetitorDTO, Date>, Map<CompetitorDTO, Date>, Map<CompetitorDTO, Boolean>> fromAndToAndOverlap,
RegattaAndRaceIdentifier race, final Date newTime, final long transitionTimeInMillis, final Iterable<CompetitorDTO> competitorsToShow) {
RegattaAndRaceIdentifier race, final Date newTime, final long transitionTimeInMillis,
final Iterable<CompetitorDTO> competitorsToShow, boolean isRedraw) {
final Map<CompetitorDTO, Date> fromTimesForQuickCall = new HashMap<>();
final Map<CompetitorDTO, Date> toTimesForQuickCall = new HashMap<>();
final Map<CompetitorDTO, Date> fromTimesForNonOverlappingTailsCall = new HashMap<>();
@@ -1009,7 +1014,8 @@ public class RaceMap extends AbstractCompositeComponent<RaceMapSettings> impleme
(settings.isShowSimulationOverlay() ? simulationOverlay.getLegIdentifier() : null),
raceCompetitorSet.getMd5OfIdsAsStringOfCompetitorParticipatingInRaceInAlphanumericOrderOfTheirID(), newTime, settings.isShowEstimatedDuration()),
GET_RACE_MAP_DATA_CATEGORY,
getRaceMapDataCallback(newTime, transitionTimeInMillis, fromAndToAndOverlap.getC(), competitorsToShow, ++boatPositionRequestIDCounter));
getRaceMapDataCallback(newTime, transitionTimeInMillis, fromAndToAndOverlap.getC(), competitorsToShow,
++boatPositionRequestIDCounter, isRedraw));
// next, if necessary, do the full thing; the two calls have different action classes, so throttling should not drop one for the other
if (!fromTimesForNonOverlappingTailsCall.keySet().isEmpty()) {
asyncActionsExecutor.execute(new GetBoatPositionsAction(sailingService, race, fromTimesForNonOverlappingTailsCall, toTimesForNonOverlappingTailsCall,
@@ -1037,7 +1043,7 @@ public class RaceMap extends AbstractCompositeComponent<RaceMapSettings> impleme
final Date newTime,
final long transitionTimeInMillis,
final Map<CompetitorDTO, Boolean> hasTailOverlapForCompetitor,
final Iterable<CompetitorDTO> competitorsToShow, final int requestID) {
final Iterable<CompetitorDTO> competitorsToShow, final int requestID, boolean isRedraw) {
return new MarkedAsyncCallback<>(new AsyncCallback<RaceMapDataDTO>() {
@Override
public void onFailure(Throwable caught) {
@@ -1066,10 +1072,14 @@ public class RaceMap extends AbstractCompositeComponent<RaceMapSettings> impleme
// Do boat specific actions
Map<CompetitorDTO, List<GPSFixDTOWithSpeedWindTackAndLegType>> boatData = raceMapDataDTO.boatPositions;
updateBoatPositions(newTime, transitionTimeInMillis, hasTailOverlapForCompetitor, competitorsToShow, boatData, /* updateTailsOnly */ false);
if (douglasMarkers != null) {
removeAllMarkDouglasPeuckerpoints();
if (!isRedraw) {
// only remove markers if the time is actually changed
if (douglasMarkers != null) {
removeAllMarkDouglasPeuckerpoints();
}
maneuverMarkersAndLossIndicators.clearAllManeuverMarkers();
}
maneuverMarkersAndLossIndicators.clearAllManeuverMarkers();
if (requiresCoordinateSystemUpdateWhenCoursePositionAndWindDirectionIsKnown) {
updateCoordinateSystemFromSettings();
}
@@ -2433,7 +2443,7 @@ public class RaceMap extends AbstractCompositeComponent<RaceMapSettings> impleme
showCompetitorInfoOnMap(timer.getTime(), -1, competitorSelection.getSelectedFilteredCompetitors());
} else {
// adding a single competitor; may need to re-load data, so refresh:
timeChanged(timer.getTime(), null);
redraw();
}
} else {
// only change highlighting
@@ -2446,7 +2456,7 @@ public class RaceMap extends AbstractCompositeComponent<RaceMapSettings> impleme
// seems like an internal error not to find the lowlighted marker; but maybe the
// competitor was added late to the race;
// data for newly selected competitor supposedly missing; refresh
timeChanged(timer.getTime(), null);
redraw();
}
}
// Now update tails for all competitors because selection change may also affect all unselected competitors
@@ -2469,13 +2479,13 @@ public class RaceMap extends AbstractCompositeComponent<RaceMapSettings> impleme
if (isShowAnyHelperLines()) {
// helper lines depend on which competitor is visible, because the *visible* leader is used for
// deciding which helper lines to show:
timeChanged(timer.getTime(), null);
redraw();
} else {
// try a more incremental update otherwise
if (settings.isShowOnlySelectedCompetitors()) {
// if selection is now empty, show all competitors
if (Util.isEmpty(competitorSelection.getSelectedCompetitors())) {
timeChanged(timer.getTime(), null);
redraw();
} else {
// otherwise remove only deselected competitor's boat images and tail
BoatOverlay removedBoatOverlay = boatOverlays.remove(competitor);
@@ -2755,12 +2765,12 @@ public class RaceMap extends AbstractCompositeComponent<RaceMapSettings> impleme
@Override
public void competitorsListChanged(Iterable<CompetitorDTO> competitors) {
timeChanged(timer.getTime(), null);
redraw();
}
@Override
public void filteredCompetitorsListChanged(Iterable<CompetitorDTO> filteredCompetitors) {
timeChanged(timer.getTime(), null);
redraw();
}
@Override
@@ -6,7 +6,6 @@ import java.util.List;
import java.util.Map;
import com.sap.sailing.domain.common.DetailType;
import com.sap.sailing.domain.common.LeaderboardType;
import com.sap.sailing.domain.common.dto.AbstractLeaderboardDTO;
import com.sap.sailing.domain.common.dto.CompetitorDTO;
import com.sap.sailing.domain.common.dto.LeaderboardDTO;
@@ -96,8 +95,7 @@ public class MultiRaceLeaderboardPanel extends LeaderboardPanel<MultiRaceLeaderb
@Override
protected boolean canShowCompetitorBoatInfo() {
boolean isMetaLeaderboard = leaderboard.type == LeaderboardType.FlexibleMetaLeaderboard || leaderboard.type == LeaderboardType.RegattaMetaLeaderboard;
return this.leaderboard.canBoatsOfCompetitorsChangePerRace == false && !isMetaLeaderboard;
return !this.leaderboard.canBoatsOfCompetitorsChangePerRace && !leaderboard.type.isMetaLeaderboard();
}
@Override
@@ -152,8 +150,8 @@ public class MultiRaceLeaderboardPanel extends LeaderboardPanel<MultiRaceLeaderb
@Override
public SettingsDialogComponent<MultiRaceLeaderboardSettings> getSettingsDialogComponent(
MultiRaceLeaderboardSettings useTheseSettings) {
return new MultiRaceLeaderboardSettingsDialogComponent((MultiRaceLeaderboardSettings) useTheseSettings,
leaderboard.getNamesOfRaceColumns(), stringMessages, availableDetailTypes, !leaderboard.canBoatsOfCompetitorsChangePerRace);
return new MultiRaceLeaderboardSettingsDialogComponent(useTheseSettings, leaderboard.getNamesOfRaceColumns(),
stringMessages, availableDetailTypes, canShowCompetitorBoatInfo());
}
@Override
@@ -2,6 +2,9 @@
package com.sap.sailing.gwt.ui.raceboard;
import java.util.Collections;
import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Style.Unit;
@@ -12,6 +15,7 @@ import com.google.gwt.user.client.ui.DockLayoutPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.RootLayoutPanel;
import com.sap.sailing.domain.common.DetailType;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.gwt.common.authentication.FixedSailingAuthentication;
import com.sap.sailing.gwt.common.authentication.SAPSailingHeaderWithAuthentication;
import com.sap.sailing.gwt.common.communication.routing.ProvidesLeaderboardRouting;
@@ -26,13 +30,15 @@ import com.sap.sailing.gwt.ui.client.RemoteServiceMappingConstants;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.shared.RaceWithCompetitorsAndBoatsDTO;
import com.sap.sailing.gwt.ui.shared.RaceboardDataDTO;
import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO;
import com.sap.sse.common.settings.Settings;
import com.sap.sse.gwt.client.EntryPointHelper;
import com.sap.sse.gwt.client.async.AsyncActionsExecutor;
import com.sap.sse.gwt.client.formfactor.DeviceDetector;
import com.sap.sse.gwt.client.player.Timer;
import com.sap.sse.gwt.client.player.Timer.PlayModes;
import com.sap.sse.gwt.client.shared.components.Component;
import com.sap.sse.gwt.client.shared.perspective.PerspectiveCompositeSettings;
import com.sap.sse.gwt.client.shared.settings.ComponentContext;
import com.sap.sse.gwt.client.shared.settings.DefaultOnSettingsLoadedCallback;
import com.sap.sse.gwt.settings.SettingsToUrlSerializer;
import com.sap.sse.security.ui.authentication.generic.sapheader.SAPHeaderWithAuthentication;
@@ -40,7 +46,6 @@ import com.sap.sse.security.ui.settings.ComponentContextWithSettingsStorage;
import com.sap.sse.security.ui.settings.StoredSettingsLocation;
public class RaceBoardEntryPoint extends AbstractSailingEntryPoint implements ProvidesLeaderboardRouting {
private RaceWithCompetitorsAndBoatsDTO selectedRace;
/**
* Controls the predefined mode into which to switch or configure the race viewer.
@@ -48,131 +53,83 @@ public class RaceBoardEntryPoint extends AbstractSailingEntryPoint implements Pr
private final MediaServiceAsync mediaService = GWT.create(MediaService.class);
private RaceboardContextDefinition raceboardContextDefinition;
private String leaderBoardName;
@Override
protected void doOnModuleLoad() {
super.doOnModuleLoad();
EntryPointHelper.registerASyncService((ServiceDefTarget) mediaService,
RemoteServiceMappingConstants.mediaServiceRemotePath);
raceboardContextDefinition = new SettingsToUrlSerializer()
.deserializeFromCurrentLocation(new RaceboardContextDefinition());
final RaceBoardModes finalMode;
if (raceboardContextDefinition.getMode() != null) {
finalMode = RaceBoardModes.valueOf(raceboardContextDefinition.getMode());
} else {
finalMode = null;
}
if (raceboardContextDefinition.getRegattaName() == null || raceboardContextDefinition.getRegattaName().isEmpty()
|| raceboardContextDefinition.getRaceName() == null
|| raceboardContextDefinition.getRaceName().isEmpty()
|| raceboardContextDefinition.getLeaderboardName() == null
|| raceboardContextDefinition.getLeaderboardName().isEmpty()) {
createErrorPage(getStringMessages().requiresRegattaRaceAndLeaderboard());
return;
}
leaderBoardName = raceboardContextDefinition.getLeaderboardName();
AsyncCallback<RaceboardDataDTO> asyncCallback = new AsyncCallback<RaceboardDataDTO>() {
@Override
public void onSuccess(RaceboardDataDTO raceboardData) {
if (!raceboardData.isValidLeaderboard()) {
createErrorPage(getStringMessages().noSuchLeaderboard());
return;
}
if (raceboardContextDefinition.getEventId() != null && !raceboardData.isValidEvent()) {
createErrorPage(getStringMessages().noSuchEvent());
}
if (raceboardContextDefinition.getLeaderboardGroupName() != null) {
if (!raceboardData.isValidLeaderboardGroup()) {
createErrorPage(getStringMessages().leaderboardNotContainedInLeaderboardGroup(
raceboardContextDefinition.getLeaderboardName(),
raceboardContextDefinition.getLeaderboardGroupName()));
return;
}
if (raceboardContextDefinition.getEventId() != null && raceboardData.isValidLeaderboardGroup()
&& !raceboardData.isValidEvent()) {
createErrorPage(getStringMessages().leaderboardGroupNotContainedInEvent(
raceboardContextDefinition.getLeaderboardGroupName(),
raceboardContextDefinition.getEventId().toString()));
return;
}
}
if (raceboardData.getRace() == null) {
createErrorPage(getStringMessages().couldNotFindRaceInRegatta(
raceboardContextDefinition.getRaceName(), raceboardContextDefinition.getRegattaName()));
return;
}
final boolean showChartMarkEditMediaButtonsAndVideo = !DeviceDetector.isMobile();
final StoredSettingsLocation storageDefinition = StoredSettingsLocationFactory
.createStoredSettingsLocatorForRaceBoard(raceboardContextDefinition,
finalMode != null ? finalMode.name() : null);
// TODO bug2822: also determine regatta's boat change parameter and parameterize Leaderboard settings
// --> show boat info column yes/no?
getSailingService().determineDetailTypesForCompetitorChart(
raceboardContextDefinition.getLeaderboardGroupName(),
raceboardData.getRace().getRaceIdentifier(), new AsyncCallback<Iterable<DetailType>>() {
@Override
public void onFailure(Throwable caught) {
reportError("Error trying to create the raceboard: " + caught.getMessage());
}
@Override
public void onSuccess(Iterable<DetailType> chartAllowedTypes) {
getSailingService().getAvailableDetailTypesForLeaderboard(
raceboardContextDefinition.getLeaderboardName(),
raceboardData.getRace().getRaceIdentifier(), new AsyncCallback<Iterable<DetailType>>() {
@Override
public void onFailure(Throwable caught) {
reportError(
"Error trying to create the raceboard: " + caught.getMessage());
}
@Override
public void onSuccess(Iterable<DetailType> leaderboardAllowedTypes) {
final RaceBoardPerspectiveLifecycle lifeCycle = new RaceBoardPerspectiveLifecycle(
StringMessages.INSTANCE, chartAllowedTypes, getUserService(),
leaderboardAllowedTypes);
RaceBoardComponentContext componentContext = new RaceBoardComponentContext(
lifeCycle, getUserService(), storageDefinition);
componentContext.getInitialSettings(
new DefaultOnSettingsLoadedCallback<PerspectiveCompositeSettings<RaceBoardPerspectiveOwnSettings>>() {
@Override
public void onSuccess(
PerspectiveCompositeSettings<RaceBoardPerspectiveOwnSettings> initialSettings) {
Timer timer = new Timer(PlayModes.Replay, 1000l);
final RaceBoardPanel raceBoardPanel = createPerspectivePage(
null, componentContext, initialSettings,
raceboardData,
showChartMarkEditMediaButtonsAndVideo,
lifeCycle, timer, leaderboardAllowedTypes);
if (finalMode != null) {
finalMode.getMode().applyTo(raceBoardPanel);
}
}
});
}
});
}
checkRaceboardContextPreConditions(new RaceboardContextDefinition()).ifPresent(raceboardContextDefinition -> {
RaceBoardEntryPoint.this.raceboardContextDefinition = raceboardContextDefinition;
final String modeString = raceboardContextDefinition.getMode();
final RaceBoardModes mode = modeString == null ? null : RaceBoardModes.valueOf(modeString);
checkRaceboardDataConditions(raceboardData -> {
loadAvailableDetailTypes(raceboardData, (chartDetailTypes, lbDetailTypes) -> {
loadLeaderboardInformation(leaderboard -> {
initLifecycle(mode, chartDetailTypes, lbDetailTypes, leaderboard, (lifecycle, compContext) -> {
loadInitialSettings(compContext, settings -> {
createPerspective(mode, compContext, settings, raceboardData, lifecycle, lbDetailTypes);
});
});
}
});
});
});
});
}
private <C extends RaceboardContextDefinition> Optional<C> checkRaceboardContextPreConditions(C settings) {
final C raceboardContext = new SettingsToUrlSerializer().deserializeFromCurrentLocation(settings);
if (raceboardContext.getRegattaName() == null || raceboardContext.getRegattaName().isEmpty()
|| raceboardContext.getRaceName() == null || raceboardContext.getRaceName().isEmpty()
|| raceboardContext.getLeaderboardName() == null || raceboardContext.getLeaderboardName().isEmpty()) {
createErrorPage(getStringMessages().requiresRegattaRaceAndLeaderboard());
return Optional.empty();
}
return Optional.of(raceboardContext);
}
@Override
public void onFailure(Throwable caught) {
reportError("Error trying to create the raceboard: " + caught.getMessage());
}
};
private void checkRaceboardDataConditions(final Consumer<RaceboardDataDTO> callback) {
getSailingService().getRaceboardData(raceboardContextDefinition.getRegattaName(),
raceboardContextDefinition.getRaceName(), raceboardContextDefinition.getLeaderboardName(),
raceboardContextDefinition.getLeaderboardGroupName(), raceboardContextDefinition.getEventId(),
asyncCallback);
new AbstractRaceBoardAsyncCallback<RaceboardDataDTO>() {
@Override
public void onSuccess(RaceboardDataDTO raceboardData) {
if (!raceboardData.isValidLeaderboard()) {
createErrorPage(getStringMessages().noSuchLeaderboard());
return;
}
if (raceboardContextDefinition.getEventId() != null && !raceboardData.isValidEvent()) {
createErrorPage(getStringMessages().noSuchEvent());
}
if (raceboardContextDefinition.getLeaderboardGroupName() != null) {
if (!raceboardData.isValidLeaderboardGroup()) {
createErrorPage(getStringMessages().leaderboardNotContainedInLeaderboardGroup(
raceboardContextDefinition.getLeaderboardName(),
raceboardContextDefinition.getLeaderboardGroupName()));
return;
}
if (raceboardContextDefinition.getEventId() != null
&& raceboardData.isValidLeaderboardGroup() && !raceboardData.isValidEvent()) {
createErrorPage(getStringMessages().leaderboardGroupNotContainedInEvent(
raceboardContextDefinition.getLeaderboardGroupName(),
raceboardContextDefinition.getEventId().toString()));
return;
}
}
if (raceboardData.getRace() == null) {
createErrorPage(getStringMessages().couldNotFindRaceInRegatta(
raceboardContextDefinition.getRaceName(),
raceboardContextDefinition.getRegattaName()));
return;
}
callback.accept(raceboardData);
}
});
}
private void createErrorPage(String message) {
final DockLayoutPanel vp = new DockLayoutPanel(Unit.PX);
final SAPHeaderWithAuthentication header = new SAPSailingHeaderWithAuthentication();
@@ -183,31 +140,99 @@ public class RaceBoardEntryPoint extends AbstractSailingEntryPoint implements Pr
infoText.getElement().getStyle().setMargin(1, Unit.EM);
vp.add(infoText);
// TODO: Styling of error page slightly differs from the other usages of SAPSailingHeaderWithAuthentication
// because of the root font-size. Adjustments are postponed because they might affect the hole page content.
// because of the root font-size. Adjustments are postponed because they might affect the whole page content.
}
private RaceBoardPanel createPerspectivePage(Component<?> parent,
private void loadAvailableDetailTypes(final RaceboardDataDTO raceboardData,
final BiConsumer<Iterable<DetailType>, Iterable<DetailType>> callback) {
final RegattaAndRaceIdentifier raceIdentifier = raceboardData.getRace().getRaceIdentifier();
getSailingService().determineDetailTypesForCompetitorChart(raceboardContextDefinition.getLeaderboardGroupName(),
raceIdentifier, new AbstractRaceBoardAsyncCallback<Iterable<DetailType>>() {
@Override
public void onSuccess(Iterable<DetailType> allowedChartDetailTypes) {
final String leaderboardName = raceboardContextDefinition.getLeaderboardName();
getSailingService().getAvailableDetailTypesForLeaderboard(leaderboardName, raceIdentifier,
new AbstractRaceBoardAsyncCallback<Iterable<DetailType>>() {
@Override
public void onSuccess(Iterable<DetailType> allowedLeaderboardDetailTypes) {
callback.accept(allowedChartDetailTypes, allowedLeaderboardDetailTypes);
}
});
}
});
}
private void loadLeaderboardInformation(final Consumer<StrippedLeaderboardDTO> callback) {
getSailingService().getLeaderboard(raceboardContextDefinition.getLeaderboardName(),
new AbstractRaceBoardAsyncCallback<StrippedLeaderboardDTO>() {
@Override
public void onSuccess(StrippedLeaderboardDTO result) {
callback.accept(result);
}
});
}
private void initLifecycle(final RaceBoardModes raceBoardMode, final Iterable<DetailType> chartDetailTypes,
final Iterable<DetailType> leaderboardDetailTypes, final StrippedLeaderboardDTO leaderboard,
final BiConsumer<RaceBoardPerspectiveLifecycle, RaceBoardComponentContext> callback) {
final StoredSettingsLocation storageDefinition = StoredSettingsLocationFactory
.createStoredSettingsLocatorForRaceBoard(raceboardContextDefinition,
raceBoardMode != null ? raceBoardMode.name() : null);
final RaceBoardPerspectiveLifecycle lifeCycle = new RaceBoardPerspectiveLifecycle(leaderboard,
StringMessages.INSTANCE, chartDetailTypes, getUserService(), leaderboardDetailTypes);
RaceBoardComponentContext componentContext = new RaceBoardComponentContext(lifeCycle, getUserService(),
storageDefinition);
callback.accept(lifeCycle, componentContext);
}
private <S extends Settings> void loadInitialSettings(final ComponentContext<S> ctx, final Consumer<S> callback) {
ctx.getInitialSettings(new DefaultOnSettingsLoadedCallback<S>() {
@Override
public void onSuccess(S settings) {
callback.accept(settings);
}
});
}
private void createPerspective(final RaceBoardModes raceBoardMode,
ComponentContextWithSettingsStorage<PerspectiveCompositeSettings<RaceBoardPerspectiveOwnSettings>> context,
PerspectiveCompositeSettings<RaceBoardPerspectiveOwnSettings> settings, RaceboardDataDTO raceboardData,
boolean showChartMarkEditMediaButtonsAndVideo, RaceBoardPerspectiveLifecycle raceLifeCycle, Timer timer,
Iterable<DetailType> availableDetailTypes) {
selectedRace = raceboardData.getRace();
RaceBoardPerspectiveLifecycle raceLifeCycle, Iterable<DetailType> availableDetailTypes) {
final Timer timer = new Timer(PlayModes.Replay, 1000l);
final boolean showChartMarkEditMediaButtonsAndVideo = !DeviceDetector.isMobile();
final RaceWithCompetitorsAndBoatsDTO selectedRace = raceboardData.getRace();
Window.setTitle(selectedRace.getName());
AsyncActionsExecutor asyncActionsExecutor = new AsyncActionsExecutor();
RaceTimesInfoProvider raceTimesInfoProvider = new RaceTimesInfoProvider(getSailingService(), asyncActionsExecutor, this,
Collections.singletonList(selectedRace.getRaceIdentifier()), 5000l /* requestInterval*/);
RaceBoardPanel raceBoardPerspective = new RaceBoardPanel(parent, context, raceLifeCycle, settings,
RaceTimesInfoProvider raceTimesInfoProvider = new RaceTimesInfoProvider(getSailingService(),
asyncActionsExecutor, this, Collections.singletonList(selectedRace.getRaceIdentifier()),
5000l /* requestInterval */);
RaceBoardPanel raceBoardPerspective = new RaceBoardPanel(/* parent */ null, context, raceLifeCycle, settings,
getSailingService(), mediaService, getUserService(), asyncActionsExecutor,
raceboardData.getCompetitorAndTheirBoats(), timer, selectedRace.getRaceIdentifier(),
raceboardContextDefinition.getLeaderboardName(), raceboardContextDefinition.getLeaderboardGroupName(),
raceboardContextDefinition.getEventId(), RaceBoardEntryPoint.this, getStringMessages(), userAgent,
raceTimesInfoProvider, showChartMarkEditMediaButtonsAndVideo, true, availableDetailTypes);
RootLayoutPanel.get().add(raceBoardPerspective.getEntryWidget());
return raceBoardPerspective;
if (raceBoardMode != null) {
raceBoardMode.getMode().applyTo(raceBoardPerspective);
}
}
@Override
public String getLeaderboardName() {
return leaderBoardName;
return raceboardContextDefinition.getLeaderboardName();
}
private abstract class AbstractRaceBoardAsyncCallback<T> implements AsyncCallback<T> {
@Override
public final void onFailure(Throwable caught) {
reportError("Error trying to create the raceboard: " + caught.getMessage());
}
}
}
@@ -28,13 +28,6 @@ public class RaceBoardPerspectiveLifecycle extends AbstractPerspectiveLifecycle<
public static final String ID = "rb";
//constructor used by Standalone RaceBoard
public RaceBoardPerspectiveLifecycle(StringMessages stringMessages,
Iterable<DetailType> competitorChartAllowedDetailTypes, UserService userService,
Iterable<DetailType> availableDetailTypes) {
this(null, stringMessages, competitorChartAllowedDetailTypes, userService, availableDetailTypes);
}
public RaceBoardPerspectiveLifecycle(AbstractLeaderboardDTO leaderboard, StringMessages stringMessages,
Iterable<DetailType> competitorChartAllowedDetailTypes, UserService userService,
Iterable<DetailType> availableDetailTypes) {
@@ -42,7 +35,8 @@ public class RaceBoardPerspectiveLifecycle extends AbstractPerspectiveLifecycle<
raceMapLifecycle = new RaceMapLifecycle(stringMessages);
windChartLifecycle = new WindChartLifecycle(stringMessages);
maneuverTableLifecycle = new ManeuverTableLifecycle(stringMessages);
leaderboardPanelLifecycle = new SingleRaceLeaderboardPanelLifecycle(stringMessages, availableDetailTypes);
leaderboardPanelLifecycle = new SingleRaceLeaderboardPanelLifecycle(stringMessages, availableDetailTypes,
leaderboard.canBoatsOfCompetitorsChangePerRace);
multiCompetitorRaceChartLifecycle = new MultiCompetitorRaceChartLifecycle(stringMessages, competitorChartAllowedDetailTypes);
mediaPlayerLifecycle = new MediaPlayerLifecycle(stringMessages);
raceTimePanelLifecycle = new RaceTimePanelLifecycle(stringMessages, userService);
@@ -16,10 +16,13 @@ public class SingleRaceLeaderboardPanelLifecycle extends LeaderboardPanelLifecyc
private static final long DEFAULT_REFRESH_INTERVAL = 1000L;
private final boolean isScreenLargeEnoughToInitiallyDisplayLeaderboard;
private final boolean canBoatsOfCompetitorsChangePerRace;
public SingleRaceLeaderboardPanelLifecycle(StringMessages stringMessages, Iterable<DetailType> availableDetailTypes) {
public SingleRaceLeaderboardPanelLifecycle(final StringMessages stringMessages,
final Iterable<DetailType> availableDetailTypes, final boolean canBoatsOfCompetitorsChangePerRace) {
super(stringMessages, availableDetailTypes);
this.isScreenLargeEnoughToInitiallyDisplayLeaderboard = Document.get().getClientWidth() >= 1024;
this.canBoatsOfCompetitorsChangePerRace = canBoatsOfCompetitorsChangePerRace;
}
@Override
@@ -31,27 +34,24 @@ public class SingleRaceLeaderboardPanelLifecycle extends LeaderboardPanelLifecyc
raceDetails.add(DetailType.NUMBER_OF_MANEUVERS);
raceDetails.add(DetailType.RACE_DISPLAY_LEGS);
List<DetailType> overallDetails = new ArrayList<>();
SingleRaceLeaderboardSettings defaultSettings = new SingleRaceLeaderboardSettings();
SingleRaceLeaderboardSettings defaultSettings = new SingleRaceLeaderboardSettings(canBoatsOfCompetitorsChangePerRace);
// don't show competitor fullName column if even leaderboard isn't shown initially
final boolean showCompetitorFullNameColumn = isScreenLargeEnoughToInitiallyDisplayLeaderboard;
SingleRaceLeaderboardSettings settings = new SingleRaceLeaderboardSettings(
defaultSettings.getManeuverDetailsToShow(), defaultSettings.getLegDetailsToShow(),
defaultSettings.getRaceDetailsToShow(), overallDetails, DEFAULT_REFRESH_INTERVAL,
defaultSettings.isShowAddedScores(),
/* showCompetitorShortNameColumn */ true,
/*
* don't showCompetitorFullNameColumn in case screen is so small that we don't even display the
* leaderboard initially
*/ isScreenLargeEnoughToInitiallyDisplayLeaderboard,
/* showCompetitorBoatInfoColumn */ true, false, false);
showCompetitorFullNameColumn,
/* showCompetitorBoatInfoColumn */ canBoatsOfCompetitorsChangePerRace,
/* isCompetitorNationalityColumnVisible */ false,
/* showRaceRankColumn */ false);
SettingsUtil.copyDefaultsFromValues(settings, settings);
return settings;
}
public boolean isScreenLargeEnoughToInitiallyDisplayLeaderboard() {
return isScreenLargeEnoughToInitiallyDisplayLeaderboard;
}
@Override
public SingleRaceLeaderboardSettings extractDocumentSettings(SingleRaceLeaderboardSettings currentLeaderboardSettings) {
SingleRaceLeaderboardSettings defaultLeaderboardSettings = createDefaultSettings();
@@ -91,7 +91,7 @@ public class MasterDataResource extends AbstractSailingServerResource {
getService().getSensorFixStore(), exportWind, deviceConfigurations);
final StreamingOutput streamingOutput;
if (compress) {
streamingOutput= new CompressingStreamingOutput(masterData, competitorIds, startTime);
streamingOutput = new CompressingStreamingOutput(masterData, competitorIds, startTime);
} else {
streamingOutput = new NonCompressingStreamingOutput(masterData, competitorIds, startTime);
}
@@ -0,0 +1,40 @@
package com.sap.sse.gwt.client;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.user.client.ui.Widget;
/**
* Utility class contained convenience methods regarding DOM operations.
*/
public final class DOMUtils {
private DOMUtils() {
}
/**
* Scrolls the top of the provided {@link Widget widget} to the topmost possible position in the viewport.
*
* @param widget
* {@link Widget} to scroll to the topmost possible position
*/
public static void scrollToTop(final Widget widget) {
getViewportElement().setScrollTop(widget.getAbsoluteTop());
}
/**
* Scrolls the top of the provided {@link Element element} to the topmost possible position in the viewport.
*
* @param element
* {@link Element} to scroll to the topmost possible position
*/
public static void scrollToTop(final Element element) {
getViewportElement().setScrollTop(element.getAbsoluteTop());
}
private static Element getViewportElement() {
final Document doc = Document.get();
return doc.isCSS1Compat() ? doc.getDocumentElement() : doc.getBody();
}
}
@@ -36,7 +36,7 @@ public class LogEntry {
public LogEntry(String logEntry) {
final Iterator<String> i = Util.splitAlongWhitespaceRespectingDoubleQuotedPhrases(logEntry).iterator();
hostname = i.next();
hostname = i.next().trim();
requestorIpString = i.next();
remoteLogname = i.next();
remoteUser = i.next();
@@ -1,7 +1,5 @@
package com.sap.sailing.racecommittee.app.services;
import java.util.Collection;
import com.sap.sailing.domain.abstractlog.race.state.RaceStateEvent;
import com.sap.sailing.domain.abstractlog.race.state.RaceStateEventScheduler;
import com.sap.sailing.domain.abstractlog.race.state.impl.RaceStateEvents;
@@ -18,7 +16,7 @@ public class RaceStateEventSchedulerOnService implements RaceStateEventScheduler
}
@Override
public void scheduleStateEvents(Collection<RaceStateEvent> stateEvents) {
public void scheduleStateEvents(Iterable<RaceStateEvent> stateEvents) {
for (RaceStateEvent stateEvent : stateEvents) {
service.setAlarm(race, stateEvent);
}
+3 -2
View File
@@ -13,8 +13,8 @@ First of all, make sure you've looked at [http://www.amazon.de/Patterns-Elements
3. Git (e.g. Git for Windows v2.18), [http://git-scm.com](http://git-scm.com) / [https://git-for-windows.github.io](https://git-for-windows.github.io)
4. MongoDB (e.g. Production Release 2.6.7), download: [https://www.mongodb.com/](https://www.mongodb.com/)
5. RabbitMQ, download from [http://www.rabbitmq.com](http://www.rabbitmq.com). Requires Erlang to be installed. RabbitMQ installer will assist in installing Erlang. Some sources report that there may be trouble with latest versions of RabbitMQ. In some cases, McAffee seems to block the installation of the latest version on SAP hardware; in other cases connection problems to newest versions have been reported. We know that version 3.6.8 works well. [https://github.com/rabbitmq/rabbitmq-server/releases/tag/rabbitmq_v3_6_8](https://github.com/rabbitmq/rabbitmq-server/releases/tag/rabbitmq_v3_6_8) is the link.
6. JDK 1.7 (Java SE 7), [http://www.oracle.com/technetwork/java/javase/downloads/java-archive-downloads-javase7-521261.html](http://www.oracle.com/technetwork/java/javase/downloads/java-archive-downloads-javase7-521261.html)
7. JDK 1.8 (Java SE 8), [http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html)
6. JDK 1.7 (Java SE 7), [http://www.oracle.com/technetwork/java/javase/downloads/java-archive-downloads-javase7-521261.html](http://www.oracle.com/technetwork/java/javase/downloads/java-archive-downloads-javase7-521261.html) --- Alternatively you can use the SAPJVM 1.7: Go to [http://sapjvm.wdf.sap.corp:1080/downloads](http://sapjvm.wdf.sap.corp:1080/downloads), select JVM 1.7, extract the downloaded .zip into desired location (e.g. C:\Program Files\Java), then Go to Window -> Preferences -> Java -> Installed JREs and add the VM.
7. JDK 1.8 (Java SE 8), [http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html) --- Alternatively you can use the SAPJVM 1.8: Go to [http://sapjvm.wdf.sap.corp:1080/downloads](http://sapjvm.wdf.sap.corp:1080/downloads), select JVM 1.8, extract the downloaded .zip into desired location (e.g. C:\Program Files\Java), then Go to Window -> Preferences -> Java -> Installed JREs and add the VM.
8. Maven 3.1.1 (or higher), [http://maven.apache.org](http://maven.apache.org)
9. GWT SDK 2.8.2 ([http://www.gwtproject.org/download.html](http://www.gwtproject.org/download.html))
10. Standalone Android SDK (see section "Additional steps required for Android projects"). OPTIONALLY: You may additionally install Android Studio ([https://developer.android.com/tools/studio/index.html](https://developer.android.com/tools/studio/index.html)) or IntelliJ IDEA ([https://www.jetbrains.com/idea/download/](https://www.jetbrains.com/idea/download/)).
@@ -119,6 +119,7 @@ Copy the settings.xml **and** the toolchains.xml from the top-level git folder t
#### Further hints
- Configure Eclipse to use Chrome or Firefox as the default browser
- Install the GWT Browser Plugin for the GWT Development mode <del>(Chrome or Firefox; as of this writing (2013-11-05), Firefox is the only platform where the plug-in runs stably)</del> (As of 2016-08-31 Firefox is the only browser supporting the GWT plugin, you have to download Firefox version 24 for it to work)
- Use SAP JVM Profiler. If you used the script above and installed the SAPJVM instead of the jdk, you can now open the profiling perspective by clicking on Window -> Perspective -> Open Perspective -> Profiling)
#### Git usage troubleshooting
Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

+22 -1
View File
@@ -430,6 +430,12 @@ In a live event scenario, the SAP Sailing Analytics are largely bandwidth bound.
To still get the usual logging and URL re-writing features, replicas need to run their local Apache server with a bit of configuration. Luckily, most of the grunt work is done for you automatically. You simply need to tell the replicas in their instance details to start replicating automatically, provide an `EVENT_ID` and set the `SERVER_NAME` variable properly. The Apache configuration on the replica will then automatically be adjusted such that the lower-case version of $SERVER_NAME.sapsailing.com will re-direct users to the event page for the event with ID $EVENT_ID.
Amazon puts up limits regarding to the maximum number of rules that an Application Load Balancer (ALB) may have. We use one such ALB as the DNS CNAME target for ``*.sapsailing.com`` (Sailing-eu-west-1-135628335.eu-west-1.elb.amazonaws.com). Adding rules to this ALB is especially convenient because no DNS / Route53 manipulation is necessary at all. New sub-domains can be mapped to target groups this way quite flexibly and quickly.
However, as the number of sub-domains we use grows, we also approach the limit of 100 rules for this load balancer. In order to keep this flexibility in particular for event set-ups, we started introducing more ALBs in August 2018 that use dedicated Route 53 DNS CNAME records for sepcific sub-domains. This way, with the current AWS limits for load balancers (see https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-limits.html) we will have up to 20 ALBs per region with 100 rules each, giving us 2000 rules per region which should suffice for the foreseeable future.
The set-up process needs to distinguish now between only adding a rule to an ALB listener targeted by the ``*.sapsailing.com`` DNS entry, and adding a rule to an ALB listener targeted only by DNS rules for specific sub-domains. In the latter case, a DNS record set needs to be created, providing the CNAME of the ALB that maps the sub-domain to the target group.
Here are the steps to create a load balanced setup, assuming there is already an "Application" load balancer defined in the region(s) where you need them:
- Add a master+replica target group for the master and its replicas that external users will be directed to, using HTTP port 80 as the protocol settings. Note: as this target group will also be used for the HTTPS listener, "SSL offloading" will take place here. The re-directing from HTTP to HTTPS that shall occur when the user hits the server with an HTTP request will happen in the central instance's Apache server if and only if the `X-Forwarded-Proto` is `http` (https://stackoverflow.com/questions/26620670/apache-httpx-forwarded-proto-in-htaccess-is-causing-redirect-loop-in-dev-envir explains how a. See also http://docs.aws.amazon.com/elasticloadbalancing/latest/classic/x-forwarded-headers.html#x-forwarded-proto.)
@@ -441,7 +447,22 @@ Here are the steps to create a load balanced setup, assuming there is already an
- Add a rule to the HTTPS listener for the hostname ${SERVER_NAME}-master.sapsailing.com that forwards traffic to the master-only target group just created.
- Add the master to the master-only target group.
- For both target groups configure the health checks, choosing HTTP as the protocol, using the default "traffic port" and setting the path to /index.html. Lower the interval to 10s and the "Healthy threshold" to 2 to ensure that servers are quickly recognized after adding them to the ELB. With the default settings (30 seconds interval, healthy threshold 10) this would last up to 5 minutes.
- When using the Race Committee App (RCApp), make sure the app is configured to send its data to the ${SERVER_NAME}-master.sapsailing.com URL (otherwise, write requests may end up at replicas which then have to reverse-replicate these to the master which adds significant overhead).
- When using the Race Committee App (RCApp), make sure the app is configured to send its data to the ${SERVER_NAME}-master.sapsailing.com URL (otherwise, write requests may end up at replicas which then have to reverse-replicate these to the master which adds significant overhead).
The steps to register such a sub-domain mapping also in Route53 in case you've chosen an ALB that is not the target of ``*.sapsailing.com`` work as follows:
Start by creating a new record set:
<img src="/wiki/images/amazon/DNS1.png" />
Then enter the sub-domain name you'd like to map. Choose ``CNAME`` for the type, reduce the default TTL to 60s and paste the DNS name of the ALB you'd like to target:
<img src="/wiki/images/amazon/DNS2.png" />
The DNS name of your load balancer can be copied from the "Basic Configuration" section in the "Description" tab:
<img src="/wiki/images/amazon/CopyingAlbDnsName.png" />
The insertion of the rule into the ALB that maps your sub-domain's name to the corresponding target group works as usual and as described above:
<img src="/wiki/images/amazon/DNS3.png" />
<img src="/wiki/images/amazon/DNS4.png" />
It is important to understand that it wouldn't help to let all traffic run through our central Apache httpd server which usually acts as a reverse proxy with comprehensive URL rewriting rules and macros. This would make the Apache server the bandwidth bottleneck. Instead, the event traffic needs to go straight to the ELB. This is established by the *.sapsailing.com DNS entry pointing to the Application ELB which then applies its filter rules to dispatch to the URL-specific target groups. Other than adding the hostname filter rules in the ELB as described above, no interaction with the Route 53 DNS is generally needed. Neither is it necessary to manually modify any 001-events.conf Apache configuration file.
+226
View File
@@ -0,0 +1,226 @@
# Cloud Orchestration - Project Plan
[[_TOC_]]
The cloud landscape used to run the SAP Sailing Analytics as well as its development infrastructure has grown increasingly comprehensive and elaborate over the last few years. It has largely been configured through manual efforts on behalf of event and infrastructure operators and administrators.
With a growing number of events---be it through sailing leagues or championship events in one or more classes---and with a plan to scale the SAP Sailing Analytics to a broader audience through the use of the SAP Race Manager and the SAP Sail InSight apps, more automation is required for the cloud infrastructure.
We believe that a central *orchestrator* approach should be used to solve this challenge. Such an orchestrator, a bit like outlined in the figure below, will accept requests immediately from authenticated and authorized users through a Web interface, as well as from processes running on other servers in the cloud, through REST APIs on behalf of authenticated and authorized users.
![](https://wiki.sapsailing.com/wiki/images/orchestration/architecture.png)
## Orchestrator Architecture
In order to authenticate and authorize user requests the orchestrator will benefit from a powerful security infrastructure. The *Shiro* framework that is being used by the SAP Sailing Analytics has proven to be sufficiently configurable, powerful and extensible to support all our needs. It seems desirable to share the security service between the orchestrator and the application servers in the landscape. This will allow us to extend landscape-related permissions to users that can already be authenticated by the application and which can then make requests to the orchestrator, such as providing a new event with a new dedicated database, install a sub-domain prefix with the corresponding load balancer settings, or move an event to a dedicated server/replica cluster with its own scalability limits based on the user's credentials.
There will also need to be a fair amount of negotiations between the application instances and the orchestrator, relating domain aspects such as event dates, numbers of competitors, number of races to be tracked and the number of regattas, with infrastructure provisioning aspects such as server and network capacities required to support the application set-up.
### Java OSGi
This, together with the skill set profile and the set of components found in our team, suggests the use of the architecture that proved successful during the construction of the SAP Sailing Analytics themselves. The orchestrator shall be developed as a Java OSGi application that uses the existing *com.sap.sse...* bundles for basic aspects such as security (Shiro), replication, mail services and replication (e.g., for replicating the central SecurityService with its user store).
The orchestrator could become the host for the central user store / SecurityService that other application instances replicate by means of partial replication.
Managing an Amazon Web Services (AWS) Elastic Cloud Computing (EC2) landscape will be carried out through Amazon's AWS Java SDK.
### Google Web Toolkit (GWT)
The Web UI of the orchestrator shall be built using the Google Web Toolkit (GWT), using the AdminConsole components from *com.sap.sse.gwt.adminconsole* as well as further GWT UI support from *com.sap.sse.gwt*. This will also allow us to use the *UserManagementPanel* in the administration UI.
### JAX-RS for REST
The REST APIs will be built according to the same patterns of OSGi Web Bundles that use JAX-RS as the implementation pattern for the servlets implementing the APIs. Other than for the grown application domain model, here we may choose to use the *Jackson* framework in a controlled way to pass structured information back and forth between application instances and the orchestrator.
The REST APIs will use the existing authentication/authorization realm implementation that we use already in the application in just the same way that the security-enabled REST endpoints that already exist for the SAP Sailing Analytics do.
### In-Memory with Write-Through Persistence with MongoDB
A running orchestrator instance shall not need to access a persistence layer for reading requests. Just like the applications, the orchestrator has to be able to launch / re-launch with an already established landscape. In this case, the orchestrator will need to be able to find out about the current status of the landscape it is expected to orchestrate. Instead of having to explore the world-wide cloud landscape to find instances the orchestrator may be expected to orchestrate, a persistence layer will help it to recover the last managed status.
Instead of introducing another persistence architecture, the orchestrator should use the existing MongoDB persistence pattern. It has proven useful for the scenarios we have in mind. The orchestrator will not have to deal with excessive write loads, and data volumes we expect to remain in the few megabytes range at most as all these data need to describe are the instances and their roles.
### Logging
The orchestrator needs to log in detail on behalf of which user which landscape interaction has taken place. In particular, this must include all cost-driving interactions such as launching instances, and creating load balancers. The log can be a *java.util.logging* (*jul*) log where the set-up needs to ensure that no log is ever lost upon rotation. www.sapsailing.com:/var/log/old could be the host for those logs as it is the ever-growing place for all sorts of logs we never want to lose.
## Orchestration Elements
### Application Load Balancers (ALBs)
Up to 20 application load balancers (ALBs) can be used in a single AWS region. Each of them can have up to 100 rules that map requests to a target group based on hostname and URL path patterns. We use a default ALB that is the CNAME target for **.sapsailing.com*, and as such allows the orchestrator to add rules to it without the need to add new DNS records to Route53. Dedicated ALBs can be used in addition to circumvent the limit of 100 rules per ALB. To ensure those dedicated ALBs are targeted by user requests, DNS records for the sub-domains that those ALBs have rules for need to be added to Route53.
All ALBs need default rules that forward requests to the archive server (production or fail-over).
Currently, several sub-domain names exist (e.g., wiki, bugzilla and hudson) that are also handled by the default ALB, forwarding to our central webserver which then acts as a reverse proxy, using rewrite rules to redirect the clients to the specific hosts running those services. We may consider changing this and introduce DNS records for those service-based sub-domains, mapping them explicitly to the elastic IP of the central webserver. This would allow us to separate the Apache httpd instances for those central services from the httpd instances used for the archive servers.
### Target Groups
Those hold the nodes responding to requests. In a replication cluster we usually want to have one for the "-master" URL that event administrators use
### Volumes
There are a number of disk volumes that are critical to monitor and, where necessary, scale. This includes:
- Backup server (*/home/backup*)
- Database server (*/var/lib/mongodb* and sub-mounts, */var/lib/mysql*)
- Central Webserver (*/var/log/old*, */var/log/old/cache*, */var/www/static*, */var/www/home* hosting the git repository)
Through the AWS API the read/write throughputs can be observed, and peaks as well as bottlenecks may be identified. At least as importantly, the file system fill state has to be observed which is not possible through the AWS API and needs to happen through the respective instances' operating systems.
It shall be possible to define alerts based on file systems whose free space drops below a given threshold. Re-sizing volumes automatically may be an interesting option in the future.
### Alerts
Alerts can be defined for different metrics and with different notification targets. SMS text messages and e-mail notifications are available.
The orchestrator shall be able of managing such alerts. It shall be possible to attach such alerting rules to an entire replication cluster, meaning that all target groups used for managing the cluster shall receive the same monitoring and alerting rule (see also [Sharding](#sharding)).
### DNS Record Sets
As the number of rules per load balancer is limited to 100 as of now, and only one ALB can be set up to handle **.sapsailing.com*, this ALB can only be used for the volatile, fast-changing sub-domain names and event-driven set-ups, given there are not even too many of those happening concurrently to exceed the limit.
All other, specifically the longer-running, sub-domains shall be mapped through a dedicated Route53 DNS entry in the *sapsailing.com* hosted zone. Up to 20 ALBs can be created per region, and each of those can have up to 100 rules. This should suffice for some time to come. We should also consider asking users to pay for the special service of a dedicated sub-domain in the future.
Those dedicated DNS entries then point to a non-default ALB that has the rules for that sub-domain. Those ALBs also default to the archive server and central web server if no other rule matches. Should a sub-domain's content be archived, the DNS entry can be removed. The default rules of both, the dedicated ALB and the default ALB for **.sapsailing.com* will forward requests to the archive server.
### Apache Web Servers
The landscape relies on Apache *httpd* web servers mostly for two reasons:
- consolidated, harmonized logging of web requests
- powerful reverse proxying with redirect macros and SSL support
All httpd instances have common elements for SSL certificate configuration as well as a set of redirect and rewrite macros that are aware of the application and its URL configurations, for example Plain-SSL-Redirect, Home-SSL-Redirect, Series-SSL-Redirect, and Event-SSL-Redirect. These macros can then be used to rewrite requests made for a base URL such as *worlds2018.sapsailing.com* to the corresponding event landing page.
Other macros set up end points for server monitoring (*/internal-server-status*) and for the health checks performed by the load balancer's target group which uses the host's internal IP address as the request server name.
All our hosts can run their own Apache httpd process. Currently, the two archive servers don't. Instead, they rely on the single central www.sapsailing.com webserver which hosts all redirect macro usages for all sub-domains that shall be handled by the archive server. This should probably be changed in the future, such that each archive server runs its own httpd process. It would remove the single point of failure that the current central webserver represents and may make room for a more clever set-up where the fail-over archive server is handled by a target group that is the default in the ALB rule set even after the default rule that points at the target group for the production archive server. This way, together with an alarm defined, failover to the secondary archive server could be automatic and instant.
The interface for such a web server will need to allow the orchestrator to add and remove such rewrite macro usages, configure the macro parameters such as the event ID to use for the Event-SSL-Redirect macro, and to tell the *httpd* process to re-load its configuration to make any changes effective.
### Java Application Instances and their Health
Application nodes have to provide a REST API with reliable health information.
A replica is not healthy while its initial load is about to start, or is still on-going or its replication queue is yet to drain after the initial load has finished. A replica will take harm from requests received while the initial load is received or being processed. Requests may be permitted after the initial load has finished processing and while the replication queue is being drained, although the replica will not yet have reached the state that the master is in.
A master is not healthy until it has finished loading all data from the MongoDB during the activation of the *RacingEventService*. It will take harm from requests received during this loading phase. After loading the "master data," a master server will try to restore all race trackers, starting to track the races for which a so-called "restore record" exists. During this phase the master is not fully ready yet but will not take harm from requests after loading all master data has completed. For example, in an emergency situation where otherwise the replication cluster would be unavailable it may be useful to already direct requests at such a master to at least display a landing page or a meaningful error page.
The Java instances have a few and shall have more interesting observable parameters. Some values can be observed through the AWS API, such as general network and CPU loads. So far, the number of leaderboards and the restore process can be observed using JMX managed beans, as can be seen in the JConsole. Other interesting parameters to observe by the orchestrator would be the memory usage and garbage collection (GC) stats, as well as information about the thread pools, their usage and their contention. It would be great if the orchestrator could find out about bottlenecks and how they may be avoided, e.g., hitting bandwidth limitations or not having enough CPUs available to serialize data fast enough for the bandwidth available and the demand observed.
### Multi-Instances
A "multi-instance" is a single EC2 host that runs several application processes that share the physical memory and a large locally-attached SSD (typically 2TB or more) used for swap space. Each of these processes has its own configuration in an *env.sh* file which provides the process with a server name, the amount of heap space memory to reserve, a specific MongoDB configuration (DB name, host, port), a RabbitMQ configuration in case the process shall scale with replicas, a port through which the embedded Jetty web server will be reachable, a telnet port at which the OSGi console registers itself, as well as a UDP port for the Expedition connector.
Each server process has its own directory under */home/sailing/servers* that is named after the server name. Should a multi-instance host be re-booted, it will launch all application processes it finds under */home/sailing/servers*. This is important to keep in mind for the scenario of temporarily or permanently migrating a node from a multi-instance set-up to a dedicated replication cluster set-up.
Scaling a multi-instance with replication has not yet been exercised. It may pose a few new challenges due to the replicas likely serving the application on a standard port (usually *8888*) as opposed to the node on the multi-instance host which uses a different port. In this case, separate target groups for the master and the replicas will be required, and the master will not be able to register with the public-facing target group which then only can contain replicas.
### Dedicated Instances
A dedicated server will typically be used for an event whose size in terms of competitors and races tracked and the number of users concurrently watching those races live exceeds what we think a multi-instance set-up can reasonably carry. Dedicated CPU and memory resources will ensure that such a live event runs smoothly and can scale elastically, independent of other nodes that would be running on the same host in a multi-instance set-up.
As in the multi-instance case, replication can be used for dedicated instances. Here, since the dedicated instance will also run on the default port, optionally the master and the replica instances can all be put into the public-facing load balancer target group.
A dedicated instance will grab the amount of RAM it reasonably can with the physical RAM provided by the EC2 host it is deployed to. Its HTTP port will default to 8888, the telnet port to 14888 and the Expedition UDP port to 2010. Like a multi-instance it has a server name as well as a MongoDB and RabbitMQ configuration.
### Master Data Import (MDI)
An instance can run a Master Data Import (MDI) for one or more leaderboard groups from another server instance. The typical scenario is that of importing an event into the archive server, but other scenarios may be conceived.
We will need a secured API to trigger an MDI on a server instance. Beyond this, connectivity data needs to be obtained from the exporting server so that all tracked races can be restored on the importing server. Afterwards, a comparison has to be made, ensuring that all data has arrived correctly on the importing side.
Part of such a scenario can also be to manage the remote server references on the importing server: should the importing instance be the archive server and should the archive server have had a remote server reference to the exporting server, this reference can be cleared after the import has succeeded.
### Archive Servers
We run a production copy of the "archive server" that hosts selected events for which we decide that they have sufficient quality to be promoted through sapsailing.com's landing page. Most events sponsored by SAP fall into this category. The archive server is the target of a "Master Data Import" (MDI) after an event is finished, where data is moved from a dedicated event server into the archive.
As the archive server requires several hours for a re-start, a failover instance exists that usually runs a release that is not as new as the one on the production archive server. The reason for this choice of release is that in case we run into a regression that tests have not revealed, switching to the failover archive allows us to "revert" to a release that hopefully does not have this regression while we then have some time to fix the issue on the production archive server.
The failover archive server may be used for special purposes such as tricky data-mining queries that we don't want to affect the performance of the production archive server, or acting as an additional Hudson build slave because its CPUs are usually idling.
The orchestrator shall be aware of the two archive server instances and shall know which one is the production and which one the fail-over copy. It should have an awareness of the releases installed and should offer the release upgrade as an action that will install the latest release to the fail-over copy, restart it, wait for it to become "healthy" and then compare the contents with the production archive server. If all runs well, routing/ALB/Apache rules can be switched to swap production and fail-over copy.
There should in the future be dedicated target groups for production and fail-over archive servers that can be used to define an automatic fail-over order in the ALB. A severe alert should be sent out if the production archive target group has no healthy target.
### Sharding
Requests to the */gwt/service/sailing* RPC service that cause calculations for a specific leaderboard are suffixed with the leaderboard name to which they are specific. Example: */gwt/service/sailing/leaderboard/Sailing_World_Championships_Aarhus_2018___Laser_Radial*. The leaderboard name undergoes escaping of special characters which are then replaced by an underscore character each.
With this approach, a load balancer can use the URL path with the escaped leaderboard name as criterion in a rule that dispatches requests to target groups based on the leaderboard. This way, although all replicas in a replication cluster maintain an equal memory content, calculations in a replica can be constrained to only a subset of the leaderboards the replica maintains.
Without this mechanism, all replicas in a replication cluster would be targeted with requests for all leaderboards in a random, round-robin fashion, leading to all replicas running the live calculations for all leaderboards redundantly. As more replicas are added to a replication cluster for a domain with several live leaderboards, using this sharding feature makes more sense because the recalculation load can be split evenly across the replicas.
A target group for each subset of leaderboards has to be created. The ALB will then have to have a rule for each leaderboard name, deciding the target group to which to route requests for that leaderboard. A default target group for the replication cluster shall exist which catches all other requests and which becomes the default in case a sharding target group runs out of healthy hosts.
### MongoDB Databases
We currently have a single server node *dbserver.internal.sapsailing.com* on which there are four MongoDB processes running. Three of those are for testing (*dev.sapsailing.com* uses the process listening on port 10201) or legacy purposes, and only one, listening on port 10202, has all the content relevant for the entire production landscape.
This MongoDB process hosts various MongoDB databases, each with their separate names and separate collections. With the exception of the production and fail-over archive servers which use the *winddb* database, all other master servers use a DB name that should equal that of the server name which usually is a technical short name for the event. Example: *KW2018* represents the server and DB name for the *Kieler Woche 2018* event.
Replication clusters use a second database that is used by all replicas in the cluster. Right now, replicas don't interact in any well-defined way with the database, and we say that a database accessed by a replica is not in a well-defined state. Replicas don't read from their database which is the reason why this does not matter. For example, in addition to the *KW2018* database there is a *KW2018-replica* database used by all replicas in the *KW2018* replication cluster.
The MongoDB host currently has a fast EBS volume attached to which it stores all the databases. There is only a single instance in the landscape, making this currently a single point of failure. We should consider changing this.
The orchestrator shall know about the MongoDB instances we run in the landscape, shall know their ports and hosts and thereby shall be able to monitor in particular the disk volume holding the DB contents. Alerts should be put in place in case those volumes reach critical fill states. In the future, the orchestrator should learn to run MongoDB in various regions, optionally in replicated mode also across regions, making it easier for us to deploy master/replica set-ups across regions. There may even be MongoDB instances in each region that are not part of the cluster that are used only for the phony replica databases.
### RabbitMQ Servers and Exchanges
Replication is based on RabbitMQ which is used to establish a channel through which the initial load can be transferred safely even under unstable network conditions, and through which the shipping of operations from master to replicas happens, using a "fanout" exchange.
We currently have only a single RabbitMQ instance in the landscape that hosts all exchanges and all initial load queues. As such, it represents a single point of failure.
The orchestrator should know about all RabbitMQ instances in the landscape and should know how its exchanges relate to replication clusters, and how its queues relate to the master and replica instances in the landscape. It can thus observe whether all those objects are cleanly removed again when replicas or replication clusters are dismantled and can, if necessary, clean up remnants.
### Backup Server
A single EC2 instance called "Backup" with large EBS disk volumes attached hosts various forms of backups. We use *bup*, a variant of *git* capable of dealing well with large binary files. This gives us historizing backups where each version may be fully restored.
Subject to backup are all production MongoDB databases, the MySQL database content for our Bugzilla server, as well as the file system of the central Webserver, there in particular the configuration and log files.
The backup script on *dbserver.internal.sapsailing.com:/opt/backup.sh* does *not* backup all MongoDB databases available. I think that it should. See also *configuration/backup_full.sh* in our git repository for a new version.
## Orchestration Use Cases
### Create a New Event on a Dedicated Replication Cluster
A user wants to create a new event. But instead of creating it on an existing server instance, such as the archive server or a club server, he/she would like to create a new dedicated server instance such that the server is a master which later may receive its own replicas and thus form the core of a new replication cluster. The user defines the technical event name, such as *TW2018* for the "Travemünder Woche 2018," which is then used as the server name, MongoDB database name, and replication channel name.
The steps are:
- Launch a new instance from a prepared AMI (either the AMI is regularly updated with the latest packages and kernel patches, or a "yum update" needs to be run and then the instance rebooted)
- The latest (or a specified) release is installed to */home/sailing/servers/server*
- The */home/sailing/servers/server/env.sh* file is adjusted to reflect the server name, MongoDB settings, as well as the ports to be used (for a dedicated server probably the defaults at 8888 for the HTTP server, 14888 for the OSGi console telnet port, and 2010 for the Expedition UDP connector)
- The Java process is launched
- An event is created; it doesn't necessarily have to have the correct name and attributes yet; it's only important to obtain its UUID.
- With the new security implementation, the event will be owned by the user requesting the dedicated replication cluster, and a new group named after the unique server name is created of which the user is made a part.
- The user obtains a qualified *admin:&lt;servername&gt;* role that grants him/her administrative permissions for all objects owned by the group pertinent to the new replication cluster.
- An Apache *httpd* macro call for the event with its UUID is inserted into a *.conf* file in the instance's */etc/httpd/conf.d* directory
- The Apache *httpd* server is launched
- Two target groups *S-ded-&lt;servername&gt;* and *S-ded-&lt;servername&gt;-master* are created, and the new instance is added to both of them
- Two new ALB rules for *&lt;servername&gt;.sapsailing.com* and *&lt;servername&gt;-master.sapsailing.com* are created, forwarding their requests to the respective target group from the previous step
asdf
### Create a New "Club Set-Up" in a Multi-Instance
### Migrate from a Multi-Instance to a Dedicated Replication Cluster
### Migrate from a Dedicated Replication Cluster into a Multi-Instance
### Observe and Automatically Scale a Replication Cluster
### Archiving
### Archive Server Upgrade and Switch
### Automatic Archive Server Fail-Over
### Configure Sharding in a Replication Cluster
### Amazon Machine Image (AMI) Upgrades