Merge branch 'master' of ssh://sapsailing.com/home/trac/git into bug4232

This commit is contained in:
Peter Siegmund
2018-01-05 08:42:41 +01:00
341 changed files with 56155 additions and 28113 deletions
+169
View File
@@ -0,0 +1,169 @@
Beschreibung des Skripts:
0. Voraussetzungen f�r Nutzung des Skripts
- AWS CLI (https://aws.amazon.com/de/cli)
- Cygwin Packages:
-- tmux
-- jq
-- openssh
-- wget
-- curl
0.1 AWS CLI konfigurieren
"aws configure" durchf�hren.
Falls noch kein Keypair vorhanden:
IAM-Konsole >> Users >> dein Benutzername >> Security Credentials >> Create access key
0.2 Default-Werte anpassen
Default-Werte in "aws_variables.sh" anpassen (key_name, key_file), damit diese nicht
immer neu eingegeben werden m�ssen. Testweise erst einmal in Region "eu-west-2" bleiben.
key_name: Name des Keypairs zur Verbindung zur Instanz
key_file: Pfad zur Datei
...
0.3 Skript ausprobieren
./aws-setup.sh --help
./aws-setup instance-with-load-balancer
Falls Fehler auftaucht:
Skript mit "-d"-Parameter starten und in den Logs der Instanz nachsehen
(./aws-setup.sh --tail --public-dns-name ec2-x...amazon.com)
1. Aufbau:
1.1 Dateien und ihre Hauptaufgaben:
aws-setup-sh:
Parameteraufnahme, Main-Funktion, Dokumentation der Bedienung, Sourcing von lib/utils.sh
lib/aws-functions.sh:
Funktionen f�r AWS (z.B. Load Balancer erstellen) und App-Instanz (z.B. Event erstellen)
lib/aws-variables.sh:
Festlegung von regionspezifischen Konstanten (z.B Hosted Zone ID) und Defaultwerten (z.B Region)
lib/scenario_instance.sh:
Erstellung einer Instanz und anschlie�endes Hinzuf�gen zu einem Load Balancer der �ber einen Route53 Eintrag
mit einer bestimmten Domain (z.B. wcs17.sapsailing.com) verkn�pft wird.
lib/scenario_tail.sh:
Automatische Anzeige der Log-Dateien einer Instanz mithilfe von tmux
lib/tmux_functions.sh:
Oberfl�chenkonstruktion, Vorbedingungstest, Befehlsausf�hrung
lib/util_functions.sh:
Erleichterung der Benutzereingabe, Validierungsfunktionen, sonstige potenziell relevante Hilfsfunktionen
lib/util_variables.sh:
Sonstiges Hilfsvariablen (Zeitstempel, Skriptname, etc.)
lib/utils.sh:
Ausgabe von farbigen Meldungen, Logging (unbenutzt), Sourcing der restlichen Bash-Dateien im Verzeichnis
1.2 Szenarios
Szenarios sind gekapselte Ausf�hrungseinheiten, die durch Orchestrierung von AWS-Funktionen oder Bereitstellung
eigener Funktionalit�t einen Mehrwert f�r die Automatisierung darstellen.
Szenarios beinhalten folgende Funktionen:
- Funktion zum Start des Ausf�hrung des Szenarios
- Funktion zur �berpr�fung von Vorbedingungen (Abh�ngigkeiten von Paketen oder Umgebungsvariablen) [optional]
- Funktion zur Sicherstellung der Initialisierung von ben�tigen Variablen
- Funktion zur Ausf�hrung der Programmlogik
2. Funktionalit�t
2.1 Initialisierung von ben�tigten Variablen
Jedes Szenario ben�tigt f�r seine Ausf�hrung bestimmte inititalisierte Variablen. Die Zuweisung eines Werts zu einer Variable erfolgt
entweder beim Start des Skripts �ber die Mitgabe eines Parameters oder falls kein Parameter �bergeben worden ist, �ber die Eingabe des
Benutzers nach entsprechender Aufforderung.
Wenn der Benutzer zur Eingabe eines Werts f�r eine Variable aufgefordert wird, wird automatisch ein Default-Wert aus der
Datei "aws_variables.sh" vorgeschlagen. Dies erleichtert die Benutzereingabe, da sich bestimmte Werte, wie z.B. der Name
des Keypairs zur Verbindung einer Instanz selten �ndern. In der Datei werden au�erdem regionspezifische Variablenwerte
(z.B. IDs der Sicherheitsgruppen oder Images) festgelegt.
2.2 Fehlerbehandlung
Um die erfolgreiche Ausf�hrung einer Funktion zu �berpr�fen werden verschiedene Strategien verfolgt:
1. �berpr�fung des R�ckgabewerts einer Funktion
Diese Strategie wird bei AWS-spezifischen Funktionen angewandt. Bei Aufruf eines AWS-Kommandos (z.B. aws elb create-load-balancer) wird
bei Erfolg der R�ckgabewert 0 zur�ckgegeben und bei Vorkommen eines Fehlers der R�ckgabewert 1.
2. �berpr�fen des HTTP-Codes
Bei Aufruf eines curl-Kommandos wird die Option -w "\n%{http_code}" mit�bergeben. Dabei werden Response- und HTTP-Code auf zwei Zeilen
aufgeteilt und damit erm�glicht, dass nach Aufruf der Funktion die Zuordnung zu zwei verschiedenen Variablen erfolgt.
2.3 Meldungsausgabe
Innerhalb von Methoden wird der Nutzer �ber Erfolg oder Misserfolg benachrichtigt. Die Ausgabe erfolgt �ber den
Standardfehlerkanal "stderr" (Workaround).
2.4 Tail mit tmux
Beim Verwenden des Parameters --tail werden automatisch relevante Log-Dateien angezeigt.
Der Befehl ist in Kombination mit dem Parameter --public-dns-name oder auch mit dem Szenario
--instance-with-load-balancer verwendbar. Zur Ausf�hrung muss das Skript in einer tmux-Session
ausgef�hrt werden.
Beispiele:
./aws-setup.sh --tail --public-dns-name ec2-xxx.eu-west-2.compute.amazonaws.com
Zeigt relevante Log-Dateien der Instanz mit dem DNS Namen "ec2-xxx.eu-west-2.compute.amazonaws.com" an.
./aws-setup.sh --tail --instance-with-load-balancer
Zeigt relevante Log-Dateien der Instanz die gerade erstellt wird, ab dem Zeitpunkt der Aufnahme der SSH-Verbindung
3. TODO
3.1 Fehlerbehebungen
- Nutzer erstellen scheitert bisher mit "Precondition failed" (Nutzer-
name erf�llt Richtlinie nicht)
3.2 Verbesserungen
- run-instance Funktion nach "aws_functions.sh" faktorisieren
- Anpassung der Dateierzeugung an Linux ("crlf" ersetzen)
- Momentan wird der Admin-Nutzer als Eventersteller verwendet.
Die Rechtevergabe an einen anderen Nutzer �ber die REST-API
muss noch implementiert werden.
3.2 Erweiterungen
- list-Funktion mit Anzeige aller Instanznamen und deren DNS, Instanz-ID, etc. damit
--tail nicht die Web-GUI erfordert
- weitere Szenarios hinzuf�gen (Redeployment, R�ckmigration in Archiv, etc.)
- regionspezifisches Sourcing von Dateien mit Variablen (Auskommentieren nicht mehr notwendig)
- automatische Kontext�bernahme nach tmux
Getestet unter Microsoft Windows [Version 10.0.10586]:
- Cygwin 2.8.2(0.313/5/3)
- aws-cli/1.11.129
- tmux 2.8.2(0.313/5/3)
- openssh 7.5p1-1
- wget 1.19.1
- curl 7.53.0
Funktioniert (Route53-Eintrag Erstellung konnte nicht getestet werden)
Getestet unter Microsoft Windows [Version 6.3.96001]:
- Cygwin 2.9.0(0.318/5/3)
- aws-cli/1.11.166
- tmux 2.4
- openssh 7.4p1
- wget 1.19.1
- curl 7.55.1-1
Funktioniert (Route53-Eintrag Erstellung konnte nicht getestet werden)
+269
View File
@@ -0,0 +1,269 @@
#!/usr/bin/env bash
# AWS automation script for setting up SAP Sailing Analytics instance infrasctructure
# ------------------------------------------------------
version="1.0.0" # Sets version variable
scriptPath="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
utilsLocation="${scriptPath}/lib/utils.sh"
# source utils.sh which itself sources all other .sh files in the /lib directory
if [ -f "${utilsLocation}" ]; then
source "${utilsLocation}"
else
echo "Please find the file util.sh and add a reference to it in this script. Exiting."
exit 1
fi
# delete temp files when trapped
function trapCleanup() {
echo ""
deleteTemp
die "Exit trapped. In function: '${FUNCNAME[*]}'"
}
function safeExit() {
deleteTemp
trap - INT TERM EXIT
exit
}
function deleteTemp () {
if is_dir "${tmpDir}"; then
rm -r "${tmpDir}"
fi
}
quiet=false
printLog=false
verbose=true
force=false
strict=false
debug=false
args=()
# Create temp directory with three random numbers and the process ID
# in the name. This directory is removed automatically at exit.
tmpDir="./tmp.$RANDOM.$RANDOM.$RANDOM.$$/"
(umask 000 && mkdir "${tmpDir}") || {
die "Could not create temporary directory! Exiting."
}
# Logging (not used)
# To never save a logfile change variable to '/dev/null'
# Save to standard user log location use: $HOME/Library/Logs/${scriptBasename}.log
# -----------------------------------
logFile="$HOME/Library/Logs/${scriptBasename}.log"
function mainScript() {
echo -n
if $tail; then
check_if_tmux_is_used
fi
if $instance; then
instance_start
if $associate_clb; then
associate_clb_start
fi
if $associate_elastic_ip; then
associate_elastic_ip_start
fi
if $associate_alb; then
associate_alb_start
fi
confirm_reset_panes
safeExit
fi
if $tail ; then
tail_start
confirm_reset_panes
safeExit
fi
}
usage() {
echo -n "${scriptName} [OPTION]...
This is an AWS automation bash script for deploying SAP Sailing Analytics
instances and their depending infrastructure
${bold}Parameter:${reset}
-r, --region AWS region (e.g. \"eu-west-2\" for London)
-t, --instance-type Instance type (e.g. \"t2.medium\")
-k, --key-name IAM keypair name (e.g. \"leonradeck-keypair\")
-i, --key-file Path to keypair file
-s, --ssh-user SSH user to connect to instance (e.g. \"root\")
-u, --user-username Username of user to create
-q, --user-password Password of user to create
-n, --instance-name Name for instance (e.g. \"WC Santander 2017\")
-l, --instance-short-name Short name for instance (e.g. subdomain \"wcs17\")
-a, --new-admin-password New password for the admin user
-p, --public-dns-name Dns name of instance (e.g. \"ec2-35-176...amazonaws.com\")
-f, --force Skip user input and use default variables
-d, --debug Debug mode
${bold}Scenarios:${reset}
--instance Create instance
--associate-alb Associate instance with existing application load balancer whos
listener is defined in variables_aws.sh. Automatically
create necessary target group and host name rule.
Currently *.dummy.sapsailing.com is used for test purposes.
--associate-clb Associate instance with new classic load balancer.
--asslociate-elastic-ip Associate instance with new elastic ip.
--tail Tail logs from instance using tmux
${bold}Other:${reset}
--version Output version information and exit
${bold}Examples:${reset}
Create instance:
> ./aws-setup.sh --instance
Associate application load balancer:
> ./aws-setup.sh --instance --associate-alb
Associate instance with classic load balancer while
automatically tailing important log files (tmux required):
> ./aws-setup.sh --instance --associate-clb --tail
Associate instance with elastic ip:
> ./aws-setup.sh --instance --associate-elastic-ip
Tail logfiles of running instance with dns name:
> ./aws-setup.sh --tail --public-dns-name ec2-x.compute.amazonaws.com
Create instance and use default values:
> ./aws-setup.sh --instance --force
Create instance and use default values except instance name
and instance short name:
> ./aws-setup.sh --instance --instance-name Test --instance-short-name t --force
Associate instance with classic load balancer by
passing all relevant parameters to script.
Also use debug mode.
> ./aws-setup.sh --region eu-west-2 --instance-type t2.medium
--key-name leonradeck-keypair --key-file /cygdrive/c/Users/d069485/
.ssh/leonradeck-keypair.pem --user-username test --user-password test
--instance-name \"WC Santander 2017\" --instance-short-name test
--new-admin-password admin -d --instance-with-load-balancer
"
}
# Iterate over options breaking -ab into -a -b when needed and --foo=bar into
# --foo bar
optstring=h
unset options
while (($#)); do
case $1 in
# If option is of type -ab
-[!-]?*)
# Loop over each character starting with the second
for ((i=1; i < ${#1}; i++)); do
c=${1:i:1}
# Add current char to options
options+=("-$c")
# If option takes a required argument, and it's not the last char make
# the rest of the string its argument
if [[ $optstring = *"$c:"* && ${1:i+1} ]]; then
options+=("${1:i+1}")
break
fi
done
;;
# If option is of type --foo=bar
--?*=*) options+=("${1%%=*}" "${1#*=}") ;;
# add --endopts for --
--) options+=(--endopts) ;;
# Otherwise, nothing special
*) options+=("$1") ;;
esac
shift
done
set -- "${options[@]}"
unset options
# Print help if no arguments were passed.
# Uncomment to force arguments when invoking the script
[[ $# -eq 0 ]] && set -- "--help"
# Set default value of variable without parameter value to false
associate_clb=false
associate_alb=false
associate_elastic_ip=false
instance=false
tail=false
# Read the options and set variables
while [[ $1 = -?* ]]; do
case $1 in
-h|--help) usage >&2; safeExit ;;
--version) echo "$(basename $0) ${version}"; safeExit ;;
-r|--region) shift; region_param=${1} ;;
-t|--instance-type) shift; instance_type_param=${1} ;;
-k|--key-name) shift; key_name_param=${1} ;;
-i|--key-file) shift; key_file_param=${1} ;;
-s|--ssh-user) shift; ssh_user_param=${1} ;;
-u|--user-username) shift; user_username_param=${1} ;;
-q|--user-password) shift; user_password_param=${1} ;;
-n|--instance-name) shift; instance_name_param=${1} ;;
-l|--instance-short-name) shift; instance_short_name_param=${1} ;;
-a|--new-admin-password) shift; new_admin_password_param=${1} ;;
-p|--public-dns-name) shift; public_dns_name_param=${1} ;;
-f|--force) force=true ;;
-d|--debug) debug=true ;;
--associate-clb) associate_clb=true ;;
--associate-alb) associate_alb=true ;;
--associate-elastic-ip) associate_elastic_ip=true ;;
--instance) instance=true ;;
--tail) tail=true ;;
--endopts) shift; break ;;
*) die "invalid option: '$1'." ;;
esac
shift
done
# Store the remaining part as arguments.
args+=("$@")
############# RUN SCRIPT #############
# Trap bad exits with your cleanup function
trap trapCleanup EXIT INT TERM
# Set IFS to preferred implementation
IFS=$'\n\t'
# Exit on error. Append '||true' when you run the script if you expect an error.
# set -o errexit
# Run in debug mode, if set
if ${debug}; then set -x ; fi
# Exit on empty variable
if ${strict}; then set -o nounset ; fi
# Bash will remember & return the highest exitcode in a chain of pipes.
# This way you can catch the error in case mysqldump fails in `mysqldump |gzip`, for example.
set -o pipefail
# Invoke the checkDependenices function to test for Bash packages. Uncomment if needed.
# checkDependencies
# Run script
mainScript
# Exit cleanlyd
safeExit
@@ -0,0 +1,116 @@
#!/usr/bin/env bash
# -----------------------------------------------------------
# Get latest release build from releases.sapsailing.com
# @return latest build
# -----------------------------------------------------------
function get_latest_release(){
# get html from releases.sapsailing.com
local html=$(wget releases.sapsailing.com -q -O -)
# extract all links
local links=$(grep -Po '(?<=href=")[^"]*' <<< "$html")
# extract build strings (e.g. build-201709291756)
local builds=$(grep -Po 'build-\d+' <<< "$links")
# sort build strings reverse using their date
local result=$(sort -k1.1,1.8 -k1.9nr <<< "$builds")
# take latest build
echo "$result" | head -1
}
# -----------------------------------------------------------
# Get access token
# @param $1 admin username
# @param $2 admin password
# @param $3 dns name of instance
# @return access token
# -----------------------------------------------------------
function get_access_token(){
local_echo "Getting access token..."
curl_wrapper -X GET "http://$1:$2@$3:8888/security/api/restsecurity/access_token" | get_attribute '.access_token'
}
# -----------------------------------------------------------
# Creates a new event with event name = instance name, no regatta and venuename="Default"
# @param $1 access token of privileged user
# @param $2 dns name of instance
# @param $3 event name
# @return event_id of created event
# -----------------------------------------------------------
function create_event(){
local_echo "Creating event with name $3..."
curl_wrapper -X POST -H "Authorization: Bearer $1" "http://$2:8888/sailingserver/api/v1/events/createEvent" --data "eventName=$3" --data "venuename=Default" --data "createregatta=false" | get_attribute '.eventid'
}
# -----------------------------------------------------------
# Changes password of user
# @param $1 access token of privileged user
# @param $2 dns name of instance
# @param $3 admin username
# @param $4 admin new password
# -----------------------------------------------------------
function change_admin_password(){
local_echo "Changing password of user $3 to $4..."
curl_wrapper -X POST -H "Authorization: Bearer $1" "http://$2:8888/security/api/restsecurity/change_password" --data "username=$3" --data "password=$4"
}
# -----------------------------------------------------------
# Creates new user
# @param $1 access token of privileged user
# @param $2 dns name of instance
# @param $3 user username
# @param $4 user password
# -----------------------------------------------------------
function create_new_user(){
local_echo "Creating new user \"$3\" with password \"$4\"..."
curl_wrapper -X POST -H "Authorization: Bearer $1" "http://$2:8888/security/api/restsecurity/create_user" --data "username=$3" --data "password=$4"
}
# -----------------------------------------------------------
# Wait until access token resource is available
# @param $1 admin username
# @param $2 admin password
# @param $3 public_dns_name
# -----------------------------------------------------------
function wait_for_access_token_resource(){
echo -n "Wait until resource \"/security/api/restsecurity/access_token\" is available..."
curl_until_http_200 http://$1:$2@$3:8888/security/api/restsecurity/access_token
}
# -----------------------------------------------------------
# Wait until create event resource is available
# @param $1 public_dns_name
# -----------------------------------------------------------
function wait_for_create_event_resource(){
echo -n 'Wait until resource "/sailingserver/api/v1/events/createEvent" is available...'
curl_until_http_401 http://$1:8888/sailingserver/api/v1/events/createEvent
}
# -----------------------------------------------------------
# Patch 001-events.conf with ssl macros
# @param $1 dns name
# @param $2 event id
# @param $3 ssh user
# @param $4 public dns name
# @param $5 use ssl
# -----------------------------------------------------------
function configure_apache(){
wait_for_ssh_connection "$3" "$4"
wait_for_001_events_patch "$3" "$4"
local patched_content="Use Event-SSL $1 \"$2\" 127.0.0.1 8888"
ssh_wrapper $3@$4 echo "$patched_content >> $events_conf"
#ssh_wraper $3@$4 "/etc/init.d/httpd reload"
}
function wait_for_001_events_patch(){
echo -n "Waiting for 001-events.conf to be created..."
while [[ $(ssh_wrapper $1@$2 test -f $events_conf && echo "ok") != "ok" ]]; do
echo -n .
sleep 2
done
success "001-events.conf now exists. Appending macros to overwrite sailing script"
}
@@ -0,0 +1,120 @@
#!/usr/bin/env bash
# -----------------------------------------------------------
# Query for public dns name of instance
# @param $1 instance id
# @return public dns name
# -----------------------------------------------------------
function query_public_dns_name(){
local_echo "Querying for the instance public dns name..."
aws_wrapper --region "$region" ec2 describe-instances --instance-ids "$1" --output text --query 'Reservations[*].Instances[*].PublicDnsName'
}
# -----------------------------------------------------------
# Query for default vpc id
# @return default vpc id
# -----------------------------------------------------------
function get_default_vpc_id(){
local_echo "Querying for the default vpc id..."
aws_wrapper ec2 describe-vpcs --query 'Vpcs[?IsDefault==`true`].VpcId' --output text
}
# -----------------------------------------------------------
# Allocates an elastic ip address
# @return elastic ip
# -----------------------------------------------------------
function allocate_address(){
local_echo "Allocating elastic ip..."
aws_wrapper ec2 allocate-address | get_attribute '.PublicIp'
}
# -----------------------------------------------------------
# Associate an elastic ip address with an instance
# @param $1 instance_id
# @param $2 elastic_ip
# -----------------------------------------------------------
function associate_address(){
local_echo "Associating instance \"$1\" with elastic ip \"$2\"..."
aws_wrapper ec2 associate-address --instance-id "$1" --public-ip "$2"
}
# -----------------------------------------------------------
# Get all availability zones of region
# @return availability zones array
# -----------------------------------------------------------
function get_availability_zones(){
aws_wrapper ec2 --region $region describe-availability-zones --query "AvailabilityZones[].ZoneName"
}
# -----------------------------------------------------------
# Wait until instance is recognized by AWS
# @param $1 instance_id
# -----------------------------------------------------------
function wait_instance_exists(){
local_echo "Wait until instance \"$1\" is recognized by AWS..."
aws_wrapper ec2 wait instance-exists --instance-ids $1
}
# -----------------------------------------------------------
# Wait until ssh connection is established
# @param $1 ssh user
# @param $2 dns name of instance
# -----------------------------------------------------------
function wait_for_ssh_connection(){
echo -n "Connecting to $1@$2..."
while ! ssh_wrapper -q $1@$2 true 1>&2; do
echo -n .
sleep 2
done
success "SSH Connection \"$1@$2\" is established."
}
# -----------------------------------------------------------
# Creates $count instances inside $region with image $image_id and
# $instance_type and $user_data as well as according $tag_specifications,
# $security_group_ids and $key_name
# @return instance id
# -----------------------------------------------------------
function run_instance(){
local_echo "Creating instance..."
write_user_data_to_file
local command="aws_wrapper --region $region ec2 run-instances"
command+=$(add_param "region" $region)
command+=$(add_param "image-id" $image_id)
command+=$(add_param "count" $instance_count)
command+=$(add_param "instance-type" $instance_type)
command+=$(add_param "key-name" $key_name)
command+=$(add_param "security-group-ids" $instance_security_group_ids)
command+=$(add_param "user-data" 'file://${tmpDir}/$user_data_file')
command+=$(add_param "tag-specifications" $(printf $tag_specifications "$instance_name"))
eval "$command"
}
# -----------------------------------------------------------
# Creates a file with user data for instance creation
# -----------------------------------------------------------
function write_user_data_to_file(){
local MONGODB_HOST="$mongodb_host"
local MONGODB_PORT="$mongodb_port"
local MONGODB_NAME="$(lower_trim $instance_name | only_letters_and_numbers)"
local REPLICATION_CHANNEL="$MONGODB_NAME"
local SERVER_NAME="$MONGODB_NAME"
local USE_ENVIRONMENT="live-server"
local INSTALL_FROM_RELEASE="$(get_latest_release)"
local SERVER_STARTUP_NOTIFY="$default_server_startup_notify"
local content=
content+=$(add_user_data_variable "MONGODB_HOST" $MONGODB_HOST)
content+=$(add_user_data_variable "MONGODB_PORT" $MONGODB_PORT)
content+=$(add_user_data_variable "MONGODB_NAME" $MONGODB_NAME)
content+=$(add_user_data_variable "INSTALL_FROM_RELEASE" $INSTALL_FROM_RELEASE)
content+=$(add_user_data_variable "USE_ENVIRONMENT" $USE_ENVIRONMENT)
content+=$(add_user_data_variable "REPLICATION_CHANNEL" $REPLICATION_CHANNEL)
content+=$(add_user_data_variable "SERVER_NAME" $SERVER_NAME)
content+=$(add_user_data_variable "SERVER_STARTUP_NOTIFY" $SERVER_STARTUP_NOTIFY)
echo "$content" > "${tmpDir}/$user_data_file"
}
@@ -0,0 +1,113 @@
#!/usr/bin/env bash
# -----------------------------------------------------------
# Create target group for load balancer
# @param $1 target group name
# @return json result
# -----------------------------------------------------------
function create_target_group(){
local_echo "Creating target group..."
local target_group_name=$(echo "$1" | only_letters_and_numbers)
aws_wrapper elbv2 create-target-group --name $target_group_name --protocol HTTPS --port 443 --vpc-id "$2"
}
# -----------------------------------------------------------
# Register instances within target group
# @param $1 target group arn
# @param $2 instance id
# -----------------------------------------------------------
function register_targets(){
local_echo "Register instance within target group..."
aws elbv2 register-targets --target-group-arn "$1" --targets Id="$2"
}
# -----------------------------------------------------------
# Create rule for listener
# @param $1 listener arn
# @param $2 sub domain
# @param $3 target group arn
# @return json result
# -----------------------------------------------------------
function create_rule(){
local_echo "Creating rule for listener..."
local subdomain=$(echo "$2" | only_letters_and_numbers)
local priority=$(($(get_rule_with_highest_priority $1) + 1))
local domain="$subdomain.dummy.sapsailing.com"
aws_wrapper elbv2 create-rule --listener-arn $1 --priority $priority --conditions Field=host-header,Values="$domain" --actions Type=forward,TargetGroupArn=$3 1>&2
echo "$domain"
}
# -----------------------------------------------------------
# Get rule with highest priority out of listener
# @param $1 listener arn
# @return highest priority
# -----------------------------------------------------------
function get_rule_with_highest_priority(){
local max_priority=$(aws_wrapper elbv2 describe-rules --listener-arn $1 --query "sort_by(Rules, &Priority)[*].{P: Priority} | [-2:-1]" --output text)
if is_number $max_priority; then
echo $max_priority
else
echo 1
fi
}
# -----------------------------------------------------------
# Create load balancer with HTTP rule
# @param $1 load_balancer_name
# @return json result
# -----------------------------------------------------------
function create_load_balancer_http(){
local load_balancer_name=$(echo "$1" | only_letters_and_numbers)
local_echo "Creating load balancer \"$load_balancer_name\"..."
aws_wrapper elb create-load-balancer --load-balancer-name $load_balancer_name --listeners "Protocol=HTTP,LoadBalancerPort=80,InstanceProtocol=HTTP,InstancePort=80" --availability-zones "$(get_availability_zones)" --security-groups "$elb_security_group_ids"
}
# NOT TESTED
# -----------------------------------------------------------
# Create load balancer with HTTPS rule
# @param $1 load_balancer_name
# @param $2 certificate arn
# @param $2 security group ids
# @return json result
# -----------------------------------------------------------
function create_load_balancer_https(){
local load_balancer_name=$(echo "$1" | only_letters_and_numbers)
local_echo "Creating load balancer \"$load_balancer_name\"..."
aws_wrapper elb create-load-balancer --load-balancer-name $load_balancer_name --listeners "Protocol=HTTP,LoadBalancerPort=80,InstanceProtocol=HTTP,InstancePort=80" "Protocol=HTTPS,LoadBalancerPort=443,InstanceProtocol=HTTP,InstancePort=80,SSLCertificateId=$2" --availability-zones "$(get_availability_zones)" --security-groups "$elb_security_group_ids"
}
# -----------------------------------------------------------
# Configure health check HTTP
# @param $1 load balancer name
# @return json result
# -----------------------------------------------------------
function configure_health_check_http(){
local load_balancer_name=$(echo "$1" | only_letters_and_numbers)
local_echo "Configuring load balancer health check..."
aws_wrapper elb configure-health-check --load-balancer-name "$load_balancer_name" --health-check Target=TCP:8888,Interval=15,UnhealthyThreshold=2,HealthyThreshold=3,Timeout=5
}
# NOT TESTED
# -----------------------------------------------------------
# Configure health check HTTPS
# @param $1 load balancer name
# @return json result
# -----------------------------------------------------------
function configure_health_check_https(){
local_echo "Configuring load balancer health check..."
local load_balancer_name=$(echo "$1" | only_letters_and_numbers)
aws_wrapper elb configure-health-check --load-balancer-name "$load_balancer_name" --health-check Target=HTTPS:443/index.html,Interval=15,UnhealthyThreshold=2,HealthyThreshold=3,Timeout=5
}
# -----------------------------------------------------------
# Add instance to load balancer
# @param $1 load balancer name
# @param $2 instance id
# @return json result
# -----------------------------------------------------------
function add_instance_to_elb(){
local_echo "Adding instance to elb..."
local load_balancer_name=$(echo "$1" | only_letters_and_numbers)
aws_wrapper elb register-instances-with-load-balancer --load-balancer-name $load_balancer_name --instances $2
}
@@ -0,0 +1,119 @@
#!/usr/bin/env bash
# -----------------------------------------------------------
# As long as variable is empty, prompt again.
# @param $1 prompt message
# @param $2 default value
# @param $3 variable that is receiving the value
# -----------------------------------------------------------
function ask(){
while [[ -z "${!3}" ]]
do
require_input "$1" "$2" $3
done
}
# -----------------------------------------------------------
# If the needed variable was not passed as a parameter and is not
# already set, then prompt the user to input a value and also
# show a default value. If the variable was passed by using a parameter,
# then set its value to the parameter value.
# @param $1 parameter value
# @param $2 variable
# @param $3 default value
# @param $4 prompt message
# -----------------------------------------------------------
function require_variable(){
# if parameter is empty
if [ -z "$1" ]; then
# if required variable is empty
if [ -z "${!2}" ]; then
# if force is enabled
if [ "$force" = true ]; then
# read default value into required variable
read -r "$2" <<< "$3"
fi
# if force not enabled, ask user for input
ask "$4" "$3" $2
fi
else
# if parameter is not empty, read its value into required variable
read -r "$2" <<< "$1"
fi
}
# -----------------------------------------------------------
# Workaround: use stderr stream for console output
# @param $1 message
# -----------------------------------------------------------
function local_echo(){
echo "$1" >&2
}
# -----------------------------------------------------------
# Prompt user for input and save value to variable
# @param $1 prompt message
# @param $2 default value
# @param $3 variable that is receiving the value
# -----------------------------------------------------------
function require_input(){
read -e -p "$1" -i "$2" $3
}
function require_region(){
require_variable "$region_param" region "$default_region" "$region_ask_message"
}
function require_instance_type(){
require_variable "$instance_type_param" instance_type "$default_instance_type" "$instance_type_ask_message"
}
function require_instance_name(){
require_variable "$instance_name_param" instance_name "$default_instance_name" "$instance_name_ask_message"
}
function require_instance_short_name(){
require_variable "$instance_short_name_param" instance_short_name "$default_instance_short_name" "$instance_short_name_ask_message"
}
function require_key_name(){
require_variable "$key_name_param" key_name "$default_key_name" "$key_name_ask_message"
}
function require_key_file(){
require_variable "$key_file_param" key_file "$default_key_file" "$key_file_ask_message"
}
function require_new_admin_password(){
require_variable "$new_admin_password_param" new_admin_password "$default_new_admin_password" "$new_admin_password_ask_message"
}
function require_user_username(){
require_variable "$user_username_param" user_username "$default_user_username" "$user_username_ask_message"
}
function require_user_password(){
require_variable "$user_password_param" user_password "$default_user_password" "$user_password_ask_message"
}
function require_public_dns_name(){
require_variable "$public_dns_name_param" public_dns_name "" "$public_dns_name_ask_message"
}
function require_ssh_user(){
require_variable "$ssh_user_param" ssh_user "" "$ssh_user_ask_message"
}
region_ask_message="Please enter the region for the instance: "
instance_type_ask_message="Please enter the instance type: "
key_name_ask_message="Please enter the name of your keypair to connect to the instance: "
instance_name_ask_message="Please enter a name for the instance: (e.g \"WC Santander 2017\"): "
instance_short_name_ask_message="Please enter a short name for the instance (e.g. \"wcs17\"): "
key_file_ask_message="Please enter the file path of the keypair or leave empty to use default ssh key: "
new_admin_password_ask_message="Please enter a new password for the admin user: "
mongo_db_host_ask_message="Please enter the ip adress of the mongo db server: "
mongo_db_port_ask_message="Please enter the port of the mongo db server: "
user_username_ask_message="Please enter the username of your new user: "
user_password_ask_message="Please enter the password of your new user: "
public_dns_name_ask_message="Please enter the public dns name: "
ssh_user_ask_message="Please enter the ssh user: "
@@ -0,0 +1,27 @@
#!/usr/bin/env bash
# NOT TESTED
# -----------------------------------------------------------
# Create CNAME record for the hosted zone to forward to a load balancer
# @param $1 subdomain name
# @param $2 TTL
# @param $3 load balancer dns name
# @return json result
# -----------------------------------------------------------
function route53_change_resource_record(){
create_route53_record_file "$1" "$2" "$3"
local_echo "Creating Route53 record set (Name: $1.sapsailing.com Value: $3 Type: CNAME)..."
aws_wrapper route53 change-resource-record-sets --hosted-zone-id "$hosted_zone_id" --change-batch "file://${tmpDir}/$change_resource_record_set_file"
}
# -----------------------------------------------------------
# Creates a file which includes the record change as json
# @param $1 subdomain name
# @param $2 TTL
# @param $3 load balancer dns name
# -----------------------------------------------------------
function create_route53_record_file(){
local json=$(printf '{"Changes":[{"Action":"CREATE","ResourceRecordSet": {"Name": "%s.sapsailing.com","Type": "CNAME","TTL": %s,"ResourceRecords":[{"Value": "%s"}]}}]}' $1 $2 $3)
echo "$json" > "${tmpDir}/$change_resource_record_set_file"
}
@@ -0,0 +1,108 @@
#!/usr/bin/env bash
# -----------------------------------------------------------
# Construct tmux user interface
# -----------------------------------------------------------
function construct_ui() {
# enable scrolling, clicking on panes etc.
tmux set -g mouse on
# enable named border at the top of panes
tmux set -g pane-border-status top
tmux set -g pane-border-format " [#{pane_index}] - #T "
# close all panes except one
reset_panes
sleep 1
# construct pane layout
tmux split-window -h -p 50
tmux select-pane -t 0
tmux split-window -v -p 50
tmux select-pane -t 2
tmux split-window -v -p 50
tmux select-pane -t 0
}
# -----------------------------------------------------------
# Opens ssh connections on all three panes
# @param $1 ssh user
# @param $2 dns name of instance
# -----------------------------------------------------------
function open_connections() {
# Can't use ssh wrapper at this point because function cannot be export within tmux environment.
if [ -z $key_file ]; then
command="ssh -o StrictHostKeyChecking=no $1@$2"
else
command="ssh -o StrictHostKeyChecking=no -i $key_file $1@$2"
fi
# Send $command to all three panes and execute
tmux send-keys -t 1 $command C-m
tmux send-keys -t 2 $command C-m
tmux send-keys -t 3 $command C-m
}
# -----------------------------------------------------------
# Close all panes except first
# -----------------------------------------------------------
function reset_panes(){
if more_panes_are_open; then
tmux kill-pane -a -t 0
fi
}
function get_number_of_panes(){
tmux display-message -p '#{window_panes}'
}
function more_panes_are_open(){
if inside_tmux_session; then
if [ "$(get_number_of_panes)" -gt 1 ]; then
return 0;
fi
fi
return 1;
}
function check_if_tmux_is_used(){
check_dependencies
check_environment
}
function check_dependencies() {
if ! is_tmux_available; then
echo "The package \"tmux\" is required to run the script with this option"
safeExit
fi
}
function check_environment() {
if ! inside_tmux_session; then
echo "To tail instance log files, please run this script inside a tmux session. To do so enter \"tmux\" into the console and start the script from there."
safeExit
fi
}
function is_tmux_available(){
type tmux >/dev/null 2>/dev/null
}
function inside_tmux_session(){
[ "$TERM" = "screen" ] && [ -n "$TMUX" ]
}
# -----------------------------------------------------------
# Prompts user if open panes should be closed
# -----------------------------------------------------------
function confirm_reset_panes(){
if more_panes_are_open; then
seek_confirmation "Do you want to close all open panes?"
if is_confirmed; then
reset_panes
else
safeExit
fi
fi
}
@@ -0,0 +1,82 @@
#!/usr/bin/env bash
# -----------------------------------------------------------
# Does the error checking for curl commands.
# Captures response and http code of curl command into variable $out.
# Checks if http code is 200.
# If yes print success and return response.
# If not then print error.
# @return latest build
# -----------------------------------------------------------
function curl_wrapper(){
local out;
out=$(curl -qSfsw '\n%{http_code}' "$@")
#local return_code=$?
local command=$(echo "$@" | tr '\n' ' ')
local status_code=$(get_status_code "$out")
local response=$(get_response "$out")
local message=$(get_http_code_message $status_code)
if is_http_ok $status_code; then
success "[ OK ]"
echo $response
else
error "[ ERROR ]"
notice "Function (${FUNCNAME[1]}): [$status_code] $message\n\n$command"
return 1
fi
}
# -----------------------------------------------------------
# Does the error checking for aws commands. It also remove all
# carriage return symbols from response.
# -----------------------------------------------------------
function aws_wrapper(){
local out;
out=$(aws "$@")
if command_was_successful $?; then
success "[ OK ]"
echo $out | sanitize
else
error "[ ERROR ]"
return 1
fi
}
# -----------------------------------------------------------
# Automatically uses key file for ssh if possible.
# -----------------------------------------------------------
function ssh_wrapper(){
if [ -z $key_file ]; then
ssh -o StrictHostKeyChecking=no "$@"
else
ssh -o StrictHostKeyChecking=no -i $key_file "$@"
fi
}
# -----------------------------------------------------------
# Executes curl command until status code is 200
# -----------------------------------------------------------
function curl_until_http_200(){
while [[ $(curl -s -o /dev/null -w ''%{http_code}'' --connect-timeout $http_retry_interval "$@") != "200" ]];
do
echo -n "."
sleep $http_retry_interval;
done
echo ""
success "[ OK ]"
}
# -----------------------------------------------------------
# Executes curl command until status code is 401.
# TODO: Fix code duplication (curl_until_http_200, curl_until_http_401)
# -----------------------------------------------------------
function curl_until_http_401(){
while [[ $(curl -s -o /dev/null -w ''%{http_code}'' --connect-timeout $http_retry_interval "$@") != "401" ]];
do
echo -n "."
sleep $http_retry_interval;
done
echo ""
success "[ OK ]"
}
@@ -0,0 +1,36 @@
#!/usr/bin/env bash
# Add instance to application load balancer
# ------------------------------------------------------
function associate_alb_start(){
associate_alb_require
associate_alb_execute
}
# -----------------------------------------------------------
# All these variables are needed for this scenario
# If one variable is not assigned or passed by parameter
# the user will be prompted to enter a value
# -----------------------------------------------------------
function associate_alb_require(){
require_region
require_instance_type
require_instance_name
require_instance_short_name
require_key_name
require_key_file
require_new_admin_password
require_user_username
require_user_password
}
function associate_alb_execute() {
local vpc_id=$(get_default_vpc_id)
local target_group_arn=$(create_target_group $instance_name $vpc_id | get_attribute '.TargetGroups[0].TargetGroupArn')
register_targets $target_group_arn $instance_id
local domain=$(create_rule $listener_arn $instance_short_name $target_group_arn)
header "Apache configuration"
configure_apache "$domain" "$event_id" "$ssh_user" "$public_dns_name" "ssl"
}
@@ -0,0 +1,37 @@
#!/usr/bin/env bash
# Create classic load balancer for instance
# ------------------------------------------------------
function associate_clb_start(){
associate_clb_require
associate_clb_execute
}
# -----------------------------------------------------------
# All these variables are needed for this scenario
# If one variable is not assigned or passed by parameter
# the user will be prompted to enter a value
# -----------------------------------------------------------
function associate_clb_require(){
require_region
require_instance_type
require_instance_name
require_instance_short_name
require_key_name
require_key_file
require_new_admin_password
require_user_username
require_user_password
}
function associate_clb_execute() {
header "Associating classic load balancer"
local load_balancer_dns_name=$(create_load_balancer_https "$instance_name" "$certificate_arn" | get_attribute '.DNSName')
local json_health_check=$(configure_health_check_https "$instance_name")
local json_added_instance_response=$(add_instance_to_elb "$instance_name" "$instance_id")
header "Apache configuration"
configure_apache "$public_dns_name" "$event_id" "$ssh_user" "$public_dns_name" "ssl"
}
@@ -0,0 +1,36 @@
#!/usr/bin/env bash
# Create elastic ip and associate it with instance
# ------------------------------------------------------
function associate_elastic_ip_start(){
associate_elastic_ip_require
associate_elastic_ip_execute
}
# -----------------------------------------------------------
# All these variables are needed for this scenario
# If one variable is not assigned or passed by parameter
# the user will be prompted to enter a value
# -----------------------------------------------------------
function associate_elastic_ip_require(){
require_region
require_instance_type
require_instance_name
require_instance_short_name
require_key_name
require_key_file
require_new_admin_password
require_user_username
require_user_password
}
function associate_elastic_ip_execute() {
header "Elastic ip creation"
local elastic_ip=$(allocate_address)
associate_address "$instance_id" "$elastic_ip"
header "Apache configuration"
configure_apache "$elastic_ip" "$event_id" "$ssh_user" "$elastic_ip" "ssl"
}
@@ -0,0 +1,57 @@
#!/usr/bin/env bash
# Scenario for creating an instance
# ------------------------------------------------------
function instance_start(){
instance_require
instance_execute
}
# -----------------------------------------------------------
# All these variables are needed for this scenario
# If one variable is not assigned or passed by parameter
# the user will be prompted to enter a value
# -----------------------------------------------------------
function instance_require(){
require_region
require_instance_type
require_instance_name
require_instance_short_name
require_key_name
require_key_file
require_new_admin_password
require_user_username
require_user_password
}
function instance_execute() {
header "Instance Initialization"
local json_instance=$(run_instance)
instance_id=$(echo "$json_instance" | get_attribute '.Instances[0].InstanceId')
instance_private_ip=$(echo "$json_instance" | get_attribute '.Instances[0].PrivateIpAddress')
wait_instance_exists "$instance_id"
public_dns_name=$(query_public_dns_name "$instance_id")
header "SSH Connection"
wait_for_ssh_connection "$ssh_user" "$public_dns_name"
if $tail; then
tail_start
fi
header "Event and user creation"
wait_for_access_token_resource "$admin_username" "$admin_password" "$public_dns_name"
access_token=$(get_access_token "$admin_username" "$admin_password" "$public_dns_name")
wait_for_create_event_resource "$public_dns_name"
event_id=$(create_event "$access_token" "$public_dns_name" "$instance_name")
change_admin_password "$access_token" "$public_dns_name" "$admin_username" "$new_admin_password"
create_new_user "$access_token" "$public_dns_name" "$user_username" "$user_password"
}
@@ -0,0 +1,36 @@
#!/usr/bin/env bash
function tail_start(){
tail_precondition
tail_user_input
tail_execute
}
function tail_precondition(){
check_if_tmux_is_used
}
# -----------------------------------------------------------
# All these variables are needed for this scenario
# If one variable is not assigned or passed by parameter
# the user will be prompted to enter a value
# -----------------------------------------------------------
function tail_user_input(){
require_key_file
require_ssh_user
require_public_dns_name
}
function tail_execute() {
echo "Open tmux panes and start tailing log files..."
construct_ui
wait_for_ssh_connection "$ssh_user" "$public_dns_name"
open_connections "$ssh_user" "$public_dns_name"
tail_logfiles
}
function tail_logfiles(){
tmux send-keys -t 1 "clear;echo \"Waiting for file $sailing_0 to appear...\";touch $sailing_0;cat $sailing_0;tail -F -v $sailing_0" C-m
tmux send-keys -t 2 "clear;cat $sailing_out;tail -F -v $sailing_out" C-m
tmux send-keys -t 3 "clear;cat $sailing_err;tail -F -v $sailing_err" C-m
}
@@ -0,0 +1,738 @@
#!/usr/bin/env bash
# -----------------------------------------------------------
# Creates a parameter from a key and value
# @param $1 key
# @param $2 value
# @return result ("--key value")
# -----------------------------------------------------------
function add_param() {
if [ ! -z "$2" ]; then
local result=" --$1 $2"
fi
echo "$result"
}
# -----------------------------------------------------------
# Constructs string of user data variable name and value plus linebreak
# @param $1 user data variable name
# @param $2 user data variable value
# @return name=value (linebreak) if value not empty else nothing
# -----------------------------------------------------------
function add_user_data_variable(){
if ! [ -z "$2" ]; then
local CR_LF=$'\r'$'\n'
local content="$1=$2"
content+=$CR_LF
echo "$content"
fi
}
function command_was_successful(){
[ $1 -eq 0 ]
}
# -----------------------------------------------------------
# Checks if variable is a number
# @param $1 returnvariablevalue
# @return 0 if variable is a number
# -----------------------------------------------------------
function is_number(){
[[ $1 =~ ^-?[0-9]+$ ]]
}
# -----------------------------------------------------------
# Check if variable is a number and its value is 200
# @param $1 variable
# @return 0 if value is 200
# -----------------------------------------------------------
function is_http_ok(){
is_number $1 && [ $1 == 200 ]
}
function get_response(){
echo "$1" | head -n-1
}
function get_status_code(){
echo "$1" | tail -n1
}
function get_attribute(){
jq -r $1 | sanitize
}
function sanitize(){
tr -d '\r'
}
# ------------------------------------------------------
# The following functions were part of a bash template
# and could be useful in the future
# ------------------------------------------------------
# Traps
# ------------------------------------------------------
# These functions are for use with different trap scenarios
# ------------------------------------------------------
# Non destructive exit for when script exits naturally.
# Usage: Add this function at the end of every script
function safeExit() {
# Delete temp files, if any
if is_dir "${tmpDir}"; then
rm -r "${tmpDir}"
fi
kill -WINCH $$
trap - INT TERM EXIT
exit
}
# readFile
# ------------------------------------------------------
# Function to read a line from a file.
#
# Most often used to read the config files saved in my etc directory.
# Outputs each line in a variable named $result
# ------------------------------------------------------
function readFile() {
unset "${result}"
while read result
do
echo "${result}"
done < "$1"
}
# Escape a string
# ------------------------------------------------------
# usage: var=$(escape "String")
# ------------------------------------------------------
escape() { echo "${@}" | sed 's/[]\.|$(){}?+*^]/\\&/g'; }
# needSudo
# ------------------------------------------------------
# If a script needs sudo access, call this function which
# requests sudo access and then keeps it alive.
# ------------------------------------------------------
function needSudo() {
# Update existing sudo time stamp if set, otherwise do nothing.
sudo -v
while true; do sudo -n true; sleep 60; kill -0 "$$" || exit; done 2>/dev/null &
}
# convertsecs
# ------------------------------------------------------
# Convert Seconds to human readable time
#
# To use this, pass a number (seconds) into the function as this:
# print "$(convertsecs $TOTALTIME)"
#
# To compute the time it takes a script to run use tag the start and end times with
# STARTTIME=$(date +"%s")
# ENDTIME=$(date +"%s")
# TOTALTIME=$(($ENDTIME-$STARTTIME))
# ------------------------------------------------------
function convertsecs() {
((h=${1}/3600))
((m=(${1}%3600)/60))
((s=${1}%60))
printf "%02d:%02d:%02d\n" $h $m $s
}
# Join
# ----------------------------------------------
# This function joins items together with a user specified separator
# Taken whole cloth from: http://stackoverflow.com/questions/1527049/bash-join-elements-of-an-array
#
# Usage:
# join , a "b c" d #a,b c,d
# join / var local tmp #var/local/tmp
# join , "${FOO[@]}" #a,b,c
# ----------------------------------------------
function join() { local IFS="${1}"; shift; echo "${*}"; }
# File Checks
# ------------------------------------------------------
# A series of functions which make checks against the filesystem. For
# use in if/then statements.
#
# Usage:
# if is_file "file"; then
# ...
# fi
# ------------------------------------------------------
function is_exists() {
if [[ -e "$1" ]]; then
return 0
fi
return 1
}
function is_not_exists() {
if [[ ! -e "$1" ]]; then
return 0
fi
return 1
}
function is_file() {
if [[ -f "$1" ]]; then
return 0
fi
return 1
}
function is_not_file() {
if [[ ! -f "$1" ]]; then
return 0
fi
return 1
}
function is_dir() {
if [[ -d "$1" ]]; then
return 0
fi
return 1
}
function is_not_dir() {
if [[ ! -d "$1" ]]; then
return 0
fi
return 1
}
function is_symlink() {
if [[ -L "$1" ]]; then
return 0
fi
return 1
}
function is_not_symlink() {
if [[ ! -L "$1" ]]; then
return 0
fi
return 1
}
function is_empty() {
if [[ -z "$1" ]]; then
return 0
fi
return 1
}
function is_not_empty() {
if [[ -n "$1" ]]; then
return 0
fi
return 1
}
# Test whether a command exists
# ------------------------------------------------------
# Usage:
# if type_exists 'git'; then
# some action
# else
# some other action
# fi
# ------------------------------------------------------
function type_exists() {
if [ "$(type -P "$1")" ]; then
return 0
fi
return 1
}
function type_not_exists() {
if [ ! "$(type -P "$1")" ]; then
return 0
fi
return 1
}
# Test which OS the user runs
# $1 = OS to test
# Usage: if is_os 'darwin'; then
function is_os() {
if [[ "${OSTYPE}" == $1* ]]; then
return 0
fi
return 1
}
# SEEKING CONFIRMATION
# ------------------------------------------------------
# Asks questions of a user and then does something with the answer.
# y/n are the only possible answers.
#
# USAGE:
# seek_confirmation "Ask a question"
# if is_confirmed; then
# some action
# else
# some other action
# fi
#
# Credt: https://github.com/kevva/dotfiles
# ------------------------------------------------------
# Ask the question
function seek_confirmation() {
# echo ""
input "$@"
read -p " (y/n) " -n 1
echo ""
}
# Test whether the result of an 'ask' is a confirmation
function is_confirmed() {
if "${force}"; then
return 0
else
if [[ "${REPLY}" =~ ^[Yy]$ ]]; then
return 0
fi
return 1
fi
}
function is_not_confirmed() {
if "${force}"; then
return 1
else
if [[ "${REPLY}" =~ ^[Nn]$ ]]; then
return 0
fi
return 1
fi
}
# Skip something
# ------------------------------------------------------
# Offer the user a chance to skip something.
# Credit: https://github.com/cowboy/dotfiles
# ------------------------------------------------------
function skip() {
REPLY=noskip
read -t 5 -n 1 -s -p "${bold}To skip, press ${underline}X${reset}${bold} within 5 seconds.${reset}"
if [[ "$REPLY" =~ ^[Xx]$ ]]; then
notice " Skipping!"
return 0
else
notice " Continuing..."
return 1
fi
}
# help
# ------------------------------------------------------
# Prints help for a script when invoked from the command
# line. Typically via '-h'. If additional flags or help
# text is available in the script they will be printed
# in the '$usage' variable.
# ------------------------------------------------------
function help () {
echo "" 1>&2
input " $@" 1>&2
if [ -n "${usage}" ]; then # print usage information if available
echo " ${usage}" 1>&2
fi
echo "" 1>&2
exit 1
}
function pauseScript() {
# A simple function used to pause a script at any point and
# only continue on user input
seek_confirmation "Ready to continue?"
if is_confirmed; then
info "Continuing"
else
warning "Exiting Script."
safeExit
fi
}
function in_array() {
# Determine if a value is in an array.
# Usage: if in_array "VALUE" "${ARRAY[@]}"; then ...
local value="$1"; shift
for arrayItem in "$@"; do
[[ "${arrayItem}" == "${value}" ]] && return 0
done
return 1
}
# Text Transformations
# -----------------------------------
# Transform text using these functions.
# Adapted from https://github.com/jmcantrell/bashful
# -----------------------------------
lower() {
# Convert stdin to lowercase.
# usage: text=$(lower <<<"$1")
# echo "MAKETHISLOWERCASE" | lower
tr '[:upper:]' '[:lower:]'
}
upper() {
# Convert stdin to uppercase.
# usage: text=$(upper <<<"$1")
# echo "MAKETHISUPPERCASE" | upper
tr '[:lower:]' '[:upper:]'
}
function lower_trim(){
echo $1 | lower | trim
}
# Removes all whitespaces
trim() {
tr -d '[:space:]'
}
ltrim() {
# Removes all leading whitespace (from the left).
local char=${1:-[:space:]}
sed "s%^[${char//%/\\%}]*%%"
}
rtrim() {
# Removes all trailing whitespace (from the right).
local char=${1:-[:space:]}
sed "s%[${char//%/\\%}]*$%%"
}
lttrim() {
# Removes all leading/trailing whitespace
# Usage examples:
# echo " foo bar baz " | trim #==> "foo bar baz"
ltrim "$1" | rtrim "$1"
}
only_letters_and_numbers(){
tr -d -c '[:alnum:]'
}
squeeze() {
# Removes leading/trailing whitespace and condenses all other consecutive
# whitespace into a single space.
#
# Usage examples:
# echo " foo bar baz " | squeeze #==> "foo bar baz"
local char=${1:-[[:space:]]}
sed "s%\(${char//%/\\%}\)\+%\1%g" | trim "$char"
}
squeeze_lines() {
# <doc:squeeze_lines> {{{
#
# Removes all leading/trailing blank lines and condenses all other
# consecutive blank lines into a single blank line.
#
# </doc:squeeze_lines> }}}
sed '/^[[:space:]]\+$/s/.*//g' | cat -s | trim_lines
}
progressBar() {
# progressBar
# -----------------------------------
# Prints a progress bar within a for/while loop.
# To use this function you must pass the total number of
# times the loop will run to the function.
#
# usage:
# for number in $(seq 0 100); do
# sleep 1
# progressBar 100
# done
# -----------------------------------
if [[ "${quiet}" = "true" ]] || [ "${quiet}" == "1" ]; then
return
fi
local width
width=30
bar_char="#"
# Don't run this function when scripts are running in verbose mode
if ${verbose}; then return; fi
# Reset the count
if [ -z "${progressBarProgress}" ]; then
progressBarProgress=0
fi
# Do nothing if the output is not a terminal
if [ ! -t 1 ]; then
echo "Output is not a terminal" 1>&2
return
fi
# Hide the cursor
tput civis
trap 'tput cnorm; exit 1' SIGINT
if [ ! "${progressBarProgress}" -eq $(( $1 - 1 )) ]; then
# Compute the percentage.
perc=$(( progressBarProgress * 100 / $1 ))
# Compute the number of blocks to represent the percentage.
num=$(( progressBarProgress * width / $1 ))
# Create the progress bar string.
bar=
if [ ${num} -gt 0 ]; then
bar=$(printf "%0.s${bar_char}" $(seq 1 ${num}))
fi
# Print the progress bar.
progressBarLine=$(printf "%s [%-${width}s] (%d%%)" "Running Process" "${bar}" "${perc}")
echo -en "${progressBarLine}\r"
progressBarProgress=$(( progressBarProgress + 1 ))
else
# Clear the progress bar when complete
echo -ne "${width}%\033[0K\r"
unset progressBarProgress
fi
tput cnorm
}
htmlDecode() {
# Decode HTML characters with sed
# Usage: htmlDecode <string>
echo "${1}" | sed -f "${SOURCEPATH}/htmlDecode.sed"
}
htmlEncode() {
# Encode HTML characters with sed
# Usage: htmlEncode <string>
echo "${1}" | sed -f "${SOURCEPATH}/htmlEncode.sed"
}
urlencode() {
# URL encoding/decoding from: https://gist.github.com/cdown/1163649
# Usage: urlencode <string>
local length="${#1}"
for (( i = 0; i < length; i++ )); do
local c="${1:i:1}"
case $c in
[a-zA-Z0-9.~_-]) printf "%s" "$c" ;;
*) printf '%%%02X' "'$c"
esac
done
}
urldecode() {
# Usage: urldecode <string>
local url_encoded="${1//+/ }"
printf '%b' "${url_encoded//%/\x}"
}
parse_yaml() {
# Function to parse YAML files and add values to variables. Send it to a temp file and source it
# https://gist.github.com/DinoChiesa/3e3c3866b51290f31243 which is derived from
# https://gist.github.com/epiloque/8cf512c6d64641bde388
#
# Usage:
# $ parse_yaml sample.yml > /some/tempfile
#
# parse_yaml accepts a prefix argument so that imported settings all have a common prefix
# (which will reduce the risk of name-space collisions).
#
# $ parse_yaml sample.yml "CONF_"
local prefix=$2
local s
local w
local fs
s='[[:space:]]*'
w='[a-zA-Z0-9_]*'
fs="$(echo @|tr @ '\034')"
sed -ne "s|^\($s\)\($w\)$s:$s\"\(.*\)\"$s\$|\1$fs\2$fs\3|p" \
-e "s|^\($s\)\($w\)$s[:-]$s\(.*\)$s\$|\1$fs\2$fs\3|p" "$1" |
awk -F"$fs" '{
indent = length($1)/2;
if (length($2) == 0) { conj[indent]="+";} else {conj[indent]="";}
vname[indent] = $2;
for (i in vname) {if (i > indent) {delete vname[i]}}
if (length($3) > 0) {
vn=""; for (i=0; i<indent; i++) {vn=(vn)(vname[i])("_")}
printf("%s%s%s%s=(\"%s\")\n", "'"$prefix"'",vn, $2, conj[indent-1],$3);
}
}' | sed 's/_=/+=/g'
}
httpStatus() {
# -----------------------------------
# Shamelessly taken from: https://gist.github.com/rsvp/1171304
#
# Usage: httpStatus URL [timeout] [--code or --status] [see 4.]
# ^message with code (default)
# ^code (numeric only)
# ^in secs (default: 3)
# ^URL without "http://" prefix works fine.
#
# 4. curl options: e.g. use -L to follow redirects.
#
# Dependencies: curl
#
# Example: $ httpStatus bit.ly
# 301 Redirection: Moved Permanently
#
# Example: $ httpStatus www.google.com 100 -c
# 200
#
# -----------------------------------
local curlops
local arg4
local arg5
local arg6
local arg7
local flag
local timeout
local url
saveIFS=${IFS}
IFS=$' \n\t'
url=${1}
timeout=${2:-'3'}
# ^in seconds
flag=${3:-'--status'}
# curl options, e.g. -L to follow redirects
arg4=${4:-''}
arg5=${5:-''}
arg6=${6:-''}
arg7=${7:-''}
curlops="${arg4} ${arg5} ${arg6} ${arg7}"
# __________ get the CODE which is numeric:
code=`echo $(curl --write-out %{http_code} --silent --connect-timeout ${timeout} \
--no-keepalive ${curlops} --output /dev/null ${url})`
# __________ get the STATUS (from code) which is human interpretable:
status=$(get_http_code_message $code)
# _______________ MAIN
case ${flag} in
--status) echo "${code} ${status}" ;;
-s) echo "${code} ${status}" ;;
--code) echo "${code}" ;;
-c) echo "${code}" ;;
*) echo " !! httpstatus: bad flag" && safeExit;;
esac
IFS="${saveIFS}"
}
function get_http_code_message(){
case $1 in
000) status="Not responding within timeout" ;;
100) status="Informational: Continue" ;;
101) status="Informational: Switching Protocols" ;;
200) status="Successful: OK within timeout" ;;
201) status="Successful: Created" ;;
202) status="Successful: Accepted" ;;
203) status="Successful: Non-Authoritative Information" ;;
204) status="Successful: No Content" ;;
205) status="Successful: Reset Content" ;;
206) status="Successful: Partial Content" ;;
300) status="Redirection: Multiple Choices" ;;
301) status="Redirection: Moved Permanently" ;;
302) status="Redirection: Found residing temporarily under different URI" ;;
303) status="Redirection: See Other" ;;
304) status="Redirection: Not Modified" ;;
305) status="Redirection: Use Proxy" ;;
306) status="Redirection: status not defined" ;;
307) status="Redirection: Temporary Redirect" ;;
400) status="Client Error: Bad Request" ;;
401) status="Client Error: Unauthorized" ;;
402) status="Client Error: Payment Required" ;;
403) status="Client Error: Forbidden" ;;
404) status="Client Error: Not Found" ;;
405) status="Client Error: Method Not Allowed" ;;
406) status="Client Error: Not Acceptable" ;;
407) status="Client Error: Proxy Authentication Required" ;;
408) status="Client Error: Request Timeout within timeout" ;;
409) status="Client Error: Conflict" ;;
410) status="Client Error: Gone" ;;
411) status="Client Error: Length Required" ;;
412) status="Client Error: Precondition Failed" ;;
413) status="Client Error: Request Entity Too Large" ;;
414) status="Client Error: Request-URI Too Long" ;;
415) status="Client Error: Unsupported Media Type" ;;
416) status="Client Error: Requested Range Not Satisfiable" ;;
417) status="Client Error: Expectation Failed" ;;
500) status="Server Error: Internal Server Error" ;;
501) status="Server Error: Not Implemented" ;;
502) status="Server Error: Bad Gateway" ;;
503) status="Server Error: Service Unavailable" ;;
504) status="Server Error: Gateway Timeout within timeout" ;;
505) status="Server Error: HTTP Version Not Supported" ;;
*) status="status not defined." ;;
esac
echo "$status"
}
function makeCSV() {
# Creates a new CSV file if one does not already exist.
# Takes passed arguments and writes them as a header line to the CSV
# Usage 'makeCSV column1 column2 column3'
# Set the location and name of the CSV File
if [ -z "${csvLocation}" ]; then
csvLocation="${HOME}/Desktop"
fi
if [ -z "${csvName}" ]; then
csvName="$(LC_ALL=C date +%Y-%m-%d)-${FUNCNAME[1]}.csv"
fi
csvFile="${csvLocation}/${csvName}"
# Overwrite existing file? If not overwritten, new content is added
# to the bottom of the existing file
if [ -f "${csvFile}" ]; then
seek_confirmation "${csvFile} already exists. Overwrite?"
if is_confirmed; then
rm "${csvFile}"
writeCSV "$@"
fi
fi
}
function writeCSV() {
# Takes passed arguments and writes them as a comma separated line
# Usage 'writeCSV column1 column2 column3'
csvInput=($@)
saveIFS=$IFS
IFS=','
echo "${csvInput[*]}" >> "${csvFile}"
IFS=$saveIFS
}
function json2yaml() {
# convert json files to yaml using python and PyYAML
python -c 'import sys, yaml, json; yaml.safe_dump(json.load(sys.stdin), sys.stdout, default_flow_style=False)' < "$1"
}
function yaml2json() {
# convert yaml files to json using python and PyYAML
python -c 'import sys, yaml, json; json.dump(yaml.load(sys.stdin), sys.stdout, indent=4)' < "$1"
}
@@ -0,0 +1,23 @@
#!/usr/bin/env bash
# ------------------------------------------------------
# The following variables were part of a bash template
# and could be useful in the future
# ------------------------------------------------------
# SCRIPTNAME
scriptName=`basename $0` #Set Script Name variable
scriptBasename="$(basename ${scriptName} .sh)" # Strips '.sh' from scriptName
# TIMESTAMPS
now=$(LC_ALL=C date +"%Y-%m-%d %H:%M:%S") # Returns: 2015-06-15 22:34:40
datestamp=$(LC_ALL=C date +%Y-%m-%d) # Returns: 2015-06-14
hourstamp=$(LC_ALL=C date +%r) # Returns: 10:34:40 PM
timestamp=$(LC_ALL=C date +%Y%m%d_%H%M%S) # Returns: 20150614_223440
today=$(LC_ALL=C date +"%m-%d-%Y") # Returns: 06-14-2015
longdate=$(LC_ALL=C date +"%a, %d %b %Y %H:%M:%S %z") # Returns: Sun, 10 Jan 2016 20:47:53 -0500
gmtdate=$(LC_ALL=C date -u -R | sed 's/\+0000/GMT/') # Returns: Wed, 13 Jan 2016 15:55:29 GMT
# THISHOST
thisHost=$(hostname)
+91
View File
@@ -0,0 +1,91 @@
#!/usr/bin/env bash
# Logging and Colors
# ------------------------------------------------------
# Here we set the colors for our script feedback.
# Example usage: success "sometext"
#------------------------------------------------------
# Set Colors
bold=$(tput bold)
underline=$(tput sgr 0 1)
reset=$(tput sgr0)
purple='\033[0;35m'
red='\033[0;31m'
green='\033[0;32m'
tan=$(tput setaf 3)
blue='\033[0;36m'
function _alert() {
if [ "${1}" = "emergency" ]; then
local color="${bold}${red}"
fi
if [ "${1}" = "error" ]; then local color="${bold}${red}"; fi
if [ "${1}" = "warning" ]; then local color="${red}"; fi
if [ "${1}" = "success" ]; then local color="${green}"; fi
if [ "${1}" = "debug" ]; then local color="${purple}"; fi
if [ "${1}" = "header" ]; then local color="${bold}""${tan}"; fi
if [ "${1}" = "input" ]; then local color="${bold}"; printLog="false"; fi
if [ "${1}" = "info" ] || [ "${1}" = "notice" ]; then local color=""; fi
# Don't use colors on pipes or non-recognized terminals
# if [[ "${TERM}" != "xterm"* ]] || [ -t 1 ]; then color=""; reset=""; fi
# Print to $logFile
if [[ ${printLog} = "true" ]] || [ "${printLog}" == "1" ]; then
echo -e "$(date +"%m-%d-%Y %r") $(printf "[%9s]" "${1}") ${_message}" >> "${logFile}";
fi
# Print to console when script is not 'quiet'
if [[ "${quiet}" = "true" ]] || [ "${quiet}" == "1" ]; then
return
else
echo -e "${color}$(printf "" "${1}") ${_message}${reset}" >&2;
fi
}
function die () { local _message="${*} Exiting."; echo "$(_alert emergency)"; safeExit;}
function error () { local _message="\r${*}"; echo "$(_alert error)" >&2; }
function warning () { local _message="\r${*}"; echo "$(_alert warning)" >&2; }
function notice () { local _message="\r${*}"; echo "$(_alert notice)" >&2; }
function info () { local _message="\r${*}"; echo "$(_alert info)" >&2; }
function debug () { local _message="\r${*}"; echo "$(_alert debug)" >&2; }
function success () { local _message="\r${*}"; echo "$(_alert success)" >&2; }
function input() { local _message="${*}"; echo "$(_alert input)" >&2; }
function header() { local _message="\n========== ${*} ==========\n "; echo "$(_alert header)"; }
# Log messages when verbose is set to "true"
verbose() {
if [[ "${verbose}" = "true" ]] || [ "${verbose}" == "1" ]; then
debug "$@"
fi
}
# Source additional /lib files
# ------------------------------------------------------
# First we locate this script and populate the $SCRIPTPATH variable
# Doing so allows us to source additional files from this utils file.
SOURCE="${BASH_SOURCE[0]}"
while [ -h "${SOURCE}" ]; do # resolve ${SOURCE} until the file is no longer a symlink
DIR="$( cd -P "$( dirname "${SOURCE}" )" && pwd )"
SOURCE="$(readlink "${SOURCE}")"
[[ ${SOURCE} != /* ]] && SOURCE="${DIR}/${SOURCE}" # if ${SOURCE} was a relative symlink, we need to resolve it relative to the path where the symlink file was located
done
SOURCEPATH="$( cd -P "$( dirname "${SOURCE}" )" && pwd )"
if [ ! -d "${SOURCEPATH}" ]
then
die "Failed to find library files expected in: ${SOURCEPATH}"
fi
for utility_file in "${SOURCEPATH}"/*.sh
do
if [ -e "${utility_file}" ]; then
# Don't source self
if [[ "${utility_file}" == *"utils.sh"* ]]; then
continue
fi
source "$utility_file"
fi
done
@@ -0,0 +1,57 @@
#!/usr/bin/env bash
# Set variables here
# ------------------------------------------------------
# Please comment out the variables of the region you are not using
# Default region unspecific variables
default_region=eu-west-2
default_key_name=leonradeck-keypair
default_key_file='/cygdrive/c/Users/d069485/.ssh/leonradeck-keypair.pem'
default_instance_type=t2.medium
default_server_startup_notify=leon.radeck@sap.com
default_new_admin_password=admin
default_user_username=newuser
default_user_password=newpassword
hosted_zone_id=Z1R8UBAEXAMPLE
# Variables for region "eu-west-2" (London)
instance_security_group_ids=sg-871732ee
image_id=ami-39f3e25d
certificate_arn='arn:aws:acm:eu-west-2:017363970217:certificate/d2ae17b0-75ed-4b56-ac12-a80f9e5c493d'
elb_security_group_ids=sg-871732ee
mongodb_host=35.176.143.232
mongodb_port=27017
listener_arn='arn:aws:elasticloadbalancing:eu-west-2:017363970217:listener/app/DummyALB/da70f61a914fc50a/22ab628fba30d0e7'
# Variables for region "eu-west-1" (Ireland)
# instance_security_group_ids=sg-eaf31e85
# image_id=
# certificate_arn=''
# elb_security_group_ids=
# mongodb_host=54.76.64.42
# mongodb_port=27017
# listener_arn='arn:aws:elasticloadbalancing:eu-west-1:017363970217:listener/app/DummyALBIreland/a86d2ab6ddfc4f18/bcbbd28a0ca75e54'
# Other Variables
ssh_retry_interval=2
http_retry_interval=5
user_data_file=".userdata.txt"
change_resource_record_set_file=".change-resource-record-set.json"
tag_specifications="\'ResourceType=instance,Tags=[{Key=Name,Value=%s}]\'"
admin_username=admin
admin_password=admin
instance_count=1
ssh_user=root
default_instance_name="$now"
default_instance_short_name="$timestamp"
sailing_0='/home/sailing/servers/server/logs/sailing0.log.0'
sailing_err='/var/log/sailing.err'
sailing_out='/var/log/sailing.out'
events_conf='/etc/httpd/conf.d/001-events.conf'
+84
View File
@@ -0,0 +1,84 @@
#!/bin/bash
# Production:
CACHE=/var/log/old/cache/unique-ips-per-referrer
# Test:
#CACHE=/var/log/old/cache/unique-ips-per-referrer/test
VISITED_FILES=$CACHE/visited
STATS=$CACHE/stats
OUTPUT=$STATS/results
MONTHS=$OUTPUT/permonth
TOTALS=$OUTPUT/totals
EVENTTOTALS=$OUTPUT/eventtotals
function monthMapper() {
monthname=$1
case $monthname in
Jan)
echo "01"
;;
Feb)
echo "02"
;;
Mar)
echo "03"
;;
Apr)
echo "04"
;;
May)
echo "05"
;;
Jun)
echo "06"
;;
Jul)
echo "07"
;;
Aug)
echo "08"
;;
Sep)
echo "09"
;;
Oct)
echo "10"
;;
Nov)
echo "11"
;;
Dec)
echo "12"
;;
esac
}
mkdir -p $OUTPUT
rm $OUTPUT/*
mkdir -p $MONTHS
rm $MONTHS/*
# Now filter and group by month
for i in ${STATS}/*.ips; do
hostname=`basename $i .ips`
cat $i | while read line; do
#echo line is $line
read ipaddress dayandmonth useragent <<< "$line"
read day month <<< "${dayandmonth/\// }"
read monthname year <<< "${month//\// }"
monthfile=$MONTHS/${year}-$(monthMapper $monthname)
#echo ipaddress is $ipaddress and dayandmonth is $dayandmonth and month is $month and monthname is $monthname and year is $year and monthfile is $monthfile
echo "$hostname $line" >>$monthfile
echo "$hostname $line" >>$EVENTTOTALS
echo "$line" >>$TOTALS
done
done
cat $EVENTTOTALS | sort -u | awk '{ print $1; }' | uniq -c | awk '{ print $2, $1; }' >${EVENTTOTALS}.unique
rm $EVENTTOTALS
cat $TOTALS | sort -u | wc | awk '{ print $1; }' >${TOTALS}.unique
rm $TOTALS
for monthfile in $MONTHS/*; do
cat $monthfile | sort -u | awk '{ print $1; }' | uniq -c | awk '{ print $2, $1; }' >$monthfile.unique
rm $monthfile
done
echo Done with $0 at `date`
+5 -1
View File
@@ -1,4 +1,4 @@
# Welcome to the SAP Sailing Wiki
# Welcome to the SAP Sailing Wiki
This is the <img src="https://www.sapsailing.com/images/sap-logo_grey.png"/> Wiki where useful information regarding this project can be found.
@@ -110,10 +110,13 @@ SAP is at the center of today’s technology revolution, developing innovations
* [[Operating Igtimi WindBots|wiki/howto/eventmanagers/windbot-operations]]
* [[Linking Race Videos|wiki/howto/eventmanagers/linking-race-videos]]
* [[Import official results|wiki/howto/eventmanagers/results-import]]
* [[Pairing lists|wiki/howto/eventmanagers/pairing-lists]]
* [[Manage media content|wiki/howto/eventmanagers/Manage-media-content]]
### Setup
* [[Configure Races on Server|wiki/howto/setup/configure-races-on-server]]
* [[Setup local webserver to serve 360° videos|wiki/howto/setup/webserver/chrome-webserver]]
* [[Setting up internal Jenkins on SAP Monsoon|wiki/howto/setup/setting-up-jenkins-on-sap-monsoon]]
### Miscellaneous
@@ -138,6 +141,7 @@ SAP is at the center of today’s technology revolution, developing innovations
* [[Kieler Woche event page|wiki/events/kieler-woche-2015]]
* [[Charleston Race Week 2016|wiki/events/Charleston-Race-Week-2016]]
* [[Sailing Leagues 2016|wiki/events/sailing-Leagues-2016]]
* [[Media Content|wiki/events/Sailing-events-media-content]]
## Planning
* [[Overview|https://wiki.sapsailing.com/pages/wiki/planning/]]
@@ -25,7 +25,8 @@ Require-Bundle: com.sap.sailing.domain,
com.sap.sailing.udpconnector,
com.googlecode.mgwt;bundle-version="2.0.0";resolution:=optional,
com.sap.sse.gwt.adminconsole,
org.apache.commons.math;bundle-version="2.1.0"
org.apache.commons.math;bundle-version="2.1.0",
com.sap.sse.security.ui
Bundle-ActivationPolicy: lazy
Import-Package: com.sap.sse.util,
javax.servlet;version="3.1.0",
@@ -217,9 +217,23 @@ public class RaceOfCompetitorWithContext implements HasRaceOfCompetitorContext {
private int getNumberOf(ManeuverType maneuverType) {
TrackedRace trackedRace = getTrackedRace();
int number = 0;
for (Maneuver maneuver : trackedRace.getManeuvers(getCompetitor(), trackedRace.getStartOfRace(), trackedRace.getEndOfTracking(), false)) {
if (maneuver.getType() == maneuverType) {
number++;
if (trackedRace != null && trackedRace.getStartOfRace() != null) {
final TimePoint end;
final TimePoint endOfTracking = trackedRace.getEndOfTracking();
if (trackedRace.getEndOfRace() != null) {
end = trackedRace.getEndOfRace();
} else {
final TimePoint now = MillisecondsTimePoint.now();
if (endOfTracking != null && endOfTracking.before(now)) {
end = endOfTracking;
} else {
end = now;
}
}
for (Maneuver maneuver : trackedRace.getManeuvers(getCompetitor(), trackedRace.getStartOfRace(), end, false)) {
if (maneuver.getType() == maneuverType) {
number++;
}
}
}
return number;
@@ -4,6 +4,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Date;
import java.util.concurrent.atomic.AtomicBoolean;
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
import com.sap.sailing.domain.common.impl.DegreePosition;
@@ -21,8 +22,9 @@ import com.sap.sailing.server.trackfiles.impl.BravoExtendedDataImporterImpl;
public class BravoGPSFixImporter implements GPSFixImporter {
@Override
public void importFixes(InputStream inputStream, Callback callback, boolean inferSpeedAndBearing, final String filename)
public boolean importFixes(InputStream inputStream, Callback callback, boolean inferSpeedAndBearing, final String filename)
throws FormatNotSupportedException, IOException {
final AtomicBoolean importedFixes = new AtomicBoolean(false);
TrackFileImportDeviceIdentifier device = new TrackFileImportDeviceIdentifierImpl(filename, getType() + "@" + new Date());
new BravoExtendedDataImporterImpl().importFixes(inputStream,
(Iterable<DoubleVectorFix> fixes, TrackFileImportDeviceIdentifier deviceIdentifier)->{
@@ -34,8 +36,10 @@ public class BravoGPSFixImporter implements GPSFixImporter {
new KnotSpeedWithBearingImpl(fix.get(BravoExtendedSensorDataMetadata.SOG.getColumnIndex()),
new DegreeBearingImpl(fix.get(BravoExtendedSensorDataMetadata.COG.getColumnIndex()))));
callback.addFix(gpsFix, device);
importedFixes.set(true);
}
}, filename, /* sourceName */ getType(), /* downsample */ false);
return importedFixes.get();
}
@Override
@@ -75,7 +75,7 @@ public enum DetailType implements Serializable {
RACE_SPEED_OVER_GROUND_FIVE_SECONDS_BEFORE_START(2, DESCENDING),
RACE_CALCULATED_TIME_TRAVELED(0, ASCENDING), RACE_CALCULATED_TIME_AT_ESTIMATED_ARRIVAL_AT_COMPETITOR_FARTHEST_AHEAD(0, ASCENDING),
RACE_TIME_TRAVELED(0, ASCENDING), RACE_TIME_TRAVELED_UPWIND(0, ASCENDING), RACE_TIME_TRAVELED_DOWNWIND(0, ASCENDING), RACE_TIME_TRAVELED_REACHING(0, ASCENDING),
DISTANCE_TO_START_LINE(0, ASCENDING), BEAT_ANGLE(0, ASCENDING), COURSE_OVER_GROUND_TRUE_DEGREES(0, ASCENDING);
DISTANCE_TO_START_LINE(0, ASCENDING), BEAT_ANGLE(0, ASCENDING), COURSE_OVER_GROUND_TRUE_DEGREES(0, ASCENDING), TOTAL_SCORED_RACE_COUNT(0, ASCENDING);
private int precision;
@@ -30,7 +30,7 @@ public class BoatDTO extends NamedDTO implements Serializable {
public String getSailId() {
return sailId;
}
@Override
public int hashCode() {
final int prime = 31;
@@ -28,6 +28,7 @@ public class LeaderboardRowDTO implements Serializable {
public Double totalDistanceTraveledInMeters;
public Double totalDurationFoiledInSeconds;
public Double totalDistanceFoiledInMeters;
public Integer totalScoredRaces;
@Override
public int hashCode() {
@@ -20,6 +20,10 @@ public class MetaLeaderboardRaceColumnDTO extends RaceColumnDTO {
public void addRace(BasicRaceDTO race) {
raceList.add(race);
}
public ArrayList<BasicRaceDTO> getRaceList() {
return raceList;
}
@Override
public int hashCode() {
@@ -0,0 +1,45 @@
package com.sap.sailing.domain.common.dto;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import com.sap.sse.common.Util.Pair;
public class PairingListDTO implements Serializable{
private static final long serialVersionUID = 102220422437194196L;
private List<List<List<Pair<CompetitorDTO, BoatDTO>>>> pairingList;
private List<String> raceColumnNames;
public PairingListDTO() { }
public PairingListDTO(List<List<List<Pair<CompetitorDTO, BoatDTO>>>> result) {
this(result, null);
}
public PairingListDTO(List<List<List<Pair<CompetitorDTO, BoatDTO>>>> result, List<String> raceColumnNames) {
this.pairingList = result;
this.raceColumnNames = raceColumnNames;
}
public List<List<List<Pair<CompetitorDTO, BoatDTO>>>> getPairingList() {
return pairingList;
}
public List<BoatDTO> getBoats() {
List<BoatDTO> boats = new ArrayList<>();
for (List<Pair<CompetitorDTO, BoatDTO>> fleet : this.pairingList.get(0)) {
for (Pair<CompetitorDTO, BoatDTO> competitorAndBoatPair : fleet) {
if (!boats.contains(competitorAndBoatPair.getB())) {
boats.add(competitorAndBoatPair.getB());
}
}
}
return boats;
}
public List<String> getRaceColumnNames() {
return raceColumnNames;
}
}
@@ -0,0 +1,89 @@
package com.sap.sailing.domain.common.dto;
import java.io.Serializable;
public class PairingListTemplateDTO implements Serializable {
private static final long serialVersionUID = 7155851765154315798L;
private int flightCount = 0;
private int groupCount = 0;
private int competitorCount = 0;
private int flightMultiplier = 0;
private int[][] pairingListTemplate;
private double quality;
private Iterable<String> selectedFlightNames;
public PairingListTemplateDTO() { }
public PairingListTemplateDTO(int competitorCount, int flightMultiplier) {
this(0, 0, competitorCount, flightMultiplier, null, 0.0, null);
}
public PairingListTemplateDTO(int competitorCount, int flightMultiplier, int[][] pairingListTemplate, double quality) {
this(0, 0, competitorCount, flightMultiplier, pairingListTemplate, quality, null);
}
public PairingListTemplateDTO(int competitorCount, int[][] pairingListTemplate, double quality) {
this(0, 0, competitorCount, 0, pairingListTemplate, quality, null);
}
public PairingListTemplateDTO(int flightCount, int groupCount, int competitorCount, int flightMultiplier, int[][] pairingListTemplate, double quality) {
this(flightCount, groupCount, competitorCount, flightMultiplier, pairingListTemplate, quality, null);
}
public PairingListTemplateDTO(int flightCount, int groupCount, int competitorCount, int flightMultiplier, int[][] pairingListTemplate, double quality, Iterable<String> selectedFlightNames) {
this.flightCount = flightCount;
this.groupCount = groupCount;
this.competitorCount = competitorCount;
this.flightMultiplier = flightMultiplier;
this.quality = quality;
this.pairingListTemplate = pairingListTemplate;
this.selectedFlightNames = selectedFlightNames;
}
public void setFlightCount(int flightCount){
this.flightCount=flightCount;
}
public void setGroupCount(int groupCount) {
this.groupCount = groupCount;
}
public void setFlightMultiplier(int flightMultiplier) {
this.flightMultiplier = flightMultiplier;
}
public void setSelectedFlightNames(Iterable<String> selectedFlightNames) {
this.selectedFlightNames = selectedFlightNames;
}
public int[][] getPairingListTemplate() {
return this.pairingListTemplate;
}
public double getQuality() {
return this.quality;
}
public int getFlightCount() {
return this.flightCount;
}
public int getGroupCount() {
return this.groupCount;
}
public int getCompetitorCount() {
return this.competitorCount;
}
public int getFlightMultiplier() {
return flightMultiplier;
}
public Iterable<String> getSelectedFlightNames() {
return selectedFlightNames;
}
}
@@ -0,0 +1,39 @@
package com.sap.sailing.domain.common.dto;
import java.io.Serializable;
import java.util.Date;
public class VideoMetadataDTO implements Serializable {
private static final long serialVersionUID = 1L;
private boolean canDownload;
private Date recordStartedTime;
private boolean spherical;
private String message;
public VideoMetadataDTO() {
}
public VideoMetadataDTO(boolean canDownload, boolean spherical, Date recordStartedTime, String message) {
this.canDownload = canDownload;
this.spherical = spherical;
this.recordStartedTime = recordStartedTime;
this.message = message;
}
public boolean isDownloadable() {
return canDownload;
}
public boolean isSpherical() {
return spherical;
}
public Date getRecordStartedTime() {
return recordStartedTime;
}
public String getMessage() {
return message;
}
}
@@ -6,6 +6,7 @@ package com.sap.sailing.domain.common.security;
public enum Permission implements com.sap.sse.security.shared.Permission {
// AdminConsole permissions
MANAGE_EVENTS,
MANAGE_PAIRING_LISTS,
MANAGE_REGATTAS,
MANAGE_TRACKED_RACES,
SHOW_TRACKED_RACES,
@@ -6,6 +6,7 @@ import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.Date;
import java.util.Iterator;
import java.util.concurrent.atomic.AtomicBoolean;
import com.sap.sailing.domain.common.tracking.impl.GPSFixMovingImpl;
import com.sap.sailing.domain.deckmanadapter.DeckmanAdapter;
@@ -18,16 +19,20 @@ import com.sap.sailing.domain.trackimport.GPSFixImporter;
public class DeckmanGPSFixImporter implements GPSFixImporter {
@Override
public void importFixes(InputStream inputStream, Callback callback, boolean inferSpeedAndBearing, String sourceName)
public boolean importFixes(InputStream inputStream, Callback callback, boolean inferSpeedAndBearing,
String sourceName)
throws FormatNotSupportedException, IOException {
DeckmanAdapter deckmanAdapter = DeckmanAdapterFactoryImpl.INSTANCE.createDeckmanAdapter();
TrackFileImportDeviceIdentifier device = new TrackFileImportDeviceIdentifierImpl(sourceName, getType() + "@" + new Date());
final AtomicBoolean importedFixes = new AtomicBoolean(false);
try {
for (Iterator<Record> i = deckmanAdapter.parseLogFile(new InputStreamReader(inputStream)); i.hasNext();) {
Record record = i.next();
callback.addFix(new GPSFixMovingImpl(record.getPosition(), record.getTimePoint(), record.getGpsFix().getSpeed()),
device);
importedFixes.set(true);
}
return importedFixes.get();
} catch (RuntimeException e) {
if (e.getCause() != null) {
if (e.getCause() instanceof IOException) {
@@ -4,9 +4,9 @@ import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
@@ -18,44 +18,65 @@ import com.sap.sailing.domain.trackfiles.TrackFileImportDeviceIdentifier;
import com.sap.sailing.domain.trackfiles.TrackFileImportDeviceIdentifierImpl;
import com.sap.sailing.domain.trackimport.FormatNotSupportedException;
import com.sap.sailing.domain.trackimport.GPSFixImporter;
import com.sap.sailing.server.trackfiles.impl.CompressedStreamsUtil;
import com.sap.sailing.server.trackfiles.impl.ExpeditionExtendedDataImporterImpl;
import com.sap.sailing.server.trackfiles.impl.ExpeditionImportFileHandler;
public class ExpeditionGPSFixImporter implements GPSFixImporter {
private static final String LAT_COLUMN_HEADING = ExpeditionExtendedDataImporterImpl.COL_NAME_LAT;
private static final String LON_COLUMN_HEADING = ExpeditionExtendedDataImporterImpl.COL_NAME_LON;
private static final String COG_COLUMN_HEADING = "Cog";
private static final String SOG_COLUMN_HEADING = "Sog";
@Override
public void importFixes(InputStream inputStream, Callback callback, boolean inferSpeedAndBearing, final String sourceName)
public boolean importFixes(InputStream inputStream, Callback callback, boolean inferSpeedAndBearing,
final String sourceName)
throws FormatNotSupportedException, IOException {
TrackFileImportDeviceIdentifier device = new TrackFileImportDeviceIdentifierImpl(sourceName, getType() + "@" + new Date());
final BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
final String headerLine = br.readLine();
final Map<String, Integer> columnDefinitions = ExpeditionExtendedDataImporterImpl.parseHeader(headerLine);
final AtomicInteger lineNr = new AtomicInteger(0);
br.lines().forEach(line->{
if (!line.trim().isEmpty()) {
ExpeditionExtendedDataImporterImpl.parseLine(lineNr.incrementAndGet(), sourceName, line, columnDefinitions,
(timePoint, columnValues, columns)->{
final double latDeg = Double.parseDouble(columnValues[columns.get(LAT_COLUMN_HEADING)]);
final double lonDeg = Double.parseDouble(columnValues[columns.get(LON_COLUMN_HEADING)]);
final double cogDeg = Double.parseDouble(columnValues[columns.get(COG_COLUMN_HEADING)]);
final double sogKnots = Double.parseDouble(columnValues[columns.get(SOG_COLUMN_HEADING)]);
final GPSFixMoving fix = new GPSFixMovingImpl(new DegreePosition(latDeg, lonDeg), timePoint,
new KnotSpeedWithBearingImpl(sogKnots, new DegreeBearingImpl(cogDeg)));
callback.addFix(fix, device);
final AtomicBoolean importedFixes = new AtomicBoolean(false);
CompressedStreamsUtil.handlePotentiallyCompressedFiles(sourceName, inputStream,
new ExpeditionImportFileHandler() {
@Override
protected void handleExpeditionFile(String fileName, InputStream stream) throws IOException {
final BufferedReader br = new BufferedReader(new InputStreamReader(stream));
final String headerLine = br.readLine();
final Map<String, Integer> columnDefinitions = ExpeditionExtendedDataImporterImpl
.parseHeader(headerLine);
final AtomicInteger lineNr = new AtomicInteger(0);
br.lines().forEach(line -> {
if (!line.trim().isEmpty()) {
ExpeditionExtendedDataImporterImpl.parseLine(lineNr.incrementAndGet(), fileName, line,
columnDefinitions, (timePoint, columnValues, columns) -> {
final double latDeg = Double
.parseDouble(columnValues[columns.get(LAT_COLUMN_HEADING)]);
final double lonDeg = Double
.parseDouble(columnValues[columns.get(LON_COLUMN_HEADING)]);
final double cogDeg = Double
.parseDouble(columnValues[columns.get(COG_COLUMN_HEADING)]);
final double sogKnots = Double
.parseDouble(columnValues[columns.get(SOG_COLUMN_HEADING)]);
final GPSFixMoving fix = new GPSFixMovingImpl(
new DegreePosition(latDeg, lonDeg), timePoint,
new KnotSpeedWithBearingImpl(sogKnots,
new DegreeBearingImpl(cogDeg)));
callback.addFix(fix, device);
importedFixes.set(true);
});
}
});
}
});
}
});
return importedFixes.get();
}
@Override
public Iterable<String> getSupportedFileExtensions() {
return Arrays.asList(new String[] { "csv", "log", "txt" });
return ExpeditionImportFileHandler.supportedExpeditionLogFileExtensions;
}
@Override
public String getType() {
return "Expedition";
return GPSFixImporter.EXPEDITION_TYPE;
}
}
@@ -1,6 +1,7 @@
package com.sap.sailing.domain.persistence;
import java.io.Serializable;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.concurrent.ConcurrentHashMap;
@@ -167,7 +168,7 @@ public interface MongoObjectFactory {
*
* @see #addConnectivityParametersForRaceToRestore(RaceTrackingConnectivityParameters)
*/
void removeConnectivityParametersForRaceToRestore(RaceTrackingConnectivityParameters params);
void removeConnectivityParametersForRaceToRestore(RaceTrackingConnectivityParameters params) throws MalformedURLException;
/**
* Updates the database such that the next call to
@@ -1,6 +1,7 @@
package com.sap.sailing.domain.persistence.impl;
import java.io.Serializable;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
@@ -1590,7 +1591,7 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory {
}
@Override
public void removeConnectivityParametersForRaceToRestore(RaceTrackingConnectivityParameters params) {
public void removeConnectivityParametersForRaceToRestore(RaceTrackingConnectivityParameters params) throws MalformedURLException {
if (raceTrackingConnectivityParamsServiceFinder != null) {
final String typeIdentifier = params.getTypeIdentifier();
final RaceTrackingConnectivityParametersHandler paramsPersistenceService = raceTrackingConnectivityParamsServiceFinder.findService(typeIdentifier);
@@ -1625,6 +1626,8 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory {
} catch (NoCorrespondingServiceRegisteredException e) {
logger.log(Level.WARNING, "Couldn't find a persistence service for connectivity parameters of type "+typeIdentifier+
". Couldn't store race "+params.getTrackerID()+" for restoring.", e);
} catch (MalformedURLException e) {
logger.log(Level.WARNING, "Issue with reading a URL from the tracking params for tracker "+params.getTrackerID()+" for restoring.", e);
}
}
}
@@ -74,8 +74,12 @@ public interface RaceLogTrackingAdapter {
* Denotes the entire {@link Leaderboard} for racelog-tracking, by calling the
* {@link #denoteRaceForRaceLogTracking(RacingEventService, Leaderboard, RaceColumn, Fleet, String)} method for each
* {@link RaceLog}.
*
* @param prefix Use this parameter to set the racename in the denoteEvent. The prefix will be used for all races.
* Additional to the prefix there will be a serial number that gives every race a individual name. You can pass null
* to get the default denote name. The default looks like: regatta name + racecolumn name + race name.
*/
void denoteAllRacesForRaceLogTracking(RacingEventService service, Leaderboard leaderboard)
void denoteAllRacesForRaceLogTracking(RacingEventService service, Leaderboard leaderboard, String prefix)
throws NotDenotableForRaceLogTrackingException;
/**
@@ -81,7 +81,7 @@ public class RaceLogConnectivityParams extends AbstractRaceTrackingConnectivityP
throw new RaceNotCreatedException("No regatta for race-log tracked race");
}
DynamicTrackedRegatta trackedRegatta = trackedRegattaRegistry.getOrCreateTrackedRegatta(regatta);
return new RaceLogRaceTracker(trackedRegatta, this, windStore, raceLogResolver, this);
return new RaceLogRaceTracker(trackedRegatta, this, windStore, raceLogResolver, this, trackedRegattaRegistry);
}
@Override
@@ -1,5 +1,6 @@
package com.sap.sailing.domain.racelogtracking.impl;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
@@ -47,6 +48,7 @@ import com.sap.sailing.domain.base.Sideline;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.base.impl.CourseDataImpl;
import com.sap.sailing.domain.base.impl.CourseImpl;
import com.sap.sailing.domain.base.impl.RaceColumnListenerWithDefaultAction;
import com.sap.sailing.domain.base.impl.RaceDefinitionImpl;
import com.sap.sailing.domain.common.PassingInstruction;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
@@ -61,6 +63,7 @@ import com.sap.sailing.domain.tracking.DynamicTrackedRace;
import com.sap.sailing.domain.tracking.DynamicTrackedRegatta;
import com.sap.sailing.domain.tracking.RaceHandle;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
import com.sap.sailing.domain.tracking.WindStore;
import com.sap.sailing.domain.tracking.WindTrack;
import com.sap.sse.common.Util;
@@ -91,12 +94,14 @@ public class RaceLogRaceTracker extends AbstractRaceTrackerBaseImpl {
private final WindStore windStore;
private final DynamicTrackedRegatta trackedRegatta;
private final RaceLogResolver raceLogResolver;
private final TrackedRegattaRegistry trackedRegattaRegistry;
private volatile DynamicTrackedRace trackedRace;
public RaceLogRaceTracker(DynamicTrackedRegatta regatta, RaceLogConnectivityParams params, WindStore windStore,
RaceLogResolver raceLogResolver, RaceLogConnectivityParams connectivityParams) {
RaceLogResolver raceLogResolver, RaceLogConnectivityParams connectivityParams, TrackedRegattaRegistry trackedRegattaRegistry) {
super(connectivityParams);
this.trackedRegattaRegistry = trackedRegattaRegistry;
this.params = params;
this.windStore = windStore;
this.trackedRegatta = regatta;
@@ -144,16 +149,13 @@ public class RaceLogRaceTracker extends AbstractRaceTrackerBaseImpl {
public void visit(RegattaLogDefineMarkEvent event) {
RaceLogRaceTracker.this.onDefineMarkEvent(event);
}
};
visitors.put(log, visitor);
((RegattaLog) log).addListener(visitor);
}
}
logger.info(String.format("Created race-log tracker for: %s %s %s", params.getLeaderboard(),
params.getRaceColumn(), params.getFleet()));
// load race for which tracking already started
if (new RaceLogTrackingStateAnalyzer(params.getRaceLog()).analyze() == RaceLogTrackingState.TRACKING) {
startTracking(null);
@@ -252,7 +254,11 @@ public class RaceLogRaceTracker extends AbstractRaceTrackerBaseImpl {
update.add(new Util.Pair<>(waypoint.getControlPoint(), waypoint.getPassingInstructions()));
}
try {
trackedRace.getRace().getCourse().update(update, params.getDomainFactory());
final Course course = trackedRace.getRace().getCourse();
course.update(update, params.getDomainFactory());
if (base.getName() != null) {
course.setName(base.getName());
}
} catch (PatchFailedException e) {
logger.log(Level.WARNING, "Could not update course for race " + trackedRace.getRace().getName());
}
@@ -277,7 +283,7 @@ public class RaceLogRaceTracker extends AbstractRaceTrackerBaseImpl {
courseBase = new CourseDataImpl("Default course for " + raceName);
logger.log(Level.FINE, "Using empty course in creation of race " + raceName);
}
final Course course = new CourseImpl(raceName + " course", courseBase.getWaypoints());
final Course course = new CourseImpl(courseBase.getName() == null ? raceName + " course" : courseBase.getName(), courseBase.getWaypoints());
if (raceColumn.getTrackedRace(fleet) != null) {
if (event != null) {
try {
@@ -305,5 +311,35 @@ public class RaceLogRaceTracker extends AbstractRaceTrackerBaseImpl {
synchronized (this) {
this.notifyAll();
}
registerListenerThatWillRemoveThisRaceWhenTheRaceColumnIsRemoved();
}
private void registerListenerThatWillRemoveThisRaceWhenTheRaceColumnIsRemoved() {
getRegatta().addRaceColumnListener(new RaceColumnListenerWithDefaultAction() {
private static final long serialVersionUID = -2924864263579432528L;
@Override
public void defaultAction() {
}
@Override
public void raceColumnRemovedFromContainer(RaceColumn raceColumn) {
try {
trackedRegattaRegistry.removeRace(getRegatta(), getRace());
} catch (IOException | InterruptedException e) {
logger.log(Level.WARNING, "Error trying to remove smart phone / race log tracked race whose race column was deleted: "+
e.getMessage(), e);
}
}
/**
* This listener is transient and will therefore not be serialized to any replicas or with
* the master data import.
*/
@Override
public boolean isTransient() {
return true;
}
});
}
}
@@ -172,11 +172,17 @@ public class RaceLogTrackingAdapterImpl implements RaceLogTrackingAdapter {
}
@Override
public void denoteAllRacesForRaceLogTracking(final RacingEventService service, final Leaderboard leaderboard)
throws NotDenotableForRaceLogTrackingException {
public void denoteAllRacesForRaceLogTracking(final RacingEventService service, final Leaderboard leaderboard,
final String prefix) throws NotDenotableForRaceLogTrackingException {
int fleetcount = 1;
for (RaceColumn column : leaderboard.getRaceColumns()) {
for (Fleet fleet : column.getFleets()) {
denoteRaceForRaceLogTracking(service, leaderboard, column, fleet, null);
if (prefix != null) {
denoteRaceForRaceLogTracking(service, leaderboard, column, fleet, prefix + fleetcount);
} else {
denoteRaceForRaceLogTracking(service, leaderboard, column, fleet, null);
}
fleetcount++;
}
}
}
@@ -49,4 +49,9 @@ public class RaceLogDenoteForTrackingEventImpl extends RaceLogEventImpl implemen
public Serializable getRaceId() {
return raceId;
}
@Override
public String getShortInfo() {
return getRaceName();
}
}
@@ -4,8 +4,8 @@ import java.net.URL;
import java.util.Locale;
import java.util.Map;
import com.sap.sailing.domain.common.Renamable;
import com.sap.sse.common.NamedWithID;
import com.sap.sse.common.Renamable;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.media.ImageSize;
import com.sap.sse.shared.media.WithMedia;
@@ -2,8 +2,8 @@ package com.sap.sailing.domain.base;
import java.util.UUID;
import com.sap.sailing.domain.common.Renamable;
import com.sap.sse.common.NamedWithID;
import com.sap.sse.common.Renamable;
public interface LeaderboardGroupBase extends Renamable, NamedWithID {
UUID getId();
@@ -1,6 +1,6 @@
package com.sap.sailing.domain.base;
import com.sap.sailing.domain.common.Renamable;
import com.sap.sse.common.Renamable;
/**
* Base interface for series consisting of static information describing the Series.
@@ -1,7 +1,7 @@
package com.sap.sailing.domain.base;
import com.sap.sailing.domain.common.Renamable;
import com.sap.sse.common.Named;
import com.sap.sse.common.Renamable;
/**
* Describes the place where a sailing event with one or more regattas takes place.
@@ -3,9 +3,9 @@ package com.sap.sailing.domain.base.impl;
import java.net.URI;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.common.Renamable;
import com.sap.sse.common.Color;
import com.sap.sse.common.Duration;
import com.sap.sse.common.Renamable;
public interface DynamicCompetitor extends Competitor, Renamable {
DynamicBoat getBoat();
@@ -0,0 +1,983 @@
//stored-uri:datafiles/66ccec10-b77d-0132-26ea-60a44ce903c3.mtb
StartReplay:1
// Input from params_static:
// Input from event_parameters:
DefaultPanel:
ReCalcDirAndSpeed:1
CompMarkerUUID:2dee0384-2c45-11e4-a9f6-14dae9614d7b
CodanIsSponsor:0
DefaultTailLength:60
EnableResults:1
ResultsLayout:[{"key":"Order","header":"#","description":"Position","printable":true,"align":"right","collapsible":false},{"key":"Name","header":"Name","description":"Competitor's name","printable":true,"align":"left","collapsible":false},{"key":"ShortName","header":"Short Name","description":"Competitor's short name","printable":true,"align":"left","collapsible":true},{"key":"LegTime","header":"Leg","description":"Leg time","printable":false,"align":"right","collapsible":true},{"key":"LegPosition","header":"#","description":"Leg position","printable":false,"align":"right","collapsible":true},{"key":"Time","header":"Total","description":"Total time","printable":true,"align":"right","collapsible":false},{"key":"After","header":"Total &Delta;","description":"Total &Delta;","printable":true,"align":"right","collapsible":false}]
PerformanceTableLayout:[{"key":"Pos","header":"Pos"},{"key":"Name","header":"Name"},{"key":"Short","header":"Short"},{"key":"Country","header":"Country"},{"key":"After","header":"After"},{"key":"Leg pos","header":"Leg pos"},{"key":"Leg time","header":"Leg time"},{"key":"SOG","header":"SOG"},{"key":"VMG","header":"VMG"},{"key":"Top speed","header":"Top Speed"},{"key":"Distance","header":"Distance"}]
APzoomChangeTime:2500
disableAutoProduce:0
autoProduce:0
APstartLineShowOfLeg:0.25
APsideOfStartLine:0.30
APstartSwitch:0.8
APwholeLegBehindMarks:0.15
APmarkSwitch:0.13
APmarkZoomBehindMarks:0.10
APmarkZoomInFrontOfMarks:0.20
PopupMarkerUUID:84199024-2c45-11e4-a9f6-14dae9614d7b
ManualUrl:http://www.tractrac.com/files/userguide_sailing.pdf
Marker1UUID:2dee0384-2c45-11e4-a9f6-14dae9614d7b
Marker1Type:RGBMarkerFactory
Marker1Directional:1
Marker1Image:markers/boat_FF0000_big.png
Marker1Length:5
Marker1MinPixelSize:30
Startlocation:25.66219980468759,-33.95794373375933
MaxDiffTime:1200
MaxMetersPerSecond:20
PrecisionMeters:20
ShortAvgTime:180
LongAvgTime:900
TimeZone:2
CompMarkerTimeout:60
FramesPerSecond:8
replayspeedup:5
MatchRaceClock:0
MatchRaceClockNo:3
MatchRaceClockSpacing:300
Minimap:1
MinimapUseLayers:2
Minimapgeorect:25.537683105468773,-33.741288381182635,26.19746406249999,-34.12630292526923
MinimapMarkerUUID:84199024-2c45-11e4-a9f6-14dae9614d7b
Marker4UUID:84199024-2c45-11e4-a9f6-14dae9614d7b
Marker4Type:RGBmarkerFactory
Marker4Directional:1
Marker4Image:markers/boat_FF0000_small.png
MetersPerPixel:18
MetersPerPixelMax:30
MetersPerPixelMin:0.2
isMatchRace:0
DefaultDistanceUnits:meters
weatherEnabled:0
DefaultSpeedUnits:Knots
showRaceClock:1
BackGroundImage:graphics/background.jpg
DisableRotation:0
ControlPointMarkerRadius:13
disableselection:0
disableleaderline:0
StartTimeType:Race
ResultColumn5SpeedIn:Knots
DisableChangeRace:0
DisableLatestRoundings:0
//live-uri:tcp://secondary.traclive.dk:4400
//stored-uri:tcp://secondary.traclive.dk:4401
PopUpRow1Name:
PopUpRow1Type:Name
PopUpRow2Name:Knots
PopUpRow2Type:BoatSpeed
PopUpRow3Name:Ranking
PopUpRow3Type:RowNumber
PopUpRow4Name:Sail
PopUpRow4Type:Ini
PopUpRow5Name:After
PopUpRow5Type:BoatAfter
WindEstimator:0
MapUrl:http://tile.traclive.dk/ttm
ResultColumn1Name:#
ResultColumn1Size:20
ResultColumn1Type:RowNumber
ResultColumn2Name:sail
ResultColumn2Size:60
ResultColumn2Type:Ini
ResultColumn3Name:name
ResultColumn3Size:110
ResultColumn3Type:Name
ResultColumn4Name:after
ResultColumn4Size:45
ResultColumn4Type:BoatAfter
ResultColumn5Name:kts
ResultColumn5Size:40
ResultColumn5Type:BoatSpeed
DisableLeaderboard:0
DisableAnalysis:0
TableFontSize:11
compInitialsFontSize:11
AutoRotate:0
MassStart:0
SegmentFile:segments.php
RaceHandicapSystem:NONE
TeamCompetitionType:NONE
LiveDelaySecs: 10
//-------- AUTO GENERATED PARAMETERS START ----------------
EventID:b07bb320-b10c-0132-0752-60a44ce903c3
EventName:505 Worlds 2015
EventDB:event_20150323_Worlds
EventStartTime:2015-03-22 22:00:00
EventEndTime:2015-04-03 21:00:00
EventType:a1b74ca0-fdd8-11dc-8811-005056c00008
RaceID:66ccec10-b77d-0132-26ea-60a44ce903c3
RaceName:Worlds 7
RaceStartTime:2015-04-02 12:40:00
RaceEndTime:
RaceTrackingStartTime:2015-04-02 12:36:00
RaceTrackingEndTime:2015-04-02 15:10:00
RaceDefaultRouteUUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
RaceHandicapSystem:NONE
Image1UUID:53955ea0-b10f-0132-075b-60a44ce903c3
Image1Type:Logo
Image1Description:banner
Image1Image:pictures/53955ea0-b10f-0132-075b-60a44ce903c3/original_53955ea0-b10f-0132-075b-60a44ce903c3.png
Class1UUID:ac5ab750-b377-0132-1193-60a44ce903c3
Class1Name:5O5
Team1UUID:5f8d2600-b4fc-0132-1da5-60a44ce903c3
Team1Name:0
Team2UUID:94c361d0-b4db-0132-1cde-60a44ce903c3
Team2Name:?
Team3UUID:ac8787b0-b377-0132-11d3-60a44ce903c3
Team3Name:Aaron Ross
Team4UUID:94cd2760-b4db-0132-1ce2-60a44ce903c3
Team4Name:Aeolians
Team4Comp1UUID:94d00ef0-b4db-0132-1ce3-60a44ce903c3
Team5UUID:9589e150-b4db-0132-1d1d-60a44ce903c3
Team5Name:Alamitos Bay Yacht Club
Team5Comp1UUID:958bd200-b4db-0132-1d1e-60a44ce903c3
Team6UUID:ac6fff40-b377-0132-11b9-60a44ce903c3
Team6Name:Albrecht Holm
Team7UUID:ac6d2a40-b377-0132-11b1-60a44ce903c3
Team7Name:Alexander Ham
Team8UUID:951939c0-b4db-0132-1cfa-60a44ce903c3
Team8Name:Algoa Bay Yacht Club
Team8Comp1UUID:951a3330-b4db-0132-1cfb-60a44ce903c3
Team9UUID:ac6c7560-b377-0132-11af-60a44ce903c3
Team9Name:Andrew Arthur
Team10UUID:ac6b0950-b377-0132-11ab-60a44ce903c3
Team10Name:Angela Stenger
Team11UUID:ac6e8e60-b377-0132-11b5-60a44ce903c3
Team11Name:Anthony Parker
Team12UUID:948565c0-b4db-0132-1cca-60a44ce903c3
Team12Name:B&SYC
Team12Comp1UUID:94865d50-b4db-0132-1ccb-60a44ce903c3
Team13UUID:ac61c890-b377-0132-1195-60a44ce903c3
Team13Name:Brett Bowden
Team14UUID:ac861d30-b377-0132-11cf-60a44ce903c3
Team14Name:Bronwen Klaas 
Team15UUID:c5bcd7f0-b377-0132-11de-60a44ce903c3
Team15Name:Bronwen Klaas 
Team16UUID:9470c9c0-b4db-0132-1cc2-60a44ce903c3
Team16Name:BSYC
Team16Comp1UUID:9471bf20-b4db-0132-1cc3-60a44ce903c3
Team17UUID:45679ec0-b6e4-0132-227e-60a44ce903c3
Team17Name:BYC
Team17Comp1UUID:4568fe80-b6e4-0132-227f-60a44ce903c3
Team18UUID:94e4cf90-b4db-0132-1ce6-60a44ce903c3
Team18Name:Emarentia Sailing Club
Team18Comp1UUID:94e60210-b4db-0132-1ce7-60a44ce903c3
Team19UUID:ac7415b0-b377-0132-11c5-60a44ce903c3
Team19Name:Euan Hurter
Team20UUID:ac691ca0-b377-0132-11a5-60a44ce903c3
Team20Name:Georg Mittermayer
Team21UUID:3c5056f0-b5c5-0132-2049-60a44ce903c3
Team21Name:George Lakes Yacht Club
Team21Comp1UUID:3c51d930-b5c5-0132-204a-60a44ce903c3
Team22UUID:ac6ddae0-b377-0132-11b3-60a44ce903c3
Team22Name:Gideon Snyman
Team23UUID:ac83e910-b377-0132-11c9-60a44ce903c3
Team23Name:Grant Ballentyne
Team24UUID:ac84a3d0-b377-0132-11cb-60a44ce903c3
Team24Name:Hans Rogotski
Team25UUID:5f848520-b4fc-0132-1da1-60a44ce903c3
Team25Name:Hellerup
Team26UUID:9491d840-b4db-0132-1cce-60a44ce903c3
Team26Name:HISC
Team26Comp1UUID:949331b0-b4db-0132-1ccf-60a44ce903c3
Team27UUID:ac8b0a60-b377-0132-11d7-60a44ce903c3
Team27Name:Howard Hamlin 
Team28UUID:c5bfb860-b377-0132-11e2-60a44ce903c3
Team28Name:Howard Hamlin 
Team29UUID:467d1590-b6e4-0132-22b9-60a44ce903c3
Team29Name:HS
Team29Comp1UUID:5f853dc0-b4fc-0132-1da2-60a44ce903c3
Team30UUID:460cd720-b6e4-0132-22a6-60a44ce903c3
Team30Name:HYC
Team30Comp1UUID:94c44e80-b4db-0132-1cdf-60a44ce903c3
Team31UUID:ac669050-b377-0132-11a3-60a44ce903c3
Team31Name:Ian Pinnel
Team32UUID:45b19e00-b6e4-0132-2290-60a44ce903c3
Team32Name:Imperial Yacht Club
Team32Comp1UUID:45b290d0-b6e4-0132-2291-60a44ce903c3
Team33UUID:ac70ad50-b377-0132-11bb-60a44ce903c3
Team33Name:James Largier
Team34UUID:ac647920-b377-0132-119d-60a44ce903c3
Team34Name:Jan Saugmann
Team35UUID:ac86d340-b377-0132-11d1-60a44ce903c3
Team35Name:Jay D'Engle 
Team36UUID:c5bdbd80-b377-0132-11e0-60a44ce903c3
Team36Name:Jay D'Engle 
Team37UUID:ac72b370-b377-0132-11c1-60a44ce903c3
Team37Name:Jonathan Ham
Team38UUID:ac6a5be0-b377-0132-11a9-60a44ce903c3
Team38Name:Katharina Menge
Team39UUID:ac720620-b377-0132-11bf-60a44ce903c3
Team39Name:Kevin Foreman
Team40UUID:ac856100-b377-0132-11cd-60a44ce903c3
Team40Name:Kobus Holtzhausen 
Team41UUID:c5bc0100-b377-0132-11dc-60a44ce903c3
Team41Name:Kobus Holtzhausen 
Team42UUID:ac831b90-b377-0132-11c7-60a44ce903c3
Team42Name:Kristina Plattner
Team43UUID:ac6f46b0-b377-0132-11b7-60a44ce903c3
Team43Name:Kyle Klaas
Team44UUID:ac6277f0-b377-0132-1197-60a44ce903c3
Team44Name:Malcolm Higgins
Team45UUID:94eefa70-b4db-0132-1cea-60a44ce903c3
Team45Name:Maties
Team46UUID:ac5951f0-b377-0132-1192-60a44ce903c3
Team46Name:Michale Quirk
Team47UUID:ac884150-b377-0132-11d5-60a44ce903c3
Team47Name:Mike Holt
Team48UUID:95975fe0-b4db-0132-1d21-60a44ce903c3
Team48Name:NHYC 
Team48Comp1UUID:95984f80-b4db-0132-1d22-60a44ce903c3
Team49UUID:94b16cf0-b4db-0132-1cda-60a44ce903c3
Team49Name:Ostsee-Segler-Gemeinschaft
Team50UUID:ac6bbce0-b377-0132-11ad-60a44ce903c3
Team50Name:Peter Funke
Team51UUID:95357460-b4db-0132-1d06-60a44ce903c3
Team51Name:Point Yacht Club
Team51Comp1UUID:95368d50-b4db-0132-1d07-60a44ce903c3
Team52UUID:955fbdc0-b4db-0132-1d11-60a44ce903c3
Team52Name:Point YC 
Team52Comp1UUID:956a8030-b4db-0132-1d12-60a44ce903c3
Team53UUID:947bc310-b4db-0132-1cc6-60a44ce903c3
Team53Name:Port Kembla SC
Team53Comp1UUID:947c7c10-b4db-0132-1cc7-60a44ce903c3
Team54UUID:950350e0-b4db-0132-1cf2-60a44ce903c3
Team54Name:Pretoria Sailing Club
Team54Comp1UUID:95044100-b4db-0132-1cf3-60a44ce903c3
Team55UUID:9575e320-b4db-0132-1d15-60a44ce903c3
Team55Name:Pretoria SC 
Team56UUID:95226ee0-b4db-0132-1cfe-60a44ce903c3
Team56Name:PSC
Team56Comp1UUID:45750710-b6e4-0132-2282-60a44ce903c3
Team56Comp2UUID:2a9a8a70-b689-0132-2213-60a44ce903c3
Team56Comp3UUID:952360a0-b4db-0132-1cff-60a44ce903c3
Team57UUID:957fedb0-b4db-0132-1d19-60a44ce903c3
Team57Name:RCYC 
Team57Comp1UUID:9580e450-b4db-0132-1d1a-60a44ce903c3
Team58UUID:ac7360e0-b377-0132-11c3-60a44ce903c3
Team58Name:Ricky Robinson
Team59UUID:f1a55510-b500-0132-1e66-60a44ce903c3
Team59Name:Rock Sailing Club
Team59Comp1UUID:f1a864c0-b500-0132-1e67-60a44ce903c3
Team60UUID:ac653090-b377-0132-119f-60a44ce903c3
Team60Name:Rodger Deane
Team61UUID:946600a0-b4db-0132-1cbe-60a44ce903c3
Team61Name:Royal Brighton Yacht Club
Team61Comp1UUID:946739e0-b4db-0132-1cbf-60a44ce903c3
Team62UUID:950e5de0-b4db-0132-1cf6-60a44ce903c3
Team62Name:Royal Cape Yacht Club
Team62Comp1UUID:950fd6e0-b4db-0132-1cf7-60a44ce903c3
Team63UUID:952ba070-b4db-0132-1d02-60a44ce903c3
Team63Name:Royal Natal Yacht Club
Team63Comp1UUID:952c9010-b4db-0132-1d03-60a44ce903c3
Team64UUID:9445a600-b4db-0132-1cba-60a44ce903c3
Team64Name:RPEYC
Team64Comp1UUID:944a7e00-b4db-0132-1cbb-60a44ce903c3
Team65UUID:ac6312c0-b377-0132-1199-60a44ce903c3
Team65Name:Sam Cronin
Team66UUID:ac63c0c0-b377-0132-119b-60a44ce903c3
Team66Name:Sandy Higgins 
Team67UUID:c5a0f950-b377-0132-11d9-60a44ce903c3
Team67Name:Sandy Higgins 
Team68UUID:5f647e40-b4fc-0132-1d9d-60a44ce903c3
Team68Name:Santa Cruz Yacht Club
Team68Comp1UUID:5f8e1890-b4fc-0132-1da6-60a44ce903c3
Team68Comp2UUID:5f757d60-b4fc-0132-1d9e-60a44ce903c3
Team69UUID:949b6070-b4db-0132-1cd2-60a44ce903c3
Team69Name:SCLL
Team69Comp1UUID:949c2dc0-b4db-0132-1cd3-60a44ce903c3
Team70UUID:94a5f800-b4db-0132-1cd6-60a44ce903c3
Team70Name:Segel-Club Ville
Team70Comp1UUID:94a6ea50-b4db-0132-1cd7-60a44ce903c3
Team71UUID:459b10a0-b6e4-0132-228b-60a44ce903c3
Team71Name:SGJ
Team71Comp1UUID:94b25480-b4db-0132-1cdb-60a44ce903c3
Team72UUID:3c5a3440-b5c5-0132-204d-60a44ce903c3
Team72Name:Shotley Sailing Club
Team72Comp1UUID:f0941b50-b5ec-0132-20fb-60a44ce903c3
Team73UUID:ac715a70-b377-0132-11bd-60a44ce903c3
Team73Name:Sigi Bailes
Team74UUID:ac69c0c0-b377-0132-11a7-60a44ce903c3
Team74Name:Stefan Bohm
Team75UUID:b8ae7900-b10c-0132-075a-60a44ce903c3
Team75Name:team
Team76UUID:ac65e030-b377-0132-11a1-60a44ce903c3
Team76Name:Terry Scutcher
Team77UUID:453f5410-b6e4-0132-2277-60a44ce903c3
Team77Name:USYC
Team77Comp1UUID:94eff380-b4db-0132-1ceb-60a44ce903c3
Team78UUID:94f918c0-b4db-0132-1cee-60a44ce903c3
Team78Name:Vogelvlei Yacht Club
Team78Comp1UUID:954a24b0-b4db-0132-1d0e-60a44ce903c3
Team78Comp2UUID:94fa1150-b4db-0132-1cef-60a44ce903c3
Team79UUID:953f9670-b4db-0132-1d0a-60a44ce903c3
Team79Name: RCYC
Team79Comp1UUID:95407f70-b4db-0132-1d0b-60a44ce903c3
Comp1UUID:944a7e00-b4db-0132-1cbb-60a44ce903c3
Comp1Name:Quirk / Payne
Comp1NameShort:AUS 9134
Comp1Color:FFFF00
Comp1Nationality:AUS
Comp1Description:Michael Quirk/Luke Payne
Comp1Handicap:1.0000000000
Comp1HandicapDistance:0.00000
Comp1StartTime:2015-04-02 12:40:00
Comp1ClassUUID:ac5ab750-b377-0132-1193-60a44ce903c3
Comp1RouteUUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
Comp2UUID:946739e0-b4db-0132-1cbf-60a44ce903c3
Comp2Name:Bowden / Bath
Comp2NameShort:AUS 9018
Comp2Color:0000FF
Comp2Nationality:AUS
Comp2Description:Brett Bowden/Robert Bath
Comp2Handicap:1.0000000000
Comp2HandicapDistance:0.00000
Comp2StartTime:2015-04-02 12:40:00
Comp2ClassUUID:ac5ab750-b377-0132-1193-60a44ce903c3
Comp2RouteUUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
Comp3UUID:9471bf20-b4db-0132-1cc3-60a44ce903c3
Comp3Name:Higgins / Johnston
Comp3NameShort:AUS 8890
Comp3Color:008000
Comp3Nationality:AUS
Comp3Description:Malcolm Higgins/Nick Johnston
Comp3Handicap:1.0000000000
Comp3HandicapDistance:0.00000
Comp3StartTime:2015-04-02 12:40:00
Comp3ClassUUID:ac5ab750-b377-0132-1193-60a44ce903c3
Comp3RouteUUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
Comp4UUID:947c7c10-b4db-0132-1cc7-60a44ce903c3
Comp4Name:Cronin / Whitbread
Comp4NameShort:AUS 8637
Comp4Color:000000
Comp4Nationality:AUS
Comp4Description:Sam Cronin/Kevin Whitbread
Comp4Handicap:1.0000000000
Comp4HandicapDistance:0.00000
Comp4StartTime:2015-04-02 12:40:00
Comp4ClassUUID:ac5ab750-b377-0132-1193-60a44ce903c3
Comp4RouteUUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
Comp5UUID:94865d50-b4db-0132-1ccb-60a44ce903c3
Comp5Name:Higgins  / Marsh 
Comp5NameShort:AUS 8946
Comp5Color:008000
Comp5Nationality:AUS
Comp5Description:Sandy Higgins /Paul Marsh 
Comp5Handicap:1.0000000000
Comp5HandicapDistance:0.00000
Comp5StartTime:2015-04-02 12:40:00
Comp5ClassUUID:ac5ab750-b377-0132-1193-60a44ce903c3
Comp5RouteUUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
Comp6UUID:949331b0-b4db-0132-1ccf-60a44ce903c3
Comp6Name:Pinnell / Tellen
Comp6NameShort:GBR 9136
Comp6Color:800080
Comp6Nationality:GBR
Comp6Description:Ian Pinnell/Johannes Tellen
Comp6Handicap:1.0000000000
Comp6HandicapDistance:0.00000
Comp6StartTime:2015-04-02 12:40:00
Comp6ClassUUID:ac5ab750-b377-0132-1193-60a44ce903c3
Comp6RouteUUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
Comp7UUID:949c2dc0-b4db-0132-1cd3-60a44ce903c3
Comp7Name:Mittermayer / Barteldt
Comp7NameShort:GER 8988
Comp7Color:800080
Comp7Nationality:GER
Comp7Description:Georg Mittermayer/Dirk Barteldt
Comp7Handicap:1.0000000000
Comp7HandicapDistance:0.00000
Comp7StartTime:2015-04-02 12:40:00
Comp7ClassUUID:ac5ab750-b377-0132-1193-60a44ce903c3
Comp7RouteUUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
Comp8UUID:94a6ea50-b4db-0132-1cd7-60a44ce903c3
Comp8Name:Bohm / Roos
Comp8NameShort:GER 9135
Comp8Color:008000
Comp8Nationality:GER
Comp8Description:Stefan Bohm/Gerald Roos
Comp8Handicap:1.0000000000
Comp8HandicapDistance:0.00000
Comp8StartTime:2015-04-02 12:40:00
Comp8ClassUUID:ac5ab750-b377-0132-1193-60a44ce903c3
Comp8RouteUUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
Comp9UUID:94b25480-b4db-0132-1cdb-60a44ce903c3
Comp9Name:Menge / Wilts
Comp9NameShort:GER 9000
Comp9Color:808000
Comp9Nationality:GER
Comp9Description:Katharina Menge/Enno Wilts
Comp9Handicap:1.0000000000
Comp9HandicapDistance:0.00000
Comp9StartTime:2015-04-02 12:40:00
Comp9ClassUUID:ac5ab750-b377-0132-1193-60a44ce903c3
Comp9RouteUUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
Comp10UUID:94c44e80-b4db-0132-1cdf-60a44ce903c3
Comp10Name:Stenger / Birkner
Comp10NameShort:GER 9043
Comp10Color:008080
Comp10Nationality:GER
Comp10Description:Angela Stenger/Nicola Birkner
Comp10Handicap:1.0000000000
Comp10HandicapDistance:0.00000
Comp10StartTime:2015-04-02 12:40:00
Comp10ClassUUID:ac5ab750-b377-0132-1193-60a44ce903c3
Comp10RouteUUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
Comp11UUID:94d00ef0-b4db-0132-1ce3-60a44ce903c3
Comp11Name:Funke / Funke
Comp11NameShort:RSA 9094
Comp11Color:191970
Comp11Nationality:RSA
Comp11Description:Peter Funke/Thomas Funke
Comp11Handicap:1.0000000000
Comp11HandicapDistance:0.00000
Comp11StartTime:2015-04-02 12:40:00
Comp11ClassUUID:ac5ab750-b377-0132-1193-60a44ce903c3
Comp11RouteUUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
Comp12UUID:94e60210-b4db-0132-1ce7-60a44ce903c3
Comp12Name:Arthur / Anderson
Comp12NameShort:RSA 6266
Comp12Color:FFFF00
Comp12Nationality:RSA
Comp12Description:Andrew Arthur/John Anderson
Comp12Handicap:1.0000000000
Comp12HandicapDistance:0.00000
Comp12StartTime:2015-04-02 12:40:00
Comp12ClassUUID:ac5ab750-b377-0132-1193-60a44ce903c3
Comp12RouteUUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
Comp13UUID:94eff380-b4db-0132-1ceb-60a44ce903c3
Comp13Name:Ham / Ham
Comp13NameShort:RSA 8719
Comp13Color:FF0000
Comp13Nationality:RSA
Comp13Description:Alexander Ham/Warwick Ham
Comp13Handicap:1.0000000000
Comp13HandicapDistance:0.00000
Comp13StartTime:2015-04-02 12:40:00
Comp13ClassUUID:ac5ab750-b377-0132-1193-60a44ce903c3
Comp13RouteUUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
Comp14UUID:94fa1150-b4db-0132-1cef-60a44ce903c3
Comp14Name:Snyman / Ballantyne
Comp14NameShort:RSA 8091
Comp14Color:800000
Comp14Nationality:RSA
Comp14Description:Gideon Snyman/Courtney Ballantyne
Comp14Handicap:1.0000000000
Comp14HandicapDistance:0.00000
Comp14StartTime:2015-04-02 12:40:00
Comp14ClassUUID:ac5ab750-b377-0132-1193-60a44ce903c3
Comp14RouteUUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
Comp15UUID:95044100-b4db-0132-1cf3-60a44ce903c3
Comp15Name:Holm / Fourie
Comp15NameShort:RSA 8707
Comp15Color:191970
Comp15Nationality:RSA
Comp15Description:Albrecht Holm/Marius Fourie
Comp15Handicap:1.0000000000
Comp15HandicapDistance:0.00000
Comp15StartTime:2015-04-02 12:40:00
Comp15ClassUUID:ac5ab750-b377-0132-1193-60a44ce903c3
Comp15RouteUUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
Comp16UUID:950fd6e0-b4db-0132-1cf7-60a44ce903c3
Comp16Name:Largier / Hutton-Squire
Comp16NameShort:RSA 9063
Comp16Color:008080
Comp16Nationality:RSA
Comp16Description:James Largier/Richard Hutton-Squire
Comp16Handicap:1.0000000000
Comp16HandicapDistance:0.00000
Comp16StartTime:2015-04-02 12:40:00
Comp16ClassUUID:ac5ab750-b377-0132-1193-60a44ce903c3
Comp16RouteUUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
Comp17UUID:951a3330-b4db-0132-1cfb-60a44ce903c3
Comp17Name:Bailes / Isaac
Comp17NameShort:RSA 7608
Comp17Color:808000
Comp17Nationality:RSA
Comp17Description:Sigi Bailes/Dudley Isaac
Comp17Handicap:1.0000000000
Comp17HandicapDistance:0.00000
Comp17StartTime:2015-04-02 12:40:00
Comp17ClassUUID:ac5ab750-b377-0132-1193-60a44ce903c3
Comp17RouteUUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
Comp18UUID:952360a0-b4db-0132-1cff-60a44ce903c3
Comp18Name:Ham / Robinson
Comp18NameShort:RSA 8789
Comp18Color:FFFF00
Comp18Nationality:GBR
Comp18Description:Jonathan Ham / Michaela Robinson
Comp18Handicap:1.0000000000
Comp18HandicapDistance:0.00000
Comp18StartTime:2015-04-02 12:40:00
Comp18ClassUUID:ac5ab750-b377-0132-1193-60a44ce903c3
Comp18RouteUUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
Comp19UUID:952c9010-b4db-0132-1d03-60a44ce903c3
Comp19Name:Robinson / Robinson
Comp19NameShort:RSA 8486
Comp19Color:800000
Comp19Nationality:RSA
Comp19Description:Ricky Robinson/Brennan Robinson
Comp19Handicap:1.0000000000
Comp19HandicapDistance:0.00000
Comp19StartTime:2015-04-02 12:40:00
Comp19ClassUUID:ac5ab750-b377-0132-1193-60a44ce903c3
Comp19RouteUUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
Comp20UUID:95368d50-b4db-0132-1d07-60a44ce903c3
Comp20Name:Hurter / May
Comp20NameShort:RSA 8675
Comp20Color:0000FF
Comp20Nationality:RSA
Comp20Description:Euan Hurter/Murray May
Comp20Handicap:1.0000000000
Comp20HandicapDistance:0.00000
Comp20StartTime:2015-04-02 12:40:00
Comp20ClassUUID:ac5ab750-b377-0132-1193-60a44ce903c3
Comp20RouteUUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
Comp21UUID:95407f70-b4db-0132-1d0b-60a44ce903c3
Comp21Name:Plattner / Shelton
Comp21NameShort:RSA 9026
Comp21Color:800080
Comp21Nationality:RSA
Comp21Description:Tina Plattner/David Shelton
Comp21Handicap:1.0000000000
Comp21HandicapDistance:0.00000
Comp21StartTime:2015-04-02 12:40:00
Comp21ClassUUID:ac5ab750-b377-0132-1193-60a44ce903c3
Comp21RouteUUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
Comp22UUID:954a24b0-b4db-0132-1d0e-60a44ce903c3
Comp22Name:Ballantyne / Groenewald
Comp22NameShort:RSA 4437
Comp22Color:FF8C00
Comp22Nationality:RSA
Comp22Description:Grant Ballantyne/Evert Groenewald
Comp22Handicap:1.0000000000
Comp22HandicapDistance:0.00000
Comp22StartTime:2015-04-02 12:40:00
Comp22ClassUUID:ac5ab750-b377-0132-1193-60a44ce903c3
Comp22RouteUUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
Comp23UUID:956a8030-b4db-0132-1d12-60a44ce903c3
Comp23Name:Rogotzki / Roos 
Comp23NameShort:RSA 8584
Comp23Color:FF8C00
Comp23Nationality:RSA
Comp23Description:Hans Rogotzki/Peter Roos 
Comp23Handicap:1.0000000000
Comp23HandicapDistance:0.00000
Comp23StartTime:2015-04-02 12:40:00
Comp23ClassUUID:ac5ab750-b377-0132-1193-60a44ce903c3
Comp23RouteUUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
Comp24UUID:9580e450-b4db-0132-1d1a-60a44ce903c3
Comp24Name:D'Engle  / Nel 
Comp24NameShort:RSA 8520
Comp24Color:00FF00
Comp24Nationality:RSA
Comp24Description:Jay D'Engle /De-Wet Nel 
Comp24Handicap:1.0000000000
Comp24HandicapDistance:0.00000
Comp24StartTime:2015-04-02 12:40:00
Comp24ClassUUID:ac5ab750-b377-0132-1193-60a44ce903c3
Comp24RouteUUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
Comp25UUID:958bd200-b4db-0132-1d1e-60a44ce903c3
Comp25Name:Ross / Waterman
Comp25NameShort:USA 8084
Comp25Color:FF00FF
Comp25Nationality:USA
Comp25Description:Aaron Ross/Rob Waterman
Comp25Handicap:1.0000000000
Comp25HandicapDistance:0.00000
Comp25StartTime:2015-04-02 12:40:00
Comp25ClassUUID:ac5ab750-b377-0132-1193-60a44ce903c3
Comp25RouteUUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
Comp26UUID:95984f80-b4db-0132-1d22-60a44ce903c3
Comp26Name:Hamlin  / Nelson
Comp26NameShort:USA 9153
Comp26Color:800000
Comp26Nationality:USA
Comp26Description:Howard Hamlin / Jeff Nelson
Comp26Handicap:1.0000000000
Comp26HandicapDistance:0.00000
Comp26StartTime:2015-04-02 12:40:00
Comp26ClassUUID:ac5ab750-b377-0132-1193-60a44ce903c3
Comp26RouteUUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
Comp27UUID:5f757d60-b4fc-0132-1d9e-60a44ce903c3
Comp27Name:Holt / Smit
Comp27NameShort:USA 9072
Comp27Color:00FF00
Comp27Nationality:USA
Comp27Description:Mike Holt/Carl Smit
Comp27Handicap:1.0000000000
Comp27HandicapDistance:0.00000
Comp27StartTime:2015-04-02 12:40:00
Comp27ClassUUID:ac5ab750-b377-0132-1193-60a44ce903c3
Comp27RouteUUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
Comp28UUID:5f853dc0-b4fc-0132-1da2-60a44ce903c3
Comp28Name:Saugmann / Karbo
Comp28NameShort:DEN 9118
Comp28Color:008080
Comp28Nationality:DEN
Comp28Description:Jan Saugmann/Jakob Karbo
Comp28Handicap:1.0000000000
Comp28HandicapDistance:0.00000
Comp28StartTime:2015-04-02 12:40:00
Comp28ClassUUID:ac5ab750-b377-0132-1193-60a44ce903c3
Comp28RouteUUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
Comp29UUID:5f8e1890-b4fc-0132-1da6-60a44ce903c3
Comp29Name:Conrads / Haines
Comp29NameShort:USA 9091
Comp29Color:00FF00
Comp29Nationality:USA
Comp29Description:Ted Conrads/Brian Haines
Comp29Handicap:1.0000000000
Comp29HandicapDistance:0.00000
Comp29StartTime:2015-04-02 12:40:00
Comp29ClassUUID:ac5ab750-b377-0132-1193-60a44ce903c3
Comp29RouteUUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
Comp30UUID:f1a864c0-b500-0132-1e67-60a44ce903c3
Comp30Name:Deane / Deane
Comp30NameShort:GBR 9125
Comp30Color:191970
Comp30Nationality:GBR
Comp30Description:Roger Deane/Nigel Deane
Comp30Handicap:1.0000000000
Comp30HandicapDistance:0.00000
Comp30StartTime:2015-04-02 12:40:00
Comp30ClassUUID:ac5ab750-b377-0132-1193-60a44ce903c3
Comp30RouteUUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
Comp31UUID:3c51d930-b5c5-0132-204a-60a44ce903c3
Comp31Name:Parker / Holm
Comp31NameShort:RSA 8948
Comp31Color:FF00FF
Comp31Nationality:RSA
Comp31Description:Anthony Parker / Dietmar Holm
Comp31Handicap:1.0000000000
Comp31HandicapDistance:0.00000
Comp31StartTime:2015-04-02 12:40:00
Comp31ClassUUID:ac5ab750-b377-0132-1193-60a44ce903c3
Comp31RouteUUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
Comp32UUID:f0941b50-b5ec-0132-20fb-60a44ce903c3
Comp32Name:Scutcher / Diebitsch
Comp32NameShort:GBR 9124
Comp32Color:FF0000
Comp32Nationality:GBR
Comp32Description:Terry Scutcher/Christian Diebitsch
Comp32Handicap:1.0000000000
Comp32HandicapDistance:0.00000
Comp32StartTime:2015-04-02 12:40:00
Comp32ClassUUID:ac5ab750-b377-0132-1193-60a44ce903c3
Comp32RouteUUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
Comp33UUID:2a9a8a70-b689-0132-2213-60a44ce903c3
Comp33Name:Klaas  / Tellen
Comp33NameShort:RSA 7822
Comp33Color:000000
Comp33Nationality:RSA
Comp33Description:Bronwen Klaas /Aron Tellen
Comp33Handicap:1.0000000000
Comp33HandicapDistance:0.00000
Comp33StartTime:2015-04-02 12:40:00
Comp33ClassUUID:ac5ab750-b377-0132-1193-60a44ce903c3
Comp33RouteUUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
Comp34UUID:4568fe80-b6e4-0132-227f-60a44ce903c3
Comp34Name:Holtshauzen  / Kirchner
Comp34NameShort:RSA 8121
Comp34Color:0000FF
Comp34Nationality:RSA
Comp34Description:Kobus Holtshauzen /Danie Kirchner
Comp34Handicap:1.0000000000
Comp34HandicapDistance:0.00000
Comp34StartTime:2015-04-02 12:40:00
Comp34ClassUUID:ac5ab750-b377-0132-1193-60a44ce903c3
Comp34RouteUUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
Comp35UUID:45750710-b6e4-0132-2282-60a44ce903c3
Comp35Name:Klaas / Von Gruenewaldt
Comp35NameShort:RSA 8445
Comp35Color:FF8C00
Comp35Nationality:RSA
Comp35Description:Kyle Klaas/Robert Von Gruenewaldt
Comp35Handicap:1.0000000000
Comp35HandicapDistance:0.00000
Comp35StartTime:2015-04-02 12:40:00
Comp35ClassUUID:ac5ab750-b377-0132-1193-60a44ce903c3
Comp35RouteUUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
Comp36UUID:45b290d0-b6e4-0132-2291-60a44ce903c3
Comp36Name:Foreman / Van Der Ploeg
Comp36NameShort:RSA 8537
Comp36Color:808000
Comp36Nationality:RSA
Comp36Description:Kevin Foreman/Adam Van Der Ploeg
Comp36Handicap:1.0000000000
Comp36HandicapDistance:0.00000
Comp36StartTime:2015-04-02 12:40:00
Comp36ClassUUID:ac5ab750-b377-0132-1193-60a44ce903c3
Comp36RouteUUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
ControlPoint1UUID:25f25a90-b906-0132-2954-60a44ce903c3
ControlPoint1Name:Windward-F-30
ControlPoint1HasTwoPoints:0
ControlPoint1ShortName:1R
ControlPoint1Mark1Lon:25.694404
ControlPoint1Mark1Lat:-33.957179
ControlPoint1Mark1Static:1
ControlPoint2UUID:26126690-b4de-0132-1d39-60a44ce903c3
ControlPoint2Name:Replacement mark
ControlPoint2HasTwoPoints:0
ControlPoint2ShortName:Re
ControlPoint2Mark1Lon:25.687969
ControlPoint2Mark1Lat:-33.949951
ControlPoint2Mark1Static:0
ControlPoint3UUID:3fe5f360-b52a-0132-1f57-60a44ce903c3
ControlPoint3Name:Leeward gate PreW
ControlPoint3HasTwoPoints:1
ControlPoint3ShortName:3
ControlPoint3Mark1Lon:25.633739
ControlPoint3Mark1Lat:-33.967300
ControlPoint3Mark1Static:0
ControlPoint3Mark2Lon:25.633644
ControlPoint3Mark2Lat:-33.967327
ControlPoint3Mark2Static:0
ControlPoint4UUID:69c13f40-b37c-0132-1211-60a44ce903c3
ControlPoint4Name:Wing
ControlPoint4HasTwoPoints:0
ControlPoint4ShortName:2
ControlPoint4Mark1Lon:25.689110
ControlPoint4Mark1Lat:-33.970020
ControlPoint4Mark1Static:0
ControlPoint5UUID:8f54b350-b767-0132-2455-60a44ce903c3
ControlPoint5Name:3 Worlds 1
ControlPoint5HasTwoPoints:1
ControlPoint5ShortName:3
ControlPoint5Mark1Lon:25.665367
ControlPoint5Mark1Lat:-33.961881
ControlPoint5Mark1Static:1
ControlPoint5Mark2Lon:25.665457
ControlPoint5Mark2Lat:-33.962330
ControlPoint5Mark2Static:1
ControlPoint6UUID:9ca100f0-b68c-0132-2216-60a44ce903c3
ControlPoint6Name:3p
ControlPoint6HasTwoPoints:0
ControlPoint6ShortName:3p
ControlPoint6Mark1Lon:25.683989
ControlPoint6Mark1Lat:-33.954403
ControlPoint6Mark1Static:0
ControlPoint7UUID:ab8077c0-b37c-0132-1218-60a44ce903c3
ControlPoint7Name:Finish
ControlPoint7HasTwoPoints:1
ControlPoint7ShortName:F
ControlPoint7Mark1Lon:25.687019
ControlPoint7Mark1Lat:-33.950874
ControlPoint7Mark1Static:0
ControlPoint7Mark2Lon:25.688251
ControlPoint7Mark2Lat:-33.950226
ControlPoint7Mark2Static:0
ControlPoint8UUID:af1bb4f0-b68c-0132-2219-60a44ce903c3
ControlPoint8Name:3s
ControlPoint8HasTwoPoints:0
ControlPoint8ShortName:3S
ControlPoint8Mark1Lon:25.684313
ControlPoint8Mark1Lat:-33.954788
ControlPoint8Mark1Static:0
ControlPoint9UUID:b10792d0-b5e2-0132-20f9-60a44ce903c3
ControlPoint9Name:Fin R2
ControlPoint9HasTwoPoints:1
ControlPoint9ShortName:Fi
ControlPoint9Mark1Lon:25.724152
ControlPoint9Mark1Lat:-34.001356
ControlPoint9Mark1Static:1
ControlPoint9Mark2Lon:25.723793
ControlPoint9Mark2Lat:-34.001524
ControlPoint9Mark2Static:1
ControlPoint10UUID:cf64f6d0-b4dd-0132-1d2c-60a44ce903c3
ControlPoint10Name:Start
ControlPoint10HasTwoPoints:1
ControlPoint10ShortName:St
ControlPoint10Mark1Lon:25.686090
ControlPoint10Mark1Lat:-33.950943
ControlPoint10Mark1Static:0
ControlPoint10Mark2Lon:25.687042
ControlPoint10Mark2Lat:-33.950867
ControlPoint10Mark2Static:0
ControlPoint11UUID:d742d1b0-b68c-0132-221c-60a44ce903c3
ControlPoint11Name:Leeward gate Worlds
ControlPoint11HasTwoPoints:1
ControlPoint11ShortName:3
ControlPoint11DataSheet:P1.UUID=af1bb4f0-b68c-0132-2219-60a44ce903c3###BREAKLINE###P2.UUID=9ca100f0-b68c-0132-2216-60a44ce903c3###BREAKLINE###
ControlPoint11Mark1Lon:25.684313
ControlPoint11Mark1Lat:-33.954788
ControlPoint11Mark1Static:0
ControlPoint11Mark2Lon:25.683989
ControlPoint11Mark2Lat:-33.954403
ControlPoint11Mark2Static:0
ControlPoint12UUID:e6879b90-b4dd-0132-1d30-60a44ce903c3
ControlPoint12Name:Windward
ControlPoint12HasTwoPoints:0
ControlPoint12ShortName:1
ControlPoint12Mark1Lon:25.668341
ControlPoint12Mark1Lat:-33.968086
ControlPoint12Mark1Static:0
ControlPoint13UUID:f6f60d80-b4dd-0132-1d33-60a44ce903c3
ControlPoint13Name:Spreader
ControlPoint13HasTwoPoints:0
ControlPoint13ShortName:1a
ControlPoint13Mark1Lon:25.669888
ControlPoint13Mark1Lat:-33.968563
ControlPoint13Mark1Static:0
SegmentFile:segments.php
Route1UUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
Route1Description:Worlds 7
Route1Length:0
Route1ControlPoint1UUID:cf64f6d0-b4dd-0132-1d2c-60a44ce903c3
Route1ControlPoint2UUID:e6879b90-b4dd-0132-1d30-60a44ce903c3
Route1ControlPoint3UUID:f6f60d80-b4dd-0132-1d33-60a44ce903c3
Route1ControlPoint4UUID:d742d1b0-b68c-0132-221c-60a44ce903c3
Route1ControlPoint5UUID:e6879b90-b4dd-0132-1d30-60a44ce903c3
Route1ControlPoint6UUID:f6f60d80-b4dd-0132-1d33-60a44ce903c3
Route1ControlPoint7UUID:69c13f40-b37c-0132-1211-60a44ce903c3
Route1ControlPoint8UUID:d742d1b0-b68c-0132-221c-60a44ce903c3
Route1ControlPoint9UUID:e6879b90-b4dd-0132-1d30-60a44ce903c3
Route1ControlPoint10UUID:f6f60d80-b4dd-0132-1d33-60a44ce903c3
Route1ControlPoint11UUID:d742d1b0-b68c-0132-221c-60a44ce903c3
Route1ControlPoint12UUID:ab8077c0-b37c-0132-1218-60a44ce903c3
//-------- AUTO GENERATED PARAMETERS END ------------------
@@ -15,6 +15,9 @@ import com.sap.sse.common.TimePoint;
import com.sap.sse.common.impl.MillisecondsTimePoint;
public abstract class AbstractManeuverDetectionTestCase extends OnlineTracTracBasedTest {
/**
* A date parser for UTC+2 in format MM/dd/yyyy-HH:mm:ss.
*/
protected SimpleDateFormat dateFormat;
protected static final int TACK_TOLERANCE = 7000;
protected static final int JIBE_TOLERANCE = 7000;
@@ -5,8 +5,10 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertSame;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.junit.Test;
@@ -42,4 +44,13 @@ public class ArrayListNavigableSetTest {
Integer integerFromSet = hashSet.iterator().next();
assertSame(i1, integerFromSet); // supposedly not replaced
}
@Test
public void testMultiAdd() {
Set<Integer> arrayListNavigableSet = new ArrayListNavigableSet<Integer>(Comparator.naturalOrder());
final List<Integer> elements = Arrays.asList(1, 2, 3, 4, 5);
arrayListNavigableSet.addAll(elements);
assertEquals(5, arrayListNavigableSet.size());
assertEquals(new HashSet<>(elements), arrayListNavigableSet);
}
}
@@ -0,0 +1,70 @@
package com.sap.sailing.domain.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import java.util.Arrays;
import java.util.Collections;
import java.util.UUID;
import org.junit.Before;
import org.junit.Test;
import com.sap.sailing.domain.base.CourseArea;
import com.sap.sailing.domain.base.impl.CourseAreaImpl;
import com.sap.sailing.domain.common.sensordata.BravoExtendedSensorDataMetadata;
import com.sap.sailing.domain.common.tracking.impl.BravoExtendedFixImpl;
import com.sap.sailing.domain.common.tracking.impl.DoubleVectorFixImpl;
import com.sap.sailing.domain.tracking.DynamicBravoFixTrack;
import com.sap.sailing.domain.tracking.impl.BravoFixTrackImpl;
import com.sap.sse.common.impl.MillisecondsTimePoint;
public class BravoFixTrackAverageRideHeightTest {
private DynamicBravoFixTrack<CourseArea> track;
@Before
public void setUp() {
track = new BravoFixTrackImpl<>(new CourseAreaImpl("Test", UUID.randomUUID()),
"test", /* hasExtendedFixes */ true);
track.add(createFix(10l, /* rideHeightPort */ 0.6, /* rideHeightStarboard */ 0.6, /* heel */ 10., /* pitch */ 5.));
track.add(createFix(20l, /* rideHeightPort */ 0.6, /* rideHeightStarboard */ 0.6, /* heel */ 10., /* pitch */ 5.));
track.add(createFix(30l, /* rideHeightPort */ 0.6, /* rideHeightStarboard */ 0.6, /* heel */ 10., /* pitch */ 5.));
}
@Test
public void testAverageRideHeightCachingForEmptyInterval() {
assertNull(track.getAverageRideHeight(t(11l), t(19l)));
}
@Test
public void testAverageRideHeightCachingForNonEmptyInterval() {
assertEquals(0.6, track.getAverageRideHeight(t(11l), t(21l)).getMeters(), 0.00001); // make sure that the cached "null" result doesn't offend surrounding calculations
assertEquals(0.6, track.getAverageRideHeight(t(9l), t(21l)).getMeters(), 0.00001); // also when the first fix is included
assertEquals(0.6, track.getAverageRideHeight(t(9l), t(31l)).getMeters(), 0.00001); // ensure that results are still correct when cache intervals are "added"
}
@Test
public void testAverageRideHeightCachingForNonEmptyIntervalAfterHavingCachedEmptyIntervalResult() {
testAverageRideHeightCachingForEmptyInterval();
assertEquals(0.6, track.getAverageRideHeight(t(11l), t(21l)).getMeters(), 0.00001); // make sure that the cached "null" result doesn't offend surrounding calculations
assertEquals(0.6, track.getAverageRideHeight(t(9l), t(21l)).getMeters(), 0.00001); // also when the first fix is included
assertEquals(0.6, track.getAverageRideHeight(t(9l), t(31l)).getMeters(), 0.00001); // ensure that results are still correct when cache intervals are "added"
}
private BravoExtendedFixImpl createFix(long timePointAsMillis, Double rideHeightPort, Double rideHeightStarboard, Double heel, Double pitch) {
final Double[] fixData = new Double[Collections.max(Arrays.asList(
BravoExtendedSensorDataMetadata.HEEL.getColumnIndex()+1,
BravoExtendedSensorDataMetadata.PITCH.getColumnIndex()+1,
BravoExtendedSensorDataMetadata.RIDE_HEIGHT_PORT_HULL.getColumnIndex()+1,
BravoExtendedSensorDataMetadata.RIDE_HEIGHT_STBD_HULL.getColumnIndex()+1))];
fixData[BravoExtendedSensorDataMetadata.HEEL.getColumnIndex()] = heel;
fixData[BravoExtendedSensorDataMetadata.PITCH.getColumnIndex()] = pitch;
fixData[BravoExtendedSensorDataMetadata.RIDE_HEIGHT_PORT_HULL.getColumnIndex()] = rideHeightPort;
fixData[BravoExtendedSensorDataMetadata.RIDE_HEIGHT_STBD_HULL.getColumnIndex()] = rideHeightStarboard;
return new BravoExtendedFixImpl(new DoubleVectorFixImpl(t(timePointAsMillis), fixData));
}
private MillisecondsTimePoint t(long timePointAsMillis) {
return new MillisecondsTimePoint(timePointAsMillis);
}
}
@@ -47,6 +47,11 @@ public class DummyTrackedRegattaRegistry implements TrackedRegattaRegistry {
return null;
}
@Override
public void removeRace(Regatta regatta, RaceDefinition race)
throws MalformedURLException, IOException, InterruptedException {
}
@Override
public boolean isRaceBeingTracked(Regatta regattaContext, RaceDefinition r) {
return false;
@@ -145,6 +145,10 @@ public class LeaderboardOfflineTest extends AbstractLeaderboardTest {
public void stopTracking(Regatta regatta, RaceDefinition race) throws MalformedURLException, IOException,
InterruptedException {
}
@Override
public void removeRace(Regatta regatta, RaceDefinition race)
throws MalformedURLException, IOException, InterruptedException {
}
};
LeaderboardDTO leaderboardDTO = leaderboard.getLeaderboardDTO(now, emptySet, /* addOverallDetails */ true, trackedRegattaRegistry, DomainFactory.INSTANCE, /* fillTotalPointsUncorrected */ false);
assertNotNull(leaderboardDTO);
@@ -0,0 +1,74 @@
package com.sap.sailing.domain.test;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.text.ParseException;
import java.util.Date;
import org.junit.Before;
import org.junit.Test;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.common.NoWindException;
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
import com.sap.sailing.domain.tracking.DynamicGPSFixTrack;
import com.sap.sailing.domain.tractracadapter.ReceiverType;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.impl.MillisecondsTimePoint;
import com.tractrac.model.lib.api.event.CreateModelException;
import com.tractrac.subscription.lib.api.SubscriberInitializationException;
/**
* At the 505 Worlds 2015 Race R7 Arthur/Anderson (RSA 6266) have been tracked with a wild outlier
* around 2015-04-02T15:05:07+0200.
*
* @author Axel Uhl (d043530)
*
*/
public class OutlierDetectionTest extends AbstractManeuverDetectionTestCase {
public OutlierDetectionTest() throws MalformedURLException, URISyntaxException {
super();
}
@Before
public void setUp() throws URISyntaxException, IOException, InterruptedException, ParseException, SubscriberInitializationException, CreateModelException {
super.setUp();
URI storedUri = new URI("file:///"+new File("resources/event_20150323_Worlds-505_Worlds_7.mtb").getCanonicalPath().replace('\\', '/'));
super.setUp(new URL("file:///"+new File("resources/event_20150323_Worlds-505_Worlds_7.txt").getCanonicalPath()),
/* liveUri */ null, /* storedUri */ storedUri,
new ReceiverType[] { ReceiverType.MARKPASSINGS, ReceiverType.RACECOURSE, ReceiverType.RAWPOSITIONS });
}
protected String getExpectedEventName() {
return "505 Worlds 2015";
}
/**
* Tests the 505 Race 7 for competitor "Arthur / Anderson" at a time where an outlier exists
*/
@Test
public void testManeuversForFindelCriticalDetection() throws ParseException, NoWindException {
Competitor competitor = getCompetitorByName("Arthur / Anderson");
assertNotNull(competitor);
Date fromDate = dateFormat.parse("04/02/2015-15:04:50");
TimePoint from = new MillisecondsTimePoint(fromDate);
Date toDate = dateFormat.parse("04/02/2015-15:06:20");
TimePoint to = new MillisecondsTimePoint(toDate);
assertNotNull(fromDate);
assertNotNull(toDate);
DynamicGPSFixTrack<Competitor, GPSFixMoving> track = getTrackedRace().getTrack(competitor);
track.lockForRead();
try {
assertTrue(track.getMaximumSpeedOverGround(from, to).getB().getKnots() < 15.);
} finally {
track.unlockAfterRead();
}
}
}
@@ -550,6 +550,10 @@ public class MockedTrackedRace implements DynamicTrackedRace {
public void setControlTrackingFromStartAndFinishTimes(
boolean controlTrackingFromStartAndFinishTimes) {
}
@Override
public void setFleetsCanRunInParallelToTrue() {
}
};
}
@@ -78,7 +78,7 @@ public class MockedTrackedRaceWithFixedRank extends MockedTrackedRace {
}
@Override
public Boat getBoatOfCompetitorById(Serializable competitorID) {
public Boat getBoatOfCompetitor(Competitor competitor) {
return null;
}
@@ -52,7 +52,7 @@ public class MockedTrackedRaceWithFixedRankAndManyCompetitors extends MockedTrac
return null;
}
@Override
public Boat getBoatOfCompetitorById(Serializable competitorID) {
public Boat getBoatOfCompetitor(Competitor competitor) {
return null;
}
@Override
@@ -1,5 +1,6 @@
package com.sap.sailing.domain.tractracadapter.persistence.impl;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.util.HashMap;
@@ -12,6 +13,7 @@ import com.sap.sailing.domain.tracking.RaceTrackingConnectivityParameters;
import com.sap.sailing.domain.tracking.impl.AbstractRaceTrackingConnectivityParametersHandler;
import com.sap.sailing.domain.tractracadapter.DomainFactory;
import com.sap.sailing.domain.tractracadapter.impl.RaceTrackingConnectivityParametersImpl;
import com.sap.sailing.domain.tractracadapter.impl.TracTracRaceTrackerImpl;
import com.sap.sse.common.TypeBasedServiceFinder;
import com.sap.sse.common.impl.MillisecondsDurationImpl;
import com.sap.sse.common.impl.MillisecondsTimePoint;
@@ -52,7 +54,7 @@ public class TracTracConnectivityParamsHandler extends AbstractRaceTrackingConne
}
@Override
public Map<String, Object> mapFrom(RaceTrackingConnectivityParameters params) {
public Map<String, Object> mapFrom(RaceTrackingConnectivityParameters params) throws MalformedURLException {
assert params instanceof RaceTrackingConnectivityParametersImpl;
final RaceTrackingConnectivityParametersImpl ttParams = (RaceTrackingConnectivityParametersImpl) params;
final Map<String, Object> result = getKey(params);
@@ -94,12 +96,12 @@ public class TracTracConnectivityParamsHandler extends AbstractRaceTrackingConne
}
@Override
public Map<String, Object> getKey(RaceTrackingConnectivityParameters params) {
public Map<String, Object> getKey(RaceTrackingConnectivityParameters params) throws MalformedURLException {
assert params instanceof RaceTrackingConnectivityParametersImpl;
final RaceTrackingConnectivityParametersImpl ttParams = (RaceTrackingConnectivityParametersImpl) params;
final Map<String, Object> result = new HashMap<>();
result.put(TypeBasedServiceFinder.TYPE, params.getTypeIdentifier());
result.put(PARAM_URL, ttParams.getParamURL().toString());
result.put(PARAM_URL, TracTracRaceTrackerImpl.getParamURLStrippedOfRandomParam(new URL(ttParams.getParamURL().toString())).toString());
return result;
}
}
@@ -132,21 +132,19 @@ public interface DomainFactory {
* Provides the capability to obtain the {@link WindTrack}s for the different wind sources. A trivial
* implementation is {@link EmptyWindStore} which simply provides new, empty tracks. This is always
* available but loses track of the wind, e.g., during server restarts.
* @param timeoutInMilliseconds TODO
*/
TracTracRaceTracker createRaceTracker(RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, WindStore windStore, TrackedRegattaRegistry trackedRegattaRegistry,
RaceLogResolver raceLogResolver, RaceTrackingConnectivityParametersImpl connectivityParams, long timeoutInMilliseconds)
throws MalformedURLException, FileNotFoundException, URISyntaxException, CreateModelException, SubscriberInitializationException;
throws MalformedURLException, FileNotFoundException, URISyntaxException, CreateModelException, SubscriberInitializationException, IOException, InterruptedException;
/**
* Same as {@link #createRaceTracker(URL, URI, URI, URI, TimePoint, TimePoint, WindStore, TrackedRegattaRegistry)},
* only that a predefined {@link Regatta} is used to hold the resulting races.
* @param timeoutInMilliseconds TODO
*/
RaceTracker createRaceTracker(Regatta regatta, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, WindStore windStore, TrackedRegattaRegistry trackedRegattaRegistry,
RaceLogResolver raceLogResolver, RaceTrackingConnectivityParametersImpl connectivityParams, long timeoutInMilliseconds)
throws MalformedURLException, FileNotFoundException, URISyntaxException, CreateModelException,
SubscriberInitializationException;
SubscriberInitializationException, IOException, InterruptedException;
BoatClass getOrCreateBoatClass(String competitorClassName);
@@ -161,7 +159,6 @@ public interface DomainFactory {
* must have been created before through
* {@link #getOrCreateTrackedRegatta(com.sap.sailing.domain.base.Regatta)} because otherwise the link to
* the {@link IEvent} can't be established
* @param timeoutInMilliseconds TODO
* @param tokenToRetrieveAssociatedRace
* used to update the set of{@link RaceDefinition}s received by the {@link RaceCourseReceiver} created by
* this call
@@ -173,47 +170,39 @@ public interface DomainFactory {
IEventSubscriber eventSubscriber, IRaceSubscriber raceSubscriber, boolean useInternalMarkPassingAlgorithm, long timeoutInMilliseconds);
/**
* Creates a {@link RaceDefinition} from a TracTrac {@link IRace} and a
* domain {@link Course} definition. The resulting {@link RaceDefinition} is
* added to the {@link com.sap.sailing.domain.base.Regatta} to which
* <code>trackedRegatta</code> belongs (see
* {@link TrackedRegatta#getRegatta()}). It is added to the internal race
* cache. The corresponding {@link TrackedRace} object is also created, and
* the notification of threads waiting on the race cache such as a blocking
* {@link #getAndWaitForRaceDefinition(UUID)} happens only <em>after</em>
* the tracked race has been created and the {@link RaceDefinition} was
* {@link com.sap.sailing.domain.base.Regatta#addRace(RaceDefinition) added}
* to the domain event. This ensures that waiters for the
* {@link RaceDefinition} are guaranteed to obtain a valid,
* non-<code>null</code> tracked race already immediately after the
* notification was sent, and that the {@link RaceDefinition} is already
* {@link com.sap.sailing.domain.base.Regatta#getAllRaces() known} by its
* containing {@link com.sap.sailing.domain.base.Regatta}.
* Creates a {@link RaceDefinition} from a TracTrac {@link IRace} and a domain {@link Course} definition. The
* resulting {@link RaceDefinition} is added to the {@link com.sap.sailing.domain.base.Regatta} to which
* <code>trackedRegatta</code> belongs (see {@link TrackedRegatta#getRegatta()}). It is added to the internal race
* cache. The corresponding {@link TrackedRace} object is also created, and the notification of threads waiting on
* the race cache such as a blocking {@link #getAndWaitForRaceDefinition(UUID)} happens only <em>after</em> the
* tracked race has been created and the {@link RaceDefinition} was
* {@link com.sap.sailing.domain.base.Regatta#addRace(RaceDefinition) added} to the domain event. This ensures that
* waiters for the {@link RaceDefinition} are guaranteed to obtain a valid, non-<code>null</code> tracked race
* already immediately after the notification was sent, and that the {@link RaceDefinition} is already
* {@link com.sap.sailing.domain.base.Regatta#getAllRaces() known} by its containing
* {@link com.sap.sailing.domain.base.Regatta}.
*
* @param raceDefinitionSetToUpdate
* if not <code>null</code>, after creating the
* {@link TrackedRace}, the {@link RaceDefinition} is
* {@link DynamicRaceDefinitionSet#addRaceDefinition(RaceDefinition, DynamicTrackedRace)
* added} to that object.
* if not <code>null</code>, after creating the {@link TrackedRace}, the {@link RaceDefinition} is
* {@link DynamicRaceDefinitionSet#addRaceDefinition(RaceDefinition, DynamicTrackedRace) added} to that
* object.
* @param runBeforeExposingRace
* if not {@code null} then this consumer will be passed the
* {@link DynamicTrackedRace} if it was actually created by this
* call. This happens while still in the
* {@code synchronized(raceCache)} block, therefore before calls
* waiting for the race (e.g.,
* {@link #getAndWaitForRaceDefinition(UUID)}) return the race.
* @param tractracRace TODO
*/
DynamicTrackedRace getOrCreateRaceDefinitionAndTrackedRace(DynamicTrackedRegatta trackedRegatta, UUID raceId,
String raceName, Iterable<com.sap.sailing.domain.base.Competitor> competitors, BoatClass boatClass,
Map<Competitor, Boat> competitorBoats, Course course, Iterable<Sideline> sidelines, WindStore windStore,
long delayToLiveInMillis, long millisecondsOverWhichToAverageWind,
DynamicRaceDefinitionSet raceDefinitionSetToUpdate, URI courseDesignUpdateURI, UUID tracTracEventUuid,
String tracTracUsername, String tracTracPassword, boolean ignoreTracTracMarkPassings, RaceLogResolver raceLogResolver, Consumer<DynamicTrackedRace> runBeforeExposingRace, IRace tractracRace);
* if not {@code null} then this consumer will be passed the {@link DynamicTrackedRace} if it was
* actually created by this call. This happens while still in the {@code synchronized(raceCache)} block,
* therefore before calls waiting for the race (e.g., {@link #getAndWaitForRaceDefinition(UUID)}) return
* the race.
*/
DynamicTrackedRace getOrCreateRaceDefinitionAndTrackedRace(DynamicTrackedRegatta trackedRegatta, UUID raceId,
String raceName, Iterable<com.sap.sailing.domain.base.Competitor> competitors, BoatClass boatClass,
Map<Competitor, Boat> competitorBoats, Course course, Iterable<Sideline> sidelines, WindStore windStore,
long delayToLiveInMillis, long millisecondsOverWhichToAverageWind,
DynamicRaceDefinitionSet raceDefinitionSetToUpdate, URI courseDesignUpdateURI, UUID tracTracEventUuid,
String tracTracUsername, String tracTracPassword, boolean ignoreTracTracMarkPassings,
RaceLogResolver raceLogResolver, Consumer<DynamicTrackedRace> runBeforeExposingRace, IRace tractracRace);
/**
* The record may be for a single mark or a gate. If for a gate, the
* {@link ControlPointPositionData#getIndex() index} is used to determine
* which of its marks is affected.
* The record may be for a single mark or a gate. If for a gate, the {@link ControlPointPositionData#getIndex()
* index} is used to determine which of its marks is affected.
*/
Mark getMark(TracTracControlPoint controlPoint, int zeroBasedMarkIndex);
@@ -223,8 +212,6 @@ public interface DomainFactory {
/**
* If the vm argument tractrac.usemarkpassings=false, the RecieverType MARKPASSINGS will not return anything
* @param raceLogResolver TODO
* @param timeoutInMilliseconds TODO
*/
Iterable<Receiver> getUpdateReceivers(DynamicTrackedRegatta trackedRegatta, IRace tractracRace, WindStore windStore,
long delayToLiveInMillis, Simulator simulator, DynamicRaceDefinitionSet raceDefinitionSetToUpdate, TrackedRegattaRegistry trackedRegattaRegistry,
@@ -274,23 +261,17 @@ public interface DomainFactory {
* {@code File}) then if this parameter is {@code true} the race will be loaded from the replay file
* instead of the {@code storedURI}/{@code liveURI} specified. This is particularly useful for restoring
* races if since the last connection the race was migrated to a replay file format.
* @param timeoutInMillis TODO
*/
RaceTrackingConnectivityParameters createTrackingConnectivityParameters(URL paramURL, URI liveURI, URI storedURI,
URI courseDesignUpdateURI, TimePoint startOfTracking, TimePoint endOfTracking, long delayToLiveInMillis,
Duration offsetToStartTimeOfSimulatedRace, boolean useInternalMarkPassingAlgorithm, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore,
String tracTracUsername, String tracTracPassword, String raceStatus, String raceVisibility, boolean trackWind, boolean correctWindDirectionByMagneticDeclination,
boolean preferReplayIfAvailable, int timeoutInMillis) throws Exception;
/**
* Removes all knowledge about <code>tractracRace</code> which includes removing it from the race cache, from the
* {@link com.sap.sailing.domain.base.Regatta} and, if a {@link TrackedRace} for the corresponding
* {@link RaceDefinition} exists, from the {@link TrackedRegatta}. If removing the race from the event, the event is
* removed from the event cache such that {@link #getOrCreateDefaultRegatta(IEvent, TrackedRegattaRegistry)} will have to create a new one. Similarly,
* if the {@link TrackedRace} that was removed from the {@link TrackedRegatta} was the last one, the
* {@link TrackedRegatta} is removed such that {@link #getOrCreateTrackedRegatta(com.sap.sailing.domain.base.Regatta)}
* will have to create a new one.
* Removes all knowledge about <code>tractracRace</code> from the race cache.
*/
void removeRace(IEvent tractracEvent, IRace tractracRace, TrackedRegattaRegistry trackedRegattaRegistry);
RaceDefinition removeRace(IEvent tractracEvent, IRace tractracRace, Regatta regattaToLoadRaceInto, TrackedRegattaRegistry trackedRegattaRegistry);
/**
* Computes an ID to use for a {@link RaceDefinition} based on the TracTrac race.
@@ -1,9 +1,7 @@
package com.sap.sailing.domain.tractracadapter.impl;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.Serializable;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
@@ -457,7 +455,7 @@ public class DomainFactoryImpl implements DomainFactory {
}
@Override
public void removeRace(IEvent tractracEvent, IRace tractracRace, TrackedRegattaRegistry trackedRegattaRegistry) {
public RaceDefinition removeRace(IEvent tractracEvent, IRace tractracRace, Regatta regattaToLoadRaceInto, TrackedRegattaRegistry trackedRegattaRegistry) {
RaceDefinition raceDefinition;
synchronized (raceCache) {
raceDefinition = getExistingRaceDefinitionForRace(tractracRace.getId());
@@ -467,45 +465,33 @@ public class DomainFactoryImpl implements DomainFactory {
}
}
if (raceDefinition != null) {
// FIXME this only removes the race from its default regatta, not any explicit regatta
Pair<String, BoatClass> defaultRegattaNameAndBoatClass = getDefaultRegattaNameAndBoatClass(tractracRace);
Pair<String, String> key = new Pair<String, String>(defaultRegattaNameAndBoatClass.getA(),
defaultRegattaNameAndBoatClass.getB() == null ? null :
defaultRegattaNameAndBoatClass.getB().getName());
synchronized (regattaCache) {
Regatta regatta = regattaCache.get(key);
final Regatta regatta;
final Pair<String, String> key;
if (regattaToLoadRaceInto != null) {
regatta = regattaToLoadRaceInto;
key = new Pair<>(regatta.getName(), regatta.getBoatClass().getName());
} else {
Pair<String, BoatClass> defaultRegattaNameAndBoatClass = getDefaultRegattaNameAndBoatClass(tractracRace);
key = new Pair<String, String>(defaultRegattaNameAndBoatClass.getA(),
defaultRegattaNameAndBoatClass.getB() == null ? null :
defaultRegattaNameAndBoatClass.getB().getName());
regatta = regattaCache.get(key);
}
if (regatta != null) {
// The following fixes bug 202: when tracking of multiple races of the same event has been started, this may not
// remove any race; however, the event may already have been created by another tracker whose race hasn't
// arrived yet and therefore the races list is still empty; therefore, only remove the event if its
// race list became empty by the removal performed here.
int oldSize = Util.size(regatta.getAllRaces());
regatta.removeRace(raceDefinition);
if (oldSize > 0 && Util.size(regatta.getAllRaces()) == 0) {
if (Util.contains(regatta.getAllRaces(), raceDefinition) && Util.size(regatta.getAllRaces()) == 1) {
logger.info("Removing regatta "+regatta.getName()+" ("+regatta.hashCode()+") from TracTrac DomainFactoryImpl");
regattaCache.remove(key);
weakDefaultRegattaCache.remove(tractracRace);
}
TrackedRegatta trackedRegatta = trackedRegattaRegistry.getTrackedRegatta(regatta);
if (trackedRegatta != null) {
// see above; only remove tracked regatta if it *became* empty because of the tracked race removal here
final int oldSizeOfTrackedRaces;
final int newSizeOfTrackedRaces;
trackedRegatta.lockTrackedRacesForWrite();
try {
oldSizeOfTrackedRaces = Util.size(trackedRegatta.getTrackedRaces());
trackedRegatta.removeTrackedRace(raceDefinition);
newSizeOfTrackedRaces = Util.size(trackedRegatta.getTrackedRaces());
} finally {
trackedRegatta.unlockTrackedRacesAfterWrite();
}
if (oldSizeOfTrackedRaces > 0 && newSizeOfTrackedRaces == 0) {
trackedRegattaRegistry.removeTrackedRegatta(regatta);
}
}
}
}
}
return raceDefinition;
}
@Override
@@ -716,17 +702,19 @@ public class DomainFactoryImpl implements DomainFactory {
}
@Override
public TracTracRaceTracker createRaceTracker(RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, WindStore windStore, TrackedRegattaRegistry trackedRegattaRegistry,
RaceLogResolver raceLogResolver, RaceTrackingConnectivityParametersImpl connectivityParams, long timeoutInMilliseconds) throws MalformedURLException,
FileNotFoundException, URISyntaxException, CreateModelException, SubscriberInitializationException {
public TracTracRaceTracker createRaceTracker(RaceLogStore raceLogStore, RegattaLogStore regattaLogStore,
WindStore windStore, TrackedRegattaRegistry trackedRegattaRegistry, RaceLogResolver raceLogResolver,
RaceTrackingConnectivityParametersImpl connectivityParams, long timeoutInMilliseconds)
throws URISyntaxException, CreateModelException, SubscriberInitializationException, IOException,
InterruptedException {
return new TracTracRaceTrackerImpl(this, raceLogStore, regattaLogStore, windStore, trackedRegattaRegistry, raceLogResolver, connectivityParams, timeoutInMilliseconds);
}
@Override
public RaceTracker createRaceTracker(Regatta regatta, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, WindStore windStore,
TrackedRegattaRegistry trackedRegattaRegistry, RaceLogResolver raceLogResolver, RaceTrackingConnectivityParametersImpl connectivityParams, long timeoutInMilliseconds)
throws MalformedURLException, FileNotFoundException, URISyntaxException, CreateModelException,
SubscriberInitializationException {
throws URISyntaxException, CreateModelException,
SubscriberInitializationException, IOException, InterruptedException {
return new TracTracRaceTrackerImpl(regatta, this, raceLogStore, regattaLogStore, windStore, trackedRegattaRegistry,
raceLogResolver, connectivityParams, timeoutInMilliseconds);
}
@@ -1,7 +1,6 @@
package com.sap.sailing.domain.tractracadapter.impl;
import java.io.FileNotFoundException;
import java.net.MalformedURLException;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
@@ -116,9 +115,10 @@ public class RaceTrackingConnectivityParametersImpl extends AbstractRaceTracking
@Override
public RaceTracker createRaceTracker(TrackedRegattaRegistry trackedRegattaRegistry, WindStore windStore,
RaceLogResolver raceLogResolver, long timeoutInMilliseconds) throws MalformedURLException, FileNotFoundException, URISyntaxException,
CreateModelException, SubscriberInitializationException {
RaceTracker tracker = domainFactory.createRaceTracker(raceLogStore, regattaLogStore, windStore, trackedRegattaRegistry, raceLogResolver, this, timeoutInMilliseconds);
RaceLogResolver raceLogResolver, long timeoutInMilliseconds) throws URISyntaxException,
CreateModelException, SubscriberInitializationException, IOException, InterruptedException {
RaceTracker tracker = domainFactory.createRaceTracker(raceLogStore, regattaLogStore, windStore,
trackedRegattaRegistry, raceLogResolver, this, timeoutInMilliseconds);
return tracker;
}
@@ -1,7 +1,6 @@
package com.sap.sailing.domain.tractracadapter.impl;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -263,7 +262,7 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl
TracTracRaceTrackerImpl(DomainFactory domainFactory, RaceLogStore raceLogStore,
RegattaLogStore regattaLogStore, WindStore windStore, TrackedRegattaRegistry trackedRegattaRegistry,
RaceLogResolver raceLogResolver, RaceTrackingConnectivityParametersImpl connectivityParams, long timeoutInMilliseconds)
throws URISyntaxException, MalformedURLException, FileNotFoundException, SubscriberInitializationException {
throws URISyntaxException, SubscriberInitializationException, IOException, InterruptedException {
this(/* regatta */ null, domainFactory, raceLogStore, regattaLogStore, windStore, trackedRegattaRegistry,
raceLogResolver, connectivityParams, timeoutInMilliseconds);
}
@@ -285,7 +284,7 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl
TracTracRaceTrackerImpl(final Regatta regatta, DomainFactory domainFactory, RaceLogStore raceLogStore,
RegattaLogStore regattaLogStore, WindStore windStore, TrackedRegattaRegistry trackedRegattaRegistry,
RaceLogResolver raceLogResolver, RaceTrackingConnectivityParametersImpl connectivityParams, long timeoutInMilliseconds)
throws URISyntaxException, MalformedURLException, FileNotFoundException, SubscriberInitializationException {
throws URISyntaxException, SubscriberInitializationException, IOException, InterruptedException {
super(connectivityParams);
final URL paramURL = connectivityParams.getParamURL();
final URI liveURI = connectivityParams.getLiveURI();
@@ -385,16 +384,19 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl
} else {
effectiveRegatta = regatta;
}
// removeRace may detach the domain regatta from the domain factory if that
// removed the last race; therefore, it's important to getOrCreate the
// domain regatta *after* calling removeRace
domainFactory.removeRace(tractracRace.getEvent(), tractracRace, trackedRegattaRegistry);
// if regatta is still null, no previous assignment of any of the races in this TracTrac event to a Regatta was
// found;
// in this case, create a default regatta based on the TracTrac event data
this.regatta = effectiveRegatta == null ? domainFactory.getOrCreateDefaultRegatta(
raceLogStore, regattaLogStore, tractracRace, trackedRegattaRegistry) : effectiveRegatta;
trackedRegatta = trackedRegattaRegistry.getOrCreateTrackedRegatta(this.regatta);
// removeRace may detach the domain regatta from the domain factory if that
// removed the last race; therefore, it's important to getOrCreate the
// domain regatta *after* calling removeRace
final RaceDefinition raceDefinition = domainFactory.removeRace(tractracRace.getEvent(), tractracRace, this.regatta, trackedRegattaRegistry);
if (raceDefinition != null) {
trackedRegattaRegistry.removeRace(this.regatta, raceDefinition);
}
receivers = new HashSet<Receiver>();
for (Receiver receiver : domainFactory.getUpdateReceivers(getTrackedRegatta(), delayToLiveInMillis,
simulator, windStore, this, trackedRegattaRegistry, raceLogResolver, tractracRace,
@@ -460,7 +462,12 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl
return trackedRegatta;
}
public static Object createID(URL paramURL, URI liveURI, URI storedURI) {
public static Object createID(URL paramURL, final URI liveURI, final URI storedURI) {
URL paramURLStrippedOfRandomParam = getParamURLStrippedOfRandomParam(paramURL);
return new Util.Triple<URL, URI, URI>(paramURLStrippedOfRandomParam, liveURI, storedURI);
}
public static URL getParamURLStrippedOfRandomParam(URL paramURL) {
URL paramURLStrippedOfRandomParam;
if (paramURL == null) {
paramURLStrippedOfRandomParam = null;
@@ -496,7 +503,7 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl
}
}
}
return new Util.Triple<URL, URI, URI>(paramURLStrippedOfRandomParam, liveURI, storedURI);
return paramURLStrippedOfRandomParam;
}
@Override
@@ -3,6 +3,7 @@ package com.sap.sailing.domain.base;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import com.sap.sailing.domain.common.PassingInstruction;
import com.sap.sse.common.Renamable;
import com.sap.sse.common.Util;
import difflib.PatchFailedException;
@@ -25,7 +26,7 @@ import difflib.PatchFailedException;
* @author Axel Uhl (d043530)
*
*/
public interface Course extends CourseBase {
public interface Course extends CourseBase, Renamable {
void lockForRead();
void unlockAfterRead();
@@ -0,0 +1,26 @@
package com.sap.sailing.domain.base;
import java.util.ArrayList;
import java.util.List;
import com.sap.sailing.domain.base.impl.BoatClassImpl;
import com.sap.sailing.domain.base.impl.BoatImpl;
import com.sap.sse.common.Util.Pair;
import com.sap.sse.pairinglist.PairingList;
public class PairingListLeaderboardAdapter implements PairingList<RaceColumn, Fleet, Competitor, Boat> {
@Override
public Iterable<Pair<Competitor, Boat>> getCompetitors(RaceColumn raceColumn, Fleet fleet) {
// TODO add boat to return value (bug4403)
List<Pair<Competitor, Boat>> result = new ArrayList<>();
int boatIndex = 0;
for (Competitor competitor : raceColumn.getCompetitorsRegisteredInRacelog(fleet)) {
result.add(new Pair<Competitor, Boat>(competitor,
new BoatImpl("Boat " + (boatIndex + 1), new BoatClassImpl("49er", true), "DE" + boatIndex)));
boatIndex++;
}
return result;
}
}
@@ -29,7 +29,7 @@ public interface RaceDefinition extends NamedWithID {
/**
* Gets the boat used by the competitor for this race.
*/
Boat getBoatOfCompetitorById(Serializable competitorID);
Boat getBoatOfCompetitor(Competitor competitor);
/**
* The MD5 hash as produced by
@@ -24,7 +24,7 @@ import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.common.PassingInstruction;
import com.sap.sailing.util.CourseAsWaypointList;
import com.sap.sse.common.Util.Pair;
import com.sap.sse.common.impl.NamedImpl;
import com.sap.sse.common.impl.RenamableImpl;
import com.sap.sse.concurrent.LockUtil;
import com.sap.sse.concurrent.NamedReentrantReadWriteLock;
@@ -32,7 +32,7 @@ import difflib.DiffUtils;
import difflib.Patch;
import difflib.PatchFailedException;
public class CourseImpl extends NamedImpl implements Course {
public class CourseImpl extends RenamableImpl implements Course {
private static final long serialVersionUID = -4280487649617132403L;
@@ -27,7 +27,7 @@ public class RaceDefinitionImpl extends NamedImpl implements RaceDefinition {
private final Course course;
private final LinkedHashMap<Serializable, Competitor> competitorsById;
private final Set<Competitor> competitors;
private final Map<Serializable, Boat> competitorBoats;
private final Map<Competitor, Boat> competitorBoats;
private final BoatClass boatClass;
private final Serializable id;
private final RaceCompetitorIdsAsStringWithMD5Hash raceCompetitorsMD5Hash;
@@ -65,7 +65,7 @@ public class RaceDefinitionImpl extends NamedImpl implements RaceDefinition {
for (Entry<Competitor, Boat> competitorAndBoat : competitorsAndTheirBoats.entrySet()) {
Competitor competitor = competitorsById.get(competitorAndBoat.getKey().getId()); // only assign boat if competitor is part of race
if (competitor != null && competitorAndBoat.getValue() != null) {
competitorBoats.put(competitor.getId(), competitorAndBoat.getValue());
competitorBoats.put(competitor, competitorAndBoat.getValue());
} else {
logger.warning("Trying to set boat "+competitorAndBoat.getValue()+" for competitor "+competitorAndBoat.getKey()+
" which is not part of race "+getName()+"'s set of competitors");
@@ -111,7 +111,7 @@ public class RaceDefinitionImpl extends NamedImpl implements RaceDefinition {
}
@Override
public Boat getBoatOfCompetitorById(Serializable competitorID) {
return competitorBoats.get(competitorID);
public Boat getBoatOfCompetitor(Competitor competitor) {
return competitorBoats.get(competitor);
}
}
@@ -256,6 +256,11 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene
}
return null;
}
@Override
public void setFleetsCanRunInParallelToTrue() {
RegattaImpl.this.setFleetsCanRunInParallelToTrue();
}
};
this.regattaLikeHelper.addListener(new RegattaLogEventAdditionForwarder(raceColumnListeners));
this.raceExecutionOrderCache = new RaceExecutionOrderCache();
@@ -856,4 +861,11 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene
deregisterer.deregister(deregisterer.analyze());
}
@Override
public void setFleetsCanRunInParallelToTrue() {
for (Series series : this.series) {
series.setIsFleetsCanRunInParallel(true);
}
}
}
@@ -15,7 +15,6 @@ import com.sap.sailing.domain.base.RaceColumnListener;
import com.sap.sailing.domain.base.RaceDefinition;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.base.Series;
import com.sap.sailing.domain.common.impl.RenamableImpl;
import com.sap.sailing.domain.leaderboard.ResultDiscardingRule;
import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule;
import com.sap.sailing.domain.racelog.RaceLogIdentifier;
@@ -24,6 +23,7 @@ import com.sap.sailing.domain.tracking.TrackedRegatta;
import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
import com.sap.sailing.util.impl.RaceColumnListeners;
import com.sap.sse.common.Util;
import com.sap.sse.common.impl.RenamableImpl;
/**
* A series listens on its columns; however, a veto for column addition isn't done here but in a {@link RegattaLeaderboard}.
@@ -5,10 +5,10 @@ import com.sap.sailing.domain.base.Fleet;
import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.base.Series;
import com.sap.sailing.domain.common.Renamable;
import com.sap.sailing.domain.regattalike.IsRegattaLike;
import com.sap.sailing.domain.regattalike.LeaderboardThatHasRegattaLike;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sse.common.Renamable;
/**
* A leaderboard that allows its clients to flexibly modify the race columns arranged in this leaderboard without the
@@ -1,8 +1,8 @@
package com.sap.sailing.domain.leaderboard;
import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.common.Renamable;
import com.sap.sailing.domain.regattalike.IsRegattaLike;
import com.sap.sse.common.Renamable;
public interface FlexibleRaceColumn extends RaceColumn, Renamable {
void setIsMedalRace(boolean isMedalRace);
@@ -1,8 +1,8 @@
package com.sap.sailing.domain.leaderboard;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.common.Renamable;
import com.sap.sailing.domain.regattalike.LeaderboardThatHasRegattaLike;
import com.sap.sse.common.Renamable;
public interface RegattaLeaderboard extends Renamable, LeaderboardThatHasRegattaLike {
Regatta getRegatta();
@@ -3,6 +3,7 @@ package com.sap.sailing.domain.leaderboard.impl;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
@@ -39,6 +40,7 @@ import com.sap.sailing.domain.common.Bearing;
import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.LegType;
import com.sap.sailing.domain.common.ManeuverType;
import com.sap.sailing.domain.common.MaxPointsReason;
import com.sap.sailing.domain.common.NoWindException;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.RegattaNameAndRaceName;
@@ -85,6 +87,9 @@ import com.sap.sse.util.impl.FutureTaskWithTracingGet;
public abstract class AbstractLeaderboardWithCache implements Leaderboard {
private static final long serialVersionUID = -5651389357061229100L;
private static final Logger logger = Logger.getLogger(AbstractLeaderboardWithCache.class.getName());
private final MaxPointsReason[] MAX_POINTS_REASONS_THAT_IDENTIFY_NON_FINISHED_RACES = new MaxPointsReason[] {
MaxPointsReason.DNS, MaxPointsReason.DNF, MaxPointsReason.DNC };
private transient LiveLeaderboardUpdater liveLeaderboardUpdater;
protected static final ExecutorService executor = ThreadPoolUtil.INSTANCE.getDefaultForegroundTaskThreadPoolExecutor();
private transient LeaderboardDTOCache leaderboardDTOCache;
@@ -488,6 +493,11 @@ public abstract class AbstractLeaderboardWithCache implements Leaderboard {
+ ". Leaving empty.", e);
}
}
if (addOverallDetails) {
//this reuses several prior calculated fields, so must be evaluated after them
row.totalScoredRaces = this.getTotalRaces(competitor, row, timePoint);
}
result.rows.put(competitorDTO, row);
String displayName = this.getDisplayName(competitor);
if (displayName != null) {
@@ -499,7 +509,39 @@ public abstract class AbstractLeaderboardWithCache implements Leaderboard {
+ (System.currentTimeMillis() - startOfRequestHandling) + "ms");
return result;
}
private Integer getTotalRaces(Competitor competitor, LeaderboardRowDTO row, TimePoint timePoint) {
int amount = 0;
for (RaceColumn raceColumn : getRaceColumns()) {
// reuse calculations already done earlier
LeaderboardEntryDTO entry = row.fieldsByRaceColumnName.get(raceColumn.getName());
if (entry.netPoints != null) {
if (entry.reasonForMaxPoints.equals(MaxPointsReason.NONE)
|| !Util.contains(Arrays.asList(MAX_POINTS_REASONS_THAT_IDENTIFY_NON_FINISHED_RACES),
entry.reasonForMaxPoints)) {
if (raceColumn instanceof MetaLeaderboardColumn) {
Leaderboard leaderBoardForMeta = ((MetaLeaderboardColumn) raceColumn).getLeaderboard();
for (RaceColumn subRace : leaderBoardForMeta.getRaceColumns()) {
Double netPointsForSubRace = leaderBoardForMeta.getNetPoints(competitor, subRace, timePoint);
MaxPointsReason subMaxPointsReason = leaderBoardForMeta.getMaxPointsReason(competitor, subRace, timePoint);
if(netPointsForSubRace != null){
if (subMaxPointsReason.equals(MaxPointsReason.NONE) || !Util.contains(
Arrays.asList(MAX_POINTS_REASONS_THAT_IDENTIFY_NON_FINISHED_RACES),
subMaxPointsReason)) {
amount++;
}
}
}
} else {
amount++;
}
}
}
}
return amount;
}
/**
* @param waitForLatestAnalyses
* if <code>false</code>, this method is allowed to read the maneuver analysis results from a cache that
@@ -1023,12 +1065,12 @@ public abstract class AbstractLeaderboardWithCache implements Leaderboard {
legTypeCache.put(trackedLeg, trackedLegType);
}
if (legType == trackedLegType) {
Duration timeSpentOnDownwind = trackedLegOfCompetitor.getTime(timePoint);
if (timeSpentOnDownwind != null) {
Duration timeSpentInLegOfType = trackedLegOfCompetitor.getTime(timePoint);
if (timeSpentInLegOfType != null) {
if (result == null) {
result = timeSpentOnDownwind;
result = timeSpentInLegOfType;
} else {
result = result.plus(timeSpentOnDownwind);
result = result.plus(timeSpentInLegOfType);
}
} else {
// Although the competitor has started the leg, no value was produced. This
@@ -104,6 +104,11 @@ public class FlexibleLeaderboardImpl extends AbstractLeaderboardImpl implements
public RaceColumn getRaceColumnByName(String raceColumnName) {
return getRaceColumnByName(raceColumnName);
}
@Override
public void setFleetsCanRunInParallelToTrue() {
// no need to do anything; a FlexibleLeaderboard only has one (default) fleet
}
};
this.regattaLikeHelper.addListener(new RegattaLogEventAdditionForwarder(getRaceColumnListeners()));
this.raceExecutionOrderProvider = new RaceExecutionOrderCache();
@@ -381,4 +386,9 @@ public class FlexibleLeaderboardImpl extends AbstractLeaderboardImpl implements
}
}
}
@Override
public void setFleetsCanRunInParallelToTrue() {
// no need to do anything because a FlexibleLeaderboard only has one (default) fleet
}
}
@@ -136,4 +136,8 @@ public class RegattaLeaderboardImpl extends AbstractLeaderboardImpl implements R
public CompetitorProviderFromRaceColumnsAndRegattaLike getOrCreateCompetitorsProvider() {
return getRegatta().getOrCreateCompetitorsProvider();
}
public void setFleetsCanRunInParallelToTrue() {
this.getRegattaLike().setFleetsCanRunInParallelToTrue();
}
}
@@ -67,4 +67,6 @@ public interface IsRegattaLike extends Serializable {
* @see #getTimeOnTimeFactor(Competitor)
*/
Duration getTimeOnDistanceAllowancePerNauticalMile(Competitor competitor);
void setFleetsCanRunInParallelToTrue();
}
@@ -33,7 +33,8 @@ public interface BaseDoubleVectorFixImporter {
* @throws IOException
* if there is a problem while reading the file
*/
void importFixes(InputStream inputStream, Callback callback, String filename, String sourceName, boolean downsample)
boolean importFixes(InputStream inputStream, Callback callback, String filename, String sourceName,
boolean downsample)
throws FormatNotSupportedException, IOException;
/**
@@ -12,7 +12,10 @@ import com.sap.sse.common.TimePoint;
/**
* Definition of importers used by SensorDataImportServlet to do the import of a specific file type.
*/
public interface DoubleVectorFixImporter extends BaseDoubleVectorFixImporter {
public interface DoubleVectorFixImporter extends BaseDoubleVectorFixImporter {
public static final String EXPEDITION_EXTENDED_TYPE = "EXPEDITION_EXTENDED";
/**
* Creates the {@link RegattaLogEvent} for the DeviceMapping.
*/
@@ -30,7 +30,9 @@ import com.sap.sailing.domain.trackfiles.TrackFileImportDeviceIdentifier;
* @author Fredrik Teschke
*
*/
public interface GPSFixImporter {
public interface GPSFixImporter {
public static final String EXPEDITION_TYPE = "Expedition";
String FILE_EXTENSION_PROPERTY = "fileExt";
/**
@@ -43,16 +45,20 @@ public interface GPSFixImporter {
}
/**
* Retrieves the fixes from the {@code inputStream}, and calls the
* {@code callback} with every new fix.
* @param inferSpeedAndBearing Should speed and bearing be inferred by looking
* at the previous fix, if that data is not directly present within the file?
* @throws FormatNotSupportedException If the input format cannot be read. The import process
* might then decide to try attempt importing fixes using the next suitable importer.
* Retrieves the fixes from the {@code inputStream}, and calls the {@code callback} with every new fix.
*
* @param sourceName some name that identifies the source, e.g. the file name if a file
* @param inferSpeedAndBearing
* Should speed and bearing be inferred by looking at the previous fix, if that data is not directly
* present within the file?
* @throws FormatNotSupportedException
* If the input format cannot be read. The import process might then decide to try attempt importing
* fixes using the next suitable importer.
*
* @param sourceName
* some name that identifies the source, e.g. the file name if a file
* @return returns if import was succesful or not
*/
void importFixes(InputStream inputStream, Callback callback, boolean inferSpeedAndBearing,
boolean importFixes(InputStream inputStream, Callback callback, boolean inferSpeedAndBearing,
String sourceName)
throws FormatNotSupportedException, IOException;
@@ -24,7 +24,12 @@ public interface RaceTrackingConnectivityParameters {
* Starts a {@link RaceTracker} using the connectivity parameters provided by this object. As no specific
* {@link Regatta} is provided, this will first look up a regatta for the race from
* {@link TrackedRegattaRegistry#getRememberedRegattaForRace(java.io.Serializable)} and if not found will look up or
* create a default regatta based on race data such as an event name and the boat class.
* create a default regatta based on race data such as an event name and the boat class.<p>
*
* If a race equal to the one being created by this call already exists in the default regatta, its tracking
* will be stopped (if any), the {@link RaceDefinition} will be removed from the default regatta, and the
* {@link TrackedRace} will be removed from the {@link TrackedRegatta}. All this happens by means of
* calling {@link TrackedRegattaRegistry#removeRace(Regatta, com.sap.sailing.domain.base.RaceDefinition)}.
*
* @param timeoutInMilliseconds
* gives the tracker a possibility to abort tracking the race after so many milliseconds of
@@ -37,11 +42,15 @@ public interface RaceTrackingConnectivityParameters {
RaceLogResolver raceLogResolver, long timeoutInMilliseconds) throws Exception;
/**
* Starts a {@link RaceTracker}, associating the resulting races with the {@link Regatta} passed as argument
* instead of using the tracker's domain factory to obtain a default {@link Regatta} object for the tracking
* parameters. This is particularly useful if a predefined regatta with {@link Series} and {@link Fleet}s
* is to be used.
* @param timeoutInMilliseconds TODO
* Starts a {@link RaceTracker}, associating the resulting races with the {@link Regatta} passed as argument instead
* of using the tracker's domain factory to obtain a default {@link Regatta} object for the tracking parameters.
* This is particularly useful if a predefined regatta with {@link Series} and {@link Fleet}s is to be used.
* <p>
*
* If a race equal to the one being created by this call already exists in the {@code regatta}, its tracking will be
* stopped (if any), the {@link RaceDefinition} will be removed from the default regatta, and the
* {@link TrackedRace} will be removed from the {@link TrackedRegatta}. All this happens by means of calling
* {@link TrackedRegattaRegistry#removeRace(Regatta, com.sap.sailing.domain.base.RaceDefinition)}.
*/
RaceTracker createRaceTracker(Regatta regatta, TrackedRegattaRegistry trackedRegattaRegistry, WindStore windStore,
RaceLogResolver raceLogResolver, long timeoutInMilliseconds) throws Exception;
@@ -1,5 +1,6 @@
package com.sap.sailing.domain.tracking;
import java.net.MalformedURLException;
import java.util.Map;
import java.util.UUID;
@@ -26,7 +27,7 @@ public interface RaceTrackingConnectivityParametersHandler {
* The types of the value objects must be restricted to thos types that MongoDB can handle as field values,
* in particular {@link String}, {@link Number} and {@link UUID}.
*/
Map<String, Object> mapFrom(RaceTrackingConnectivityParameters params);
Map<String, Object> mapFrom(RaceTrackingConnectivityParameters params) throws MalformedURLException;
/**
* Produces a {@link RaceTrackingConnectivityParameters} object from the contents of {@code map}, ignoring the
@@ -45,5 +46,5 @@ public interface RaceTrackingConnectivityParametersHandler {
* {@link RaceTrackingConnectivityParameters#getTypeIdentifier()} as well as a non-empty set of further
* entries that uniquely identify the {@code params} object in the database, in particular for removing.
*/
Map<String, Object> getKey(RaceTrackingConnectivityParameters params);
Map<String, Object> getKey(RaceTrackingConnectivityParameters params) throws MalformedURLException;
}
@@ -4,6 +4,8 @@ import java.io.IOException;
import java.io.Serializable;
import java.net.MalformedURLException;
import com.sap.sailing.domain.base.Fleet;
import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.base.RaceDefinition;
import com.sap.sailing.domain.base.Regatta;
@@ -28,6 +30,27 @@ public interface TrackedRegattaRegistry {
*/
DynamicTrackedRegatta getTrackedRegatta(Regatta regatta);
/**
* Removes <code>race</code> and any corresponding {@link #getTrackedRace(Regatta, RaceDefinition) tracked race}
* from this service. If it was the last {@link RaceDefinition} in its {@link Regatta} and the regatta
* {@link Regatta#isPersistent() is not stored persistently}, the <code>regatta</code> is removed as well and will no
* longer be returned by {@link #getAllRegattas()}. The wind tracking is stopped for <code>race</code>.
* <p>
*
* Any {@link RaceTracker} for which <code>race</race> is the last race tracked that is still reachable
* from {@link #getAllRegattas()} will be {@link RaceTracker#stop(boolean) stopped}.
*
* The <code>race</code> will be also removed from all leaderboards containing a column that has <code>race</code>'s
* {@link #getTrackedRace(Regatta, RaceDefinition) corresponding} {@link TrackedRace} as its
* {@link RaceColumn#getTrackedRace(Fleet)}.
*
* @param regatta
* the regatta from which to remove the race
* @param race
* the race to remove
*/
void removeRace(Regatta regatta, RaceDefinition race) throws MalformedURLException, IOException,InterruptedException;
void removeTrackedRegatta(Regatta regatta);
/**
@@ -21,6 +21,7 @@ import com.sap.sailing.domain.tracking.Track;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sse.common.Duration;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util.Pair;
import com.sap.sse.common.WithID;
import com.sap.sse.common.scalablevalue.ScalableValue;
@@ -39,7 +40,11 @@ public class BravoFixTrackImpl<ItemType extends WithID & Serializable> extends S
private transient TimeRangeCache<Distance> foilingDistanceCache;
private transient TimeRangeCache<Distance> averageRideHeightCache;
/**
* Caches the sum of the ride height of all fixes in the interval, paired with the number of fixes
* that constituted the basis for the ride height sum.
*/
private transient TimeRangeCache<Pair<Distance, Long>> averageRideHeightCache;
/**
* If a GPS track was provided at construction time, remember it non-transiently. It is needed when restoring
@@ -112,7 +117,7 @@ public class BravoFixTrackImpl<ItemType extends WithID & Serializable> extends S
}
}
private TimeRangeCache<Distance> createAverageRideHeightCache(ItemType trackedItem) {
private TimeRangeCache<Pair<Distance, Long>> createAverageRideHeightCache(ItemType trackedItem) {
return createTimeRangeCache(trackedItem, "averageRideHeightCache");
}
@@ -177,13 +182,15 @@ public class BravoFixTrackImpl<ItemType extends WithID & Serializable> extends S
@Override
public Distance getAverageRideHeight(TimePoint from, TimePoint to) {
return getValueSum(from, to, /* nullElement */ Distance.NULL, Distance::add, averageRideHeightCache,
/* valueCalculator */ new Track.TimeRangeValueCalculator<Distance>() {
final Pair<Distance, Long> nullElement = new Pair<>(Distance.NULL, 0l);
Pair<Distance, Long> rideHeightSumAndCount = getValueSum(from, to, nullElement,
(a, b)->new Pair<>(a.getA().add(b.getA()), a.getB()+b.getB()),
averageRideHeightCache,
/* valueCalculator */ new Track.TimeRangeValueCalculator<Pair<Distance, Long>>() {
@Override
public Distance calculate(TimePoint from, TimePoint to) {
Distance result;
public Pair<Distance, Long> calculate(TimePoint from, TimePoint to) {
Distance sum = Distance.NULL;
int count = 0;
long count = 0;
for (final BravoFix fix : getFixes(from, true, to, true)) {
final Distance rideHeight = fix.getRideHeight();
if (rideHeight != null) {
@@ -191,14 +198,10 @@ public class BravoFixTrackImpl<ItemType extends WithID & Serializable> extends S
count++;
}
}
if (count > 0) {
result = sum.scale(1./count);
} else {
result = null;
}
return result;
return new Pair<>(sum, count);
}
});
return rideHeightSumAndCount.getB() == 0l ? null : rideHeightSumAndCount.getA().scale(1./(double) rideHeightSumAndCount.getB());
}
private boolean isFoiling(BravoFix fix) {
@@ -114,7 +114,7 @@ public class CrossTrackErrorCache extends AbstractRaceChangeListener {
}
public boolean add(CrossTrackErrorSumAndNumberOfFixes entry) {
return getInternalRawFixes().add(entry);
return super.add(entry, /* replace */ true);
}
}
@@ -294,7 +294,9 @@ public class MaxSpeedCache<ItemType, FixType extends GPSFix> implements GPSTrack
} else {
speedAtFixTime = track.getEstimatedSpeed(fix.getTimePoint());
}
if (speedAtFixTime != null && speedAtFixTime.compareTo(max) > 0) {
// accept max speeds only if they don't exceed our validity threshold
if (speedAtFixTime != null && speedAtFixTime.compareTo(GPSFixTrack.DEFAULT_MAX_SPEED_FOR_SMOOTHING) < 0
&& speedAtFixTime.compareTo(max) > 0) {
max = speedAtFixTime;
maxSpeedFix = fix;
}
@@ -154,4 +154,10 @@
version="0.5.0.qualifier"
unpack="false"/>
<plugin
id="org.mp4parser.isoparser"
download-size="0"
install-size="0"
version="1.9.31"/>
</feature>
+7 -4
View File
@@ -28,11 +28,14 @@
<link rel="stylesheet" type="text/css" href="RaceBoard.css">
<link href="js/video-js/video-js.css" rel="stylesheet">
<link href="js/video-js/video-js.min.css" rel="stylesheet">
<link href="js/video-js/video-js-sapsailing.css" rel="stylesheet">
<script src="js/video-js/video.js"></script>
<script src="js/vjs.youtube.js"></script>
<script src="js/vjs.vimeo.js"></script>
<script src="js/video-js/video.min.js"></script>
<script src="js/three.js"></script>
<link rel="stylesheet" type="text/css" href="js/videojs-panorama.min.css"/>
<script src="js/videojs-panorama.v5.min.js"></script>
<script src="js/Youtube.min.js"></script>
<script src="js/Vimeo.js"></script>
<script>
videojs.options.techOrder = ['vimeo', 'youtube', 'html5'];
</script>
+10 -6
View File
@@ -14,12 +14,16 @@
href="/sailing-fontface-1.0.cache.css">
<script src="js/jquery-1.11.1.min.js"></script>
<link rel="stylesheet" type="text/css" href="js/jquery.slick/slick-1.5.8.css"/>
<script type="text/javascript" src="js/jquery.slick/slick-1.5.8.min.js"></script>
<link href="js/video-js/video-js.css" rel="stylesheet">
<link href="js/video-js/video-js-sapsailing.css" rel="stylesheet">
<script src="js/video-js/video.js"></script>
<script src="js/vjs.youtube.js"></script>
<script src="js/vjs.vimeo.js"></script>
<script type="text/javascript" src="js/jquery.slick/slick-1.5.8.min.js"></script>
<link href="js/video-js/video-js.min.css" rel="stylesheet">
<link href="js/video-js/video-js-sapsailing.css" rel="stylesheet">
<script src="js/video-js/video.min.js"></script>
<script src="js/three.js"></script>
<link rel="stylesheet" type="text/css" href="js/videojs-panorama.min.css"/>
<script src="js/videojs-panorama.v5.min.js"></script>
<script src="js/Youtube.min.js"></script>
<script src="js/Vimeo.js"></script>
<script>
videojs.options.techOrder = ['vimeo', 'youtube', 'html5'];
</script>
@@ -55,7 +55,8 @@ Require-Bundle: com.sap.sailing.domain,
com.sap.sse.shared.android,
com.sap.sailing.competitorimport,
com.sap.sailing.expeditionconnector,
com.sap.sailing.expeditionconnector.common
com.sap.sailing.expeditionconnector.common,
org.mp4parser.isoparser;bundle-version="1.9.31"
Bundle-Activator: com.sap.sailing.gwt.ui.server.Activator
Bundle-ActivationPolicy: lazy
Import-Package: javax.servlet;version="3.1.0",
+154
View File
@@ -0,0 +1,154 @@
/** Add css rules here for your application. */
.clear {
clear: both;
font-size: 1px;
height: 0px;
line-height: 0px;
margin: -1px 0 0;
width: 100%; }
.cover {
left: -1000px;
overflow: hidden;
position: absolute;
top: -1000px; }
html {
font-size: 15px;
}
html, body {
height: 100%;
width: 100%;
}
body {
background: #fff;
font-family: 'Open Sans', Arial, Verdana, sans-serif;
line-height: 1;
font-size: 14px;
font-weight: 400;
margin: 0;
}
/** Example rules used by the template application (remove for your app) */
h1 {
font-size: 2em;
font-weight: bold;
color: #777777;
margin: 40px 0px 70px;
text-align: center;
}
table {
border-collapse: separate; }
.refreshAndSettings {
background: #f2f2f2;
border-bottom: 2px solid #fff;
border-top: 2px solid #fff;
height: 30px; }
.refreshAndSettings table td {
padding: 0 10px 0 0;}
.refreshAndSettings table table td{
padding: 0 0 0 0;}
input.magnifier{
display: block;
margin: 3px auto 0 auto !important;
}
body, table td, select, button {
font-family: 'Open Sans', Arial, Verdana, sans-serif;
font-size: 14px;
}
.errorLabel {
color: #FF0000;
}
.abstractChartPanel-importantMessageOfChart {
font-family: 'Open Sans',Arial,Verdana,sans-serif;
font-weight: bold;
font-size: 20px;
height: 70px;
margin: 0 auto;
padding: 49px 0 0 80px;
width: 300px;
color: #7c7c7c;
background: url(images/important-message.png) left center no-repeat;
}
.LeaderboardHeader {
position: relative;
margin: 0 auto;
width: 100%;
text-align: center;
font-family: 'Open Sans', Arial, Verdana, sans-serif;
font-size: 18px;
color: #fff;
margin-top: 22px;
}
.leaderboardHeading {
font-size: 16px;
font-family: 'Open Sans', Arial, Verdana, sans-serif;
font-weight: bold;
}
.gwt-DialogBox {
z-index: 20;
}
.bold {
font-family: 'Open Sans Condensed', sans-serif;
font-weight: bold;
}
.trackingQuality-circle {
width: 8px;
height: 8px;
-moz-border-radius-topleft: 50%;
-webkit-border-top-left-radius: 50%;
-o-border-top-left-radius: 50%;
-ms-border-top-left-radius: 50%;
border-top-left-radius: 50%;
-moz-border-radius-topright: 50%;
-webkit-border-top-right-radius: 50%;
-o-border-top-right-radius: 50%;
-ms-border-top-right-radius: 50%;
border-top-right-radius: 50%;
-moz-border-radius-bottomleft: 50%;
-webkit-border-bottom-left-radius: 50%;
-o-border-bottom-left-radius: 50%;
-ms-border-bottom-left-radius: 50%;
border-bottom-left-radius: 50%;
-moz-border-radius-bottomright: 50%;
-webkit-border-bottom-right-radius: 50%;
-o-border-bottom-right-radius: 50%;
-ms-border-bottom-right-radius: 50%;
border-bottom-right-radius: 50%;
float: left;
}
.circleYellow {
background: #fbba00;
}
.circleGreen {
background: #8ab54e;
}
.circleRed {
background: #f01010;
}
.busyIndicator-Simple {
margin-top: 7px;
}
.busyIndicator-Circle {
}
@@ -0,0 +1,36 @@
<!doctype html>
<html>
<head>
<!--[if lt IE 9]>
<meta http-equiv="REFRESH" content="0; url=http://www.sapsailing.com/browser-info.html">
<![endif]-->
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<title>SAP Sailing Analytics PairingList</title>
<meta content='width = device-width, initial-scale = 1.0, user-scalable = yes' name='viewport'>
<meta content='yes' name='apple-mobile-web-app-capable'>
<meta content='black' name='apple-mobile-web-app-status-bar-style'>
<!-- <link href='iphone-splash-screen.png' rel='apple-touch-startup-image'> -->
<link href='images/sap-sailing-app-icon.png' rel='apple-touch-icon'>
<link rel="shortcut icon" type="image/x-icon" href="images/sap.ico" />
<link rel="stylesheet" type="text/css" href="/sailing-normalize-3.0.2.cache.css">
<link rel="stylesheet" type="text/css" href="/sailing-fontface-1.0.cache.css">
<link rel="stylesheet" type="text/css" href="PairingList.css">
<script type="text/javascript" src="js/jquery-1.5.2.min.js"></script>
<script type="text/javascript" src="com.sap.sailing.gwt.ui.PairingList/com.sap.sailing.gwt.ui.PairingList.nocache.js"></script>
</head>
<body>
<iframe src="javascript:''" id="__gwt_historyFrame" tabIndex='-1' style="position:absolute;width:0;height:0;border:0"></iframe>
<!-- RECOMMENDED if your web app will not function without JavaScript enabled -->
<noscript>
<div style="width: 22em; position: absolute; left: 50%; margin-left: -11em; color: red; background-color: white; border: 1px solid red; padding: 4px; font-family: sans-serif">
Your web browser must have JavaScript enabled
in order for this application to display correctly.
</div>
</noscript>
</body>
</html>
+1 -1
View File
@@ -17,7 +17,7 @@
* {
font-family: 'Open Sans', sans-serif !important;
font-family: 'Open Sans', sans-serif;
font-size: 12px;
}
+13 -1
View File
@@ -21,7 +21,19 @@
<link rel="stylesheet" type="text/css" href="RaceBoard.css">
<script type="text/javascript" src="js/jquery-1.5.2.min.js"></script>
<link href="js/video-js/video-js.min.css" rel="stylesheet">
<link href="js/video-js/video-js-sapsailing.css" rel="stylesheet">
<script src="js/video-js/video.min.js"></script>
<script src="js/three.js"></script>
<link rel="stylesheet" type="text/css"
href="js/videojs-panorama.min.css" />
<script src="js/videojs-panorama.v5.min.js"></script>
<script src="js/Vimeo.js"></script>
<script>
videojs.options.techOrder = [ 'vimeo', 'html5' ];
</script>
<script type="text/javascript" language="javascript" src="com.sap.sailing.gwt.ui.RaceBoard/com.sap.sailing.gwt.ui.RaceBoard.nocache.js"></script>
</head>
@@ -61,7 +61,7 @@
<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/>
<stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.google.gwt.dev.DevMode"/>
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-style PRETTY -incremental -workDir &quot;${project_loc:com.sap.sailing.gwt.ui}/.tmp/gwt-work&quot; -war &quot;${project_loc:com.sap.sailing.gwt.ui}&quot; -noserver -remoteUI &quot;${gwt_remote_ui_server_port}:${unique_id}&quot; -logLevel INFO -codeServerPort 9876 -startupUrl /gwt/Home.html com.sap.sailing.gwt.home.Home -startupUrl /gwt/AdminConsole.html com.sap.sailing.gwt.ui.AdminConsole -startupUrl /gwt/LeaderboardEditing.html com.sap.sailing.gwt.ui.LeaderboardEditing -startupUrl /gwt/Leaderboard.html com.sap.sailing.gwt.ui.Leaderboard -startupUrl /gwt/Spectator.html com.sap.sailing.gwt.ui.Spectator -startupUrl /gwt/EmbeddedMapAndWindChart com.sap.sailing.gwt.ui.EmbeddedMapAndWindChart -startupUrl /gwt/RaceBoard.html com.sap.sailing.gwt.ui.RaceBoard -startupUrl /gwt/RegattaOverview.html com.sap.sailing.gwt.regattaoverview.RegattaOverview -startupUrl /gwt/DataMining.html com.sap.sailing.gwt.ui.DataMining -startupUrl /gwt/Simulator.html com.sap.sailing.gwt.ui.Simulator -startupUrl /gwt/VideoPopup.html com.sap.sailing.gwt.ui.VideoPopup -startupUrl /gwt/YoutubePopup.html com.sap.sailing.gwt.ui.YoutubePopup -startupUrl /gwt/AutoPlay.html com.sap.sailing.gwt.autoplay.AutoPlay"/>
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-style PRETTY -incremental -workDir &quot;${project_loc:com.sap.sailing.gwt.ui}/.tmp/gwt-work&quot; -war &quot;${project_loc:com.sap.sailing.gwt.ui}&quot; -noserver -remoteUI &quot;${gwt_remote_ui_server_port}:${unique_id}&quot; -logLevel INFO -codeServerPort 9876 -startupUrl /gwt/Home.html com.sap.sailing.gwt.home.Home -startupUrl /gwt/AdminConsole.html com.sap.sailing.gwt.ui.PairingList -startupUrl /gwt/PairingList.html com.sap.sailing.gwt.ui.AdminConsole -startupUrl /gwt/LeaderboardEditing.html com.sap.sailing.gwt.ui.LeaderboardEditing -startupUrl /gwt/Leaderboard.html com.sap.sailing.gwt.ui.Leaderboard -startupUrl /gwt/Spectator.html com.sap.sailing.gwt.ui.Spectator -startupUrl /gwt/EmbeddedMapAndWindChart com.sap.sailing.gwt.ui.EmbeddedMapAndWindChart -startupUrl /gwt/RaceBoard.html com.sap.sailing.gwt.ui.RaceBoard -startupUrl /gwt/RegattaOverview.html com.sap.sailing.gwt.regattaoverview.RegattaOverview -startupUrl /gwt/DataMining.html com.sap.sailing.gwt.ui.DataMining -startupUrl /gwt/Simulator.html com.sap.sailing.gwt.ui.Simulator -startupUrl /gwt/VideoPopup.html com.sap.sailing.gwt.ui.VideoPopup -startupUrl /gwt/YoutubePopup.html com.sap.sailing.gwt.ui.YoutubePopup -startupUrl /gwt/AutoPlay.html com.sap.sailing.gwt.autoplay.AutoPlay"/>
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="com.sap.sailing.gwt.ui"/>
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Xms1024m -Xmx3072m -Dgwt.watchFileChanges=false"/>
</launchConfiguration>
@@ -9,6 +9,19 @@
<link rel="stylesheet" type="text/css" href="/sailing-normalize-3.0.2.cache.css">
<link rel="stylesheet" type="text/css" href="VideoPopup.css">
<link href="js/video-js/video-js.min.css" rel="stylesheet">
<link href="js/video-js/video-js-sapsailing.css" rel="stylesheet">
<script src="js/video-js/video.min.js"></script>
<script src="js/three.js"></script>
<link rel="stylesheet" type="text/css"
href="js/videojs-panorama.min.css" />
<script src="js/videojs-panorama.v5.min.js"></script>
<script src="js/Youtube.min.js"></script>
<script src="js/Vimeo.js"></script>
<script>
videojs.options.techOrder = [ 'vimeo', 'youtube', 'html5' ];
</script>
<style type="text/css">
html {overflow: auto;}
html, body, div, iframe {margin: 0px; padding: 0px; height: 100%; border: none;}
+4 -1
View File
@@ -51,5 +51,8 @@ bin.includes = META-INF/,\
com.sap.sailing.gwt.ui.EmbeddedMapAndWindChart/,\
Calendar.html,\
imprint.json,\
licenses/
licenses/,\
PairingList.css,\
PairingList.html,\
com.sap.sailing.gwt.ui.PairingList/
output.. = WEB-INF/classes/
+23 -3
View File
@@ -697,7 +697,7 @@
},
{
"name": "video-js",
"version": "4.12.10",
"version": "5.20.3",
"owner": "Brightcove, Inc.",
"homepage": "https://github.com/videojs/video.js",
"acknowledgements": [
@@ -707,7 +707,7 @@
},
{
"name": "video-js-vimeo",
"version": "master-20140814",
"version": "2.0.0",
"owner": "Benoit Tremblay",
"homepage": "http://github.com/eXon/videojs-vimeo/",
"acknowledgements": [
@@ -717,13 +717,33 @@
},
{
"name": "video-js-youtube",
"version": "1.2.5",
"version": "2.0.8",
"owner": "Benoit Tremblay",
"homepage": "http://github.com/eXon/videojs-youtube/",
"acknowledgements": [
"Copyright (c) 2015 Benoit Tremblay <trembl.ben@gmail.com>"
],
"key": "MITL"
},
{
"name": "three.js",
"version": "76",
"owner": "\"Mr.doob\"",
"homepage": "https://threejs.org/",
"acknowledgements": [
"Copyright © 2010-2017 three.js authors"
],
"key": "MIT"
},
{
"name": "video-js-panorma",
"version": "0.1.5",
"owner": "Wensheng Yan",
"homepage": "https://github.com/yanwsh/videojs-panorama/",
"acknowledgements": [
"Copyright 2016 yanwsh@gmail.com"
],
"key": "Apache License 2.0"
}
],
"licenses": [
+657
View File
@@ -0,0 +1,657 @@
/* The MIT License (MIT)
Copyright (c) 2014-2015 Benoit Tremblay <trembl.ben@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. */
(function() {
'use strict';
var VimeoState = {
UNSTARTED: -1,
ENDED: 0,
PLAYING: 1,
PAUSED: 2,
BUFFERING: 3
};
var Tech = videojs.getComponent('Tech');
var Vimeo = videojs.extend(Tech, {
constructor: function(options, ready) {
Tech.call(this, options, ready);
if(options.poster != "") {this.setPoster(options.poster);}
this.setSrc(this.options_.source.src, true);
// Set the vjs-vimeo class to the player
// Parent is not set yet so we have to wait a tick
setTimeout(function() {
this.el_.parentNode.className += ' vjs-vimeo';
}.bind(this));
},
dispose: function() {
this.el_.parentNode.className = this.el_.parentNode.className.replace(' vjs-vimeo', '');
},
createEl: function() {
this.vimeo = {};
this.vimeoInfo = {};
this.baseUrl = 'https://player.vimeo.com/video/';
this.baseApiUrl = 'http://www.vimeo.com/api/v2/video/';
this.videoId = Vimeo.parseUrl(this.options_.source.src).videoId;
this.iframe = document.createElement('iframe');
this.iframe.setAttribute('id', this.options_.techId);
this.iframe.setAttribute('title', 'Vimeo Video Player');
this.iframe.setAttribute('class', 'vimeoplayer');
this.iframe.setAttribute('src', this.baseUrl + this.videoId + '?api=1&player_id=' + this.options_.techId);
this.iframe.setAttribute('frameborder', '0');
this.iframe.setAttribute('scrolling', 'no');
this.iframe.setAttribute('marginWidth', '0');
this.iframe.setAttribute('marginHeight', '0');
this.iframe.setAttribute('webkitAllowFullScreen', '0');
this.iframe.setAttribute('mozallowfullscreen', '0');
this.iframe.setAttribute('allowFullScreen', '0');
var divWrapper = document.createElement('div');
divWrapper.setAttribute('style', 'margin:0 auto;padding-bottom:56.25%;width:100%;height:0;position:relative;overflow:hidden;');
divWrapper.setAttribute('class', 'vimeoFrame');
divWrapper.appendChild(this.iframe);
if (!_isOnMobile && !this.options_.ytControls) {
var divBlocker = document.createElement('div');
divBlocker.setAttribute('class', 'vjs-iframe-blocker');
divBlocker.setAttribute('style', 'position:absolute;top:0;left:0;width:100%;height:100%');
// In case the blocker is still there and we want to pause
divBlocker.onclick = function() {
this.onPause();
}.bind(this);
divWrapper.appendChild(divBlocker);
}
if (Vimeo.isApiReady) {
this.initPlayer();
} else {
Vimeo.apiReadyQueue.push(this);
}
if(this.options_.poster == "") {
$.getJSON(this.baseApiUrl + this.videoId + '.json?callback=?', {format: "json"}, (function(_this){
return function(data) {
// Set the low resolution first
_this.setPoster(data[0].thumbnail_large);
};
})(this));
}
return divWrapper;
},
initPlayer: function() {
var self = this;
var vimeoVideoID = Vimeo.parseUrl(this.options_.source.src).videoId;
//load vimeo
if (this.vimeo && this.vimeo.api) {
this.vimeo.api('unload');
delete this.vimeo;
}
self.vimeo = $f(self.iframe);
self.vimeoInfo = {
state: VimeoState.UNSTARTED,
volume: 1,
muted: false,
muteVolume: 1,
time: 0,
duration: 0,
buffered: 0,
url: self.baseUrl + self.videoId,
error: null
};
this.vimeo.addEvent('ready', function(id){
self.onReady();
self.vimeo.addEvent('loadProgress', function(data, id){ self.onLoadProgress(data); });
self.vimeo.addEvent('playProgress', function(data, id){ self.onPlayProgress(data); });
self.vimeo.addEvent('play', function(id){ self.onPlay(); });
self.vimeo.addEvent('pause', function(id){ self.onPause(); });
self.vimeo.addEvent('finish', function(id){ self.onFinish(); });
self.vimeo.addEvent('seek', function(data, id){ self.onSeek(data); });
});
},
onReady: function(){
this.isReady_ = true;
this.triggerReady();
this.trigger('loadedmetadata');
if (this.startMuted) {
this.setMuted(true);
this.startMuted = false;
}
},
onLoadProgress: function(data) {
var durationUpdate = !this.vimeoInfo.duration;
this.vimeoInfo.duration = data.duration;
this.vimeoInfo.buffered = data.percent;
this.trigger('progress');
if (durationUpdate) this.trigger('durationchange');
},
onPlayProgress: function(data) {
this.vimeoInfo.time = data.seconds;
this.trigger('timeupdate');
},
onPlay: function() {
this.vimeoInfo.state = VimeoState.PLAYING;
this.trigger('play');
},
onPause: function() {
this.vimeoInfo.state = VimeoState.PAUSED;
this.trigger('pause');
},
onFinish: function() {
this.vimeoInfo.state = VimeoState.ENDED;
this.trigger('ended');
},
onSeek: function(data) {
this.trigger('seeking');
this.vimeoInfo.time = data.seconds;
this.trigger('timeupdate');
this.trigger('seeked');
},
onError: function(error){
this.error = error;
this.trigger('error');
},
error: function() {
switch (this.errorNumber) {
case 2:
return { code: 'Unable to find the video' };
case 5:
return { code: 'Error while trying to play the video' };
case 100:
return { code: 'Unable to find the video' };
case 101:
case 150:
return { code: 'Playback on other Websites has been disabled by the video owner.' };
}
return { code: 'Vimeo unknown error (' + this.errorNumber + ')' };
},
src: function() {
return this.source;
},
poster: function() {
return this.poster_;
},
setPoster: function(poster) {
this.poster_ = poster;
},
setSrc: function(source) {
if (!source || !source.src) {
return;
}
this.source = source;
this.url = Vimeo.parseUrl(source.src);
if (!this.options_.poster) {
if (this.url.videoId) {
$.getJSON(this.baseApiUrl + this.videoId + '.json?callback=?', {format: "json"}, (function(_this){
return function(data) {
// Set the low resolution first
_this.poster_ = data[0].thumbnail_small;
};
})(this));
// Check if their is a high res
this.checkHighResPoster();
}
}
if (this.options_.autoplay && !_isOnMobile) {
if (this.isReady_) {
this.play();
} else {
this.playOnReady = true;
}
}
},
supportsFullScreen: function() {
return true;
},
//TRIGGER
load : function(){},
play : function(){ this.vimeo.api('play'); },
pause : function(){ this.vimeo.api('pause'); },
paused : function(){
return this.vimeoInfo.state !== VimeoState.PLAYING &&
this.vimeoInfo.state !== VimeoState.BUFFERING;
},
currentTime : function(){ return this.vimeoInfo.time || 0; },
setCurrentTime :function(seconds){
this.vimeo.api('seekTo', seconds);
this.player_.trigger('timeupdate');
},
duration :function(){ return this.vimeoInfo.duration || 0; },
buffered :function(){ return videojs.createTimeRange(0, (this.vimeoInfo.buffered*this.vimeoInfo.duration) || 0); },
volume :function() { return (this.vimeoInfo.muted)? this.vimeoInfo.muteVolume : this.vimeoInfo.volume; },
setVolume :function(percentAsDecimal){
this.vimeo.api('setvolume', percentAsDecimal);
this.vimeoInfo.volume = percentAsDecimal;
this.player_.trigger('volumechange');
},
currentSrc :function() {
return this.el_.src;
},
muted :function() { return this.vimeoInfo.muted || false; },
setMuted :function(muted) {
if (muted) {
this.vimeoInfo.muteVolume = this.vimeoInfo.volume;
this.setVolume(0);
} else {
this.setVolume(this.vimeoInfo.muteVolume);
}
this.vimeoInfo.muted = muted;
this.player_.trigger('volumechange');
},
// Tries to get the highest resolution thumbnail available for the video
checkHighResPoster: function(){
var uri = '';
try {
$.getJSON(this.baseApiUrl + this.videoId + '.json?callback=?', {format: "json"}, (function(_uri){
return function(data) {
// Set the low resolution first
_uri = data[0].thumbnail_large;
};
})(uri));
var image = new Image();
image.onload = function(){
// Onload thumbnail
if('naturalHeight' in this){
if(this.naturalHeight <= 90 || this.naturalWidth <= 120) {
this.onerror();
return;
}
} else if(this.height <= 90 || this.width <= 120) {
this.onerror();
return;
}
this.poster_ = uri;
this.trigger('posterchange');
}.bind(this);
image.onerror = function(){};
image.src = uri;
}
catch(e){}
}
});
Vimeo.isSupported = function() {
return true;
};
Vimeo.canPlaySource = function(e) {
return (e.type === 'video/vimeo');
};
var _isOnMobile = /(iPad|iPhone|iPod|Android)/g.test(navigator.userAgent);
Vimeo.parseUrl = function(url) {
var result = {
videoId: null
};
var regex = /^.*(vimeo\.com\/)((channels\/[A-z]+\/)|(groups\/[A-z]+\/videos\/))?([0-9]+)/;
var match = url.match(regex);
if (match) {
result.videoId = match[5];
}
return result;
};
function injectCss() {
var css = // iframe blocker to catch mouse events
'.vjs-vimeo .vjs-iframe-blocker { display: none; }' +
'.vjs-vimeo.vjs-user-inactive .vjs-iframe-blocker { display: block; }' +
'.vjs-vimeo .vjs-poster { background-size: cover; }' +
'.vjs-vimeo { height:100%; }' +
'.vimeoplayer { width:100%; height:180%; position:absolute; left:0; top:-40%; }';
var head = document.head || document.getElementsByTagName('head')[0];
var style = document.createElement('style');
style.type = 'text/css';
if (style.styleSheet){
style.styleSheet.cssText = css;
} else {
style.appendChild(document.createTextNode(css));
}
head.appendChild(style);
}
Vimeo.apiReadyQueue = [];
var vimeoIframeAPIReady = function() {
Vimeo.isApiReady = true;
injectCss();
for (var i = 0; i < Vimeo.apiReadyQueue.length; ++i) {
Vimeo.apiReadyQueue[i].initPlayer();
}
};
vimeoIframeAPIReady();
videojs.registerTech('Vimeo', Vimeo);
// Froogaloop API -------------------------------------------------------------
// From https://github.com/vimeo/player-api/blob/master/javascript/froogaloop.js
// Init style shamelessly stolen from jQuery http://jquery.com
var Froogaloop = (function(){
// Define a local copy of Froogaloop
function Froogaloop(iframe) {
// The Froogaloop object is actually just the init constructor
return new Froogaloop.fn.init(iframe);
}
var eventCallbacks = {},
hasWindowEvent = false,
isReady = false,
slice = Array.prototype.slice,
playerOrigin = '*';
Froogaloop.fn = Froogaloop.prototype = {
element: null,
init: function(iframe) {
if (typeof iframe === "string") {
iframe = document.getElementById(iframe);
}
this.element = iframe;
return this;
},
/*
* Calls a function to act upon the player.
*
* @param {string} method The name of the Javascript API method to call. Eg: 'play'.
* @param {Array|Function} valueOrCallback params Array of parameters to pass when calling an API method
* or callback function when the method returns a value.
*/
api: function(method, valueOrCallback) {
if (!this.element || !method) {
return false;
}
var self = this,
element = self.element,
target_id = element.id !== '' ? element.id : null,
params = !isFunction(valueOrCallback) ? valueOrCallback : null,
callback = isFunction(valueOrCallback) ? valueOrCallback : null;
// Store the callback for get functions
if (callback) {
storeCallback(method, callback, target_id);
}
postMessage(method, params, element);
return self;
},
/*
* Registers an event listener and a callback function that gets called when the event fires.
*
* @param eventName (String): Name of the event to listen for.
* @param callback (Function): Function that should be called when the event fires.
*/
addEvent: function(eventName, callback) {
if (!this.element) {
return false;
}
var self = this,
element = self.element,
target_id = element.id !== '' ? element.id : null;
storeCallback(eventName, callback, target_id);
// The ready event is not registered via postMessage. It fires regardless.
if (eventName != 'ready') {
postMessage('addEventListener', eventName, element);
}
else if (eventName == 'ready' && isReady) {
callback.call(null, target_id);
}
return self;
},
/*
* Unregisters an event listener that gets called when the event fires.
*
* @param eventName (String): Name of the event to stop listening for.
*/
removeEvent: function(eventName) {
if (!this.element) {
return false;
}
var self = this,
element = self.element,
target_id = element.id !== '' ? element.id : null,
removed = removeCallback(eventName, target_id);
// The ready event is not registered
if (eventName != 'ready' && removed) {
postMessage('removeEventListener', eventName, element);
}
}
};
/**
* Handles posting a message to the parent window.
*
* @param method (String): name of the method to call inside the player. For api calls
* this is the name of the api method (api_play or api_pause) while for events this method
* is api_addEventListener.
* @param params (Object or Array): List of parameters to submit to the method. Can be either
* a single param or an array list of parameters.
* @param target (HTMLElement): Target iframe to post the message to.
*/
function postMessage(method, params, target) {
if (!target.contentWindow.postMessage) {
return false;
}
var data = JSON.stringify({
method: method,
value: params
});
target.contentWindow.postMessage(data, playerOrigin);
}
/**
* Event that fires whenever the window receives a message from its parent
* via window.postMessage.
*/
function onMessageReceived(event) {
var data, method;
try {
data = JSON.parse(event.data);
method = data.event || data.method;
}
catch(e) {
//fail silently... like a ninja!
}
if (method == 'ready' && !isReady) {
isReady = true;
}
// Handles messages from the vimeo player only
if (!(/^https?:\/\/player.vimeo.com/).test(event.origin)) {
return false;
}
if (playerOrigin === '*') {
playerOrigin = event.origin;
}
var value = data.value,
eventData = data.data,
target_id = target_id === '' ? null : data.player_id,
callback = getCallback(method, target_id),
params = [];
if (!callback) {
return false;
}
if (value !== undefined) {
params.push(value);
}
if (eventData) {
params.push(eventData);
}
if (target_id) {
params.push(target_id);
}
return params.length > 0 ? callback.apply(null, params) : callback.call();
}
/**
* Stores submitted callbacks for each iframe being tracked and each
* event for that iframe.
*
* @param eventName (String): Name of the event. Eg. api_onPlay
* @param callback (Function): Function that should get executed when the
* event is fired.
* @param target_id (String) [Optional]: If handling more than one iframe then
* it stores the different callbacks for different iframes based on the iframe's
* id.
*/
function storeCallback(eventName, callback, target_id) {
if (target_id) {
if (!eventCallbacks[target_id]) {
eventCallbacks[target_id] = {};
}
eventCallbacks[target_id][eventName] = callback;
}
else {
eventCallbacks[eventName] = callback;
}
}
/**
* Retrieves stored callbacks.
*/
function getCallback(eventName, target_id) {
if (target_id && eventCallbacks[target_id]) {
return eventCallbacks[target_id][eventName];
}
else if (eventCallbacks[eventName]) {
return eventCallbacks[eventName];
}
}
function removeCallback(eventName, target_id) {
if (target_id && eventCallbacks[target_id]) {
if (!eventCallbacks[target_id][eventName]) {
return false;
}
eventCallbacks[target_id][eventName] = null;
}
else {
if (!eventCallbacks[eventName]) {
return false;
}
eventCallbacks[eventName] = null;
}
return true;
}
function isFunction(obj) {
return !!(obj && obj.constructor && obj.call && obj.apply);
}
function isArray(obj) {
return toString.call(obj) === '[object Array]';
}
// Give the init function the Froogaloop prototype for later instantiation
Froogaloop.fn.init.prototype = Froogaloop.fn;
// Listens for the message event.
// W3C
if (window.addEventListener) {
window.addEventListener('message', onMessageReceived, false);
}
// IE
else {
window.attachEvent('onmessage', onMessageReceived);
}
// Expose froogaloop to the global object
return (window.Froogaloop = window.$f = Froogaloop);
})();
})();
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More