diff --git a/.gitignore b/.gitignore index 8bed8bde408..35a16d43c1d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,36 +1,37 @@ -/target -www-test -war -build.log -reporting.log -.metadata -*.class -*.swp -*~ -manifest.txt -java/com.sap.sailing.gwt.ui/gwt-unitCache/* -java/com.sap.sailing.gwt.ui/war/WEB-INF/deploy/* -java/com.sap.sse.security.ui/WEB-INF/deploy/* -sailing_debug0* -dist -build -.DS_Store -java/com.sap.sailing.simulator/bin/com/sap/sailing/simulator/Simulator.gwt.xml -/mobile/com.sap.sailing.racecommittee.app/res/values/keys.xml -java/com.sap.sailing.gwt.ui/SailingGWT Remote Profiling (Run-Mode only).launch -java/com.sap.sailing.gwt.ui/SailingGWT.launch - -## Android Studio and Intellij and Android in general -gen/ -.idea/ -*.ipr -*.iws -*.iml -out/ -.gradle -local.properties -git.xml -mobile/github-advrecyclerview_lib/bin -captures -/.recommenders/ -/java/.recommenders/ \ No newline at end of file +/target +www-test +war +build.log +reporting.log +.metadata +*.class +*.swp +*~ +manifest.txt +java/com.sap.sailing.gwt.ui/gwt-unitCache/* +java/com.sap.sailing.gwt.ui/war/WEB-INF/deploy/* +java/com.sap.sse.security.ui/WEB-INF/deploy/* +sailing_debug0* +dist +build +.DS_Store +java/com.sap.sailing.simulator/bin/com/sap/sailing/simulator/Simulator.gwt.xml +/mobile/com.sap.sailing.racecommittee.app/res/values/keys.xml +java/com.sap.sailing.gwt.ui/SailingGWT Remote Profiling (Run-Mode only).launch +java/com.sap.sailing.gwt.ui/SailingGWT.launch + +## Android Studio and Intellij and Android in general +gen/ +.idea/ +*.ipr +*.iws +*.iml +out/ +.gradle +local.properties +git.xml +mobile/github-advrecyclerview_lib/bin +captures +/.recommenders/ +/java/.recommenders/ +java/com.sap.sailing.selenium.test/com.sap.sailing.selenium.test (No Proxy) individual.launch diff --git a/configuration/buildAndUpdateProduct.sh b/configuration/buildAndUpdateProduct.sh index 7555e3d4c12..80289012902 100755 --- a/configuration/buildAndUpdateProduct.sh +++ b/configuration/buildAndUpdateProduct.sh @@ -27,6 +27,15 @@ find_project_home () echo $(correct_file_path "$1") } +clean_gwt_artifacts () +{ + cd $PROJECT_HOME/java + rm -rf com.sap.$PROJECT_TYPE.gwt.ui/com.sap.$PROJECT_TYPE.* com.sap.$PROJECT_TYPE.gwt.ui/.generated + rm -rf com.sap.sailing.dashboards.gwt/com.sap.sailing.dashboards.gwt.* com.sap.sailing.dashboards.gwt/.generated + rm -rf com.sap.sse.security.ui/com.sap.sse.security.ui.* com.sap.sse.security.ui/.generated + rm -rf com.sap.sse.gwt/com.sap.sse.gwt.* com.sap.sse.gwt/.generated +} + # this holds for default installation USER_HOME=~ START_DIR="`pwd`" @@ -207,10 +216,7 @@ if [[ "$@" == "clean" ]]; then if [[ $? != 0 ]]; then exit 100 fi - cd $PROJECT_HOME/java - rm -rf com.sap.$PROJECT_TYPE.gwt.ui/com.sap.$PROJECT_TYPE.* - rm -rf com.sap.sailing.dashboards.gwt/com.sap.sailing.dashboards.gwt.* - rm -rf com.sap.sse.security.ui/com.sap.sse.security.ui.* + clean_gwt_artifacts cd $PROJECT_HOME echo "Using following command: mvn $extra -DargLine=\"$APP_PARAMETERS\" -fae -s $MAVEN_SETTINGS $clean" echo "Maven version used: `mvn --version`" @@ -559,27 +565,26 @@ if [[ "$@" == "build" ]] || [[ "$@" == "all" ]]; then cd $PROJECT_HOME/java if [ $gwtcompile -eq 1 ] && [[ "$clean" == "clean" ]]; then echo "INFO: Compiling GWT (rm -rf com.sap.$PROJECT_TYPE.gwt.ui/com.sap.$PROJECT_TYPE.*)" - rm -rf com.sap.$PROJECT_TYPE.gwt.ui/com.sap.$PROJECT_TYPE.* - GWT_XML_FILES=`find . -name '*.gwt.xml'` - if [ $onegwtpermutationonly -eq 1 ]; then - echo "INFO: Patching .gwt.xml files such that only one GWT permutation needs to be compiled" - for i in $GWT_XML_FILES; do - echo "INFO: Patching $i files such that only one GWT permutation needs to be compiled" - cp $i $i.bak - cat $i | sed -e 's/AllPermutations/SinglePermutation/' >$i.sed - mv $i.sed $i - done - else - echo "INFO: Patching .gwt.xml files such that all GWT permutations are compiled" - for i in $GWT_XML_FILES; do - echo "INFO: Patching $i files such that all GWT permutations are compiled" - cp $i $i.bak - cat $i | sed -e 's/SinglePermutation/AllPermutations/' >$i.sed - mv $i.sed $i - - done - fi - + clean_gwt_artifacts + GWT_XML_FILES=`find . -name '*.gwt.xml'` + if [ $onegwtpermutationonly -eq 1 ]; then + echo "INFO: Patching .gwt.xml files such that only one GWT permutation needs to be compiled" + for i in $GWT_XML_FILES; do + echo "INFO: Patching $i files such that only one GWT permutation needs to be compiled" + cp $i $i.bak + cat $i | sed -e 's/AllPermutations/SinglePermutation/' >$i.sed + mv $i.sed $i + done + else + echo "INFO: Patching .gwt.xml files such that all GWT permutations are compiled" + for i in $GWT_XML_FILES; do + echo "INFO: Patching $i files such that all GWT permutations are compiled" + cp $i $i.bak + cat $i | sed -e 's/SinglePermutation/AllPermutations/' >$i.sed + mv $i.sed $i + + done + fi else echo "INFO: GWT Compilation disabled" extra="$extra -Pdebug.no-gwt-compile" diff --git a/configuration/launchhudsonslave b/configuration/launchhudsonslave index 7a70f7e4697..71dcf06e6e8 100755 --- a/configuration/launchhudsonslave +++ b/configuration/launchhudsonslave @@ -1,8 +1,8 @@ #!/bin/bash AWS=/root/.local/bin/aws REGION=eu-west-1 -#HUDSON_SLAVE_AMI_ID=ami-0812bc35c7464732e -HUDSON_SLAVE_AMI_ID=ami-04956fe5a5293da6f +#HUDSON_SLAVE_AMI_ID=ami-04956fe5a5293da6f +HUDSON_SLAVE_AMI_ID=ami-0251a199f8d4a4729 echo Launching instance... instanceid=`$AWS ec2 run-instances --image-id $HUDSON_SLAVE_AMI_ID --count 1 --instance-type c5d.4xlarge --key-name Axel --security-groups "Sailing Analytics App" --region $REGION --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=Hudson Ubuntu Slave}]' --instance-initiated-shutdown-behavior terminate | tee /tmp/slavelaunch.out | jq .Instances[0].InstanceId | sed -e 's/"//g'` if [ "$instanceid" = "" ]; then @@ -17,7 +17,7 @@ else echo Instance $instanceid seems running now private_ip=`$AWS ec2 describe-instances --region $REGION --instance-ids $instanceid | jq .Reservations[0].Instances[0].PrivateIpAddress | sed -e 's/"//g'` echo Probing for SSH on private IP $private_ip - while su - hudson -c "ssh -o ConnectTimeout=10,StrictHostKeyChecking=no hudson@$private_ip ls >/dev/null"; do + while su - hudson -c "ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=no hudson@$private_ip ls >/dev/null"; do echo SSH daemon not reachable yet. Trying again... done echo SSH daemon reached. State should be ready to connect to now. diff --git a/configuration/launchhudsonslave-java11 b/configuration/launchhudsonslave-java11 index 6b02d18147f..1e06b77b531 100755 --- a/configuration/launchhudsonslave-java11 +++ b/configuration/launchhudsonslave-java11 @@ -16,7 +16,7 @@ else echo Instance $instanceid seems running now private_ip=`$AWS ec2 describe-instances --region $REGION --instance-ids $instanceid | jq .Reservations[0].Instances[0].PrivateIpAddress | sed -e 's/"//g'` echo Probing for SSH on private IP $private_ip - while su - hudson -c "ssh -o ConnectTimeout=10,StrictHostKeyChecking=no hudson@$private_ip ls >/dev/null"; do + while su - hudson -c "ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=no hudson@$private_ip ls >/dev/null"; do echo SSH daemon not reachable yet. Trying again... done echo SSH daemon reached. State should be ready to connect to now. diff --git a/configuration/mail_forward.js b/configuration/mail_forward.js deleted file mode 100644 index 6bc037db681..00000000000 --- a/configuration/mail_forward.js +++ /dev/null @@ -1,38 +0,0 @@ -// Taken from https://github.com/sirceljm/AWS-SES-simple-mail-forward/blob/master/mail_forward.js -// Install as an AWS Lambda according to https://github.com/sirceljm/AWS-SES-simple-mail-forward -// and configure the to_address environment variable as a JSON array of strings representing -// the recipients' e-mail addresses. Subscribe the lambda to your SNS topic that receives the -// notification messages about your inbound e-mail. Use mail_forward_policy.json for the IAM role's -// security policy. -var AWS = require("aws-sdk"); -var ses = new AWS.SES(); - -exports.handler = (event, context, callback) => { - let data = JSON.parse(event.Records[0].Sns.Message); - let source = data.content; - - source = source.replace(/^DKIM-Signature/im, "X-Original-DKIM-Signature"); - // source = source.replace(/^From/im, "X-Original-From"); - // source = source.replace(/^Source/im, "X-Original-Source"); - // source = source.replace(/^Sender/im, "X-Original-Sender"); - // source = source.replace(/^Return-Path/im, "X-Original-Return-Path"); - source = source.replace(/^Domainkey-Signature/im, "X-Original-Domainkey-Signature"); - // source = `From: AWS SES Mail Forwarding <${data.mail.destination}>\n` + source; - - if (!process.env.to_address) { - console.log("You need to supply to_address enviromnent variable"); - } else if (data.mail.destination == process.env.to_address) { - console.log("MAIL SEND ABORTED! - Forwarded mail address and to_address are the same - THIS CAN CAUSE AN INFINITE LOOP!"); - } else { - ses.sendRawEmail({ - RawMessage: { - Data: source, - }, - Destinations: JSON.parse(process.env.to_address), - }, function (err, data) { - if (err) { - console.error(err, err.stack); - } - }); - } -}; diff --git a/configuration/mail_forward.py b/configuration/mail_forward.py new file mode 100644 index 00000000000..d986da6e81a --- /dev/null +++ b/configuration/mail_forward.py @@ -0,0 +1,117 @@ +# Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# This file is licensed under the Apache License, Version 2.0 (the "License"). +# You may not use this file except in compliance with the License. A copy of the +# License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS +# OF ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +import os +import boto3 +import email +import re +import json +from botocore.exceptions import ClientError +from email.parser import Parser +from email.policy import default + +region = os.environ['Region'] + +def get_message_from_s3(bucket_name, object_key): + # Create a new S3 client. + client_s3 = boto3.client("s3") + # Get the email object from the S3 bucket. + object_s3 = client_s3.get_object(Bucket=bucket_name, Key=object_key) + # Read the content of the message. + file = object_s3['Body'].read() + return file + +def create_message(file): + senderMap = json.loads(os.environ["MailSenderJSON"]) + recipientMap = json.loads(os.environ["MailRecipientsJSON"]) + mailobject = email.message_from_string(file.decode('utf-8')) + # Define sender, either from 'To'-Header or from 'Cc'-Header + sender = None + # Check 'To'-Header + toHeader = Parser(policy=default).parsestr('To: ' + mailobject['To']) + for x in toHeader['to'].addresses: + if x.addr_spec in recipientMap: + recipientList = recipientMap.get(x.addr_spec) + sender = senderMap.get(x.addr_spec, x.addr_spec) + # Check 'Cc'-Header + if sender is None: + if mailobject['Cc']: + ccHeader = Parser(policy=default).parsestr('Cc: ' + mailobject['Cc']) + for x in ccHeader['cc'].addresses: + if x.addr_spec in recipientMap: + recipientList = recipientMap.get(x.addr_spec) + sender = senderMap.get(x.addr_spec, x.addr_spec) + # Bcc mail, there is no header so find address from 'Received'-Header + else: + receivedString = re.search(r'\<(.*?)\>', ''.join(mailobject.get_all('Received'))).group(1) + if receivedString in recipientMap: + recipientList = recipientMap.get(receivedString) + sender = senderMap.get(receivedString, receivedString) + # Change original headers to X- + mailobject['X-From'] = mailobject['From'] + if not mailobject['Reply-To']: + mailobject['Reply-To'] = mailobject['From'] + mailobject['X-To'] = mailobject['To'] + mailobject['X-Return-Path'] = mailobject['Return-Path'] + # Replace original headers and set to SES Value + del mailobject['DKIM-Signature'] + mailobject.replace_header('From', sender) + mailobject.replace_header('Return-Path', sender) + mailobject.replace_header('To', ','.join(recipientList)) + message = { + "Source": sender, + "Destinations": recipientList, + "Data" : mailobject.as_string() + } + return message + +def send_email(message): + # Create a new SES client. + client_ses = boto3.client('ses', region) + # Send the email. + try: + #Provide the contents of the email. + response = client_ses.send_raw_email( + Source=message['Source'], + Destinations= + message['Destinations'] + , + RawMessage={ + 'Data':message['Data'] + } + ) + # Display an error if something goes wrong. + except ClientError as e: + output = e.response['Error']['Message'] + else: + # Modify output message + if re.search(r'\<(.*?)\>', message['Source']): + fromString = ','.join(re.findall(r'\<(.*?)\>', message['Source'])) + else: + fromString = message['Source'] + output = "Email for: " + fromString + " forwarded to: " + ', '.join(message['Destinations']) + "! Message ID: " + response['MessageId'] + return output + +def lambda_handler(event, context): + # Get the unique ID of the message. This corresponds to the name of the file in S3. + sns = event['Records'][0]['Sns'] + message = json.loads(sns['Message']) + bucket_name = message['receipt']['action']['bucketName'] + object_key = message['receipt']['action']['objectKey'] + print(f"Received message with object key {object_key} in bucket {bucket_name}") + # Retrieve the file from the S3 bucket. + file = get_message_from_s3(bucket_name, object_key) + # Create the message. + message = create_message(file) + # Send the email and print the result. + result = send_email(message) + print(result) diff --git a/configuration/mail_forward_policy.json b/configuration/mail_forward_policy.json index e586059e9e8..18bfd2e3fa4 100644 --- a/configuration/mail_forward_policy.json +++ b/configuration/mail_forward_policy.json @@ -1,22 +1,27 @@ { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "logs:CreateLogGroup", - "logs:CreateLogStream", - "logs:PutLogEvents" - ], - "Resource": "arn:aws:logs:*:*:*" - }, - { - "Effect": "Allow", - "Action": [ - "ses:SendEmail", - "ses:SendRawEmail" - ], - "Resource": "*" - } - ] -} + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "VisualEditor0", + "Effect": "Allow", + "Action": [ + "logs:CreateLogStream", + "logs:CreateLogGroup", + "logs:PutLogEvents" + ], + "Resource": "*" + }, + { + "Sid": "VisualEditor1", + "Effect": "Allow", + "Action": [ + "s3:GetObject", + "ses:SendRawEmail" + ], + "Resource": [ + "arn:aws:s3:::emails-for-sapsailing-com/*", + "arn:aws:ses:eu-west-1:017363970217:identity/*" + ] + } + ] +} \ No newline at end of file diff --git a/docker/Dockerfile.tpl b/docker/Dockerfile.tpl index 302a49a5548..deb26fe8027 100755 --- a/docker/Dockerfile.tpl +++ b/docker/Dockerfile.tpl @@ -1,4 +1,4 @@ -FROM donaldduck70/sapjvm8:8.1.059 +FROM donaldduck70/sapjvm8:8.1.062 LABEL maintainer=axel.uhl@sap.com # Download and extract the release WORKDIR /home/sailing/servers/server diff --git a/docker/Dockerfile_sapjvm b/docker/Dockerfile_sapjvm index 53b15227628..3b8a44845ad 100644 --- a/docker/Dockerfile_sapjvm +++ b/docker/Dockerfile_sapjvm @@ -6,7 +6,7 @@ ENV JAVA_HOME=/opt/sapjvm_8 WORKDIR /opt RUN apt-get update \ && apt-get install -y wget unzip \ - && curl --cookie eula_3_1_agreed=tools.hana.ondemand.com/developer-license-3_1.txt "https://tools.hana.ondemand.com/additional/sapjvm-8.1.059-linux-x64.zip" --output sapjvm8-linux-x64.zip \ + && curl --cookie eula_3_1_agreed=tools.hana.ondemand.com/developer-license-3_1.txt "https://tools.hana.ondemand.com/additional/sapjvm-8.1.062-linux-x64.zip" --output sapjvm8-linux-x64.zip \ && unzip sapjvm8-linux-x64.zip \ && rm sapjvm8-linux-x64.zip \ && echo "export JAVA_HOME=/opt/sapjvm_8; export PATH=\${PATH}:/opt/sapjvm_8/bin" > /etc/profile.d/javahome.sh diff --git a/java/com.sap.sailing.dashboards.gwt/META-INF/MANIFEST.MF b/java/com.sap.sailing.dashboards.gwt/META-INF/MANIFEST.MF index 83a4af85605..de6b585902a 100644 --- a/java/com.sap.sailing.dashboards.gwt/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.dashboards.gwt/META-INF/MANIFEST.MF @@ -12,6 +12,7 @@ Require-Bundle: com.sap.sailing.domain, com.google.gwt.user;bundle-version="2.8.2";resolution:=optional, com.google.gwt.servlet;bundle-version="2.8.2", com.sap.sse.common, + com.sap.sse.debranding, com.sap.sailing.domain.common, com.sap.sailing.domain.shared.android, com.sap.sailing.gwt.ui, diff --git a/java/com.sap.sailing.dashboards.gwt/RibDashboard.html b/java/com.sap.sailing.dashboards.gwt/RibDashboard.html index 3bea985d183..6c55c754bf9 100644 --- a/java/com.sap.sailing.dashboards.gwt/RibDashboard.html +++ b/java/com.sap.sailing.dashboards.gwt/RibDashboard.html @@ -7,44 +7,38 @@ - - - - - - - - -Dashboard - - - - - - - - + + + + + + Dashboard + + + + + - - - - - - - - + + + + diff --git a/java/com.sap.sailing.dashboards.gwt/WEB-INF/web.xml b/java/com.sap.sailing.dashboards.gwt/WEB-INF/web.xml index 32fcb29c829..48a75890c33 100644 --- a/java/com.sap.sailing.dashboards.gwt/WEB-INF/web.xml +++ b/java/com.sap.sailing.dashboards.gwt/WEB-INF/web.xml @@ -1,74 +1,83 @@ + xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" + xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" + version="2.5"> - - default - org.eclipse.jetty.servlet.DefaultServlet - - - dirAllowed - false - - - useFileMappedBuffer - false - - + + default + org.eclipse.jetty.servlet.DefaultServlet + + + dirAllowed + false + + + useFileMappedBuffer + false + + - - Sailing Service - SailingService - com.sap.sailing.gwt.ui.server.SailingServiceImpl - - - SailingService - /service/sailing - + + Sailing Service + SailingService + com.sap.sailing.gwt.ui.server.SailingServiceImpl + + + SailingService + /service/sailing + - - LocaleInjectionFilter - com.sap.sse.security.ui.server.LocaleInjectionFilter - - - LocaleInjectionFilter - *.html - + + ClientConfigurationServlet + ClientConfigurationServlet + com.sap.sse.debranding.ClientConfigurationServlet + + + ClientConfigurationServlet + *.html + - - GWTCacheControlServletFilter - com.sap.sse.gwt.server.GWTCacheControlServletFilter - - - GWTCacheControlServletFilter - /* - - - - Dashboard Dispatch Service - dashboardDispatch - com.sap.sailing.dashboards.gwt.server.DashboardDispatchServlet - - - dashboardDispatch - /service/dispatch - - - - - remoteLoggingServlet - com.google.gwt.logging.server.RemoteLoggingServiceImpl - - - remoteLoggingServlet - /com.sap.sailing.dashboards.gwt.RibDashboard/remote_logging - + + LocaleInjectionFilter + com.sap.sse.security.ui.server.LocaleInjectionFilter + + + LocaleInjectionFilter + *.html + - - manifest - text/cache-manifest - + + GWTCacheControlServletFilter + com.sap.sse.gwt.server.GWTCacheControlServletFilter + + + GWTCacheControlServletFilter + /* + + + Dashboard Dispatch Service + dashboardDispatch + com.sap.sailing.dashboards.gwt.server.DashboardDispatchServlet + + + dashboardDispatch + /service/dispatch + + + + + remoteLoggingServlet + com.google.gwt.logging.server.RemoteLoggingServiceImpl + + + remoteLoggingServlet + /com.sap.sailing.dashboards.gwt.RibDashboard/remote_logging + + + + manifest + text/cache-manifest + diff --git a/java/com.sap.sailing.dashboards.gwt/images/dashboardicon-whitelabeled.png b/java/com.sap.sailing.dashboards.gwt/images/dashboardicon-whitelabeled.png new file mode 100644 index 00000000000..d109e11fc07 Binary files /dev/null and b/java/com.sap.sailing.dashboards.gwt/images/dashboardicon-whitelabeled.png differ diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/header/DashboardHeader.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/header/DashboardHeader.java index 3f641a2b5b3..55f0b9290da 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/header/DashboardHeader.java +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/header/DashboardHeader.java @@ -12,6 +12,7 @@ import com.google.gwt.user.client.ui.HasWidgets; import com.google.gwt.user.client.ui.Image; import com.google.gwt.user.client.ui.Widget; import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sse.gwt.shared.ClientConfiguration; public class DashboardHeader extends Composite implements HasWidgets { @@ -37,7 +38,9 @@ public class DashboardHeader extends Composite implements HasWidgets { public DashboardHeader() { dashboardHeaderResources.gss().ensureInjected(); initWidget(uiBinder.createAndBindUi(this)); - sapLogo.setResource(DashboardHeaderResources.INSTANCE.sapLogo()); + if (ClientConfiguration.getInstance().isBrandingActive()) { + sapLogo.setResource(DashboardHeaderResources.INSTANCE.sapLogo()); + } event.setInnerHTML(StringMessages.INSTANCE.dashboardHeader()); } diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/linker/DashboardManifestLinker.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/linker/DashboardManifestLinker.java index 9ce145b54d3..6e2de373572 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/linker/DashboardManifestLinker.java +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/linker/DashboardManifestLinker.java @@ -16,6 +16,7 @@ public class DashboardManifestLinker extends ManifestLinker { return new String[] { "/dashboards/RibDashboard.html", "/dashboards/RibDashboard.css", "/dashboards/images/dashboardicon.png", + "/dashboards/images/dashboardicon-whitelabeled.png", "/dashboards/js/jquery-3.3.1.min.js", "/sailing-fontface-1.0.cache.css"}; } diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/DomainObjectFactoryImpl.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/DomainObjectFactoryImpl.java index ba674d1f8e4..33938635f67 100644 --- a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/DomainObjectFactoryImpl.java +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/DomainObjectFactoryImpl.java @@ -1292,6 +1292,8 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory { .get(FieldNames.REGATTA_USE_START_TIME_INFERENCE.name()); final Boolean controlTrackingFromStartAndFinishTimes = (Boolean) dbRegatta .get(FieldNames.REGATTA_CONTROL_TRACKING_FROM_START_AND_FINISH_TIMES.name()); + final Boolean autoRestartTrackingUponCompetitorSetChange = (Boolean) dbRegatta + .get(FieldNames.REGATTA_AUTO_RESTART_TRACKING_UPON_COMPETITOR_SET_CHANGE.name()); Boolean canBoatsOfCompetitorsChangePerRace = (Boolean) dbRegatta .get(FieldNames.REGATTA_CAN_BOATS_OF_COMPETITORS_CHANGE_PER_RACE.name()); // for backward compatibility @@ -1300,7 +1302,6 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory { canBoatsOfCompetitorsChangePerRace = false; createMigratableRegatta = true; } - CompetitorRegistrationType competitorRegistrationType = CompetitorRegistrationType .valueOfOrDefault((String) dbRegatta.get(FieldNames.REGATTA_COMPETITOR_REGISTRATION_TYPE.name())); final RankingMetricConstructor rankingMetricConstructor = loadRankingMetricConstructor(dbRegatta); @@ -1313,7 +1314,7 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory { : buoyZoneRadiusInHullLengths, useStartTimeInference == null ? true : useStartTimeInference, controlTrackingFromStartAndFinishTimes == null ? false : controlTrackingFromStartAndFinishTimes, - rankingMetricConstructor, new MongoObjectFactoryImpl(database), registrationLinkSecret); + autoRestartTrackingUponCompetitorSetChange, rankingMetricConstructor, new MongoObjectFactoryImpl(database), registrationLinkSecret); } else { result = new RegattaImpl(getRaceLogStore(), getRegattaLogStore(), name, boatClass, canBoatsOfCompetitorsChangePerRace, competitorRegistrationType, startDate, endDate, series, /* persistent */true, @@ -1322,7 +1323,7 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory { : buoyZoneRadiusInHullLengths, useStartTimeInference == null ? true : useStartTimeInference, controlTrackingFromStartAndFinishTimes == null ? false : controlTrackingFromStartAndFinishTimes, - rankingMetricConstructor, registrationLinkSecret); + autoRestartTrackingUponCompetitorSetChange == null ? false : autoRestartTrackingUponCompetitorSetChange, rankingMetricConstructor, registrationLinkSecret); } result.setRegattaConfiguration(configuration); } diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/FieldNames.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/FieldNames.java index 352a3c8cc9d..5467cb8de54 100644 --- a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/FieldNames.java +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/FieldNames.java @@ -38,6 +38,7 @@ public enum FieldNames { REGATTA_BUOY_ZONE_RADIUS_IN_HULL_LENGTHS, REGATTA_USE_START_TIME_INFERENCE, REGATTA_CONTROL_TRACKING_FROM_START_AND_FINISH_TIMES, + REGATTA_AUTO_RESTART_TRACKING_UPON_COMPETITOR_SET_CHANGE, REGATTA_CAN_BOATS_OF_COMPETITORS_CHANGE_PER_RACE, REGATTA_COMPETITOR_REGISTRATION_TYPE, REGATTA_REGISTRATION_LINK_SECRET, diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MigratableRegattaImpl.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MigratableRegattaImpl.java index 9ed54fa3fc7..9b06d52e891 100644 --- a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MigratableRegattaImpl.java +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MigratableRegattaImpl.java @@ -32,12 +32,12 @@ public class MigratableRegattaImpl extends RegattaImpl implements MigratableRega TimePoint startDate, TimePoint endDate, Iterable series, boolean persistent, ScoringScheme scoringScheme, Serializable id, CourseArea courseArea, Double buoyZoneRadiusInHullLengths, boolean useStartTimeInference, - boolean controlTrackingFromStartAndFinishTimes, RankingMetricConstructor rankingMetricConstructor, - MongoObjectFactory mongoObjectFactory, String registrationLinkSecret) { + boolean controlTrackingFromStartAndFinishTimes, boolean autoRestartTrackingUponCompetitorSetChange, + RankingMetricConstructor rankingMetricConstructor, MongoObjectFactory mongoObjectFactory, String registrationLinkSecret) { super(raceLogStore, regattaLogStore, name, boatClass, canBoatsOfCompetitorsChangePerRace, competitorRegistrationType, startDate, endDate, series, persistent, scoringScheme, id, courseArea, buoyZoneRadiusInHullLengths, useStartTimeInference, - controlTrackingFromStartAndFinishTimes, rankingMetricConstructor, - registrationLinkSecret); + controlTrackingFromStartAndFinishTimes, autoRestartTrackingUponCompetitorSetChange, + rankingMetricConstructor, registrationLinkSecret); this.mongoObjectFactory = mongoObjectFactory; mongoObjectFactory.storeRegatta(this); // make sure the canBoatsOfCompetitorsChangePerRace flag makes it into the DB } diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoObjectFactoryImpl.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoObjectFactoryImpl.java index a1e67c42e72..1aabc2b38b0 100644 --- a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoObjectFactoryImpl.java +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoObjectFactoryImpl.java @@ -729,6 +729,7 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory { dbRegatta.put(FieldNames.REGATTA_BUOY_ZONE_RADIUS_IN_HULL_LENGTHS.name(), regatta.getBuoyZoneRadiusInHullLengths()); dbRegatta.put(FieldNames.REGATTA_USE_START_TIME_INFERENCE.name(), regatta.useStartTimeInference()); dbRegatta.put(FieldNames.REGATTA_CONTROL_TRACKING_FROM_START_AND_FINISH_TIMES.name(), regatta.isControlTrackingFromStartAndFinishTimes()); + dbRegatta.put(FieldNames.REGATTA_AUTO_RESTART_TRACKING_UPON_COMPETITOR_SET_CHANGE.name(), regatta.isAutoRestartTrackingUponCompetitorSetChange()); dbRegatta.put(FieldNames.REGATTA_CAN_BOATS_OF_COMPETITORS_CHANGE_PER_RACE.name(), regatta.canBoatsOfCompetitorsChangePerRace()); dbRegatta.put(FieldNames.REGATTA_COMPETITOR_REGISTRATION_TYPE.name(), regatta.getCompetitorRegistrationType().name()); dbRegatta.put(FieldNames.REGATTA_REGISTRATION_LINK_SECRET.name(), regatta.getRegistrationLinkSecret()); diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/CreateAndTrackWithRaceLogTest.java b/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/CreateAndTrackWithRaceLogTest.java index 89822fc7c60..7cf78cf058c 100644 --- a/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/CreateAndTrackWithRaceLogTest.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/CreateAndTrackWithRaceLogTest.java @@ -103,7 +103,7 @@ public class CreateAndTrackWithRaceLogTest extends RaceLogTrackingTestHelper { /* registrationLinkSecret */ null, /* startDate */null, /* endDate */null, UUID.randomUUID(), Collections. singletonList(series), /* persistent */ true, new HighPoint(), UUID.randomUUID(), /* buoyZoneRadiusInHullLengths */2.0, /* useStartTimeInference */true, - /* controlTrackingFromStartAndFinishTimes */ false, OneDesignRankingMetric::new); + /* controlTrackingFromStartAndFinishTimes */ false, /* autoRestartTrackingUponCompetitorSetChange */ false, OneDesignRankingMetric::new); series.addRaceColumn(columnName, /* trackedRegattaRegistry */null); leaderboard = service.addRegattaLeaderboard(regatta.getRegattaIdentifier(), "RegattaLeaderboard", new int[] {}); adapter = RaceLogTrackingAdapterFactory.INSTANCE.getAdapter(DomainFactory.INSTANCE); diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogRaceTracker.java b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogRaceTracker.java index 596e42a0509..f0280bd3dc5 100644 --- a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogRaceTracker.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogRaceTracker.java @@ -20,6 +20,7 @@ import com.sap.sailing.domain.abstractlog.race.RaceLogCourseDesignChangedEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogEndOfTrackingEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogEventVisitor; import com.sap.sailing.domain.abstractlog.race.RaceLogRaceStatusEvent; +import com.sap.sailing.domain.abstractlog.race.RaceLogRevokeEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogStartOfTrackingEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogStartTimeEvent; import com.sap.sailing.domain.abstractlog.race.analyzing.impl.LastPublishedCourseDesignFinder; @@ -28,12 +29,18 @@ import com.sap.sailing.domain.abstractlog.race.impl.BaseRaceLogEventVisitor; import com.sap.sailing.domain.abstractlog.race.impl.RaceLogEndOfTrackingEventImpl; import com.sap.sailing.domain.abstractlog.race.impl.RaceLogStartOfTrackingEventImpl; import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogDenoteForTrackingEvent; +import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogRegisterCompetitorEvent; import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogStartTrackingEvent; import com.sap.sailing.domain.abstractlog.race.tracking.analyzing.impl.RaceInformationFinder; import com.sap.sailing.domain.abstractlog.race.tracking.analyzing.impl.RaceLogTrackingStateAnalyzer; +import com.sap.sailing.domain.abstractlog.race.tracking.analyzing.impl.RegisteredCompetitorsAndBoatsAnalyzer; +import com.sap.sailing.domain.abstractlog.race.tracking.impl.RaceLogRegisterCompetitorEventImpl; import com.sap.sailing.domain.abstractlog.regatta.RegattaLog; import com.sap.sailing.domain.abstractlog.regatta.RegattaLogEventVisitor; import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDefineMarkEvent; +import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogRegisterCompetitorEvent; +import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogRevokeEvent; +import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogRegisterCompetitorEventImpl; import com.sap.sailing.domain.abstractlog.regatta.impl.BaseRegattaLogEventVisitor; import com.sap.sailing.domain.base.Boat; import com.sap.sailing.domain.base.BoatClass; @@ -146,6 +153,26 @@ public class RaceLogRaceTracker extends AbstractRaceTrackerBaseImpl { trackedRace.updateStartAndEndOfTracking(/* waitForGPSFixesToLoad */ false); } } + + @Override + public void visit(RaceLogRevokeEvent event) { + if (event.getRevokedEventType().equals(RaceLogRegisterCompetitorEventImpl.class.getName())) { + try { + checkForChangeOfCompetitorSet(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + + @Override + public void visit(RaceLogRegisterCompetitorEvent event) { + try { + checkForChangeOfCompetitorSet(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } }; visitors.put(log, visitor); ((RaceLog) log).addListener(visitor); @@ -155,6 +182,26 @@ public class RaceLogRaceTracker extends AbstractRaceTrackerBaseImpl { public void visit(RegattaLogDefineMarkEvent event) { RaceLogRaceTracker.this.onDefineMarkEvent(event); } + + @Override + public void visit(RegattaLogRevokeEvent event) { + if (event.getRevokedEventType().equals(RegattaLogRegisterCompetitorEventImpl.class.getName())) { + try { + checkForChangeOfCompetitorSet(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + + @Override + public void visit(RegattaLogRegisterCompetitorEvent event) { + try { + checkForChangeOfCompetitorSet(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } }; visitors.put(log, visitor); ((RegattaLog) log).addListener(visitor); @@ -168,6 +215,13 @@ public class RaceLogRaceTracker extends AbstractRaceTrackerBaseImpl { } } + private void checkForChangeOfCompetitorSet() throws Exception { + final Map competitorsAndTheirBoats = new RegisteredCompetitorsAndBoatsAnalyzer(params.getRaceLog(), params.getRegattaLog()).analyze(); + if (!Util.setEquals(competitorsAndTheirBoats.keySet(), getRace().getCompetitors())) { + trackedRegattaRegistry.updateRaceCompetitors(getRegatta(), getRace()); + } + } + @Override protected void onStop(boolean preemptive, boolean willBeRemoved) { RaceLog raceLog = params.getRaceLog(); @@ -273,18 +327,18 @@ public class RaceLogRaceTracker extends AbstractRaceTrackerBaseImpl { } private void startTracking(RaceLogStartTrackingEvent event) { - RaceLog raceLog = params.getRaceLog(); - RaceColumn raceColumn = params.getRaceColumn(); - Fleet fleet = params.getFleet(); - RaceLogDenoteForTrackingEvent denoteEvent = new RaceInformationFinder(raceLog).analyze(); + final RaceLog raceLog = params.getRaceLog(); + final RaceColumn raceColumn = params.getRaceColumn(); + final Fleet fleet = params.getFleet(); + final RaceLogDenoteForTrackingEvent denoteEvent = new RaceInformationFinder(raceLog).analyze(); final Pair trackingTimes = new TrackingTimesFinder(raceLog).analyze(); if (!trackedRegatta.getRegatta().isControlTrackingFromStartAndFinishTimes() && (trackingTimes == null || trackingTimes.getA() == null || trackingTimes.getA().getTimePoint() == null)) { // the start of tracking interval is unset or set to null; enter "now" as start of tracking into the race log raceLog.add(new RaceLogStartOfTrackingEventImpl(MillisecondsTimePoint.now(), raceLogEventAuthor, raceLog.getCurrentPassId())); } - BoatClass boatClass = denoteEvent.getBoatClass(); - String raceName = denoteEvent.getRaceName(); + final BoatClass boatClass = denoteEvent.getBoatClass(); + final String raceName = denoteEvent.getRaceName(); CourseBase courseBase = new LastPublishedCourseDesignFinder(raceLog, /* onlyCoursesWithValidWaypointList */ true).analyze(); if (courseBase == null) { courseBase = new CourseDataImpl("Default course for " + raceName); @@ -303,8 +357,8 @@ public class RaceLogRaceTracker extends AbstractRaceTrackerBaseImpl { } throw new RaceNotCreatedException(String.format("Race for racelog (%s) has already been created", raceLog)); } - Map competitorsAndTheirBoats = raceColumn.getAllCompetitorsAndTheirBoats(params.getFleet()); - Serializable raceId = denoteEvent.getRaceId(); + final Map competitorsAndTheirBoats = raceColumn.getAllCompetitorsAndTheirBoats(params.getFleet()); + final Serializable raceId = denoteEvent.getRaceId(); try { final RaceDefinition raceDef = raceTrackingHandler.createRaceDefinition(trackedRegatta.getRegatta(), raceName, course, boatClass, competitorsAndTheirBoats, raceId); diff --git a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/analyzing/test/FinishedTimeFinderTest.java b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/analyzing/test/FinishedTimeFinderTest.java index 09f56c2cb77..b9f3abc2125 100644 --- a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/analyzing/test/FinishedTimeFinderTest.java +++ b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/analyzing/test/FinishedTimeFinderTest.java @@ -2,8 +2,10 @@ package com.sap.sailing.domain.racelog.analyzing.test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; import static org.mockito.Mockito.when; +import java.io.Serializable; import java.util.Arrays; import org.junit.Test; @@ -12,6 +14,7 @@ import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor; import com.sap.sailing.domain.abstractlog.race.RaceLog; import com.sap.sailing.domain.abstractlog.race.RaceLogEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogRaceStatusEvent; +import com.sap.sailing.domain.abstractlog.race.RaceLogRevokeEvent; import com.sap.sailing.domain.abstractlog.race.analyzing.impl.FinishedTimeFinder; import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; import com.sap.sse.common.TimePoint; @@ -43,10 +46,8 @@ public class FinishedTimeFinderTest extends PassAwareRaceLogAnalyzerTest, WithID { */ boolean isEmpty(); + /** + * Callers that want to iterate over the collection returned need to use {@link #lockForRead()} and + * {@link #unlockAfterRead()} to avoid {@link ConcurrentModificationException}s. + */ Iterable getRawFixesDescending(); + /** + * Callers that want to iterate over the collection returned need to use {@link #lockForRead()} and + * {@link #unlockAfterRead()} to avoid {@link ConcurrentModificationException}s. + */ Iterable getFixesDescending(); void addAllListeners(Iterable listeners); @@ -103,14 +112,17 @@ extends Track, WithID { EventT getEventById(Serializable id); /** - * Get a {@link NavigableSet} of unrevoked events regardless of the {@code pass}. Events are sorted by - * their {@link TimePoint} and the oldest is returned first. - * @return + * Get a {@link NavigableSet} of unrevoked events regardless of the {@code pass}. Events are sorted by their + * {@link TimePoint} and the oldest is returned first. Callers that want to iterate over the collection returned + * need to use {@link #lockForRead()} and {@link #unlockAfterRead()} to avoid + * {@link ConcurrentModificationException}s. */ NavigableSet getUnrevokedEvents(); /** - * Get a {@link NavigableSet} of unrevoked events regardless of the {@code pass}. + * Get a {@link NavigableSet} of unrevoked events regardless of the {@code pass}. Callers that want to iterate over + * the collection returned need to use {@link #lockForRead()} and {@link #unlockAfterRead()} to avoid + * {@link ConcurrentModificationException}s. */ NavigableSet getUnrevokedEventsDescending(); diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/BaseLogAnalyzer.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/BaseLogAnalyzer.java index d6e33462b4a..d2b90eb52e2 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/BaseLogAnalyzer.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/BaseLogAnalyzer.java @@ -30,6 +30,9 @@ public abstract class BaseLogAnalyzer } } + /** + * Always called under the {@link #getLog() log's} read lock + */ protected abstract ResultT performAnalysis(); protected Iterable getAllEvents() { diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/impl/AbstractLogImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/impl/AbstractLogImpl.java index 5c16f3c9d1a..d9fc807d5c0 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/impl/AbstractLogImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/impl/AbstractLogImpl.java @@ -33,13 +33,13 @@ import com.sap.sse.util.impl.ArrayListNavigableSet; /** * {@link Track} implementation for {@link AbstractLogEvent}s. - * + * *

* {@link TrackImpl#getDummyFix(com.sap.sailing.domain.common.TimePoint)} is not overridden, see * {@link RaceLogEventComparator} for sorting when interface methods like * {@link Track#getFirstFixAfter(com.sap.sailing.domain.common.TimePoint)} are used. *

- * + * */ public abstract class AbstractLogImpl, VisitorT> extends TrackImpl implements AbstractLog { @@ -52,7 +52,7 @@ extends TrackImpl implements AbstractLog { * Clients can use the {@link #add(RaceLogEvent, UUID)} method */ private transient Map> eventsDeliveredToClient = new HashMap>(); - + private Map eventsById = new HashMap(); private final Serializable id; @@ -67,7 +67,7 @@ extends TrackImpl implements AbstractLog { /** * Initializes a new {@link RaceLogImpl}. - * + * * @param nameForReadWriteLock * name of lock. */ @@ -82,7 +82,7 @@ extends TrackImpl implements AbstractLog { public Serializable getId() { return this.id; } - + protected void onSuccessfulAdd(EventT event, boolean notifyListeners) { revokeIfNecessary(event); eventsById.put(event.getId(), event); @@ -90,7 +90,7 @@ extends TrackImpl implements AbstractLog { notifyListenersAboutReceive(event); } } - + protected boolean add(EventT event, boolean notifyListeners) { boolean isAdded = false; lockForWrite(); @@ -107,7 +107,7 @@ extends TrackImpl implements AbstractLog { } return isAdded; } - + @Override public boolean add(EventT event) { return add(event, true); @@ -117,7 +117,7 @@ extends TrackImpl implements AbstractLog { public boolean load(EventT event) { return add(event, false); } - + private void revokeIfNecessary(EventT newEvent) { if (newEvent instanceof RevokeEvent) { RevokeEvent revokeEvent = (RevokeEvent) newEvent; @@ -134,7 +134,7 @@ extends TrackImpl implements AbstractLog { } } } - + private void checkIfSuccessfullyRevokes(RevokeEvent revokeEvent) throws NotRevokableException { lockForRead(); EventT revokedEvent; @@ -166,7 +166,7 @@ extends TrackImpl implements AbstractLog { add(event); return getEventsToDeliver(clientId, event); } - + @Override public Iterable getEventsToDeliver(UUID clientId) { return getEventsToDeliver(clientId, null); @@ -311,25 +311,15 @@ extends TrackImpl implements AbstractLog { assertReadLock(); return eventsById.get(id); } - + @Override public NavigableSet getUnrevokedEvents() { - return new PartialNavigableSetView(super.getInternalFixes()) { - @Override - protected boolean isValid(EventT e) { - return ! (e instanceof RevokeEvent) && ! revokedEventIds.contains(e.getId()); - } - }; + return new FilteredPartialNavigableSetView<>(super.getInternalFixes(), new RevokedValidator<>(revokedEventIds)); } - + @Override public NavigableSet getUnrevokedEventsDescending() { - return new PartialNavigableSetView(super.getInternalFixes().descendingSet()) { - @Override - protected boolean isValid(EventT e) { - return ! (e instanceof RevokeEvent) && ! revokedEventIds.contains(e.getId()); - } - }; + return new FilteredPartialNavigableSetView(super.getInternalFixes().descendingSet(), new RevokedValidator<>(revokedEventIds)); } @Override @@ -375,14 +365,14 @@ extends TrackImpl implements AbstractLog { unlockAfterWrite(); } } - + @Override public void revokeEvent(AbstractLogEventAuthor author, EventT toRevoke) throws NotRevokableException { revokeEvent(author, toRevoke, null); } - + protected abstract EventT createRevokeEvent(AbstractLogEventAuthor author, EventT toRevoke, String reason); - + @Override public void revokeEvent(AbstractLogEventAuthor author, EventT toRevoke, String reason) throws NotRevokableException { if (toRevoke == null) { @@ -392,4 +382,43 @@ extends TrackImpl implements AbstractLog { checkIfSuccessfullyRevokes((RevokeEvent) revokeEvent); add(revokeEvent); } + + @FunctionalInterface + public interface NavigableSetViewValidator { + boolean isValid(T item); + } + + /** + * Considers an event valid if it is not a {@link RevokeEvent} and is not contained in the set of events passed to + * the constructor that have been revoked. + */ + public static class RevokedValidator, VisitorT> implements NavigableSetViewValidator { + final Set revokedEventIds; + + public RevokedValidator(Set revokedEventIds) { + this.revokedEventIds = revokedEventIds; + } + + @Override + public boolean isValid(EventT item) { + return !(item instanceof RevokeEvent) && !revokedEventIds.contains(item.getId()); + } + } + + public static class FilteredPartialNavigableSetView extends PartialNavigableSetView { + private final NavigableSetViewValidator validator; + + public FilteredPartialNavigableSetView(NavigableSet set, final NavigableSetViewValidator validator) { + super(set); + if (validator == null) { + throw new NullPointerException(); + } + this.validator = validator; + } + + @Override + protected boolean isValid(T t) { + return validator.isValid(t); + } + } } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/RaceLog.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/RaceLog.java index 84199147b1f..bd136ad5575 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/RaceLog.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/RaceLog.java @@ -1,18 +1,33 @@ package com.sap.sailing.domain.abstractlog.race; import com.sap.sailing.domain.abstractlog.AbstractLog; +import com.sap.sailing.domain.abstractlog.impl.AbstractLogImpl; import com.sap.sse.common.WithID; /** -* "Fix" validity is decided based on the {@link #getCurrentPassId() current pass}. The validity is not cached. -*/ + * "Fix" validity is decided based on the {@link #getCurrentPassId() current pass}. The validity is not cached. + */ public interface RaceLog extends AbstractLog, WithID { public static final int DefaultPassId = 0; - + + class PassValidator implements AbstractLogImpl.NavigableSetViewValidator { + final int pass; + + public PassValidator(int pass) { + this.pass = pass; + } + + @Override + public boolean isValid(RaceLogEvent item) { + return item.getPassId() == pass; + } + } + /** * Gets the current pass id. - * + * * @return the pass id. */ int getCurrentPassId(); + } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/RaceLogRaceStatusEvent.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/RaceLogRaceStatusEvent.java index 4e455a2ca7f..d7cd0a7f16c 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/RaceLogRaceStatusEvent.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/RaceLogRaceStatusEvent.java @@ -1,12 +1,13 @@ package com.sap.sailing.domain.abstractlog.race; +import com.sap.sailing.domain.abstractlog.Revokable; import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; /** * Events to indicate the new race status which is valid for all events * arriving after this event (on the same pass). */ -public interface RaceLogRaceStatusEvent extends RaceLogEvent { +public interface RaceLogRaceStatusEvent extends RaceLogEvent, Revokable { RaceLogRaceStatus getNextStatus(); diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/analyzing/impl/FinishedTimeFinder.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/analyzing/impl/FinishedTimeFinder.java index e4a07981d29..07034e1211a 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/analyzing/impl/FinishedTimeFinder.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/analyzing/impl/FinishedTimeFinder.java @@ -7,16 +7,32 @@ import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; import com.sap.sse.common.TimePoint; public class FinishedTimeFinder extends RaceLogAnalyzer { - public FinishedTimeFinder(RaceLog raceLog) { super(raceLog); } + public RaceLogRaceStatusEvent findFinishedEvent() { + log.lockForRead(); + try { + for (RaceLogEvent event : getPassUnrevokedEventsDescending()) { + if (event instanceof RaceLogRaceStatusEvent) { + final RaceLogRaceStatusEvent statusEvent = (RaceLogRaceStatusEvent) event; + if (statusEvent.getNextStatus().equals(RaceLogRaceStatus.FINISHED)) { + return statusEvent; + } + } + } + return null; + } finally { + log.unlockAfterRead(); + } + } + @Override protected TimePoint performAnalysis() { - for (RaceLogEvent event : getPassEventsDescending()) { + for (RaceLogEvent event : getPassUnrevokedEventsDescending()) { if (event instanceof RaceLogRaceStatusEvent) { - RaceLogRaceStatusEvent statusEvent = (RaceLogRaceStatusEvent) event; + final RaceLogRaceStatusEvent statusEvent = (RaceLogRaceStatusEvent) event; if (statusEvent.getNextStatus().equals(RaceLogRaceStatus.FINISHED)) { return statusEvent.getLogicalTimePoint(); } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/analyzing/impl/FinishingTimeFinder.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/analyzing/impl/FinishingTimeFinder.java index 143f5f6f2a8..79f32482732 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/analyzing/impl/FinishingTimeFinder.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/analyzing/impl/FinishingTimeFinder.java @@ -12,18 +12,33 @@ public class FinishingTimeFinder extends RaceLogAnalyzer { super(raceLog); } + public RaceLogRaceStatusEvent findFinishingEvent() { + log.lockForRead(); + try { + for (RaceLogEvent event : getPassUnrevokedEventsDescending()) { + if (event instanceof RaceLogRaceStatusEvent) { + final RaceLogRaceStatusEvent statusEvent = (RaceLogRaceStatusEvent) event; + if (statusEvent.getNextStatus().equals(RaceLogRaceStatus.FINISHING)) { + return statusEvent; + } + } + } + return null; + } finally { + log.unlockAfterRead(); + } + } + @Override protected TimePoint performAnalysis() { - for (RaceLogEvent event : getPassEventsDescending()) { + for (RaceLogEvent event : getPassUnrevokedEventsDescending()) { if (event instanceof RaceLogRaceStatusEvent) { - RaceLogRaceStatusEvent statusEvent = (RaceLogRaceStatusEvent) event; + final RaceLogRaceStatusEvent statusEvent = (RaceLogRaceStatusEvent) event; if (statusEvent.getNextStatus().equals(RaceLogRaceStatus.FINISHING)) { return statusEvent.getLogicalTimePoint(); } } } - return null; } - } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/analyzing/impl/RaceLogAnalyzer.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/analyzing/impl/RaceLogAnalyzer.java index aeaf179fb2d..3a737723c63 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/analyzing/impl/RaceLogAnalyzer.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/analyzing/impl/RaceLogAnalyzer.java @@ -1,22 +1,54 @@ package com.sap.sailing.domain.abstractlog.race.analyzing.impl; +import java.util.ConcurrentModificationException; + import com.sap.sailing.domain.abstractlog.BaseLogAnalyzer; +import com.sap.sailing.domain.abstractlog.impl.AbstractLogImpl; import com.sap.sailing.domain.abstractlog.race.RaceLog; import com.sap.sailing.domain.abstractlog.race.RaceLogEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogEventVisitor; +import com.sap.sailing.domain.tracking.Track; public abstract class RaceLogAnalyzer extends BaseLogAnalyzer - { + { public RaceLogAnalyzer(RaceLog raceLog) { super(raceLog); } + /** + * Callers that want to iterate over the collection returned need to use {@link Track#lockForRead()} and + * {@link Track#unlockAfterRead()} on the underlying {@link #getLog() log} to avoid + * {@link ConcurrentModificationException}s. + */ protected Iterable getPassEvents() { return log.getFixes(); } - + + /** + * Callers that want to iterate over the collection returned need to use {@link Track#lockForRead()} and + * {@link Track#unlockAfterRead()} on the underlying {@link #getLog() log} to avoid + * {@link ConcurrentModificationException}s. + */ protected Iterable getPassEventsDescending() { return log.getFixesDescending(); } + + /** + * Callers that want to iterate over the collection returned need to use {@link Track#lockForRead()} and + * {@link Track#unlockAfterRead()} on the underlying {@link #getLog() log} to avoid + * {@link ConcurrentModificationException}s. + */ + protected Iterable getPassUnrevokedEvents() { + return new AbstractLogImpl.FilteredPartialNavigableSetView<>(log.getUnrevokedEvents(), new RaceLog.PassValidator(log.getCurrentPassId())); + } + + /** + * Callers that want to iterate over the collection returned need to use {@link Track#lockForRead()} and + * {@link Track#unlockAfterRead()} on the underlying {@link #getLog() log} to avoid + * {@link ConcurrentModificationException}s. + */ + protected Iterable getPassUnrevokedEventsDescending() { + return new AbstractLogImpl.FilteredPartialNavigableSetView<>(log.getUnrevokedEventsDescending(), new RaceLog.PassValidator(log.getCurrentPassId())); + } } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/analyzing/impl/RaceStatusAnalyzer.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/analyzing/impl/RaceStatusAnalyzer.java index ae4b65c7cf1..f025c5f35f6 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/analyzing/impl/RaceStatusAnalyzer.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/analyzing/impl/RaceStatusAnalyzer.java @@ -59,7 +59,7 @@ public class RaceStatusAnalyzer extends RaceLogAnalyzer performAnalysis() { ArrayListNavigableSet statusEvents = new ArrayListNavigableSet<>( RaceLogRaceStatusEventComparator.INSTANCE); - for (RaceLogEvent event : getPassEvents()) { + for (RaceLogEvent event : getPassUnrevokedEvents()) { if (event instanceof RaceLogRaceStatusEvent) { statusEvents.add((RaceLogRaceStatusEvent) event); } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/impl/RaceLogImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/impl/RaceLogImpl.java index 290bbea15e9..140f89cd242 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/impl/RaceLogImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/impl/RaceLogImpl.java @@ -9,22 +9,21 @@ import com.sap.sailing.domain.abstractlog.race.RaceLog; import com.sap.sailing.domain.abstractlog.race.RaceLogEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogEventVisitor; import com.sap.sailing.domain.tracking.Track; -import com.sap.sailing.domain.tracking.impl.PartialNavigableSetView; import com.sap.sailing.domain.tracking.impl.TrackImpl; /** * {@link Track} implementation for {@link RaceLogEvent}s. - * + * *

* "Fix" validity is decided based on the {@link #getCurrentPassId() current pass}. The validity is not cached. *

- * + * *

* {@link TrackImpl#getDummyFix(com.sap.sailing.domain.common.TimePoint)} is not overridden, see * {@link RaceLogEventComparator} for sorting when interface methods like * {@link Track#getFirstFixAfter(com.sap.sailing.domain.common.TimePoint)} are used. *

- * + * */ public class RaceLogImpl extends AbstractLogImpl implements RaceLog { private static final long serialVersionUID = 98032278604708475L; @@ -39,19 +38,19 @@ public class RaceLogImpl extends AbstractLogImpl getInternalFixes() { - return new PartialNavigableSetView(super.getInternalFixes()) { - @Override - protected boolean isValid(RaceLogEvent e) { - return e.getPassId() == getCurrentPassId(); - } - }; + return new FilteredPartialNavigableSetView<>(super.getInternalFixes(), new PassValidator(getCurrentPassId())); } } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/regatta/tracking/analyzing/impl/RegattaLogDeviceMappingFinder.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/regatta/tracking/analyzing/impl/RegattaLogDeviceMappingFinder.java index 4a7b9da2d40..22364e72637 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/regatta/tracking/analyzing/impl/RegattaLogDeviceMappingFinder.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/regatta/tracking/analyzing/impl/RegattaLogDeviceMappingFinder.java @@ -77,16 +77,22 @@ public class RegattaLogDeviceMappingFinder protected void findUnrevokedMappingAndClosingEvents(Map>> events, Map closingEvents) { - for (RegattaLogEvent e : getLog().getUnrevokedEvents()) { - if (e instanceof RegattaLogDeviceMappingEvent && isValidMapping(((RegattaLogDeviceMappingEvent) e))) { - @SuppressWarnings("unchecked") - RegattaLogDeviceMappingEvent mappingEvent = (RegattaLogDeviceMappingEvent) e; - getMappingEventsForItem(events, mappingEvent.getMappedTo()).add(mappingEvent); - } else if (e instanceof RegattaLogCloseOpenEndedDeviceMappingEvent) { - //a higher priority closing events for the same mapping event overwrites the lower priority one - RegattaLogCloseOpenEndedDeviceMappingEvent closingEvent = (RegattaLogCloseOpenEndedDeviceMappingEvent) e; - closingEvents.put(closingEvent.getDeviceMappingEventId(), closingEvent); + final RegattaLog regattaLog = getLog(); + regattaLog.lockForRead(); + try { + for (RegattaLogEvent e : regattaLog.getUnrevokedEvents()) { + if (e instanceof RegattaLogDeviceMappingEvent && isValidMapping(((RegattaLogDeviceMappingEvent) e))) { + @SuppressWarnings("unchecked") + RegattaLogDeviceMappingEvent mappingEvent = (RegattaLogDeviceMappingEvent) e; + getMappingEventsForItem(events, mappingEvent.getMappedTo()).add(mappingEvent); + } else if (e instanceof RegattaLogCloseOpenEndedDeviceMappingEvent) { + //a higher priority closing events for the same mapping event overwrites the lower priority one + RegattaLogCloseOpenEndedDeviceMappingEvent closingEvent = (RegattaLogCloseOpenEndedDeviceMappingEvent) e; + closingEvents.put(closingEvent.getDeviceMappingEventId(), closingEvent); + } } + } finally { + regattaLog.unlockAfterRead(); } } diff --git a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/DomainFactory.java b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/DomainFactory.java index 85dbc373119..a49609626b5 100755 --- a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/DomainFactory.java +++ b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/DomainFactory.java @@ -92,4 +92,6 @@ public interface DomainFactory { */ void addUpdateHandlers(String updateURL, String username, String password, Serializable eventId, RaceDefinition raceDefinition, DynamicTrackedRace trackedRace) throws URISyntaxException; + + Map createCompetitorsAndBoats(StartList startList, String raceId, BoatClass boatClass, RaceTrackingHandler raceTrackHandler); } diff --git a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/DomainFactoryImpl.java b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/DomainFactoryImpl.java index 20229dd6b72..8f84c702e45 100755 --- a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/DomainFactoryImpl.java +++ b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/DomainFactoryImpl.java @@ -291,7 +291,8 @@ public class DomainFactoryImpl implements DomainFactory { return result; } - private Map createCompetitorsAndBoats(StartList startList, String raceId, BoatClass boatClass, + @Override + public Map createCompetitorsAndBoats(StartList startList, String raceId, BoatClass boatClass, RaceTrackingHandler raceTrackHandler) { Map result = new LinkedHashMap<>(); for (com.sap.sailing.domain.swisstimingadapter.Competitor swissTimingCompetitor : startList.getCompetitors()) { diff --git a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingRaceTrackerImpl.java b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingRaceTrackerImpl.java index 94289ae02a9..6d61ce4a071 100644 --- a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingRaceTrackerImpl.java +++ b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingRaceTrackerImpl.java @@ -18,6 +18,7 @@ import java.util.UUID; import java.util.logging.Level; import java.util.logging.Logger; +import com.sap.sailing.domain.base.Boat; import com.sap.sailing.domain.base.BoatClass; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.CompetitorAndBoatStore; @@ -65,6 +66,7 @@ import com.sap.sailing.domain.tracking.TrackedRegattaRegistry; import com.sap.sailing.domain.tracking.TrackingDataLoader; import com.sap.sailing.domain.tracking.WindStore; import com.sap.sailing.domain.tracking.WindTrack; +import com.sap.sailing.domain.tracking.RaceTrackingHandler.DefaultRaceTrackingHandler; import com.sap.sailing.domain.tracking.impl.TrackedRaceStatusImpl; import com.sap.sailing.domain.tracking.impl.TrackingConnectorInfoImpl; import com.sap.sailing.domain.tracking.impl.UpdateHandler; @@ -443,6 +445,15 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl this.startList = startList; if (oldStartList == null && course != null) { createRaceDefinition(course); + } else if (trackedRace != null) { + final Map competitorsAndTheirBoats = domainFactory.createCompetitorsAndBoats(startList, raceID, boatClass, new DefaultRaceTrackingHandler()); + if (!Util.setEquals(competitorsAndTheirBoats.keySet(), getRace().getCompetitors())) { + try { + trackedRegattaRegistry.updateRaceCompetitors(getRegatta(), getRace()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } } } } @@ -470,7 +481,7 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl assert startList != null; assert course != null; // now we can create the RaceDefinition and most other things - Race swissTimingRace = new RaceImpl(raceID, raceName, raceDescription, boatClass); + final Race swissTimingRace = new RaceImpl(raceID, raceName, raceDescription, boatClass); try { synchronized (this) { race = domainFactory.createRaceDefinition(regatta, swissTimingRace, startList, course, diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/ranking/TestFarthestAheadWithSkippedMarkPassing.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/ranking/TestFarthestAheadWithSkippedMarkPassing.java index 781b78d1b11..8c69d95c3cd 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/ranking/TestFarthestAheadWithSkippedMarkPassing.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/ranking/TestFarthestAheadWithSkippedMarkPassing.java @@ -102,8 +102,8 @@ public class TestFarthestAheadWithSkippedMarkPassing { /* canBoatsOfCompetitorsChangePerRace */ true, CompetitorRegistrationType.CLOSED, /*startDate*/ null, /*endDate*/ null, /* trackedRegattaRegistry */ null, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), "123", /* courseArea */ null, - /* controlTrackingFromStartAndFinishTimes */ false, OneDesignRankingMetric::new, - /* registrationLinkSecret */ UUID.randomUUID().toString()); + /* controlTrackingFromStartAndFinishTimes */ false, /* autoRestartTrackingUponCompetitorSetChange */ false, + OneDesignRankingMetric::new, /* registrationLinkSecret */ UUID.randomUUID().toString()); TrackedRegatta trackedRegatta = new DynamicTrackedRegattaImpl(regatta); List waypoints = new ArrayList(); // create a two-lap upwind/downwind course: diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/ranking/TestSimpleTimeOnTimeRankingWithOneUpwindLeg.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/ranking/TestSimpleTimeOnTimeRankingWithOneUpwindLeg.java index 96fb14346e8..989e2e72836 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/ranking/TestSimpleTimeOnTimeRankingWithOneUpwindLeg.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/ranking/TestSimpleTimeOnTimeRankingWithOneUpwindLeg.java @@ -83,8 +83,8 @@ public class TestSimpleTimeOnTimeRankingWithOneUpwindLeg { RegattaImpl.getDefaultName("Test Regatta", boatClass.getName()), boatClass, /* canBoatsOfCompetitorsChangePerRace */ true, CompetitorRegistrationType.CLOSED, /*startDate*/ null, /*endDate*/ null, /* trackedRegattaRegistry */ null, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), "123", /* courseArea */ null, - /* controlTrackingFromStartAndFinishTimes */ false, TimeOnTimeAndDistanceRankingMetric::new, - /* registrationLinkSecret */ UUID.randomUUID().toString()); + /* controlTrackingFromStartAndFinishTimes */ false, /* autoRestartTrackingUponCompetitorSetChange */ false, + TimeOnTimeAndDistanceRankingMetric::new, /* registrationLinkSecret */ UUID.randomUUID().toString()); TrackedRegatta trackedRegatta = new DynamicTrackedRegattaImpl(regatta); List waypoints = new ArrayList(); // create a two-lap upwind/downwind course: diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/DummyTrackedRegattaRegistry.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/DummyTrackedRegattaRegistry.java index cdc1c355622..bfdfa2e6fc0 100644 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/DummyTrackedRegattaRegistry.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/DummyTrackedRegattaRegistry.java @@ -9,6 +9,7 @@ import java.util.Map; import com.sap.sailing.domain.base.RaceDefinition; import com.sap.sailing.domain.base.Regatta; import com.sap.sailing.domain.tracking.DynamicTrackedRegatta; +import com.sap.sailing.domain.tracking.RaceHandle; import com.sap.sailing.domain.tracking.RaceTracker; import com.sap.sailing.domain.tracking.TrackedRegattaRegistry; import com.sap.sailing.domain.tracking.impl.DynamicTrackedRegattaImpl; @@ -72,4 +73,9 @@ public class DummyTrackedRegattaRegistry implements TrackedRegattaRegistry { public Regatta getOrCreateDefaultRegatta(String name, String boatClassName, Serializable id) { return null; } + + @Override + public RaceHandle updateRaceCompetitors(Regatta regatta, RaceDefinition race) throws Exception { + return null; + } } diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardOfflineTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardOfflineTest.java index e43f4d80eb9..329e61de846 100644 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardOfflineTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardOfflineTest.java @@ -40,6 +40,7 @@ import com.sap.sailing.domain.leaderboard.impl.ThresholdBasedResultDiscardingRul import com.sap.sailing.domain.test.mock.MockedTrackedRaceWithFixedRank; import com.sap.sailing.domain.test.mock.MockedTrackedRaceWithFixedRankAndManyCompetitors; import com.sap.sailing.domain.tracking.DynamicTrackedRegatta; +import com.sap.sailing.domain.tracking.RaceHandle; import com.sap.sailing.domain.tracking.RaceTracker; import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sailing.domain.tracking.TrackedRegattaRegistry; @@ -159,6 +160,10 @@ public class LeaderboardOfflineTest extends AbstractLeaderboardTest { public Regatta getOrCreateDefaultRegatta(String name, String boatClassName, Serializable id) { return null; } + @Override + public RaceHandle updateRaceCompetitors(Regatta regatta, RaceDefinition race) throws Exception { + return null; + } }; LeaderboardDTO leaderboardDTO = leaderboard.getLeaderboardDTO(now, emptySet, /* addOverallDetails */ true, trackedRegattaRegistry, DomainFactory.INSTANCE, /* fillTotalPointsUncorrected */ false); assertNotNull(leaderboardDTO); diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TestDeadlockInRegattaListener.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TestDeadlockInRegattaListener.java index f5bf2e0c4c7..902b6a9c411 100644 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TestDeadlockInRegattaListener.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TestDeadlockInRegattaListener.java @@ -55,7 +55,7 @@ public class TestDeadlockInRegattaListener { /* startDate */ null, /* endDate */ null, /* trackedRegattaRegistry */ null, new LowPoint(), UUID.randomUUID(), /* courseArea */ null, /* controlTrackingFromStartAndFinishTimes */ true, - OneDesignRankingMetric::new, /* registrationLinkSecret */ UUID.randomUUID().toString()); + /* autoRestartTrackingUponCompetitorSetChange */ false, OneDesignRankingMetric::new, /* registrationLinkSecret */ UUID.randomUUID().toString()); DynamicTrackedRegatta trackedRegatta = new DynamicTrackedRegattaImpl(regatta) { private static final long serialVersionUID = -3599667964201700780L; diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRace.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRace.java index ff855818226..b0757e95608 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRace.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRace.java @@ -618,6 +618,16 @@ public class MockedTrackedRace implements DynamicTrackedRace { @Override public void setCompetitorRegistrationType(CompetitorRegistrationType competitorRegistrationType) { } + + @Override + public boolean isAutoRestartTrackingUponCompetitorSetChange() { + return false; + } + + @Override + public void setAutoRestartTrackingUponCompetitorSetChange( + boolean autoRestartTrackingUponCompetitorSetChange) { + } }; } diff --git a/java/com.sap.sailing.domain.tractracadapter.persistence/src/com/sap/sailing/domain/tractracadapter/persistence/impl/TracTracConnectivityParamsHandler.java b/java/com.sap.sailing.domain.tractracadapter.persistence/src/com/sap/sailing/domain/tractracadapter/persistence/impl/TracTracConnectivityParamsHandler.java index 1e28e51757e..7306248e547 100644 --- a/java/com.sap.sailing.domain.tractracadapter.persistence/src/com/sap/sailing/domain/tractracadapter/persistence/impl/TracTracConnectivityParamsHandler.java +++ b/java/com.sap.sailing.domain.tractracadapter.persistence/src/com/sap/sailing/domain/tractracadapter/persistence/impl/TracTracConnectivityParamsHandler.java @@ -1,16 +1,13 @@ package com.sap.sailing.domain.tractracadapter.persistence.impl; import java.io.IOException; -import java.io.InputStreamReader; import java.net.MalformedURLException; import java.net.URI; +import java.net.URISyntaxException; import java.net.URL; -import java.net.URLConnection; import java.util.HashMap; import java.util.Map; -import org.json.simple.JSONObject; -import org.json.simple.parser.JSONParser; import org.json.simple.parser.ParseException; import com.sap.sailing.domain.racelog.RaceLogStore; @@ -28,7 +25,8 @@ import com.sap.sse.common.impl.MillisecondsDurationImpl; import com.sap.sse.common.impl.MillisecondsTimePoint; import com.sap.sse.security.SecurityService; import com.sap.sse.security.SessionUtils; -import com.sap.sse.util.HttpUrlConnectionHelper; +import com.tractrac.model.lib.api.event.CreateModelException; +import com.tractrac.util.lib.api.exceptions.TimeOutException; /** * Handles mapping TracTrac connectivity parameters from and to a map with {@link String} keys. The @@ -144,14 +142,8 @@ public class TracTracConnectivityParamsHandler extends AbstractRaceTrackingConne } private void updatePersistentTracTracConfiguration(RaceTrackingConnectivityParametersImpl params) - throws MalformedURLException, IOException, ParseException { - final String EVENT_MANAGER_HOSTNAME_PREFIX = "em."; - final URL paramsJsonUrl = new URL(params.getParamURL().getProtocol(), - params.getParamURL().getHost().startsWith(EVENT_MANAGER_HOSTNAME_PREFIX) ? params.getParamURL().getHost() : EVENT_MANAGER_HOSTNAME_PREFIX+params.getParamURL().getHost(), - "/events/"+params.getTractracRace().getEvent().getId().toString()+"/races/"+params.getTractracRace().getId().toString()+".json"); - final URLConnection conn = HttpUrlConnectionHelper.redirectConnection(paramsJsonUrl); - final JSONObject paramsJson = (JSONObject) new JSONParser().parse(new InputStreamReader(conn.getInputStream())); - final String jsonURL = (String) paramsJson.get("eventJSON"); + throws MalformedURLException, IOException, ParseException, CreateModelException, URISyntaxException, TimeOutException { + final String jsonURL = params.getTractracRace().getParameterSet().getParameter("eventJSON"); final String creatorName = SessionUtils.getPrincipal().toString(); final TracTracConfigurationImpl tracTracConfiguration = new TracTracConfigurationImpl(creatorName, params.getTractracRace().getEvent().getName(), jsonURL, /* live URI */ null, /* stored URI */ null, // we mainly want to enable the user to list the event's races again in case they are removed; live/stored stuff comes from the tracking params diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/DomainFactory.java b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/DomainFactory.java index 1ad3b41a51c..67db80c8ba2 100755 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/DomainFactory.java +++ b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/DomainFactory.java @@ -65,6 +65,7 @@ import com.tractrac.model.lib.api.route.IControlPoint; import com.tractrac.subscription.lib.api.IEventSubscriber; import com.tractrac.subscription.lib.api.IRaceSubscriber; import com.tractrac.subscription.lib.api.SubscriberInitializationException; +import com.tractrac.util.lib.api.exceptions.TimeOutException; import difflib.PatchFailedException; @@ -152,8 +153,8 @@ public interface DomainFactory { WindStore windStore, TrackedRegattaRegistry trackedRegattaRegistry, RaceLogAndTrackedRaceResolver raceLogResolver, LeaderboardGroupResolver leaderboardGroupResolver, RaceTrackingConnectivityParametersImpl connectivityParams, long timeoutInMilliseconds, - RaceTrackingHandler raceTrackingHandler) - throws URISyntaxException, SubscriberInitializationException, IOException, InterruptedException; + RaceTrackingHandler raceTrackingHandler) throws URISyntaxException, SubscriberInitializationException, + IOException, InterruptedException, CreateModelException, TimeOutException; /** * Same as {@link #createRaceTracker(URL, URI, URI, URI, TimePoint, TimePoint, WindStore, TrackedRegattaRegistry)}, @@ -165,7 +166,7 @@ public interface DomainFactory { RaceTrackingConnectivityParametersImpl connectivityParams, long timeoutInMilliseconds, RaceTrackingHandler raceTrackingHandler) throws MalformedURLException, FileNotFoundException, URISyntaxException, CreateModelException, - SubscriberInitializationException, IOException, InterruptedException; + SubscriberInitializationException, IOException, InterruptedException, TimeOutException; BoatClass getOrCreateBoatClass(String competitorClassName); diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/DomainFactoryImpl.java b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/DomainFactoryImpl.java index 597b7fc3a80..63c90d7fbcc 100755 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/DomainFactoryImpl.java +++ b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/DomainFactoryImpl.java @@ -116,6 +116,7 @@ import com.tractrac.model.lib.api.route.IControlPoint; import com.tractrac.subscription.lib.api.IEventSubscriber; import com.tractrac.subscription.lib.api.IRaceSubscriber; import com.tractrac.subscription.lib.api.SubscriberInitializationException; +import com.tractrac.util.lib.api.exceptions.TimeOutException; import difflib.PatchFailedException; @@ -497,8 +498,8 @@ public class DomainFactoryImpl implements DomainFactory { trackedRegattaRegistry, // use the low-point system as the default scoring scheme getBaseDomainFactory().createScoringScheme(ScoringSchemeType.LOW_POINT), race.getId(), null, - /* controlTrackingFromStartAndFinishTimes */ false, rankingMetricConstructor, - /* registrationLinkSecret */ UUID.randomUUID().toString()); + /* controlTrackingFromStartAndFinishTimes */ false, /* autoRestartTrackingUponCompetitorSetChange */ false, + rankingMetricConstructor, /* registrationLinkSecret */ UUID.randomUUID().toString()); regattaCache.put(key, result); weakDefaultRegattaCache.put(race, result); logger.info("Created regatta "+result.getName()+" ("+result.hashCode()+") because none found for key "+key); @@ -645,7 +646,8 @@ public class DomainFactoryImpl implements DomainFactory { try { raceDefinition = raceTrackingHandler.createRaceDefinition(trackedRegatta.getRegatta(), raceName, course, boatClass, competitorsAndBoats, raceId); } catch (RuntimeException exception) { - final String reasonForNotAddingRaceToRegatta = "Error while creating race " + raceDefinition + " for regatta " + trackedRegatta.getRegatta(); + final String reasonForNotAddingRaceToRegatta = "Error while creating race " + raceDefinition + + " for regatta " + trackedRegatta.getRegatta() + ": " + exception.getMessage(); errorWhileTryingToTrackRace(trackedRegatta, raceDefinitionSetToUpdate, raceDefinition, reasonForNotAddingRaceToRegatta); } } else { @@ -1029,7 +1031,7 @@ public class DomainFactoryImpl implements DomainFactory { LeaderboardGroupResolver leaderboardGroupResolver, RaceTrackingConnectivityParametersImpl connectivityParams, long timeoutInMilliseconds, RaceTrackingHandler raceTrackingHandler) - throws URISyntaxException, SubscriberInitializationException, IOException, InterruptedException { + throws URISyntaxException, SubscriberInitializationException, IOException, InterruptedException, CreateModelException, TimeOutException { return new TracTracRaceTrackerImpl(this, raceLogStore, regattaLogStore, windStore, trackedRegattaRegistry, raceLogResolver, leaderboardGroupResolver, connectivityParams, timeoutInMilliseconds, raceTrackingHandler); } @@ -1040,7 +1042,7 @@ public class DomainFactoryImpl implements DomainFactory { LeaderboardGroupResolver leaderboardGroupResolver, RaceTrackingConnectivityParametersImpl connectivityParams, long timeoutInMilliseconds, RaceTrackingHandler raceTrackingHandler) throws URISyntaxException, CreateModelException, - SubscriberInitializationException, IOException, InterruptedException { + SubscriberInitializationException, IOException, InterruptedException, TimeOutException { return new TracTracRaceTrackerImpl(regatta, this, raceLogStore, regattaLogStore, windStore, trackedRegattaRegistry, raceLogResolver, leaderboardGroupResolver, connectivityParams, timeoutInMilliseconds, raceTrackingHandler); } diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/RaceTrackingConnectivityParametersImpl.java b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/RaceTrackingConnectivityParametersImpl.java index 6e1257a58df..8307fb03296 100755 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/RaceTrackingConnectivityParametersImpl.java +++ b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/RaceTrackingConnectivityParametersImpl.java @@ -25,6 +25,7 @@ import com.tractrac.model.lib.api.ModelLocator; import com.tractrac.model.lib.api.event.CreateModelException; import com.tractrac.model.lib.api.event.IRace; import com.tractrac.subscription.lib.api.SubscriberInitializationException; +import com.tractrac.util.lib.api.exceptions.TimeOutException; public class RaceTrackingConnectivityParametersImpl extends AbstractRaceTrackingConnectivityParameters { @@ -49,7 +50,6 @@ public class RaceTrackingConnectivityParametersImpl extends AbstractRaceTracking private final String raceVisibility; private final boolean useInternalMarkPassingAlgorithm; private final boolean preferReplayIfAvailable; - private final transient IRace tractracRace; private final int timeoutInMillis; private final boolean useOfficialEventsToUpdateRaceLog; @@ -78,11 +78,7 @@ public class RaceTrackingConnectivityParametersImpl extends AbstractRaceTracking this.useOfficialEventsToUpdateRaceLog = useOfficialEventsToUpdateRaceLog; this.paramURL = paramURL; this.timeoutInMillis = timeoutInMillis; - if (timeoutInMillis == -1) { - this.tractracRace = ModelLocator.getEventFactory().createRace(new URI(paramURL.toString())); - } else { - this.tractracRace = ModelLocator.getEventFactory().createRace(new URI(paramURL.toString()), timeoutInMillis); - } + final IRace tractracRace = getTractracRace(); if (preferReplayIfAvailable && isReplayRace(tractracRace) && (!Util.equalsWithNull(liveURI, tractracRace.getLiveURI()) || !Util.equalsWithNull(storedURI, tractracRace.getStoredURI()))) { logger.info("Replay format available and preferred for race " + tractracRace.getName() @@ -114,8 +110,14 @@ public class RaceTrackingConnectivityParametersImpl extends AbstractRaceTracking return tractracRace.getStoredURI() != null && tractracRace.getStoredURI().toString().toLowerCase().endsWith(".mtb"); } - public IRace getTractracRace() { - return tractracRace; + public IRace getTractracRace() throws CreateModelException, URISyntaxException, TimeOutException { + final IRace result; + if (getTimeoutInMillis() == -1) { + result = ModelLocator.getEventFactory().createRace(new URI(paramURL.toString())); + } else { + result = ModelLocator.getEventFactory().createRace(new URI(paramURL.toString()), getTimeoutInMillis()); + } + return result; } @Override @@ -127,7 +129,7 @@ public class RaceTrackingConnectivityParametersImpl extends AbstractRaceTracking public RaceTracker createRaceTracker(TrackedRegattaRegistry trackedRegattaRegistry, WindStore windStore, RaceLogAndTrackedRaceResolver raceLogResolver, LeaderboardGroupResolver leaderboardGroupResolver, long timeoutInMilliseconds, RaceTrackingHandler raceTrackingHandler) throws URISyntaxException, - CreateModelException, SubscriberInitializationException, IOException, InterruptedException { + CreateModelException, SubscriberInitializationException, IOException, InterruptedException, TimeOutException { RaceTracker tracker = domainFactory.createRaceTracker(raceLogStore, regattaLogStore, windStore, trackedRegattaRegistry, raceLogResolver, leaderboardGroupResolver, this, timeoutInMilliseconds, raceTrackingHandler); diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/TracTracRaceTrackerImpl.java b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/TracTracRaceTrackerImpl.java index 3f9d278809c..eb87642d3b6 100755 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/TracTracRaceTrackerImpl.java +++ b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/TracTracRaceTrackerImpl.java @@ -56,6 +56,7 @@ import com.sap.sse.common.Duration; import com.sap.sse.common.TimePoint; import com.sap.sse.common.Util; import com.sap.sse.util.impl.ThreadFactoryWithPriority; +import com.tractrac.model.lib.api.event.CreateModelException; import com.tractrac.model.lib.api.event.DataSource; import com.tractrac.model.lib.api.event.ICompetitor; import com.tractrac.model.lib.api.event.IEvent; @@ -71,6 +72,7 @@ import com.tractrac.subscription.lib.api.event.IConnectionStatusListener; import com.tractrac.subscription.lib.api.event.ILiveDataEvent; import com.tractrac.subscription.lib.api.event.IStoredDataEvent; import com.tractrac.subscription.lib.api.race.IRacesListener; +import com.tractrac.util.lib.api.exceptions.TimeOutException; public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl implements IConnectionStatusListener, TracTracRaceTracker, DynamicRaceDefinitionSet, TrackingDataLoader { @@ -270,11 +272,12 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl * you expect things to have loaded; see also * {@link RaceTracker#TIMEOUT_FOR_RECEIVING_RACE_DEFINITION_IN_MILLISECONDS}. */ - TracTracRaceTrackerImpl(DomainFactory domainFactory, RaceLogStore raceLogStore, - RegattaLogStore regattaLogStore, WindStore windStore, TrackedRegattaRegistry trackedRegattaRegistry, - RaceLogAndTrackedRaceResolver raceLogResolver, LeaderboardGroupResolver leaderboardGroupResolver, RaceTrackingConnectivityParametersImpl connectivityParams, long timeoutInMilliseconds, - RaceTrackingHandler raceTrackingHandler) - throws URISyntaxException, SubscriberInitializationException, IOException, InterruptedException { + TracTracRaceTrackerImpl(DomainFactory domainFactory, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, + WindStore windStore, TrackedRegattaRegistry trackedRegattaRegistry, + RaceLogAndTrackedRaceResolver raceLogResolver, LeaderboardGroupResolver leaderboardGroupResolver, + RaceTrackingConnectivityParametersImpl connectivityParams, long timeoutInMilliseconds, + RaceTrackingHandler raceTrackingHandler) throws URISyntaxException, SubscriberInitializationException, + IOException, InterruptedException, CreateModelException, TimeOutException { this(/* regatta */ null, domainFactory, raceLogStore, regattaLogStore, windStore, trackedRegattaRegistry, raceLogResolver, leaderboardGroupResolver, connectivityParams, timeoutInMilliseconds, raceTrackingHandler); @@ -299,7 +302,7 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl RaceLogAndTrackedRaceResolver raceLogResolver, LeaderboardGroupResolver leaderboardGroupResolver, RaceTrackingConnectivityParametersImpl connectivityParams, long timeoutInMilliseconds, RaceTrackingHandler raceTrackingHandler) - throws URISyntaxException, SubscriberInitializationException, IOException, InterruptedException { + throws URISyntaxException, SubscriberInitializationException, IOException, InterruptedException, CreateModelException, TimeOutException { super(connectivityParams); final URL paramURL = connectivityParams.getParamURL(); final URI liveURI = connectivityParams.getLiveURI(); @@ -358,10 +361,20 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl @Override public void deleteCompetitor(UUID competitorId) { + try { + trackedRegattaRegistry.updateRaceCompetitors(getRegatta(), getRace()); + } catch (Exception e) { + throw new RuntimeException(e); + } } @Override public void addCompetitor(ICompetitor competitor) { + try { + trackedRegattaRegistry.updateRaceCompetitors(getRegatta(), getRace()); + } catch (Exception e) { + throw new RuntimeException(e); + } } }; eventSubscriber.subscribeCompetitors(competitorsListener); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/Regatta.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/Regatta.java index 23bd93ebfd1..60997a38154 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/Regatta.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/Regatta.java @@ -238,11 +238,26 @@ public interface Regatta * show_bug.cgi?id=3588. */ boolean isControlTrackingFromStartAndFinishTimes(); - + /** * @see #isControlTrackingFromStartAndFinishTimes() */ void setControlTrackingFromStartAndFinishTimes(boolean controlTrackingFromStartAndFinishTimes); + + /** + * Tells whether in the scope of this regatta tracking of a race shall automatically be re-started if the competitor + * registrations change. Removing the {@link TrackedRace tracked races} and {@link RaceDefinition}s affected is then + * required because the {@link RaceDefinition#getCompetitors() set of competitors of a race} is modeled to be + * immutable. As the set of competitors that are part of a tracked race is provided by the tracking connector, the + * responsibility for adhering to this flag, which rather expresses a request than describes a fact, lies with the + * respective connectors. + */ + boolean isAutoRestartTrackingUponCompetitorSetChange(); + + /** + * @see #isAutoRestartTrackingUponCompetitorSetChange() + */ + void setAutoRestartTrackingUponCompetitorSetChange(boolean autoRestartTrackingUponCompetitorSetChange); @Override default QualifiedObjectIdentifier getIdentifier() { diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/AbstractRaceColumn.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/AbstractRaceColumn.java index 363c67afbf0..2e49291ab4e 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/AbstractRaceColumn.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/AbstractRaceColumn.java @@ -389,19 +389,28 @@ public abstract class AbstractRaceColumn extends SimpleAbstractRaceColumn implem } Set competitorSet = new HashSet(); Util.addAll(competitors, competitorSet); - RaceLog raceLog = getRaceLog(fleet); - for (RaceLogEvent event : raceLog.getUnrevokedEventsDescending()) { - if (event instanceof RegisterCompetitorEvent) { - RegisterCompetitorEvent registerEvent = (RegisterCompetitorEvent) event; - if (competitorSet.contains(registerEvent.getCompetitor())) { - try { - raceLog.revokeEvent(raceLogEventAuthorForRaceColumn, event, - "unregistering competitor because no longer selected for registration"); - } catch (NotRevokableException e) { - logger.log(Level.WARNING, "could not unregister competitor by adding RevokeEvent", e); + final RaceLog raceLog = getRaceLog(fleet); + final Set competitorRegistrationEventsToRevoke = new HashSet<>(); + raceLog.lockForRead(); + try { + for (RaceLogEvent event : raceLog.getUnrevokedEventsDescending()) { + if (event instanceof RegisterCompetitorEvent) { + RegisterCompetitorEvent registerEvent = (RegisterCompetitorEvent) event; + if (competitorSet.contains(registerEvent.getCompetitor())) { + competitorRegistrationEventsToRevoke.add(event); } } } + } finally { + raceLog.unlockAfterRead(); + } + for (final RaceLogEvent eventToRevoke : competitorRegistrationEventsToRevoke) { + try { + raceLog.revokeEvent(raceLogEventAuthorForRaceColumn, eventToRevoke, + "unregistering competitor because no longer selected for registration"); + } catch (NotRevokableException e) { + logger.log(Level.WARNING, "could not unregister competitor by adding RevokeEvent", e); + } } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RegattaImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RegattaImpl.java index 06cdbbd8d80..4cb2b71e4f4 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RegattaImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RegattaImpl.java @@ -152,6 +152,8 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene * show_bug.cgi?id=3588. */ private boolean controlTrackingFromStartAndFinishTimes; + + private boolean autoRestartTrackingUponCompetitorSetChange; private boolean canBoatsOfCompetitorsChangePerRace; @@ -169,7 +171,8 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene /** * Constructs a regatta with an empty {@link RaceLogStore} and with - * {@link Regatta#isControlTrackingFromStartAndFinishTimes()} set to {@code false}. + * {@link Regatta#isControlTrackingFromStartAndFinishTimes()} and + * {@link #isAutoRestartTrackingUponCompetitorSetChange()} set to {@code false}. */ public RegattaImpl(String name, BoatClass boatClass, boolean canBoatsOfCompetitorsChangePerRace, CompetitorRegistrationType competitorRegistrationType, TimePoint startDate, TimePoint endDate, @@ -178,13 +181,14 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene this(EmptyRaceLogStore.INSTANCE, EmptyRegattaLogStore.INSTANCE, name, boatClass, canBoatsOfCompetitorsChangePerRace, competitorRegistrationType, startDate, endDate, series, persistent, scoringScheme, id, courseArea, 0.0, /* useStartTimeInference */true, /* controlTrackingFromStartAndFinishTimes */ false, - rankingMetricConstructor, registrationLinkSecret); + /* autoRestartTrackingUponCompetitorSetChange */ false, rankingMetricConstructor, registrationLinkSecret); } /** * Constructs a regatta with a single default series with empty race column list, and a single default fleet which * is not {@link #isPersistent() marked for persistence}. The regatta is created with - * {@link Regatta#isControlTrackingFromStartAndFinishTimes()} set to {@code false}. + * {@link Regatta#isControlTrackingFromStartAndFinishTimes()} and + * {@link #isAutoRestartTrackingUponCompetitorSetChange()} set to {@code false}. * * @param trackedRegattaRegistry * used to find the {@link TrackedRegatta} for this column's series' {@link Series#getRegatta() regatta} @@ -199,8 +203,8 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene ScoringScheme scoringScheme, Serializable id, CourseArea courseArea, String registrationLinkSecret) { this(raceLogStore, regattaLogStore, name, boatClass, canBoatsOfCompetitorsChangePerRace, competitorRegistrationType, startDate, endDate, trackedRegattaRegistry, scoringScheme, - id, courseArea, /* controlTrackingFromStartAndFinishTimes */ false, OneDesignRankingMetric::new, - registrationLinkSecret); + id, courseArea, /* controlTrackingFromStartAndFinishTimes */ false, /* autoRestartTrackingUponCompetitorSetChange */ false, + OneDesignRankingMetric::new, registrationLinkSecret); } /** @@ -217,15 +221,15 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene boolean canBoatsOfCompetitorsChangePerRace, CompetitorRegistrationType competitorRegistrationType, TimePoint startDate, TimePoint endDate, TrackedRegattaRegistry trackedRegattaRegistry, ScoringScheme scoringScheme, Serializable id, CourseArea courseArea, - boolean controlTrackingFromStartAndFinishTimes, RankingMetricConstructor rankingMetricConstructor, - String registrationLinkSecret) { + boolean controlTrackingFromStartAndFinishTimes, boolean autoRestartTrackingUponCompetitorSetChange, + RankingMetricConstructor rankingMetricConstructor, String registrationLinkSecret) { this(raceLogStore, regattaLogStore, name, boatClass, canBoatsOfCompetitorsChangePerRace, competitorRegistrationType, startDate, endDate, Collections.singletonList(new SeriesImpl(LeaderboardNameConstants.DEFAULT_SERIES_NAME, /* isMedal */false, /* isFleetsCanRunInParallel */ true, Collections .singletonList(new FleetImpl(LeaderboardNameConstants.DEFAULT_FLEET_NAME)), /* race column names */new ArrayList(), trackedRegattaRegistry)), /* persistent */false, scoringScheme, id, courseArea, /*buoyZoneRadiusInHullLengths*/2.0, /* useStartTimeInference */true, controlTrackingFromStartAndFinishTimes, - rankingMetricConstructor, registrationLinkSecret); + autoRestartTrackingUponCompetitorSetChange, rankingMetricConstructor, registrationLinkSecret); } /** @@ -237,13 +241,14 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene BoatClass boatClass, boolean canBoatsOfCompetitorsChangePerRace, CompetitorRegistrationType competitorRegistrationType, TimePoint startDate, TimePoint endDate, Iterable series, boolean persistent, ScoringScheme scoringScheme, Serializable id, CourseArea courseArea, Double buoyZoneRadiusInHullLengths, boolean useStartTimeInference, - boolean controlTrackingFromStartAndFinishTimes, RankingMetricConstructor rankingMetricConstructor, - String registrationLinkSecret) { + boolean controlTrackingFromStartAndFinishTimes, boolean autoRestartTrackingUponCompetitorSetChange, + RankingMetricConstructor rankingMetricConstructor, String registrationLinkSecret) { super(name); this.registrationLinkSecret = registrationLinkSecret; this.rankingMetricConstructor = rankingMetricConstructor; this.useStartTimeInference = useStartTimeInference; this.controlTrackingFromStartAndFinishTimes = controlTrackingFromStartAndFinishTimes; + this.autoRestartTrackingUponCompetitorSetChange = autoRestartTrackingUponCompetitorSetChange; this.id = id; this.raceLogStore = raceLogStore; races = new ConcurrentHashMap<>(); @@ -671,10 +676,20 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene return controlTrackingFromStartAndFinishTimes; } + @Override + public boolean isAutoRestartTrackingUponCompetitorSetChange() { + return autoRestartTrackingUponCompetitorSetChange; + } + @Override public void setControlTrackingFromStartAndFinishTimes(boolean controlTrackingFromStartAndFinishTimes) { this.controlTrackingFromStartAndFinishTimes = controlTrackingFromStartAndFinishTimes; } + + @Override + public void setAutoRestartTrackingUponCompetitorSetChange(boolean autoRestartTrackingUponCompetitorSetChange) { + this.autoRestartTrackingUponCompetitorSetChange = autoRestartTrackingUponCompetitorSetChange; + } @Override public void setUseStartTimeInference(boolean useStartTimeInference) { diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRegattaRegistry.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRegattaRegistry.java index 0a557343779..d509d4aaf41 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRegattaRegistry.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRegattaRegistry.java @@ -91,4 +91,26 @@ public interface TrackedRegattaRegistry { Regatta getOrCreateDefaultRegatta(String name, String boatClassName, Serializable id); + /** + * Does what is necessary to react to an external change in the {@code race}'s set of competitors. This can, e.g., + * entail removing and re-tracking that race which, however, has to be explicitly + * {@link Regatta#isAutoRestartTrackingUponCompetitorSetChange() permitted by the regatta} as it will lead to a + * temporary unavailability of the race and any information related to it such as the leaderboard scores. + *

+ * By and large, we assume that a race's {@link RaceDefinition#getCompetitors() set of competitors} remains + * unchanged once the race has been created. There are, however, scenarios in which this causes some trouble. For + * example, if tracking for a regatta shall be started early, so as to enable race officials to see the course + * geometry and wind data for the first race of the day, then especially in self-service scenarios there may be late + * competitors registrations that happen after tracking has been started. These competitors, depending on how the + * connector in use works, may make it into the regatta's leaderboard, but not into the {@link RaceDefinition}. This + * would be unfortunate as those competitors then would remain invisible until the race is reloaded. + *

+ * + * See also bug 5219 for a discussion. + * + * @return {@code null} if it was not possible to re-load the race, either because the regatta doesn't allow for it, + * or because the connectivity parameters were not found; a {@link RaceHandle} for the re-started race + * otherwise. + */ + RaceHandle updateRaceCompetitors(Regatta regatta, RaceDefinition race) throws Exception; } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackerManager.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackerManager.java index d4e55b47316..e8c3942a619 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackerManager.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackerManager.java @@ -6,6 +6,7 @@ import java.net.URISyntaxException; import com.sap.sailing.domain.base.RaceDefinition; import com.sap.sailing.domain.base.Regatta; +import com.sap.sailing.domain.base.RegattaListener; import com.sap.sailing.domain.common.RegattaIdentifier; public interface TrackerManager { @@ -14,7 +15,13 @@ public interface TrackerManager { * Creates a {@link RaceTracker} based on the {@code params}. If the {@code params} request * {@link RaceTrackingConnectivityParameters#isTrackWind() wind tracking}, a callback is * {@link RaceTracker#add(com.sap.sailing.domain.tracking.RaceTracker.RaceCreationListener) registered} with the - * {@link RaceTracker} that, when the race has been created by the tracker, will start wind tracking. + * {@link RaceTracker} that, when the race has been created by the tracker, will start wind tracking.

+ * + * The effects of calling this operation will be replicated by means of a listener that is notified when the connector + * has successfully loaded and installed the race. {@code RacingEventServiceImpl.raceAdded(...)} which is notified + * when the race definition has been added to the regatta (implementing {@link RegattaListener#raceAdded(Regatta, RaceDefinition)}), + * and {@code RacingEventServiceImpl.RaceAdditionListener.raceAdded(...)} which is called when the {@link TrackedRace} + * is added to the {@link TrackedRegatta}. * * @param regattaToAddTo * if null or no regatta by that identifier is found, the regatta into which the race has diff --git a/java/com.sap.sailing.feature.p2build/scripts/analyzeLaunch.sh b/java/com.sap.sailing.feature.p2build/scripts/analyzeLaunch.sh deleted file mode 100755 index 6e54a0d1415..00000000000 --- a/java/com.sap.sailing.feature.p2build/scripts/analyzeLaunch.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash -target_bundles=$(cat "$1" | grep 'key="target_bundles"' | sed -e 's/^.* value="\([^"]*\)"\/>/\1/' | sed -e 's/,/\n/g' | sed -e 's/^\(.*\)@\(.*\):\(.*\)$/\1 \2 \3/') -workspace_bundles=$(cat "$1" | grep 'key="workspace_bundles"' | sed -e 's/^.* value="\([^"]*\)"\/>/\1/' | sed -e 's/,/\n/g' | sed -e 's/^\(.*\)@\(.*\):\(.*\)$/\1 \2 \3/') -echo " *** Target Bundles ***" -echo "$target_bundles" | sort -echo " *** Workspace Bundles ***" -echo "$workspace_bundles" | sort - diff --git a/java/com.sap.sailing.feature.p2build/scripts/analyzeProduct.sh b/java/com.sap.sailing.feature.p2build/scripts/analyzeProduct.sh index 0c22346e9a1..0d570e4ec2e 100755 --- a/java/com.sap.sailing.feature.p2build/scripts/analyzeProduct.sh +++ b/java/com.sap.sailing.feature.p2build/scripts/analyzeProduct.sh @@ -1,12 +1,21 @@ #!/bin/bash # Call like this: analyzeProduct.sh [ ...] -# Will output two lines in format -# -# +# Updates Eclipse .launch files with the bundles needed for starting an SAP Sailing server based on the current feature.xml. +# The .launch files must be in the workspace format that was introduced with Eclipse 2019-12. The script prints an error when no matching +# replacement section could be found. +# Will output two sections in format +# +# +# +# +# " else # contribute to workspace_bundles - workspace_bundles="${workspace_bundles},${bundle_spec_for_launch}" + workspace_bundles="${workspace_bundles}${NL}" fi done done echo ' *** Target Bundles ***' -echo "" +echo "${target_bundles}" echo ' *** Workspace Bundles ***' -echo "" +echo "${workspace_bundles}" # Now patch the .launch file if provided while [ "$2" != "" ]; do - echo "Patching $2 ..." - if echo "$2" | grep -q winddbTest; then - patched_workspace_bundles="${workspace_bundles},com.sap.sailing.server.gateway.test.support@default:default" + if grep -q key=\"target_bundles\" "$2"; then + echo "Not patching $2 ... please use script for eclipse 2019-09 or older" else - patched_workspace_bundles="${workspace_bundles}" + echo "Patching $2 ..." + if echo "$2" | grep -q winddbTest; then + patched_workspace_bundles="${workspace_bundles}${NL}${NL}" + else + patched_workspace_bundles="${workspace_bundles}" + fi + selected_target_bundles="${target_bundles}${NL}<\/setAttribute>" + patched_workspace_bundles="${patched_workspace_bundles}${NL}<\/setAttribute>" + # Used Perl CLI options: + #0 -> specifies the record separator, as you alread found out, it defaults to \0 so it does not terminate before the end of the text file. Ensures that the regular expression is applied over the whole file. + #p -> apply looping wrapper, see perl man page + #e -> denotes the (regular) expression that is goint ot be applied. + #i -> inplace editing of files + perl -i -p0e "s/.*?<\/setAttribute>/${selected_target_bundles}/s" "$2" + perl -i -p0e "s/.*?<\/setAttribute>/${patched_workspace_bundles}/s" "$2" + # remove eventuelly created bak files (this happens at least on windows platform) + rm -f "$2.bak" fi - sed -i 's/ + + diff --git a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/PingMarkViaRegattaLogTest.java b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/PingMarkViaRegattaLogTest.java index ceecd2aa025..8cf7b6a5a99 100644 --- a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/PingMarkViaRegattaLogTest.java +++ b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/PingMarkViaRegattaLogTest.java @@ -45,7 +45,7 @@ public class PingMarkViaRegattaLogTest { /* canBoatsOfCompetitorsChangePerRace */ true, CompetitorRegistrationType.CLOSED, /* registrationLinkSecret */ null, /*startDate*/ null, /*endDate*/ null, UUID.randomUUID(), Collections.singletonList(series), false, new HighPoint(), UUID.randomUUID(), /*buoyZoneRadiusInHullLengths*/2.0, /* useStartTimeInference */ true, - /* controlTrackingFromStartAndFinishTimes */ false, RankingMetricsFactory.getRankingMetricConstructor(RankingMetrics.ONE_DESIGN)); + /* controlTrackingFromStartAndFinishTimes */ false, /* autoRestartTrackingUponCompetitorSetChange */ false, RankingMetricsFactory.getRankingMetricConstructor(RankingMetrics.ONE_DESIGN)); RegattaLeaderboard leaderboard = service.addRegattaLeaderboard(regatta.getRegattaIdentifier(), "RegattaLeaderboard", new int[] {}); MarkDTO mark = new MarkDTO("mark", "mark", "m"); diff --git a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/SlicingTest.java b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/SlicingTest.java index f452ff75ebb..3414f6e3909 100644 --- a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/SlicingTest.java +++ b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/SlicingTest.java @@ -99,7 +99,7 @@ public class SlicingTest { seriesCreationParameters); final RegattaDTO regatta = sailingService.createRegatta(regattaName, boatClassName, false, CompetitorRegistrationType.CLOSED, null, null, null, regattaCreationParameters, false, - ScoringSchemeType.HIGH_POINT, null, 3.0, false, false, RankingMetrics.ONE_DESIGN); + ScoringSchemeType.HIGH_POINT, null, 3.0, false, false, /* autoRestartTrackingUponCompetitorSetChange */ false, RankingMetrics.ONE_DESIGN); final List> columnNames = new ArrayList<>(); columnNames.add(new Pair<>(columnNameOriginalRace, 0)); sailingService.addRaceColumnsToSeries(regatta.getRegattaIdentifier(), seriesName, columnNames); diff --git a/java/com.sap.sailing.gwt.ui/AdminConsole.html b/java/com.sap.sailing.gwt.ui/AdminConsole.html index 9a0d013b0f9..db0e90a4034 100644 --- a/java/com.sap.sailing.gwt.ui/AdminConsole.html +++ b/java/com.sap.sailing.gwt.ui/AdminConsole.html @@ -12,13 +12,13 @@ - + - SAP Sailing Analytics Administration Console + ${SAP}Sailing Analytics Administration Console @@ -27,12 +27,16 @@ - + - - - + + + @@ -40,72 +44,72 @@ - - + @-webkit-keyframes pulsate { + 0% { + -webkit-transform:scale(.1); + transform:scale(.1); + opacity: 0.0; + } + 50% { + opacity:1; + } + 100% { + -webkit-transform:scale(1.2); + transform:scale(1.2); + opacity:0; + } + } + + @keyframes pulsate { + 0% { + -webkit-transform:scale(.1); + transform:scale(.1); + opacity: 0.0; + } + 50% { + opacity:1; + } + 100% { + -webkit-transform:scale(1.2); + transform:scale(1.2); + opacity:0; + } + } + @@ -126,10 +130,10 @@ -

-

SAP Sailing Analytics

-

Loading, please wait...

-
-
+
+

${SAP}Sailing Analytics

+

Loading, please wait...

+
+
diff --git a/java/com.sap.sailing.gwt.ui/AutoPlay.html b/java/com.sap.sailing.gwt.ui/AutoPlay.html index 911b2e94ae1..622fe347c41 100644 --- a/java/com.sap.sailing.gwt.ui/AutoPlay.html +++ b/java/com.sap.sailing.gwt.ui/AutoPlay.html @@ -13,13 +13,13 @@ - - + + - SAP Sailing Analytics AutoPlay + ${SAP}Sailing Analytics AutoPlay @@ -39,7 +39,11 @@ - + diff --git a/java/com.sap.sailing.gwt.ui/BrowserUnsupported.html b/java/com.sap.sailing.gwt.ui/BrowserUnsupported.html index cd6ef955581..508072fa094 100644 --- a/java/com.sap.sailing.gwt.ui/BrowserUnsupported.html +++ b/java/com.sap.sailing.gwt.ui/BrowserUnsupported.html @@ -2,7 +2,7 @@ - + Your browser is unsupported diff --git a/java/com.sap.sailing.gwt.ui/DataMining.html b/java/com.sap.sailing.gwt.ui/DataMining.html index 46a0764ba3a..cea6a7dfbbd 100644 --- a/java/com.sap.sailing.gwt.ui/DataMining.html +++ b/java/com.sap.sailing.gwt.ui/DataMining.html @@ -8,18 +8,22 @@ - - + + - - - - + + + + diff --git a/java/com.sap.sailing.gwt.ui/EmbeddedMapAndWindChart.html b/java/com.sap.sailing.gwt.ui/EmbeddedMapAndWindChart.html index a0f33f2caea..13cd4d1e379 100644 --- a/java/com.sap.sailing.gwt.ui/EmbeddedMapAndWindChart.html +++ b/java/com.sap.sailing.gwt.ui/EmbeddedMapAndWindChart.html @@ -2,94 +2,98 @@ - + - SAP Sailing Analytics Race Board + ${SAP}Sailing Analytics Race Board - - - - - - - - - - + + + + + + + + + + - - + @-webkit-keyframes pulsate { + 0% { + -webkit-transform:scale(.1); + transform:scale(.1); + opacity: 0.0; + } + 50% { + opacity:1; + } + 100% { + -webkit-transform:scale(1.2); + transform:scale(1.2); + opacity:0; + } + } + + @keyframes pulsate { + 0% { + -webkit-transform:scale(.1); + transform:scale(.1); + opacity: 0.0; + } + 50% { + opacity:1; + } + 100% { + -webkit-transform:scale(1.2); + transform:scale(1.2); + opacity:0; + } + } + @@ -104,10 +108,10 @@ -
-

SAP Sailing Analytics

-

Loading, please wait...

-
-
+
+

${SAP}Sailing Analytics

+

Loading, please wait...

+
+
diff --git a/java/com.sap.sailing.gwt.ui/GWT Sailing SDM.launch b/java/com.sap.sailing.gwt.ui/GWT Sailing SDM.launch index 05821f7f895..682a0020893 100755 --- a/java/com.sap.sailing.gwt.ui/GWT Sailing SDM.launch +++ b/java/com.sap.sailing.gwt.ui/GWT Sailing SDM.launch @@ -34,7 +34,6 @@ - @@ -116,6 +115,7 @@ + diff --git a/java/com.sap.sailing.gwt.ui/Home.html b/java/com.sap.sailing.gwt.ui/Home.html index 4cbc22ab4b9..3014189d5ef 100644 --- a/java/com.sap.sailing.gwt.ui/Home.html +++ b/java/com.sap.sailing.gwt.ui/Home.html @@ -1,111 +1,115 @@ - - - - + + + + - SAP Sailing - Homepage + ${SAP}Sailing - Homepage - - - - - + + + + + - + - - - - - + @-webkit-keyframes pulsate { + 0% { + -webkit-transform:scale(.1); + transform:scale(.1); + opacity: 0.0; + } + 50% { + opacity:1; + } + 100% { + -webkit-transform:scale(1.2); + transform:scale(1.2); + opacity:0; + } + } + + @keyframes pulsate { + 0% { + -webkit-transform:scale(.1); + transform:scale(.1); + opacity: 0.0; + } + 50% { + opacity:1; + } + 100% { + -webkit-transform:scale(1.2); + transform:scale(1.2); + opacity:0; + } + } + - - -
-

SAP Sailing Analytics

-

Loading, please wait...

-
-
+ + +
+

${SAP}Sailing Analytics

+

Loading, please wait...

+
+
diff --git a/java/com.sap.sailing.gwt.ui/HomeDesktop.html b/java/com.sap.sailing.gwt.ui/HomeDesktop.html index fb2fefff91f..baad2fbccaa 100644 --- a/java/com.sap.sailing.gwt.ui/HomeDesktop.html +++ b/java/com.sap.sailing.gwt.ui/HomeDesktop.html @@ -2,9 +2,9 @@ - - - SAP Sailing - Homepage + + + ${SAP}Sailing - Homepage - + diff --git a/java/com.sap.sailing.gwt.ui/LeaderboardEditing.html b/java/com.sap.sailing.gwt.ui/LeaderboardEditing.html index f1b2e94e8e1..00cd76c45d2 100755 --- a/java/com.sap.sailing.gwt.ui/LeaderboardEditing.html +++ b/java/com.sap.sailing.gwt.ui/LeaderboardEditing.html @@ -8,7 +8,7 @@ - + @@ -17,13 +17,17 @@ - SAP Sailing Analytics Leaderboard Editing + ${SAP}Sailing Analytics Leaderboard Editing - + diff --git a/java/com.sap.sailing.gwt.ui/META-INF/MANIFEST.MF b/java/com.sap.sailing.gwt.ui/META-INF/MANIFEST.MF index dcf12e25167..a7efa0be243 100644 --- a/java/com.sap.sailing.gwt.ui/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.gwt.ui/META-INF/MANIFEST.MF @@ -55,6 +55,7 @@ Require-Bundle: com.sap.sailing.domain, com.sap.sse.datamining.annotations, com.sap.sse.shared.android, com.sap.sailing.competitorimport, + com.sap.sse.debranding, com.sap.sailing.expeditionconnector, com.sap.sailing.expeditionconnector.common, org.mp4parser.isoparser;bundle-version="1.9.37", diff --git a/java/com.sap.sailing.gwt.ui/PairingList.html b/java/com.sap.sailing.gwt.ui/PairingList.html index 8b26d9cd924..f0812a97fdd 100644 --- a/java/com.sap.sailing.gwt.ui/PairingList.html +++ b/java/com.sap.sailing.gwt.ui/PairingList.html @@ -6,20 +6,24 @@ - SAP Sailing Analytics PairingList + ${SAP}Sailing Analytics PairingList - - + + - + diff --git a/java/com.sap.sailing.gwt.ui/RaceBoard.html b/java/com.sap.sailing.gwt.ui/RaceBoard.html index 9cb9e1ede1e..de796021d04 100644 --- a/java/com.sap.sailing.gwt.ui/RaceBoard.html +++ b/java/com.sap.sailing.gwt.ui/RaceBoard.html @@ -2,25 +2,25 @@ - + - SAP Sailing Analytics Race Board + ${SAP}Sailing Analytics Race Board - - - - - - - - - - + + + + + + + + + + @@ -32,7 +32,11 @@ - + diff --git a/java/com.sap.sailing.gwt.ui/RegattaOverview.html b/java/com.sap.sailing.gwt.ui/RegattaOverview.html index b2edb59c4a0..7197ecb9282 100644 --- a/java/com.sap.sailing.gwt.ui/RegattaOverview.html +++ b/java/com.sap.sailing.gwt.ui/RegattaOverview.html @@ -9,18 +9,22 @@ - - + + - SAP Sailing Analytics Regatta Overview + ${SAP}Sailing Analytics Regatta Overview - + + type="text/javascript" + language="javascript" + src="com.sap.sailing.gwt.regattaoverview.RegattaOverview/com.sap.sailing.gwt.regattaoverview.RegattaOverview.nocache.js" + > diff --git a/java/com.sap.sailing.gwt.ui/Simulator.html b/java/com.sap.sailing.gwt.ui/Simulator.html index 88e02232e23..30f341ff11c 100644 --- a/java/com.sap.sailing.gwt.ui/Simulator.html +++ b/java/com.sap.sailing.gwt.ui/Simulator.html @@ -3,23 +3,26 @@ - SAP Sailing Analytics Race Simulator + ${SAP}Sailing Analytics Race Simulator - - - - - - - - + + + + + + + + - - + diff --git a/java/com.sap.sailing.gwt.ui/Spectator.html b/java/com.sap.sailing.gwt.ui/Spectator.html index 27d727c2b8c..ce1142917d6 100644 --- a/java/com.sap.sailing.gwt.ui/Spectator.html +++ b/java/com.sap.sailing.gwt.ui/Spectator.html @@ -13,10 +13,10 @@ - - + + - + @@ -24,13 +24,17 @@ - SAP Sailing Analytics Spectator Vision + ${SAP}Sailing Analytics Spectator Vision - + diff --git a/java/com.sap.sailing.gwt.ui/VideoPopup.html b/java/com.sap.sailing.gwt.ui/VideoPopup.html index 5a519792363..a4c6b2f5615 100644 --- a/java/com.sap.sailing.gwt.ui/VideoPopup.html +++ b/java/com.sap.sailing.gwt.ui/VideoPopup.html @@ -2,8 +2,13 @@ - SAP Sailing Video - + ${SAP}Sailing Video + + diff --git a/java/com.sap.sailing.gwt.ui/WEB-INF/web.xml b/java/com.sap.sailing.gwt.ui/WEB-INF/web.xml index e5fd3ba3e6a..969909c2fe7 100755 --- a/java/com.sap.sailing.gwt.ui/WEB-INF/web.xml +++ b/java/com.sap.sailing.gwt.ui/WEB-INF/web.xml @@ -61,6 +61,15 @@ *.html + + ClientConfigurationServlet + ClientConfigurationServlet + com.sap.sse.debranding.ClientConfigurationServlet + + + ClientConfigurationServlet + *.html + Status Status diff --git a/java/com.sap.sailing.gwt.ui/YoutubePopup.html b/java/com.sap.sailing.gwt.ui/YoutubePopup.html index b9888c7c4c3..497486a5606 100644 --- a/java/com.sap.sailing.gwt.ui/YoutubePopup.html +++ b/java/com.sap.sailing.gwt.ui/YoutubePopup.html @@ -2,8 +2,13 @@ - SAP Sailing Video - + ${SAP}Sailing Video + +